atif-sql · Impact analysis
Section titled “atif-sql · Impact analysis”The reader’s question: if I touch X, what else do I have to think about?
What “high-impact surface” means here, and why it is not inbound-import count. The packet’s
default rule is the top 8 modules by inbound reference count. That rule mis-ranks this workspace, and
the substitution is deliberate. The import-linter contracts forbid most of the packages from
importing each other (pyproject.toml:605-608), so the surfaces that cost the most to change are
exactly the ones with the fewest inbound imports: a corpus filename string has several independent
readers and no import edges. Ranking by import count would put atif_analytics’ many intra-package
edges on top and leave every cross-package contract off the list.
A surface is high-impact here when changing it forces a coordinated edit in a file that no import
edge connects to it, and a named gate fails if you miss one. Surfaces are ordered by how many
distinct enforcement mechanisms fire (a test, an import-linter contract, a CI step, a KeyError at
build time) and how many packages the edit spans.
How the consumer sets below were derived. There is no code index in this repo (no .codegraph/,
no LSP index, no symbol graph), so no count here came from one. Each set was built by grepping import
statements for the symbol and then reading every hit’s import line to confirm which package the
name resolved to. These properties of this codebase make that confirmation step load-bearing rather
than pedantic:
- Nearly every cross-package import is indented: inside a function body or an
if TYPE_CHECKING:block, because the lean-import contract defers them (packages/atif-cli/src/atif_cli/app.py:50-53). The exceptions are four atif-cli helper modules (atif_cli.converter_adapter,atif_cli.duck_errors,atif_cli.embed_lake, andatif_cli.lake_sessions) that import siblings at module scope, andatif_cli.appimports each of them only inside a function body. A line-anchored^from atif_grep finds only those four. The census behind this file allowed leading whitespace and then read each enclosing scope. - Names collide across packages.
DomainErroris declared independently in several packages andEmbeddingProviderMismatchin more than one:packages/atif-duck/src/atif_duck/domain/embedding_guard.py:33derives from bareException,packages/atif-embed/src/atif_embed/domain/errors.py:29from atif-embed’s ownDomainError. Near-names compound it:packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:44importsEmbeddingProviderUnavailable, which is not theEmbeddingProviderProtocol. - A Protocol implementation usually imports nothing from its Protocol. Structural satisfaction leaves no edge in either direction, so most adapters are invisible to any import-graph query. See the Protocols section.
The internal graph is a star: atif-cli to its declared siblings, plus exactly one
atif-analytics → atif-models edge. atif-cli doesn’t declare atif-models
(packages/atif-cli/pyproject.toml:32-36 lists its siblings, none of them atif-models), and its
only import of it is in a test (packages/atif-cli/tests/test_analyze_lake.py:44). The CLI surface was
enumerated from the @app.command decorator sites, not from route literals.
The static DuckDB catalog
Section titled “The static DuckDB catalog”Defined at: packages/atif-duck/src/atif_duck/domain/catalog.py:30
The catalogs (VIEW_NAMES, MACRO_NAMES, ANALYTICS_VIEW_NAMES,
ANALYTICS_MACRO_SIGNATURES) plus VIEW_SCHEMA, TABLE_MACRO_NAMES, DESCRIPTIONS, and
DEFAULT_PRICING. Adding one view or macro is an edit in several places, and each place has a gate.
| Downstream | Type | Touch on change | Citation |
|---|---|---|---|
atif_duck.domain.examples: derives every example from every catalog |
direct import | yes | packages/atif-duck/src/atif_duck/domain/examples.py:35-44 |
DESCRIPTIONS: one entry per object; _description() raises KeyError without it |
direct import | yes | packages/atif-duck/src/atif_duck/domain/catalog.py:487 and packages/atif-duck/src/atif_duck/domain/examples.py:182-188 |
ARG_EXEMPLARS: needed only when a macro introduces a NEW parameter name; _macro_sql() raises KeyError without it |
direct import | yes | packages/atif-duck/src/atif_duck/domain/examples.py:76-93 and packages/atif-duck/src/atif_duck/domain/examples.py:163-171 |
TABLE_MACRO_NAMES: membership required when the DDL says AS TABLE, or the derived SQL uses the wrong call shape |
direct import | yes | packages/atif-duck/src/atif_duck/domain/catalog.py:469 |
atif_duck.infrastructure.registry: owns the real DDL and imports DEFAULT_PRICING |
direct import | yes | packages/atif-duck/src/atif_duck/infrastructure/registry.py:87 |
atif_duck.infrastructure.analytics: register_analytics and register_analytics_macros own the DDL the analytics catalogs describe |
indirect | yes | packages/atif-duck/src/atif_duck/infrastructure/analytics.py:119 and packages/atif-duck/src/atif_duck/infrastructure/analytics.py:236 |
atif_duck.infrastructure.authorship: register_authorship owns the DDL for step_author, user_steps, human_turns, and session_outcomes |
indirect | yes | packages/atif-duck/src/atif_duck/infrastructure/authorship.py:38 |
atif_cli.app.schema: deferred import of VIEW_SCHEMA, MACRO_SIGNATURES, ANALYTICS_VIEW_SCHEMA, and ANALYTICS_MACRO_SIGNATURES |
direct import | no | packages/atif-cli/src/atif_cli/app.py:2481-2486 |
atif_cli.app.examples: deferred import of build_examples + the two value tuples |
direct import | no | packages/atif-cli/src/atif_cli/app.py:2399-2403 |
test_examples.py::test_every_example_executes: parametrized over every derived example, fetchall() forces materialization |
test | yes | packages/atif-duck/tests/test_examples.py:102-109 |
test_examples.py drift catchers: description coverage, exemplar coverage, AS TABLE set equality, example-or-exclusion coverage |
test | yes | packages/atif-duck/tests/test_examples.py:190-241 |
test_duck_views.py::test_view_schema_matches_describe: DESCRIBE output equals VIEW_SCHEMA column-for-column |
test | yes | packages/atif-duck/tests/test_duck_views.py:51-59 |
test_duck_views.py::test_macro_signatures_match_ddl: regex-parses the DDL and asserts equality |
test | yes | packages/atif-duck/tests/test_duck_views.py:67-89 |
test_analytics_views.py::test_analytics_macro_signatures_match_ddl: same for the analytics half |
test | yes | packages/atif-duck/tests/test_analytics_views.py:242-257 |
test_duck_views.py::test_default_pricing_matches_published_list_rates_exactly: oracle table, key-for-key |
test | yes | packages/atif-duck/tests/test_duck_views.py:519-527 |
test_app.py::TestSchema / TestExamples: the JSON payload tests assert against the catalog and build_examples(), not literals |
test | no | packages/atif-cli/tests/test_app.py:136-150 and packages/atif-cli/tests/test_app.py:184-203 |
Blast-radius notes
Section titled “Blast-radius notes”EXCLUSIONSis the only sanctioned way to add a catalog object without an example, and it is policed in both directions. The dict is currently empty (packages/atif-duck/src/atif_duck/domain/examples.py:100), and a stale key (or a key that still emits an example) failstest_exclusions_reference_real_catalog_objects(packages/atif-duck/tests/test_examples.py:200).atif-sql schemaprints every catalog, each object tagged with itsrequiresvalue. It reads the core and analytics schema and signature dicts (packages/atif-cli/src/atif_cli/app.py:2481-2486) and labels every name throughobject_requires, so a new view or macro lands in bothatif-sql schemaandatif-sql examplesoutput.packages/atif-cli/tests/test_app.py:146asserts set equality against the union ofVIEW_SCHEMAandANALYTICS_VIEW_SCHEMA, andpackages/atif-cli/tests/test_app.py:152-170pins therequiresvalues.- Column ORDER in
VIEW_SCHEMAis load-bearing. The drift test asserts tuple equality againstDESCRIBE, so reordering aSELECTlist in the DDL without reordering the catalog entry fails CI (packages/atif-duck/src/atif_duck/domain/catalog.py:55-58).
The vendored ATIF models, and the parity oracle behind the ported converters
Section titled “The vendored ATIF models, and the parity oracle behind the ported converters”Defined at: packages/atif-converter/src/atif_converter/domain/claude_code_conversion.py:981 (Claude Code) and packages/atif-converter/src/atif_converter/domain/codex_conversion.py:924 (Codex)
Production code builds on harbor 0.24.0’s ATIF data classes (harbor.models.trajectories) and
harbor.utils.trajectory_validator, vendored in packages/atif-converter/src/atif_converter/domain/atif/; harbor itself is a dev
dependency. packages/atif-converter/tests/test_vendored_atif.py compares the copy to the installed harbor. The conversion is a parity port of harbor 0.24.0’s
private converters, and those private methods are reachable from the tests only, as the oracle
(packages/atif-converter/tests/harbor_oracle.py:118, packages/atif-converter/tests/harbor_oracle.py:135), frozen to goldens under packages/atif-converter/tests/goldens/.
| Downstream | Type | Touch on change | Citation |
|---|---|---|---|
test_dev_only_imports_guard.py: ast guard: no harbor or litellm import in any src/ |
test | yes | packages/atif-converter/tests/test_dev_only_imports_guard.py |
test_vendored_atif.py: vendored files equal upstream’s; both models and validators agree on the goldens |
test | yes | packages/atif-converter/tests/test_vendored_atif.py |
harbor_adapter.convert_session() / codex_adapter.convert_codex_session(): the seams (converter in, validated dict out) |
direct import | yes | packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:157, packages/atif-converter/src/atif_converter/infrastructure/codex_adapter.py:67 |
FidelityGap / CodexFidelityGap: the losses the port inherited on purpose; fixing one is a decision to diverge from the oracle |
indirect | yes | packages/atif-converter/src/atif_converter/domain/fidelity.py:55, packages/atif-converter/src/atif_converter/domain/codex_fidelity.py:66 |
test_harbor_oracle.py: live oracle equals frozen goldens; a harbor bump that changes conversion fails here first |
test | yes | packages/atif-converter/tests/test_harbor_oracle.py:67 |
test_parity_live.py: newest N local sessions of each agent, both converters, every diverging path |
test | yes | packages/atif-converter/tests/test_parity_live.py:102 |
atif_converter.domain.enrichment / codex_enrichment: re-derive step identity by replicating the converter’s normalization order |
indirect | yes | packages/atif-converter/src/atif_converter/domain/enrichment.py:15-19, packages/atif-converter/src/atif_converter/domain/codex_enrichment.py:154 |
EXIT_CODES["harbor_missing"] = 127: retired, kept so the table never renumbers |
config | no | packages/atif-cli/src/atif_cli/errors.py:62 |
atif_cli.app.materialize: stamps the vendored models’ UPSTREAM_VERSION into every meta.json as harbor_version |
direct import | likely | packages/atif-cli/src/atif_cli/app.py:941 and packages/atif-cli/src/atif_cli/app.py:981 |
The materialized corpus artifact layout
Section titled “The materialized corpus artifact layout”Defined at: packages/atif-corpus/src/atif_corpus/domain/layout.py:18-32, specified by
docs/CONTRACT.md:22-60
atif-corpus writes it; atif-duck, atif-embed, and atif-analytics each read it independently, because the independence contract forbids them a shared reader. So the layout strings exist in several places with no import edge between them.
| Downstream | Type | Touch on change | Citation |
|---|---|---|---|
atif_corpus.application.materialize: the writer, routed through CorpusLayout |
direct import | yes | packages/atif-corpus/src/atif_corpus/application/materialize.py:135-144 and packages/atif-corpus/src/atif_corpus/application/materialize.py:1617 |
atif_duck.infrastructure.registry: builds globs under corpus_root / "sessions" |
indirect | yes | packages/atif-duck/src/atif_duck/infrastructure/registry.py:730-740 |
atif_embed.infrastructure.corpus_text_rows: its own sessions/ walk, gated on meta.json |
indirect | yes | packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:166-184 |
atif_analytics.infrastructure.corpus_reader: re-declares the filename constants |
indirect | yes | packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:72-74 and packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:178-191 |
atif_cli.app.convert: writes edges.jsonl beside a --trajectory-out trajectory on the one-shot path |
indirect | likely | packages/atif-cli/src/atif_cli/app.py:755-756 |
atif_cli.app.status: reads the watermark through the producer’s public read_watermark |
direct import | likely | packages/atif-cli/src/atif_cli/app.py:1281 |
docs/CONTRACT.md:22-60: the specification; the file declares itself orchestrator-owned and changeable only via itself |
config | yes | docs/CONTRACT.md:3 |
test_domain.py::TestCorpusLayout.test_contract_paths: pins every contract string |
test | yes | packages/atif-corpus/tests/test_domain.py:245-258 |
| Independent fixture-corpus builders, one per consuming package | test | yes | packages/atif-duck/tests/duck_fixtures.py:593, packages/atif-embed/tests/embed_fixtures.py:120, packages/atif-analytics/tests/analytics_fixtures.py:282 |
Blast-radius notes
Section titled “Blast-radius notes”- Nothing mechanical catches a layout change that updates one reader and misses the others. No
import edge connects them, so the only signal is each package’s own fixture corpus going out of
agreement with the writer. The
meta.json-written-last torn-set rule is re-implemented in every reader: evidence of the duplication cost, and one place per reader to edit if the completion marker ever moves (packages/atif-duck/src/atif_duck/infrastructure/registry.py:678-683,packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:28-29,packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:13-15). .stagingsits outsidesessions/for a reader-visibility reason, not tidiness. DuckDB’sread_jsonglob matches dot-dirs, so moving staging undersessions/exposes half-written session dirs to every reader and to the source-removal walk (packages/atif-corpus/src/atif_corpus/domain/layout.py:85-94).LossReport.to_json()’s keys are wire contract, not an internal shape. atif-duck reads them back through_LOSS_REPORT_COLUMNS, so renaming a key breaks theloss_reportsview with no type error anywhere (packages/atif-converter/src/atif_converter/domain/fidelity.py:147-171,packages/atif-duck/src/atif_duck/infrastructure/registry.py:326).
The domain Protocols
Section titled “The domain Protocols”Defined at: packages/atif-corpus/src/atif_corpus/domain/ports.py:101 (ConverterPort),
packages/atif-embed/src/atif_embed/domain/ports.py:37,
packages/atif-embed/src/atif_embed/domain/ports.py:65,
packages/atif-embed/src/atif_embed/domain/ports.py:89 (EmbeddingProvider,
VectorStorePort, TextRowsPort), packages/atif-models/src/atif_models/domain/ports.py:131
(LlmStructuredProvider)
The Protocols all sit under domain/, all satisfied structurally: there is no abstractmethod and no
DI container in this workspace. Some of them exist because an import-linter contract forbids the direct
dependency: ConverterPort (atif-corpus may not import atif-converter) and TextRowsPort (atif-embed
may not import atif-duck). The others are ordinary seams: atif-analytics is permitted to import
atif-models, and the atif-embed adapters live in the same package as their ports.
Nearly every adapter satisfies its Protocol without importing it. The exceptions take a
Protocol-typed argument themselves: LakeTextRows imports TextRowsPort to type the fallback reader
it wraps (packages/atif-embed/src/atif_embed/infrastructure/lake_text_rows.py:52), and the
analytics corpus reader imports SessionSource to type CorpusReader’s source
(packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:70). Each row below
marked indirect names a class whose only textual reference to the Protocol is a docstring, so no
import-graph query reaches it.
Blast-radius notes
Section titled “Blast-radius notes”- Adding a method to a Protocol breaks implementations that name it nowhere. Because the adapters
don’t import their Protocols, the mechanical signal is a
pyright/tyerror where an implementation is bound to a port type: a port-typed parameter, or an annotated name in a binding test. ForRealConverterthat call site is thematerializecommand inatif_cli.app, a different module from the class and a different package from the Protocol (packages/atif-cli/src/atif_cli/app.py:976,packages/atif-corpus/src/atif_corpus/application/materialize.py:1501).FakeConverterlives insrc/, so a missed update fails the type gate rather than only a test (packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py:5-8). ForLlmStructuredProviderthe call sites are the factory’s two return statements, one per adapter (packages/atif-models/src/atif_models/infrastructure/providers.py:31andpackages/atif-models/src/atif_models/infrastructure/providers.py:34). ConverterPortdeliberately permits any exception to cross it. The docstring states that implementations may raise anything and the materialize use case records the failure against the session and continues, because one broken transcript must never abort a corpus sync. Tightening this into a typed-error contract changes materialize’s control flow, not only a signature (packages/atif-corpus/src/atif_corpus/domain/ports.py:104-107).EmbeddingProvider.embed_documentsreturns one slot per input,Nonefor a failure, rather than raising. That is what bounds loss: a terminally failing batch must not discard sibling batches whose embeddings were already billed (packages/atif-embed/src/atif_embed/domain/ports.py:50-57).
The import-linter contracts
Section titled “The import-linter contracts”Defined at: pyproject.toml:553-614
The layers contracts, the independence contract over converter / corpus / duck / models / embed,
and the forbidden contract pinning atif-analytics to atif-models alone. These are the reason several of the
surfaces above exist as duplicated strings and Protocols instead of imports.
| Downstream | Type | Touch on change | Citation |
|---|---|---|---|
mise run lint:imports: uv run lint-imports, an entry in [tasks.check].depends |
config | yes | mise.toml:175 and mise.toml:250 |
root_packages: every import package must be listed, or a new member goes unchecked |
config | yes | pyproject.toml:554 |
atif_cli: the composition root, absent from independence.modules and listed in forbidden_modules; it declares its siblings and NOT atif-models, so the comment granting it that permission describes an unused allowance |
indirect | likely | pyproject.toml:608, pyproject.toml:614, packages/atif-cli/pyproject.toml:32-36 |
ConverterPort: exists because atif-corpus may not import atif-converter |
indirect | yes | packages/atif-corpus/src/atif_corpus/domain/ports.py:5-9 |
TextRowsPort: exists because atif-embed may not import atif-duck |
indirect | yes | packages/atif-embed/src/atif_embed/domain/ports.py:11-15 |
The embedding_guard twins: one rule, copied into atif-duck and atif-embed |
direct import | yes | packages/atif-duck/src/atif_duck/domain/embedding_guard.py:51 and packages/atif-embed/src/atif_embed/domain/embedding_guard.py:38 |
test_guard_twin_pin.py: reads the atif-duck copy as SOURCE TEXT via ast, never importing it |
test | yes | packages/atif-embed/tests/test_guard_twin_pin.py:41 and packages/atif-embed/tests/test_guard_twin_pin.py:55-72 |
| The independent corpus readers | indirect | yes | packages/atif-duck/src/atif_duck/infrastructure/registry.py:637, packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:166, packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:178 |
[tool.coverage.run] source: the same import packages, named rather than pathed |
config | likely | pyproject.toml:628-636 |
Blast-radius notes
Section titled “Blast-radius notes”- A new cross-package import fails the
lint:importsgate and nothing else.lint:importsrunsuv run lint-imports, and itssourceslist namespackages/*/src/**/*.py, the rootpyproject.toml, and the member manifests (mise.toml:174-175). The type checkers and the test suite stay green, so the contract violation surfaces only whenmise run checkreaches that gate. - atif-duck declares no
layerscontract. It hasdomain/andinfrastructure/and noapplication/, so its layer direction is convention rather than enforcement; only theindependencecontract constrains it (pyproject.toml:608). - Working around the contract by copying a rule is a sanctioned move that carries its own gate. The
embedding_guardtwins are the worked example: copies of one pure rule and one operator hint, compared through an AST read of the other package’s source because importing it would break the contract (packages/atif-embed/tests/test_guard_twin_pin.py:3-14).
EXIT_CODES: the CLI wire contract
Section titled “EXIT_CODES: the CLI wire contract”Defined at: packages/atif-cli/src/atif_cli/errors.py:30-66
Its keys map to the distinct codes 0, 2, 64, 65, 70, 77, 78, and 127. This is the contract an agent driving the CLI reads, so the dict is the surface, not the individual numbers, and not the messages.
| Downstream | Type | Touch on change | Citation |
|---|---|---|---|
atif_cli.app: the module-scope import; the dict is on the lean path |
direct import | yes | packages/atif-cli/src/atif_cli/app.py:73 |
atif_cli.app.convert: exits invalid_input, empty_session, runtime_error, validation_error |
direct import | yes | packages/atif-cli/src/atif_cli/app.py:742-772 |
atif_cli.app.query: parse_error, raised after the classified envelope is emitted |
direct import | yes | packages/atif-cli/src/atif_cli/app.py:1517-1528 |
atif_cli.app.embed: invalid_input plus a EXIT_CODES[kind] dispatch over the classifier’s verdict |
direct import | yes | packages/atif-cli/src/atif_cli/app.py:2003-2054 |
atif_cli.app.search: no_embeddings, the exit that tells an operator to run embed |
direct import | yes | packages/atif-cli/src/atif_cli/app.py:2256-2283 |
atif_cli.app.examples: invalid_input on an unknown --category / --requires value |
direct import | yes | packages/atif-cli/src/atif_cli/app.py:2409-2419 |
atif_cli.duck_errors: classifies duckdb.Error into parse / catalog / runtime plus embedding_mismatch |
direct import | yes | packages/atif-cli/src/atif_cli/duck_errors.py:26 and packages/atif-cli/src/atif_cli/duck_errors.py:35-90 |
atif_cli.cron: the install / status companion to the cron lanes, which exits invalid_input when it can’t find the refresh script; the lane itself, scripts/atif-sql-refresh.sh, suppresses retries on code 78 |
direct import | likely | packages/atif-cli/src/atif_cli/cron.py:32 and packages/atif-cli/src/atif_cli/cron.py:181, scripts/atif-sql-refresh.sh:489 |
ClassifiedError.to_payload(): the JSON error envelope on non-TTY stderr |
indirect | likely | packages/atif-cli/src/atif_cli/errors.py:78-86 |
test_app.py: asserts against the dict, never against a literal, except where the number itself is the claim |
test | yes | packages/atif-cli/tests/test_app.py:42 and packages/atif-cli/tests/test_app.py:1542 |
test_cron.py, test_integration.py, test_vss_commands.py: more suites keyed off the dict |
test | yes | packages/atif-cli/tests/test_cron.py:28, packages/atif-cli/tests/test_integration.py:147, packages/atif-cli/tests/test_vss_commands.py:23 |
docs/CONTRACT.md:300-336 §CLI: the documented command surface these codes are raised from |
config | likely | docs/CONTRACT.md:300-336 |
Blast-radius notes
Section titled “Blast-radius notes”- Several keys share a code on purpose.
empty_sessionandno_embeddingsboth exit 2;invalid_inputandparse_errorexit 64;catalog_error,validation_error,embedding_mismatch, andlake_mismatchexit 65;runtime_errorandsandbox_refusedexit 70; four keys exit 78. Renaming a key is safe for callers reading numbers and breaks every internalEXIT_CODES["..."]lookup, which is the opposite of the usual polarity (packages/atif-cli/src/atif_cli/errors.py:30-66). - Exit 78 is the only code with retry semantics attached.
terminal_statemeans an operator must act, so an unattended lane suppresses retries on it rather than burning identical ticks; a test pins both the mapping and the literal 78 (packages/atif-cli/src/atif_cli/errors.py:49-51,packages/atif-cli/tests/test_app.py:1542). - A code outside the dict is the failure mode this surface exists to prevent. An unhandled
traceback exits 1, which appears nowhere in
EXIT_CODES, and tests assert that a store mismatch still classifies instead of exiting 1, both at registration and when a lazily bound Lance scan trips mid-stream (packages/atif-cli/src/atif_cli/duck_errors.py:15-19,packages/atif-cli/tests/test_app.py:1449andpackages/atif-cli/tests/test_app.py:1831).
The published atif-sql distribution
Section titled “The published atif-sql distribution”Defined at: pyproject.toml:16-17
One distribution carries every capability, as a single wheel bundling every module tree. The
packages under packages/* are development members and not install targets, so their manifests
are dev wiring: what each may import, where its tests live, and how uv installs it editable.
Nothing resolves a member from an index. The published name and the entry module diverge on
purpose: the distribution is atif-sql, the console script’s module is atif_cli.
| Downstream | Type | Touch on change | Citation |
|---|---|---|---|
[project.dependencies]: the union of every member’s third-party requirements, and the ONLY thing an installer sees |
config | yes | pyproject.toml:41-56 |
[tool.hatch.build.targets.wheel] packages: the module trees the wheel carries; an omission ships a wheel missing a module |
config | yes | pyproject.toml:92-101 |
test_distribution.py: asserts the union, the absence of any atif-* requirement, and the module list with its py.typed markers |
test | yes | packages/atif-cli/tests/test_distribution.py:3-22 |
Dev pins in the CLI and analytics manifests, resolved locally through [tool.uv.sources] and never published |
config | likely | packages/atif-cli/pyproject.toml:32-36, packages/atif-analytics/pyproject.toml:23 |
[tool.commitizen] version_files: glob:regex entries rewriting the published version, every member manifest, and the dev pin blocks |
config | yes | pyproject.toml:537-542 |
[tool.commitizen] version: the single source of truth, because version_provider defaults to reading it |
config | yes | pyproject.toml:494-497 |
cz bump --check-consistency in release.yml: fails when the current version is absent from any listed file |
config | yes | .github/workflows/release.yml:222 |
pre_bump_hooks: uv lock then git add uv.lock, so the re-resolved lockfile lands in the bump commit |
config | yes | pyproject.toml:519-522 |
uv lock --check: enforced as a lefthook pre-commit step and as a CI step |
config | likely | mise.toml:96, lefthook.yml:95, .github/workflows/check.yml:57 |
The console script atif-sql = "atif_cli.app:main" |
config | likely | pyproject.toml:74 |
tag_format = "v$version": publish.yml strips the leading v, and release.yml’s gh release create --verify-tag fails if the formats diverge |
config | likely | pyproject.toml:502 |
Blast-radius notes
Section titled “Blast-radius notes”- The pin entries are per-file rather than globbed because
--check-consistencyrequires a hit in every matched file, and most members carry no dev pin at all. Adding a member-to-member dependency therefore needs a newversion_filesentry in the same change, or the next bump leaves that pin stale with nothing failing (pyproject.toml:523-542). - A third-party dependency added to a member is not added to the wheel. hatchling reads the root’s
[project.dependencies]verbatim, so a member that imports something the root does not require produces a wheel that installs cleanly and raisesModuleNotFoundErrorat run time. The union assertion intest_distribution.pyis what turns that into a failing gate (packages/atif-cli/tests/test_distribution.py:92). major_version_zero = true, so a breaking change moves 0.3.0 to 0.4.0. Reaching 1.0.0 is a decision, not a side effect of a!in a commit subject (pyproject.toml:503-505).
The lean import path of atif_cli.app
Section titled “The lean import path of atif_cli.app”Defined at: packages/atif-cli/src/atif_cli/app.py:50-53
A bare import atif_cli.app must not pull duckdb, harbor, boto3, or polars. The fast
path (schema, examples, --help, --version) needs none of them, and each costs hundreds of
milliseconds. Every heavy import is deferred into the command body that uses
it.
| Downstream | Type | Touch on change | Citation |
|---|---|---|---|
test_lean_import.py: a forbidden list checked in a FRESH interpreter via subprocess |
test | yes | packages/atif-cli/tests/test_lean_import.py:17-37 and packages/atif-cli/tests/test_lean_import.py:40-54 |
ruff PLC0415 (import-outside-top-level) ignored workspace-wide, 182 measured sites |
config | yes | pyproject.toml:225 |
atif_cli.errors: kept atif_*-free and duckdb-free so it stays on the lean path |
direct import | yes | packages/atif-cli/src/atif_cli/errors.py:19-21 |
atif_cli.duck_errors: the concrete duckdb.Error classifier, split out for exactly that reason |
direct import | yes | packages/atif-cli/src/atif_cli/duck_errors.py:26 and packages/atif-cli/src/atif_cli/duck_errors.py:35 |
atif_duck.domain.examples: a pure domain module with no duckdb import, safe on the lean path |
direct import | yes | packages/atif-duck/src/atif_duck/domain/examples.py:26-27 |
Deferred imports inside command bodies (convert, materialize, status, query, analyze, embed, search, examples, schema) |
runtime dispatch | likely | packages/atif-cli/src/atif_cli/app.py:727, packages/atif-cli/src/atif_cli/app.py:1544, packages/atif-cli/src/atif_cli/app.py:2481 |
atif_cli.cron: imported at module scope, so it must itself stay lean |
direct import | yes | packages/atif-cli/src/atif_cli/app.py:72 |
if TYPE_CHECKING: blocks: the only way to name a heavy type in an annotation on this path |
direct import | likely | packages/atif-cli/src/atif_cli/app.py:84 |
Blast-radius notes
Section titled “Blast-radius notes”- This is the one surface where moving an import to the top of the file (the normal, lint-preferred
shape) fails a test. The deferred import IS the contract, which is why
PLC0415is one of the workspace-wide ruff ignores and carries its measured site count inline (pyproject.toml:225). - The forbidden list names
atif_duck.infrastructurebut notatif_duck.domain. That split is what letsschemaandexamplesanswer from the static catalog at module-import cost whilequerypays for duckdb only when it runs (packages/atif-cli/tests/test_lean_import.py:21). - A new
atif_*package imported atapp.pymodule scope needs a forbidden-list entry too. The test enumerates modules, so a member absent from_FORBIDDEN_EAGER_IMPORTScan regress the fast path without failing anything (packages/atif-cli/tests/test_lean_import.py:17-37).
Other notable surfaces
Section titled “Other notable surfaces”DEFAULT_PRICING(packages/atif-duck/src/atif_duck/domain/catalog.py:612): models with published list rates. A model absent here lands incost_estimate’sunpriced_stepsrather than being dropped, and tests pin the table: an exact oracle (packages/atif-duck/tests/test_duck_views.py:519) and a corpus-coverage gate (packages/atif-duck/tests/test_duck_views.py:593).- The model alias registry (
packages/atif-models/src/atif_models/domain/registry.py:95): the(family, size)entries resolved throughresolve()atpackages/atif-models/src/atif_models/domain/registry.py:162.ModelSpecreaches every atif-analytics pipeline through the shared provider builder (packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:36-62), and more of it than the model id is live:chars_per_tokenturns each--dry-runprompt length into tokens (packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:341-343), and the long-context fields price the post-run usage (packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:78-84).spec.model_idappears in every pipeline’s--dry-runplan (packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:367) and in the provider log line, but no output parquet column records it, so a re-alias changes which model writes the next rows and nothing in the rows shows it. A new family needs a registry entry, a branch inbuild_structured_provider, which sends every family other thananthropicto the OpenAI adapter (packages/atif-models/src/atif_models/infrastructure/providers.py:28-34), and aRUNNABLE_FAMILIESentry (packages/atif-models/src/atif_models/infrastructure/settings.py:25). RECOVERY_HINT(packages/atif-embed/src/atif_embed/domain/embedding_guard.py:30and its atif-duck twin): one operator instruction, pinned by assertions including one that forbids naming a home directory and one that forbids inlining the path at the raise site (packages/atif-embed/tests/test_guard_twin_pin.py:85-100).- The
# noqa: S608sites:S608is enforced, and each suppression carries its static-catalog reason inline; some of them are in the examples generator, where the interpolated name comes from the catalog rather than from user input (packages/atif-duck/src/atif_duck/domain/examples.py:157,packages/atif-duck/src/atif_duck/domain/examples.py:174,pyproject.toml:210-213). - Watermark and quiescence (
packages/atif-corpus/src/atif_corpus/domain/watermark.py:57): the freshness rule that decides whatmaterializere-converts, surfaced tostatusthrough the producer’s publicread_watermark(packages/atif-cli/src/atif_cli/app.py:1291) rather than a private reader.