atif-sql · Contract map
Section titled “atif-sql · Contract map”What counts as a contract here. This workspace has almost no shared-type imports to trace,
because pyproject.toml:514-523 forbids most of them: five of the seven members may never import
each other, and atif-analytics may import only atif-models. What crosses a module boundary instead
is a shape agreed by two packages that cannot reference one another’s symbols — a file layout, a
JSON key order, a column projection, a typing.Protocol typed to a document rather than to an
implementation. So a contract in this file is any of:
- a
typing.Protocoldeclared in one package’sdomain/ports.pyand satisfied by an adapter elsewhere (there are exactly five, listed below); - a shape declared twice or more in packages that cannot import each other, where the two declarations must agree or a query silently returns wrong rows;
- a build-enforced dependency rule (import-linter), which is a stronger fact than a convention;
- an upstream API this workspace depends on and pins;
- a version constraint that ships in a wheel and binds an external installer.
Every Protocol here is satisfied STRUCTURALLY, with no import in either direction. That is the
single most important structural fact for reading this file. RealConverter
(packages/atif-cli/src/atif_cli/converter_adapter.py:44) implements ConverterPort
(packages/atif-corpus/src/atif_corpus/domain/ports.py:41) by shape alone; it never names the
Protocol, and it cannot, because atif-corpus may never import atif-converter. The Protocol name
appears at exactly two import sites in the whole workspace —
packages/atif-corpus/src/atif_corpus/application/materialize.py:92 (the module that CALLS it) and
packages/atif-cli/tests/test_converter_adapter.py:17 (the test that binds the two together in an
annotation). So each Protocol row below has a producer of the shape and an implementer the type
system never links to it, and the answer to “who finds out if the shape drifts” is a named test or
nobody. Do not read a Protocol row as a dependency edge — pyproject.toml:514-517 forbids the edge
it would imply.
Every contract below names its producer, its consumers, the verbatim shape, the assumptions
consumers make beyond the shape, and the drift risk. Consumer counts are grep-derived and
confirmed at each import, annotation, or call site — this repo has no code index
(.gitignore:24 lists .codegraph/, and no index exists on disk), so no count here comes from a
symbol graph. Two grep hazards shape every count: names collide across packages (DomainError in
three, EmbeddingProviderMismatch in two, cached_tokens in two coordinate spaces), so every
attribution here is by module path and never by bare name; and every cross-package import is
indented inside a function body or a TYPE_CHECKING block, so a line-anchored grep finds nothing.
Contracts are ordered by confirmed consumer count, descending.
The materialized corpus artifact layout
Section titled “The materialized corpus artifact layout”Producer: packages/atif-corpus/src/atif_corpus/domain/layout.py:18-22
Consumer(s):
packages/atif-duck/src/atif_duck/infrastructure/registry.py:210-213— inlines the four filenames as SQL glob literals for itsread_jsonreaders.packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:120-121— openstrajectory.jsonand gates onmeta.jsonwith its own DuckDB connection.packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:53-55— declaresTRAJECTORY_FILENAME/EDGES_FILENAME/META_FILENAMEa third time as its own constants.packages/atif-corpus/src/atif_corpus/application/materialize.py:222-238— the writer side, the only place the artifacts are produced.docs/CONTRACT.md:21-39— the hand-written specification all four agree to.
Shape:
<corpus_root>/ # default: ~/.atif-sql/corpus/<corpus-slug>/ sessions/<session_id>/ trajectory.json # compact JSON (separators=(',',':')), ATIF-v1.7 loss_report.json # atif_converter LossReport.to_json() edges.jsonl # one line per RAW record: {uuid, parent_uuid, # message_id, type, ts, is_sidechain, # is_compact_summary, source_file, tool_use_ids: [..]} meta.json # {session_id, source_mtime_ns, source_files: [...], # harbor_version, converter_version, materialized_at} watermark.json # {path: mtime_ns} across source corpusAssumptions consumers make:
meta.jsonpresent means the session dir is complete. All three readers implement the same torn-set gate independently:packages/atif-duck/src/atif_duck/infrastructure/registry.py:188-192restricts the trajectory/edges/loss readers to dirs wherev_raw_metahas a row;packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:121-124skips a dir with nometa.json;packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:171-180does the same and logs a warning. Nothing in the file layout expresses this — it is a write-ORDER promise made atpackages/atif-corpus/src/atif_corpus/application/materialize.py:209-212.- No reader ever sees a partially-written directory, because the writer stages all four
artifacts under
<corpus_root>/.staging/and swaps the whole dir withos.replace(packages/atif-corpus/src/atif_corpus/application/materialize.py:201-212), and.stagingis deliberately outsidesessions/so a DuckDB glob cannot reach it (packages/atif-corpus/src/atif_corpus/domain/layout.py:50-54). trajectory.jsonis ONE JSON document,edges.jsonlis newline-delimited. atif-duck encodes that split in its reader choice atpackages/atif-duck/src/atif_duck/infrastructure/registry.py:23-25; atif-embed re-derives it atpackages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:7-8.- A single trajectory document can be enormous. Both DuckDB readers set a 1 GiB
maximum_object_size, and their measured ceilings disagree:packages/atif-duck/src/atif_duck/infrastructure/registry.py:74-80cites 436 MB observed,packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:47-49cites 85 MB. Same constant, two independent justifications. source_mtime_nsand everywatermark.jsonvalue are epoch NANOSECONDS fromos.stat().st_mtime_ns— unit stated in the identifier and again atpackages/atif-corpus/src/atif_corpus/domain/watermark.py:12-13. atif-duck types the columnBIGINTatpackages/atif-duck/src/atif_duck/infrastructure/registry.py:136, which preserves the magnitude but drops the unit from the schema.materialized_atis an ISO-8601 UTC string, not a timestamp. atif-duck projects it asVARCHAR(packages/atif-duck/src/atif_duck/infrastructure/registry.py:140); the CLI supplies it (packages/atif-cli/src/atif_cli/app.py:400).
Where the hand-written contract disagrees with the code, the code wins — and it does disagree in
two places. docs/CONTRACT.md:61 heads its CLI section “atif-cli composes; only package importing
the other three”, while the manifest declares five sibling dependencies
(packages/atif-cli/pyproject.toml:32-36) and the CLI imports all five. docs/CONTRACT.md:16-17
lists VSS/semantic_search and the v2 LLM-analytics pipelines as out of scope and then reverses
itself at docs/CONTRACT.md:17-19; both are shipped commands
(packages/atif-cli/pyproject.toml:42 plus the embed / search / analyze commands). Read
docs/CONTRACT.md:21-39 as authoritative for the layout — that is the part four packages actually
implement — and the manifest as authoritative for who imports whom.
Drift risk: a fifth artifact, a renamed file, or a new meta.json key must be applied in four
places that no test links, and three of the four are reader-side, so an addition silently reaches
nobody. Mitigation: the writer-side constants at
packages/atif-corpus/src/atif_corpus/domain/layout.py:18-22 are the single source of truth on the
write side — any layout change starts there and then greps the three reader modules named above.
The static SQL catalog
Section titled “The static SQL catalog”Producer: packages/atif-duck/src/atif_duck/domain/catalog.py:28 (VIEW_NAMES), with
VIEW_SCHEMA:51, MACRO_NAMES:230, MACRO_SIGNATURES:247, ANALYTICS_VIEW_NAMES:269,
ANALYTICS_MACRO_SIGNATURES:287, TABLE_MACRO_NAMES:308, DESCRIPTIONS:326
Consumer(s):
packages/atif-cli/src/atif_cli/app.py:1099-1118— theschemacommand readsVIEW_SCHEMAandMACRO_SIGNATURESand answers with no DuckDB connection.packages/atif-duck/src/atif_duck/domain/examples.py:36-42— the examples generator imports all seven catalogs and derives one runnable query per object.packages/atif-cli/src/atif_cli/app.py:1025-1046— theexamplescommand callsbuild_examples().packages/atif-duck/tests/test_duck_views.py:31-33— theDESCRIBE-vs-VIEW_SCHEMAand DDL-vs-MACRO_SIGNATURESdrift tests.packages/atif-duck/tests/test_examples.py:27-35—DESCRIPTIONScoverage,ARG_EXEMPLARScoverage, and theAS TABLEset drift test.packages/atif-duck/tests/test_analytics_views.py:20-21— the analytics-side drift test.packages/atif-cli/tests/test_app.py:78-84— asserts the CLI’s JSON payload keys equal the catalog keys.
Shape:
VIEW_NAMES: tuple[str, ...] = ( "sessions", "steps", "messages", ...)
VIEW_SCHEMA: dict[str, tuple[tuple[str, str], ...]] = { "sessions": ( ("session_id", "VARCHAR"), ("cwd", "VARCHAR"), ... ), ...}
MACRO_SIGNATURES: dict[str, tuple[str, ...]] = { "ago": ("interval_text",), "model_used": ("sid",), ...}Assumptions consumers make:
- Column ORDER is part of the contract, not just the column set. The drift test asserts tuple
equality against
DESCRIBEoutput, stated atpackages/atif-duck/src/atif_duck/domain/catalog.py:47-50; reordering a view’sSELECTlist fails CI even though every column still exists. DESCRIPTIONScovers every object in every catalog, exactly.packages/atif-duck/tests/test_examples.py:181-185fails on a missing entry AND on a stale entry keyed to an object that no longer exists, so the dict is a bijection with the union of the four name catalogs.- Every macro parameter name has an
ARG_EXEMPLARSliteral, or example generation raises rather than emitting a broken query —packages/atif-duck/src/atif_duck/domain/examples.py:151-157fails loud, andpackages/atif-duck/tests/test_examples.py:189-194pre-empts it. TABLE_MACRO_NAMESdecides the SQL call shape.packages/atif-duck/src/atif_duck/domain/examples.py:159-162emitsSELECT * FROM name(args)for a member andSELECT name(args)otherwise, so a macro whose DDL gains or losesAS TABLEbreaks every derived example — pinned by the regex test atpackages/atif-duck/tests/test_examples.py:198-210.- The catalog is answerable without a corpus.
packages/atif-cli/src/atif_cli/app.py:1094-1098states the sub-50 ms, no-DuckDB-bind guarantee theschemacommand rests on; a runtimeDESCRIBEwould violate it. - The four raw readers are deliberately NOT in
VIEW_NAMES(packages/atif-duck/src/atif_duck/infrastructure/registry.py:65-68), so a consumer enumeratingVIEW_NAMESdoes not seev_raw_trajectoriesand friends.
Drift risk: adding a view or macro without a DESCRIPTIONS entry, an ARG_EXEMPLARS entry for
each new parameter name, or TABLE_MACRO_NAMES membership fails CI loudly — the risk is inverted
here, and the real exposure is a type that only the fixture corpus produces. Mitigation:
VIEW_SCHEMA["message_embeddings"] at packages/atif-duck/src/atif_duck/domain/catalog.py:218-224 hardcodes FLOAT[1024], so run the
drift test against a store built at a non-default output_dimension before changing that setting.
The seven import-linter architecture contracts
Section titled “The seven import-linter architecture contracts”Producer: pyproject.toml:462-523
Consumer(s):
mise.toml:197—lint:importsis gate 5 of the ninemise run checkgates, so the build is the consumer.packages/atif-corpus/src/atif_corpus/domain/ports.py:5-8— theConverterPortdocstring cites the independence contract as the reason the Protocol exists at all.packages/atif-embed/src/atif_embed/domain/ports.py:11-15— cites it as the reasonTextRowsPortexists.packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:5-10— cites it as the reason atif-embed carries its own corpus reader.packages/atif-duck/src/atif_duck/domain/embedding_guard.py:5-9— cites it as the reason the guard is duplicated.packages/atif-duck/src/atif_duck/infrastructure/analytics.py:13-15— cites it as the reason the analytics artifact names are pinned by hand.packages/atif-analytics/src/atif_analytics/domain/layout.py:8-10— cites it as the reason its layout is computed in one place.
Shape:
[tool.importlinter]root_packages = ["atif_converter", "atif_corpus", "atif_duck", "atif_models", "atif_analytics", "atif_embed", "atif_cli"]
[[tool.importlinter.contracts]]name = "converter / corpus / duck / models / embed are mutually independent (only atif-cli composes; atif-analytics may import atif-models only)"type = "independence"modules = ["atif_converter", "atif_corpus", "atif_duck", "atif_models", "atif_embed"]
[[tool.importlinter.contracts]]name = "atif-analytics imports only atif-models among workspace packages"type = "forbidden"source_modules = ["atif_analytics"]forbidden_modules = ["atif_converter", "atif_corpus", "atif_duck", "atif_embed", "atif_cli"]Assumptions consumers make:
- atif-cli is the only composition root, and it is absent from both the
independencemodule list (pyproject.toml:517) and theforbiddensource list (pyproject.toml:522). The one module that imports two independent packages ispackages/atif-cli/src/atif_cli/converter_adapter.py:36-38, and its docstring names that privilege explicitly atpackages/atif-cli/src/atif_cli/converter_adapter.py:5-9. - atif-analytics is absent from the independence list on purpose so it can compose atif-models,
with the
forbiddencontract pinning its other six edges shut — the reasoning is inline atpyproject.toml:509-513. Verified:grep -rn 'atif_duck' packages/atif-analytics/returns nothing, so the CONTRACT-V2-era design of reading atif-duck’s views is not what the code does. - atif-duck declares no
layerscontract. Five members do (pyproject.toml:465-508); atif-duck hasdomain/andinfrastructure/but noapplication/, so there is no third layer to order. - Every layered member’s
domain/is the innermost layer, which is why all five Protocols live indomain/ports.pyand none in anapplication/ports.py— no such file exists in this workspace. - The comment at
pyproject.toml:509-510grants a permission that is not exercised. It reads “ONLY atif-cli and atif-analytics may import atif-models”, but atif-cli neither declares atif-models inpackages/atif-cli/pyproject.toml:32-36nor imports it anywhere:grep -rn 'atif_models' packages/atif-cli/returns nothing. The live atif-models edge is atif-analytics’ alone, 22 import sites led bypackages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:28-29. - Every cross-package import in this workspace is INDENTED — inside a function body or a
TYPE_CHECKINGblock — becausePLC0415is ignored workspace-wide to satisfy the lean-import test (pyproject.toml:164). The four exceptions are module-scope imports in the two atif-cli modules that are themselves only ever imported inside a command body:packages/atif-cli/src/atif_cli/converter_adapter.py:36-38andpackages/atif-cli/src/atif_cli/duck_errors.py:26. A line-anchored grep for^from atif_finds zero cross-package consumers, which is why every count in this file comes from an unanchored grep confirmed at the site.
Drift risk: a new workspace member that is not added to root_packages (pyproject.toml:463) is silently
unchecked — import-linter reports “7 contracts, 7 kept” while the new package imports whatever it
likes. Mitigation: adding a packages/* member means adding it to root_packages and giving it
either a layers contract or a place in the independence list in the same commit.
LlmStructuredProvider — the structured-output port
Section titled “LlmStructuredProvider — the structured-output port”Producer: packages/atif-models/src/atif_models/domain/ports.py:116
Consumer(s):
packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:55(annotated atpackages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:86,packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:349)packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py:79(packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py:127,packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py:163,packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py:428)packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py:73(packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py:107,packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py:344)packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:76(packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:244,packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:538)packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:87(packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:141,packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:364)packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:28— the provider factory and the usage/budget plumbing.packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:143— the one production implementation.packages/atif-analytics/tests/analytics_fixtures.py:237— the deterministic double.
Shape:
@runtime_checkableclass LlmStructuredProvider(Protocol): """Port: one structured-output call. One adapter per backend.
The seam is deliberately narrow — everything provider-specific is fixed at adapter construction. """
async def classify_structured( self, *, system: str, prompt: str, schema: type[SchemaT] ) -> SchemaT: ...Assumptions consumers make:
-
The error taxonomy is binary and load-bearing.
RefusalErroris terminal andProviderUnavailableis retryable (packages/atif-models/src/atif_models/domain/ports.py:43-58), and the pipelines route on exactly that split — a third error type would fall through to neither the refusal sidecar nor the retry queue. -
The Protocol says nothing about token accounting, yet every consumer reads it off the concrete provider.
packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:70andpackages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:129both reach forgetattr(provider, "usage", None)becauseusageis not on the port; a conforming adapter without that attribute silently reports zero spend and theRunBudgetceiling never trips. -
CallUsagecounts are PER CALL, and two of the four are subsets of the other two.reasoning_tokensis a subset ofoutput_tokensandcached_tokensa subset ofinput_tokens(packages/atif-models/src/atif_models/domain/ports.py:64-70), soestimate_costatpackages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:73-77prices onlyinput_tokensandoutput_tokens— adding the other two would double-charge. -
UsageAccumulatormust be thread-safe, not task-safe. The lock is athreading.Lock(packages/atif-models/src/atif_models/domain/ports.py:87) because adapters dispatch blockinginvoke_modelthroughanyio.to_thread, reasoning stated atpackages/atif-models/src/atif_models/domain/ports.py:16-22. -
The budget is a stop-dispatch trigger, not a cap.
packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:101-112documents the overshoot bound asmax_cost_usdplus at mostBUDGET_CHECK_BATCHunits in flight; a consumer treatingmax_cost_usdas a hard ceiling is wrong. -
pricing_in/pricing_outonModelSpecare USD per 1,000,000 tokens, stated atpackages/atif-models/src/atif_models/domain/registry.py:42-43, andNonewhen unknown — whichestimate_cost(packages/atif-models/src/atif_models/domain/registry.py:125) turns into aNonecost rather than a zero. -
OpenAiBedrockProvidernever names the port either, and no test binds it to one. The single static link is the annotated return of the factory:packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:34declarestuple[LlmStructuredProvider, ModelSpec]andpackages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:45-57returns the concrete adapter, so ty and pyright check conformance at that one return statement.
Drift risk: the port is @runtime_checkable, so an isinstance check passes on method names
alone and would accept an adapter with the wrong parameter kinds (positional instead of
keyword-only), and no isinstance guard against it exists in the workspace anyway
(grep -rn 'isinstance(.*LlmStructuredProvider' packages/ returns nothing). Mitigation: the
keyword-only signature at packages/atif-models/src/atif_models/domain/ports.py:123-125 is the
contract, and the factory’s annotated return at
packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:34 is where a
mismatched adapter fails the typecheck gate.
The edges.jsonl line shape
Section titled “The edges.jsonl line shape”Producer: packages/atif-converter/src/atif_converter/domain/edges.py:22-32
Consumer(s):
packages/atif-duck/src/atif_duck/infrastructure/registry.py:107-117—_EDGE_COLUMNS, the same nine keys in the same order with DuckDB types.packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:300-301— reads theuuidfield for the conflicts pipeline’s returned-uuid validity guard.packages/atif-corpus/src/atif_corpus/application/materialize.py:224-227— writes the lines and owns line termination.packages/atif-duck/src/atif_duck/domain/catalog.py:330— themessagesview is defined as exactly this surface.
Shape:
#: Stable key order for one edges.jsonl line (contract-fixed shape).EDGE_FIELDS: tuple[str, ...] = ( "uuid", "parent_uuid", "message_id", "type", "ts", "is_sidechain", "is_compact_summary", "source_file", "tool_use_ids",)Assumptions consumers make:
- The producer emits lines WITHOUT trailing newlines and the writer adds them. Stated on the
port’s
edges_linesfield atpackages/atif-corpus/src/atif_corpus/domain/ports.py:30-33and honoured atpackages/atif-corpus/src/atif_corpus/application/materialize.py:224-227; a producer that terminated its own lines would double them. parent_uuidis a nullable string, and atif-duck declares itVARCHARoutright rather than letting JSON union inference decide — the reason is recorded atpackages/atif-duck/src/atif_duck/infrastructure/registry.py:103-106.tool_use_idsmeans two different things by record type: tool_use block ids for assistant records, thetool_use_idof tool_result blocks for user records (packages/atif-converter/src/atif_converter/domain/edges.py:11-13). One field, two semantics, discriminated by the siblingtypecolumn.edges.jsonlis the only source of raw-record identity. The trajectory cannot supply uuids (packages/atif-duck/src/atif_duck/infrastructure/registry.py:38-40, fidelity gap 7UUID_NOT_PRESERVED), which is why themessagesview is reconstructed from edges rather than from steps.records_totalin the loss report equals this file’s line count. Asserted atpackages/atif-converter/src/atif_converter/domain/fidelity.py:91-95— both derive from the same raw census, so a consumer may cross-check one against the other.
Drift risk: the nine keys are declared twice in packages that cannot import each other, and a
tenth key added on the producer side is simply invisible to _EDGE_COLUMNS — a projection reader
drops unknown keys without erroring. Mitigation: EDGE_FIELDS is a single tuple; changing it means
editing packages/atif-duck/src/atif_duck/infrastructure/registry.py:107-117 in the same commit.
EmbeddingProvider, VectorStorePort, TextRowsPort
Section titled “EmbeddingProvider, VectorStorePort, TextRowsPort”Producer: packages/atif-embed/src/atif_embed/domain/ports.py:31, packages/atif-embed/src/atif_embed/domain/ports.py:59, packages/atif-embed/src/atif_embed/domain/ports.py:87
Consumer(s):
packages/atif-embed/src/atif_embed/application/embed.py:35— imports all three; annotated atpackages/atif-embed/src/atif_embed/application/embed.py:46-47andpackages/atif-embed/src/atif_embed/application/embed.py:65-67.packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:285— theEmbeddingProvideradapter.packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:377— theVectorStorePortadapter.packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:156— theTextRowsPortadapter.packages/atif-embed/tests/embed_fixtures.py:171— the deterministicEmbeddingProviderdouble.packages/atif-embed/tests/test_embed_use_case.py:458— a recordingTextRowsPortdouble.
Shape:
class EmbeddingProvider(Protocol): @property def model_id(self) -> str: ... @property def dimension(self) -> int: ... async def embed_documents(self, texts: list[str]) -> list[list[float] | None]: ... def embed_query(self, text: str) -> list[float]: ...
class VectorStorePort(Protocol): def table_identity(self) -> tuple[str, int] | None: ... def get_embedded_hashes(self) -> dict[str, str]: ... def delete_uuids(self, uuids: Iterable[str]) -> int: ... def add_chunk(self, df: pl.DataFrame) -> None: ... def optimize(self) -> None: ... def ensure_index(self, *, metric: str = "cosine") -> None: ...
class TextRowsPort(Protocol): def iter_unembedded( self, corpus_root: Path, *, embedded: dict[str, str] | None = None, limit: int | None = None, ) -> Iterator[PendingText]: ...Assumptions consumers make:
-
embed_documentsreturns one slot per input text in input order, and the caller enforces it.packages/atif-embed/src/atif_embed/application/embed.py:191zips withstrict=True, so a provider returning a different length raisesValueErrorinstead of misattributing vectors to texts. -
A
Noneslot means “not embedded this run”, not “failed the run”. The port documents bounded loss atpackages/atif-embed/src/atif_embed/domain/ports.py:47-51;packages/atif-embed/src/atif_embed/application/embed.py:192-198filters theNones, counts them as skipped, and leaves those uuids for the next pass. -
The store’s
(model, dim)stamp is checked BEFORE any append,packages/atif-embed/src/atif_embed/application/embed.py:166-175, using the identitytable_identity()returns —Nonethere means empty and any provider may claim it. -
add_chunkaccepts a 7-column polars frame with a fixed-sizepl.Array, not apl.List. The schema is built atpackages/atif-embed/src/atif_embed/application/embed.py:216-238and the reason is stated atpackages/atif-embed/src/atif_embed/application/embed.py:212-215: a variable-size list is rejected by Lance for indexing. -
iter_unembedded’s laziness bounds only the CALLER’s residency. The port says so explicitly atpackages/atif-embed/src/atif_embed/domain/ports.py:111-114— peak resident text inside an implementation is each adapter’s own problem, andpackages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:54-60establishes it by batching on BYTES (4 MiB) rather than on file count. -
Staleness is decided by hash, not by uuid presence. A uuid under a different
text_hashis yielded withreplaces_existing=True(packages/atif-embed/src/atif_embed/domain/ports.py:105-109), and the caller must delete before appending or the uuid fans out to two vectors —packages/atif-embed/src/atif_embed/application/embed.py:203-209. -
The CLI does not inject these ports.
packages/atif-cli/src/atif_cli/app.py:826-831callsrun_backfillwith onlycorpus_root,settings,limit, anddry_run; the use case constructs each default adapter itself under a deferred import (packages/atif-embed/src/atif_embed/application/embed.py:103-110,packages/atif-embed/src/atif_embed/application/embed.py:157-159) so a dry run never loads boto3. -
No test binds any of the three adapters to its Protocol. Unlike
ConverterPort, these three have no conformance assertion anywhere inpackages/atif-embed/tests/; the only static link is the defaulting assignment insiderun_backfill, where each concrete class is assigned to a parameter already annotated with the port (packages/atif-embed/src/atif_embed/application/embed.py:103-110andpackages/atif-embed/src/atif_embed/application/embed.py:157-159against the annotations atpackages/atif-embed/src/atif_embed/application/embed.py:65-67). ty and pyright check those three assignments; if the defaulting branch were ever refactored to construct the adapters elsewhere, nothing would check conformance at all.
Drift risk: dimension is read once per run and stamped on every row (packages/atif-embed/src/atif_embed/application/embed.py:161-162), so
a provider whose width depends on the input rather than on configuration would write a store whose
rows disagree with their own dim column. Mitigation: dimension is a property fixed at adapter
construction from settings — packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:305-307 reads it from
packages/atif-embed/src/atif_embed/infrastructure/settings.py:31, never from a response.
ConverterPort and ConversionOutput
Section titled “ConverterPort and ConversionOutput”Producer: packages/atif-corpus/src/atif_corpus/domain/ports.py:41 (ConverterPort) and packages/atif-corpus/src/atif_corpus/domain/ports.py:21
(ConversionOutput)
Consumer(s):
packages/atif-corpus/src/atif_corpus/application/materialize.py:92— imports the port; annotated atpackages/atif-corpus/src/atif_corpus/application/materialize.py:193andpackages/atif-corpus/src/atif_corpus/application/materialize.py:480.packages/atif-cli/src/atif_cli/converter_adapter.py:38-69—RealConverter, the production adapter and the only module importing both atif-converter and atif-corpus.packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py:16-65— the test adapter that ships insrc/, not intests/.packages/atif-cli/tests/test_converter_adapter.py:17— asserts the mapping and bindsRealConverterto the port annotation atpackages/atif-cli/tests/test_converter_adapter.py:91.packages/atif-cli/src/atif_cli/app.py:399— wiresRealConverter()into the use case.
Shape:
@dataclass(frozen=True, slots=True)class ConversionOutput: trajectory_dict: dict[str, Any] loss_report_dict: dict[str, Any] edges_lines: list[str]
class ConverterPort(Protocol): """Anything that can turn one session JSONL into corpus artifacts.
Implementations may raise any exception: the materialize use case records the failure against the session and continues — one broken transcript must never abort a corpus sync. """
def convert(self, session_jsonl: Path) -> ConversionOutput: """Convert one session (main JSONL + its side-files) to artifacts.""" ...Assumptions consumers make:
RealConverternever names the Protocol it implements. It imports only the return-value dataclass (packages/atif-cli/src/atif_cli/converter_adapter.py:38), so nothing in either package links the class to the port. The single static link in the workspace is the annotated assignmentconverter: ConverterPort = RealConverter()atpackages/atif-cli/tests/test_converter_adapter.py:91, whose own comment (packages/atif-cli/tests/test_converter_adapter.py:89-90) states that the assignment is what ty verifies. Delete that test and a signature change on either side becomes a runtimeAttributeErrorat materialize time.- The port is typed to
docs/CONTRACT.md’s artifact shapes, not to converter internals — three loosely-typeddict[str, Any]/list[str]fields instead of the converter’s ownConversionResult. The docstring atpackages/atif-corpus/src/atif_corpus/domain/ports.py:3-10names the independence contract as the reason, so the weak typing is the contract, not an omission. - Exceptions cross this port by design.
packages/atif-corpus/src/atif_corpus/domain/ports.py:44-47says an implementation may raise anything and the use case records the failure and continues.RealConverterrelies on that: it raisesTrajectoryValidationErrorfor an invalid trajectory rather than materializing it (packages/atif-cli/src/atif_cli/converter_adapter.py:63-64, reasoning atpackages/atif-cli/src/atif_cli/converter_adapter.py:21-25). trajectory_dictis the ENRICHED trajectory and is NOT yet compact-serialized. The writer ownsseparators=(",", ":")— the mapping decision is stated atpackages/atif-cli/src/atif_cli/converter_adapter.py:13-14and the writer applies it atpackages/atif-corpus/src/atif_corpus/application/materialize.py:222.loss_report_dictisLossReport.to_json()-shaped with enum members flattened to strings (packages/atif-cli/src/atif_cli/converter_adapter.py:15-16), which is what makes it readable by atif-duck’s_LOSS_REPORT_COLUMNSprojection atpackages/atif-duck/src/atif_duck/infrastructure/registry.py:122-131.- Importing the adapter drags harbor.
packages/atif-cli/src/atif_cli/converter_adapter.py:27-29forbids importing it atatif_cli.appmodule scope, andpackages/atif-cli/src/atif_cli/app.py:387obeys by importing inside the command body — enforced by the fresh-interpreter lean-import test named atpyproject.toml:164.
Drift risk: the three dict[str, Any] fields mean a converter that renames a trajectory key
type-checks perfectly and produces artifacts atif-duck’s explicit projections silently null out;
and because the implementer never names the port, a convert signature change on either side is
caught by exactly one assertion. Mitigation:
packages/atif-cli/tests/test_converter_adapter.py:88-92 is that assertion — treat it as part of
the contract, not as a redundant smoke test — and
packages/atif-cli/tests/test_converter_adapter.py:17-60 pins the field mapping while atif-duck’s
DESCRIBE drift test catches a projection that stops matching.
Token semantics and the pricing table
Section titled “Token semantics and the pricing table”Producer: packages/atif-duck/src/atif_duck/infrastructure/registry.py:41-44 (the semantics)
and packages/atif-duck/src/atif_duck/domain/catalog.py:405 (DEFAULT_PRICING)
Consumer(s):
packages/atif-duck/src/atif_duck/infrastructure/registry.py:1066-1083— thecost_estimatemacro, which consumes both.packages/atif-duck/src/atif_duck/infrastructure/registry.py:1020—register_macrosresolves the pricing override againstDEFAULT_PRICING.packages/atif-duck/src/atif_duck/domain/catalog.py:72-75— thestepsview schema publishesprompt_tokens,completion_tokens,cached_tokens,cache_creationto every SQL consumer.packages/atif-duck/src/atif_duck/domain/catalog.py:347-350— the agent-facingcost_estimatedescription carries the trust condition.
Shape:
# Model pricing per 1M tokens (in_rate, out_rate) at public list rates from# Anthropic's published pricing page.DEFAULT_PRICING: dict[str, tuple[float, float]] = { "claude-fable-5": (10.0, 50.0), "claude-opus-5": (5.0, 25.0), "claude-sonnet-5": (2.0, 10.0), "claude-haiku-4-5": (1.0, 5.0), ...}Assumptions consumers make:
prompt_tokensis a CUMULATIVE TOTAL, per step: non-cached + cache_read + cache_creation. Stated atpackages/atif-duck/src/atif_duck/infrastructure/registry.py:41-44and read frommetrics.prompt_tokensatpackages/atif-duck/src/atif_duck/infrastructure/registry.py:376-377. The identifier does not say so, so a consumer summingprompt_tokens + cached_tokensdouble-counts every cache read.cached_tokensis the cache-READ subset ofprompt_tokens, not an additional quantity (packages/atif-duck/src/atif_duck/infrastructure/registry.py:380-381).cost_estimatetherefore pricesprompt_tokens - cached_tokensand documents that identity atpackages/atif-duck/src/atif_duck/infrastructure/registry.py:1043-1049; cache reads are charged nothing.in_rate/out_rateare USD per 1,000,000 tokens. The unit appears in neither name — only the comment atpackages/atif-duck/src/atif_duck/domain/catalog.py:395and the/ 1e6atpackages/atif-duck/src/atif_duck/infrastructure/registry.py:1071carry it.est_cost_usdis USD and covers the PRICED steps only. It is meaningful only whenunpriced_steps = 0, stated three times:packages/atif-duck/src/atif_duck/infrastructure/registry.py:1053-1058,packages/atif-duck/src/atif_duck/domain/catalog.py:347-350, and theLEFT JOINshape itself atpackages/atif-duck/src/atif_duck/infrastructure/registry.py:1079.unpriced_stepscounts only steps with a non-NULLmodel_name, because user steps carry no model and cost nothing —packages/atif-duck/src/atif_duck/infrastructure/registry.py:1060-1063. Counting them would put every conversation above zero and mask real pricing gaps.- Cache write and read multipliers are NOT modelled.
packages/atif-duck/src/atif_duck/domain/catalog.py:402-404states that the 1.25x / 2x / 0.1x tiers are out of scope, soest_cost_usdunder-reports a cache-heavy session. - A step’s
model_namematches a pricing row by dated-suffix-stripping prefix (packages/atif-duck/src/atif_duck/infrastructure/registry.py:1080), soclaude-haiku-4-5-20251001prices asclaude-haiku-4-5.
Drift risk: cached_tokens exists in two coordinate spaces under one name — atif-duck’s
per-step cache-read count (packages/atif-duck/src/atif_duck/infrastructure/registry.py:380) and atif-models’ per-call
CallUsage.cached_tokens (packages/atif-models/src/atif_models/domain/ports.py:75) — and neither
name states its scope, so a cross-plane join or a copied formula silently mixes them. Mitigation:
attribute the field by module path, and read the producer docstring
(packages/atif-duck/src/atif_duck/infrastructure/registry.py:41-44 or
packages/atif-models/src/atif_models/domain/ports.py:64-70) before using either in arithmetic.
The EmbeddingProviderMismatch guard, declared twice
Section titled “The EmbeddingProviderMismatch guard, declared twice”Producer: two coordinate declarations, coupled by contract rather than by import —
packages/atif-embed/src/atif_embed/domain/errors.py:29 with the rule at
packages/atif-embed/src/atif_embed/domain/embedding_guard.py:38, and
packages/atif-duck/src/atif_duck/domain/embedding_guard.py:33 with the rule at packages/atif-duck/src/atif_duck/domain/embedding_guard.py:46
Consumer(s):
packages/atif-embed/src/atif_embed/application/embed.py:166-175— the WRITE path callsensure_store_matchesbefore appending.packages/atif-duck/src/atif_duck/infrastructure/registry.py:830-861— the READ/bind path calls it before bindingmessage_embeddings(guard-before-bind).packages/atif-cli/src/atif_cli/duck_errors.py:26-31— puts atif-duck’s copy inREGISTRATION_ERRORSand maps it to exit 65 atpackages/atif-cli/src/atif_cli/duck_errors.py:74-81.packages/atif-embed/tests/test_guard_twin_pin.py:93— reads both twin modules as source text and requires the recovery hint to appear after theraisekeyword.
Shape:
def ensure_store_matches( *, stored_model: str | None, stored_dim: int | None, expected_model: str, expected_dim: int | None,) -> None: if stored_model is None or stored_dim is None: return model_ok = stored_model == expected_model dim_ok = expected_dim is None or stored_dim == expected_dim if model_ok and dim_ok: return raise EmbeddingProviderMismatch(...)Assumptions consumers make:
- The two copies must stay behaviourally identical, including the message. Both docstrings say
so —
packages/atif-duck/src/atif_duck/domain/embedding_guard.py:5-11andpackages/atif-embed/src/atif_embed/domain/embedding_guard.py:15-18— and the pin test is what makes it more than a comment. - The message text must be constructed INSIDE the
raise. Hoisting it to a local satisfies ruff’sEM102/TRY003and defeats the twin pin, so both rules are suppressed on purpose atpackages/atif-duck/src/atif_duck/domain/embedding_guard.py:67-74. - A
Noneon either stored value means “empty store, any provider may claim it” — not “unknown, be careful” (packages/atif-duck/src/atif_duck/domain/embedding_guard.py:61-62). expected_dim=Nonetrustsmodel_idalone, and dim is checked only because Cohere’s single model id can emit different Matryoshka widths (packages/atif-duck/src/atif_duck/domain/embedding_guard.py:57-59).- The two classes have different base classes and that matters at the CLI. atif-duck’s derives
from
Exception(packages/atif-duck/src/atif_duck/domain/embedding_guard.py:33) while atif-embed’s derives from that package’sDomainError(packages/atif-embed/src/atif_embed/domain/errors.py:29), so a bareexcept duckdb.Erroron the registration path would let atif-duck’s escape as exit 1 — the reasonREGISTRATION_ERRORSwidens the caught set, stated atpackages/atif-cli/src/atif_cli/duck_errors.py:14-18. - The recovery hint must not name a fixed home directory. The store lives at
<corpus_root>/embeddings_lanceby default, and both copies record that naming the wrong path makes the operator delete nothing (packages/atif-duck/src/atif_duck/domain/embedding_guard.py:21-24andpackages/atif-embed/src/atif_embed/domain/embedding_guard.py:25-29).
Drift risk: the twin pin checks that the hint constant reaches the raise; it does not check
that the two hint STRINGS are equal, so the copies could diverge in wording while both tests pass.
Mitigation: treat the two RECOVERY_HINT literals as one value — change both in the same commit,
which is what both docstrings instruct.
harbor’s private _convert_events_to_trajectory
Section titled “harbor’s private _convert_events_to_trajectory”Producer: upstream, harbor.agents.installed.claude_code.ClaudeCode, pinned
harbor>=0.22.0,<0.23 at packages/atif-converter/pyproject.toml:23
Consumer(s):
packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:32— the method name lives in one named module constant,_CONVERT_METHOD.packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:178— the sole call site, reached viagetattr.packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:112—assert_harbor_private_api(), the pre-flight probe, invoked atpackages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:167.packages/atif-converter/tests/test_snapshot_and_drift.py:303-329— the drift alarm: deletes and then corrupts the attribute and asserts the probe raises.packages/atif-converter/tests/test_convert_and_audit.py:153— calls the private method directly to pin observed 0.22.0 behavior.packages/atif-converter/src/atif_converter/domain/fidelity.py:44—FidelityGap, the seven known conversion gaps, is the typed record of what this method loses.
Shape:
#: The private harbor entry point this whole package is built on._CONVERT_METHOD = "_convert_events_to_trajectory"
def assert_harbor_private_api() -> None: from harbor.agents.installed.claude_code import ClaudeCode
if not callable(getattr(ClaudeCode, _CONVERT_METHOD, None)): ... raise HarborPrivateApiMissing(msg)Assumptions consumers make:
- The method takes a DIRECTORY, not a file, laid out as
<session>.jsonlplus optionalsubagents/*.jsonlunder a<session-stem>/subdirectory — reconstructed by symlink staging atpackages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:54-94. - harbor’s own subagent discovery cannot see workflow-nested side-files, so the adapter stages
every
*.jsonlFLAT into the harbor-visiblesubagents/dir with__-joined collision-safe names (packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:71-92). This is fidelity gap 1, and it is why the parity oracle expected a superset (docs/CONTRACT.md:74-76). - Directory symlinks are not enough. Python 3.13’s
Path.rglobdoes not descend a symlinked directory, so per-FILE symlinks under real directories are mandatory or every subagent transcript vanishes silently (packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:74-77). - A missing attribute is a FLEET failure, not a session failure. Without the probe an upstream
rename surfaces as one
AttributeErrorper session swallowed intoConversionError(packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:114-118), which is exactly the shapeHarborPrivateApiMissingexists to distinguish (packages/atif-converter/src/atif_converter/domain/errors.py:47-54). - harbor ships no
py.typed, so every import from it is untyped and the surface is confined to this one module (packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:11-12). Nonefrom the method means “no convertible events”, not “error” — mapped toEmptySessionErrorand to exit code 2 (packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:157,packages/atif-cli/src/atif_cli/errors.py:27).
Drift risk: the whole package rests on an API upstream owes nobody, and a rename inside 0.22.x
would still satisfy the pin. Mitigation: the ceiling is <0.23 with the re-audit obligation written
into the manifest (packages/atif-converter/pyproject.toml:19-23), and the probe converts a rename
into one specific, actionable error before any session is attempted.
The atif-sql distribution’s ==0.1.0 sibling pins
Section titled “The atif-sql distribution’s ==0.1.0 sibling pins”Producer: packages/atif-cli/pyproject.toml:32-36 and
packages/atif-analytics/pyproject.toml:24
Consumer(s):
pyproject.toml:446-452— commitizen’sversion_files, which rewrites every pin on each bump.packages/atif-cli/pyproject.toml:56-60—[tool.uv.sources], the local-workspace resolution that these pins deliberately do NOT express.packages/atif-analytics/pyproject.toml:56— the same pairing for its single sibling dependency.pyproject.toml:399-406—[tool.commitizen] version = "0.1.0", the one version this repo publishes.pyproject.toml:17-18— the distribution is namedatif-sqlat the workspace root, while the console script’s module staysatif_cliin a member.
Shape:
dependencies = [ "atif-analytics==0.1.0", "atif-converter==0.1.0", "atif-corpus==0.1.0", "atif-duck==0.1.0", "atif-embed==0.1.0", "cyclopts>=4.10.2", "loguru>=0.7.3",]version_files = [ 'packages/*/pyproject.toml:^version', 'packages/atif-cli/pyproject.toml:^\s*"atif-', 'packages/atif-analytics/pyproject.toml:^\s*"atif-models==',]Assumptions consumers make:
[tool.uv.sources]is invisible to an external installer. uv’s build backend does not translate a workspace source into a version constraint, so a bare name would ship asRequires-Dist: atif-duckand resolve from PyPI to whatever the newest release of that name is, owned by whoever owns it — the reasoning is inline atpackages/atif-cli/pyproject.toml:24-31.- One version covers the whole repository. commitizen reads its own
versionkey rather than the PEP 621 field (pyproject.toml:401-403), andversion_filespropagates it to the published[project] versionand to all seven member manifests; seven numbers for one artifact is the shape being refused. - The
version_filesentries are per-file rather than globbed on purpose, because--check-consistencyrequires a hit in every matched file and five of the seven members carry no dev pin between members (pyproject.toml:436-439). - The lockfile must land in the same commit as the versions it resolves.
pre_bump_hooksrunuv lockandgit add uv.lockafter the rewrite and before the commit (pyproject.toml:422-433), becauseuv.lockrecords every member’s version andmise run lock:checkwould otherwise fail on the release commit itself. - A breaking change moves 0.1.0 to 0.2.0, not 1.0.0, because
major_version_zero = true(pyproject.toml:416) — reaching 1.0.0 is a decision, not a side effect of a!in a subject line.
Drift risk: a hand-edited pin, or a renamed member whose pin line stops matching its
version_files regex, goes stale silently until a release. Mitigation: --check-consistency fails
rather than skipping a file that no longer contains the current version — the guarantee is stated at
pyproject.toml:434-436.
Other contracts
Section titled “Other contracts”LossReport.to_json()— producerpackages/atif-converter/src/atif_converter/domain/fidelity.py:114, whose docstring calls the keys wire contract; consumed bypackages/atif-duck/src/atif_duck/infrastructure/registry.py:122-131as_LOSS_REPORT_COLUMNSand bypackages/atif-cli/src/atif_cli/converter_adapter.py:67.record_countsandgaps_observedstayJSONbecause they are an enum-keyed dict and a sorted list of enum values.- The
meta.jsonprovenance record — written atpackages/atif-corpus/src/atif_corpus/application/materialize.py:230-237, projected atpackages/atif-duck/src/atif_duck/infrastructure/registry.py:134-141.harbor_versionandconverter_versionare supplied by the CLI (packages/atif-cli/src/atif_cli/app.py:401-402), so the corpus records which converter built it without atif-corpus importing either. - The embeddings-store row shape — 7 Arrow fields written at
packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:115-121, of which themessage_embeddingsview exposes 5 (packages/atif-duck/src/atif_duck/domain/catalog.py:218-224).text_hashandtruncatedare unreachable from SQL, so a query cannot distinguish a head-only-embedded row from a complete one. - The Lance schema version sidecar —
SCHEMA_VERSION = 2andschema_version.jsonatpackages/atif-embed/src/atif_embed/infrastructure/lance_store.py:61-67, kept as a sidecar rather than a column because reading it must not require the table. - The analytics parquet layout — 11 artifact names at
packages/atif-analytics/src/atif_analytics/domain/layout.py:22-41, of which atif-duck pins 9 as_ANALYTICS_SOURCES(packages/atif-duck/src/atif_duck/infrastructure/analytics.py:53-64).REFUSALS_DIRNAME(packages/atif-analytics/src/atif_analytics/domain/layout.py:32) has no view, so the refusal audit its docstring calls queryable is not reachable from SQL. - The CLI exit-code taxonomy —
EXIT_CODESatpackages/atif-cli/src/atif_cli/errors.py:25-39, consumed at 12 sites inpackages/atif-cli/src/atif_cli/app.pyand mapped from DuckDB exceptions atpackages/atif-cli/src/atif_cli/duck_errors.py:34-63. Code 78 is the load-bearing one: it means an operator must act, and unattended lanes suppress retries on it (packages/atif-cli/src/atif_cli/errors.py:35-37). PendingTextand the text stamp —packages/atif-embed/src/atif_embed/domain/text_stamp.py:50, withMAX_EMBEDDABLE_CHARS = 50_000atpackages/atif-embed/src/atif_embed/domain/text_stamp.py:35in CHARACTERS per text; the same constant governs both the row’struncatedflag and the adapter’s wire-level clip so the two can never disagree (packages/atif-embed/src/atif_embed/domain/text_stamp.py:31-34).- The
steps-view rendering semantics, mirrored without a shared symbol —packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:23-33reproduces four of atif-duck’sstepsrules by hand (message-union flattening,extra.source_uuids[0]as the step key,agent→assistant, error recovery fromobservation.results[].extra.tool_result_metadata.is_error), andpackages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:12-25reproduces two of them again. - The
ATIF_SQL_settings prefix — the one env namespace across all seven members;packages/atif-embed/src/atif_embed/infrastructure/settings.py:31pinsoutput_dimension: Literal[256, 512, 1024, 1536] = 1024, which is the value the static catalog hardcodes asFLOAT[1024]. - The lean-import contract —
pyproject.toml:164andpyproject.toml:181record thatatif_cli.appmust not import duckdb, harbor, lancedb, boto3, or polars at module scope, asserted in a fresh interpreter bypackages/atif-cli/tests/test_lean_import.py; 182 deferred-import sites exist because of it.
See also
Section titled “See also”- impact analysis — 49 shared source citations
- module map — 37 shared source citations
- processes — 35 shared source citations
- business logic — 33 shared source citations
- components — 20 shared source citations