Skip to content

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, and atif_cli.lake_sessions) that import siblings at module scope, and atif_cli.app imports 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. DomainError is declared independently in several packages and EmbeddingProviderMismatch in more than one: packages/atif-duck/src/atif_duck/domain/embedding_guard.py:33 derives from bare Exception, packages/atif-embed/src/atif_embed/domain/errors.py:29 from atif-embed’s own DomainError. Near-names compound it: packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:44 imports EmbeddingProviderUnavailable, which is not the EmbeddingProvider Protocol.
  • 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.

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

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

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

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.

Downstream Type Touch on change Citation
LlmStructuredProvider ← the atif-analytics use-case modules, every one importing it under if TYPE_CHECKING: direct import yes packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:32, packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:69, packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py:83, packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:81, packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:94
build_structured_provider: picks the adapter by spec.family and returns it typed as the port, so it’s where ty and pyright check both adapters against it direct import yes packages/atif-models/src/atif_models/infrastructure/providers.py:16 and packages/atif-models/src/atif_models/infrastructure/providers.py:20-34
OpenAiBedrockProvider / AnthropicBedrockProvider: the two real adapters, one per family; each imports CallUsage / ProviderUnavailable / RefusalError / SchemaT / UsageAccumulator from the port module and NOT the Protocol indirect yes packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:41-47 and packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:81, packages/atif-models/src/atif_models/infrastructure/anthropic_bedrock.py:61-67 and packages/atif-models/src/atif_models/infrastructure/anthropic_bedrock.py:130
test_models_ports.py: binds OpenAiBedrockProvider to the port by annotation and isinstance; the Anthropic adapter has no such test test yes packages/atif-models/tests/test_models_ports.py:29-38
FakeProvider: the deterministic double, named in prose only test yes packages/atif-analytics/tests/analytics_fixtures.py:300
ConverterPort ← materialize use case, in several signature positions direct import yes packages/atif-corpus/src/atif_corpus/application/materialize.py:615, packages/atif-corpus/src/atif_corpus/application/materialize.py:877, packages/atif-corpus/src/atif_corpus/application/materialize.py:1501
RealConverter: the production adapter; imports ConversionOutput and three other types from the port module, never ConverterPort, which it names only in docstrings indirect yes packages/atif-cli/src/atif_cli/converter_adapter.py:69-74 and packages/atif-cli/src/atif_cli/converter_adapter.py:80-81
FakeConverter: shipped in src/ rather than tests/; same shape, same non-import indirect yes packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py:21 and packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py:72
ConversionOutput: the frozen dataclass that DOES cross the boundary as an import direct import yes packages/atif-corpus/src/atif_corpus/domain/ports.py:65-66
EmbeddingProvider / VectorStorePort / TextRowsPort ← the one embed use case, under if TYPE_CHECKING: direct import yes packages/atif-embed/src/atif_embed/application/embed.py:37, packages/atif-embed/src/atif_embed/application/embed.py:48-49, packages/atif-embed/src/atif_embed/application/embed.py:67-69
The production adapters (two embedding providers, the Lance store, and the corpus text-rows reader), none importing its Protocol indirect yes packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:285, packages/atif-embed/src/atif_embed/infrastructure/gemma_local.py:193, packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:597, packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:229
test_converter_adapter.py: imports ConverterPort and binds RealConverter to it by annotation; test_embed_ports.py and test_models_ports.py do the same for the embed and model ports test yes packages/atif-cli/tests/test_converter_adapter.py:17 and packages/atif-cli/tests/test_converter_adapter.py:92, packages/atif-embed/tests/test_embed_ports.py:27
[tool.coverage.report] exclude_also: a bare ... line is excluded because a Protocol body is a signature config no pyproject.toml:652-655

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
  • A new cross-package import fails the lint:imports gate and nothing else. lint:imports runs uv run lint-imports, and its sources list names packages/*/src/**/*.py, the root pyproject.toml, and the member manifests (mise.toml:174-175). The type checkers and the test suite stay green, so the contract violation surfaces only when mise run check reaches that gate.
  • atif-duck declares no layers contract. It has domain/ and infrastructure/ and no application/, so its layer direction is convention rather than enforcement; only the independence contract constrains it (pyproject.toml:608).
  • Working around the contract by copying a rule is a sanctioned move that carries its own gate. The embedding_guard twins 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).

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

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
  • The pin entries are per-file rather than globbed because --check-consistency requires a hit in every matched file, and most members carry no dev pin at all. Adding a member-to-member dependency therefore needs a new version_files entry 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 raises ModuleNotFoundError at run time. The union assertion in test_distribution.py is 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).

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
  • 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 PLC0415 is one of the workspace-wide ruff ignores and carries its measured site count inline (pyproject.toml:225).
  • The forbidden list names atif_duck.infrastructure but not atif_duck.domain. That split is what lets schema and examples answer from the static catalog at module-import cost while query pays for duckdb only when it runs (packages/atif-cli/tests/test_lean_import.py:21).
  • A new atif_* package imported at app.py module scope needs a forbidden-list entry too. The test enumerates modules, so a member absent from _FORBIDDEN_EAGER_IMPORTS can regress the fast path without failing anything (packages/atif-cli/tests/test_lean_import.py:17-37).