This is the full developer documentation for atif-sql
# For agents
> Which atif-sql surface to fetch for which question, what not to assume about the corpus, and the shortest path from nothing to a row of results.
This page is addressed to an AI agent working with atif-sql, and it is written so the person reading over your shoulder can read it too. Every claim on it points at behavior the repository’s own tests check.
## 1. The catalog outranks this page
[Section titled “1. The catalog outranks this page”](#1-the-catalog-outranks-this-page)
`atif-sql schema` prints the whole query contract: every view with its columns, and every macro with its signature. It reads a static catalog rather than the corpus, so it answers in well under a tenth of a second, needs no materialized data, and doubles as a liveness check on the install. Its output ends with a pointer to the examples command.
`atif-sql examples` — the same thing as `atif-sql query --examples` — prints a runnable query per view and per macro. Those queries are **derived from the catalog**, never hardcoded per object, and every one is executed against a fixture corpus by `atif-duck`’s test suite. Filter with `--requires core|analytics|vss` and `--category view|table-macro|scalar-macro`. Piped output is JSON.
**For an agent.** Prefer `atif-sql schema` and `atif-sql examples` to this page wherever the two could disagree. Both are generated from the catalog, so they are right and this page is stale. This page adds what a schema cannot state: what a silence means, and which surface you are on.
## 2. Assumptions to drop
[Section titled “2. Assumptions to drop”](#2-assumptions-to-drop)
* **The materialized corpus is what the views read, not `~/.claude`.** `atif-sql` converts each session to ATIF once, writes it under the corpus root, and every view reads those artifacts. A session that has not been materialized is absent from every view — not empty, absent. `atif-sql materialize` is what makes it visible, and `atif-sql status` replays the same planning decision read-only, so it answers “what would a materialize pass do right now” without converting anything.
* **A session still being written is skipped on purpose.** Materialization is gated on quiescence: a session is converted when its newest source file has been silent for longer than the quiesce threshold and is newer than the recorded watermark. So the most interesting session — the one open in another terminal — is the one deliberately not in the corpus yet. `--force` overrides the watermark; nothing overrides physics, so wait for the write to settle.
* **`requires` on an example is a precondition, not a hint.** An example tagged `analytics` needs `atif-sql analyze` to have run; one tagged `vss` needs an embedding run. Query one before its pipeline has run and you get a catalog miss with exit 65, which is a different problem from a query that legitimately matched nothing.
* **A bare `atif-sql embed` exits 64 rather than doing the obvious thing.** A real embedding run calls Bedrock once per unembedded step, so it requires an explicit scope: `--limit N` or `--all`. The refusal exists so a mistyped command cannot start a full backfill. `--dry-run` needs no scope and spends nothing.
* **Conversion loss is accounted for, not hidden.** Every materialized session carries a loss report beside its trajectory, and each known upstream conversion gap is a named type in the converter’s fidelity policy rather than a paragraph in a changelog. A field absent from the ATIF document is not evidence that it was absent from the transcript — read the loss report before concluding that.
* **Branch on the exit code, never on the message text.** The codes are a stable wire contract: `64` for malformed input or malformed SQL, `65` for a catalog miss or a store written by another embedding provider, `70` for anything the database or an adapter raises, `78` for a state only an operator can clear, `127` for a missing harbor install, and `2` for a parsed-but-empty result an automated caller should treat as “nothing to do”. The message beside the code is prose a maintainer rewrites freely.
## 3. Which surface you are on
[Section titled “3. Which surface you are on”](#3-which-surface-you-are-on)
The test for each row is free — readable from your tool list, your shell, or the repository — and never a call that has to fail first.
| Signal | Surface | Entry point |
| ---------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| You can run a shell command | CLI | `atif-sql schema`, then the command you need |
| You can execute Python in this workspace | In-process library | Import the package that owns the capability: `atif_duck` to register views on a connection, `atif_corpus` to materialize |
| Neither | Read-only | You are reading documentation. Fetch the Markdown, not the HTML — section 7 has the URLs |
The CLI and the library are the same implementation: `atif-cli` is a composition root and holds no query logic of its own, so a fact learned on one surface transfers to the other. The layering is enforced rather than documented — import-linter fails the build when a leaf package imports another leaf package.
**For an agent.** Read your own tool list and environment to find your surface, rather than running a command and inspecting the failure. `atif-sql schema` needs no corpus and no credentials, so it is the cheapest probe that distinguishes “installed” from “not installed”.
## 4. The shortest path to a row of results
[Section titled “4. The shortest path to a row of results”](#4-the-shortest-path-to-a-row-of-results)
```plaintext
mise trust && mise install && mise run install # first-time setup, from the repository root
atif-sql materialize # scan, convert, write; skips non-quiescent sessions
atif-sql schema # the contract: views, columns, macro signatures
atif-sql query 'SELECT * FROM tool_rank(30) LIMIT 10'
```
Two habits change the shape of everything after. Output adapts to the channel: a TTY gets a plain table, a pipe gets JSON — so redirect when you intend to parse, and never parse the table. And an example is meant to be copied verbatim before it is adapted: the session-id exemplar in every example is a subquery over `sessions`, so a pasted example runs against any corpus rather than needing an id you do not have yet.
Errors arrive on stderr as a JSON envelope when the channel is a pipe, carrying a kind, a message and a hint. Exit 0 is success. An exit in the sixties is fixed by changing the call; an exit of 70 or 78 is fixed by changing the environment.
## 5. What to avoid
[Section titled “5. What to avoid”](#5-what-to-avoid)
* **Scraping these pages.** Every one of them is served as Markdown at its own path with `.md` appended, and section 7 has the URLs. Scraping the rendered HTML buys navigation chrome, a search widget and a theme toggle in exchange for reading three paragraphs.
* **Re-deriving trajectory semantics in SQL.** The whole reason the conversion happens at the boundary is that a view over raw transcript JSON re-implements the adapter, badly, once per view. If a fact about a trajectory is missing, it belongs in the converter or in the catalog, not in your query.
* **Reading an analytics or vector view before its pipeline has run.** Point back at section 2: that is a catalog miss, and the fix is a command rather than a different query.
* **Naming a Bedrock model id anywhere outside `atif-models`.** The alias registry exists so a model swap is one edit. A hardcoded id elsewhere is a second source of truth that nothing reconciles.
* **Adding a view or macro without its catalog entries.** The drift tests require a description entry, an argument exemplar for any new parameter name, table-macro membership when the DDL declares one, and a derived example that actually executes. A view added without them fails the suite rather than shipping undocumented.
* **Embedding without a scope, or with `--all` on a corpus you have not sized.** `--dry-run` prints the plan for free. Read it first.
## 6. Read next
[Section titled “6. Read next”](#6-read-next)
This table is built at publish time from the set of pages the site actually wrote, so a page added to the documentation tree appears here without an edit and a removed page cannot leave a row behind. The right-hand column is that page’s raw Markdown twin — fetch that instead of the page.
| Read this | Raw Markdown |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| [atif-sql](/atif-sql/): ATIF-native analytics over Claude Code agent trajectories — convert sessions to ATIF, materialize a corpus, query it through DuckDB views. | [`/.md`](/atif-sql/.md) |
| [Documentation](/atif-sql/readme/) | [`/readme.md`](/atif-sql/readme.md) |
| [Data flow](/atif-sql/architecture/data-flow/) | [`/architecture/data-flow.md`](/atif-sql/architecture/data-flow.md) |
| [Module map](/atif-sql/architecture/module-map/) | [`/architecture/module-map.md`](/atif-sql/architecture/module-map.md) |
| [System overview](/atif-sql/architecture/system-overview/) | [`/architecture/system-overview.md`](/atif-sql/architecture/system-overview.md) |
| [CLI](/atif-sql/reference/cli/) | [`/reference/cli.md`](/atif-sql/reference/cli.md) |
| [Public API](/atif-sql/reference/public-api/) | [`/reference/public-api.md`](/atif-sql/reference/public-api.md) |
| [Processes](/atif-sql/behavior/processes/) | [`/behavior/processes.md`](/atif-sql/behavior/processes.md) |
| [State machines](/atif-sql/behavior/state-machines/) | [`/behavior/state-machines.md`](/atif-sql/behavior/state-machines.md) |
| [Dead code](/atif-sql/analysis/dead-code/) | [`/analysis/dead-code.md`](/atif-sql/analysis/dead-code.md) |
| [Risk hotspots](/atif-sql/analysis/risk-hotspots/) | [`/analysis/risk-hotspots.md`](/atif-sql/analysis/risk-hotspots.md) |
| [Components](/atif-sql/diagrams/architecture/components/) | [`/diagrams/architecture/components.md`](/atif-sql/diagrams/architecture/components.md) |
| [Sequences](/atif-sql/diagrams/behavioral/sequences/) | [`/diagrams/behavioral/sequences.md`](/atif-sql/diagrams/behavioral/sequences.md) |
| [Dependency graph](/atif-sql/diagrams/structural/dependency-graph/) | [`/diagrams/structural/dependency-graph.md`](/atif-sql/diagrams/structural/dependency-graph.md) |
| [Business logic](/atif-sql/insights/business-logic/) | [`/insights/business-logic.md`](/atif-sql/insights/business-logic.md) |
| [Contract map](/atif-sql/insights/contract-map/) | [`/insights/contract-map.md`](/atif-sql/insights/contract-map.md) |
| [Debugging guide](/atif-sql/insights/debugging-guide/) | [`/insights/debugging-guide.md`](/atif-sql/insights/debugging-guide.md) |
| [Impact analysis](/atif-sql/insights/impact-analysis/) | [`/insights/impact-analysis.md`](/atif-sql/insights/impact-analysis.md) |
| [Tech debt](/atif-sql/insights/tech-debt/) | [`/insights/tech-debt.md`](/atif-sql/insights/tech-debt.md) |
## 7. The machine surfaces
[Section titled “7. The machine surfaces”](#7-the-machine-surfaces)
Any page here is available as Markdown: append `.md` to its path. `/agents/` is served at [`/agents.md`](/atif-sql/agents.md). That holds for every page on the site, generated ones included, and each page links its own twin from `` with `rel="alternate" type="text/markdown"`. The media type on the response is the static host’s to send, so read the path convention and the head link as the contract and the header as a courtesy.
The whole site comes three ways. [`llms.txt`](/atif-sql/llms.txt) is the index, and it lists this page first. [`llms-full.txt`](/atif-sql/llms-full.txt) is every page in one file. [`llms-small.txt`](/atif-sql/llms-small.txt) is the same corpus with non-essential content removed, for a tighter context.
Citations on the generated pages are repository permalinks pinned to one commit, so a line anchor keeps naming the line it was written about. The raw twins deliberately keep the bare `path:line` form instead: that is the string you can hand to a grep.
**For an agent.** Fetch `llms-small.txt` before `llms-full.txt`. When you already know which page you want, fetch that page’s `.md` twin instead of either bundle — a fraction of the tokens for the same text.
# atif-sql
> ATIF-native analytics over Claude Code agent trajectories — convert sessions to ATIF, materialize a corpus, query it through DuckDB views.
atif-sql reads Claude Code session transcripts, converts each session to [ATIF](https://github.com/laude-institute/harbor) — Harbor’s Agent Trajectory Interchange Format — materializes the results as a corpus of ATIF documents on disk, and layers DuckDB views and macros over that corpus. Converting once at the boundary, with an explicit and tested fidelity policy for what the upstream adapter drops, replaces re-deriving trajectory semantics inside every SQL view.
It is a uv Python workspace, Apache-2.0, and it replaces `claude-sql`.
The conversion pipeline: Claude Code session JSONL is converted once to an ATIF corpus on disk, and every DuckDB view, analytics pipeline and embedding run reads that corpus.
## The packages
[Section titled “The packages”](#the-packages)
| Package | What it owns |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `atif-converter` | The Harbor `ClaudeCode` adapter wrapper, and the fidelity policy that accounts for every known upstream conversion gap per session |
| `atif-corpus` | Corpus materialization: source discovery, watermarks, quiescence, atomic artifact writes |
| `atif-duck` | The DuckDB views and macros over the materialized corpus, declared in a drift-tested static catalog |
| `atif-models` | The model alias registry and the structured-output LLM client. No other package names a Bedrock model id |
| `atif-analytics` | The v2 pipelines: classify, trajectory, conflicts, friction, cluster, terms, community |
| `atif-embed` | Cohere Embed v4 on Bedrock, a LanceDB vector store, and the embedding backfill |
| `atif-cli` | The cyclopts CLI that composes the rest |
Each package is layered, and the independence contract is enforced by import-linter rather than by convention: converter, corpus, duck, models and embed may never import one another, and `atif-cli` is the only composition root.
## The discovery loop
[Section titled “The discovery loop”](#the-discovery-loop)
```plaintext
atif-sql schema # every view with its columns, and every macro signature, from the static catalog
atif-sql examples # runnable example queries, derived from that catalog and executed by the tests
atif-sql query 'SELECT * FROM tool_rank(30) LIMIT 10'
```
`atif-sql examples` derives its output from the catalog rather than carrying hardcoded strings, and every example is executed against a fixture corpus by `atif-duck`’s own test suite — so the listing cannot drift from the schema it documents.
**For an agent.** Start at [For agents](/atif-sql/agents/), which names which surface to fetch for which question and what not to assume about this repository. Prefer `atif-sql schema` and `atif-sql examples` to anything on this site: both are generated from the catalog, so they are right and a page can be stale.
# Dead code
# atif-sql · Dead code
[Section titled “atif-sql · Dead code”](#atif-sql--dead-code)
Measured 2026-08-28.
**Nothing in this workspace is deletable code.** Of 314 public top-level definitions across the 100 files under `packages/*/src`, 34 have no reference outside their own file, and **zero** have no reference anywhere. Every one of the 34 resolves to a call, construction, or annotation site inside its own module. What the two tables below record is a narrower defect: an **export declaration with no consumer** — a name in an `__all__` that nothing imports.
**Technique, and what it cannot see.** No code index covers this repo (there is no `.codegraph/`, no LSP index, no AST symbol graph), and no dead-code analyzer is wired into the project: `grep -n "vulture|dead|unused|deptry|knip"` over `pyproject.toml` and `mise.toml` returns one unrelated hit, ty’s `unused-ignore-comment` at [`pyproject.toml:383`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L383). The finding set is therefore derived, in three passes:
1. **AST enumeration.** `ast.parse` every file under `packages/*/src/**/*.py`, collecting every top-level `FunctionDef` / `AsyncFunctionDef` / `ClassDef` / module-level assignment whose name does not begin with `_`, together with its `__all__` membership. 314 definitions across 100 files; 56 of the 100 modules declare an `__all__`.
2. **Unanchored reference index.** For each name, the whole-word pattern `(?/sessions//` tree and exposes it as the stable SQL surface, with raw readers built as `CREATE TEMP TABLE` over `read_json` under an explicit strict-projection `columns` filter ([`packages/atif-duck/src/atif_duck/infrastructure/registry.py:3-31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L3-L31)). Every registration function follows a register-or-fail-loud contract: log through `logger.exception` and re-raise on any DDL failure ([`:31-32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L31-L32), [`:816-819`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L816-L819), [`:1217-1220`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1217-L1220)).
**Recent activity.** 10 commits in the 30-day window against a median of 3, so `↑ rising` under the mechanical rule — which here means the file was written and rewritten during the six-day authoring run, not that it is destabilizing.
**Owners.** `bgagent` at 100% — every commit touching the path, the 10 above plus 1 merge — a bot identity.
**Findings.** 9 of the workspace’s 15 `B608` sites live here, each carrying a per-line `# noqa: S608` that names its interpolation: a module-constant table name ([`:185`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L185), [`:237`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L237)), a glob escaped through `sql_literal` ([`:254`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L254), [`:277`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L277), [`:300`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L300), [`:319`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L319)), two local regex literals ([`:743`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L743)), an `int()`-coerced width ([`:962`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L962)), and the pricing rates ([`:1099`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1099)). The file also holds one of the five type-checker defects: `_pricing_values_clause` escapes the model name through `sql_literal` and coerces both rates with `float()` ([`:975-1002`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L975-L1002), specifically [`:999`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L999)), because the `pricing` parameter is reachable by an in-process embedding caller even though the CLI never passes one ([`:992-998`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L992-L998)). Its 12 uncovered units cluster in exactly the paths no test drives: 3 in `register_views` ([`:333-819`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L333-L819)), 3 in `register_macros` ([`:1005-1220`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1005-L1220)), 2 in `register_vss` ([`:846-967`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L846-L967)), 2 in `_warn_incomplete_session_dirs` ([`:175-193`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L175-L193)), and 2 in `_pricing_values_clause` itself. The `register_vss` gap is the load-bearing one — the ATTACH-failure arm that degrades a Lance directory to an empty-store fallback and logs rather than raising ([`:906-910`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L906-L910)) is untested silent degradation.
### `atif_analytics.infrastructure.parquet_cache` — score 7.3
[Section titled “atif\_analytics.infrastructure.parquet\_cache — score 7.3”](#atif_analyticsinfrastructureparquet_cache--score-73)
**What’s there.** The sharded parquet cache behind the analytics pipelines: `write_part` appends a new shard, `read_all` unions every shard, and `replace_sessions` drops a session’s prior rows so a re-flush cannot duplicate them ([`packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py:90-111`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L90-L111), [`:85-100`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L85-L100), [`:159-221`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L159-L221)). `_shard_may_hold` prunes shards by parquet footer statistics and fails open on missing or unreadable stats ([`:116-156`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L116-L156)), which is the safe direction.
**Recent activity.** 3 commits, exactly the median, so `→ flat`. This file ranks second on defect signals alone with no help from churn, which is the clearest demonstration that the two are independent here.
**Owners.** `bgagent` at 100% — every commit touching the path — a bot identity.
**Findings.** Zero scanner findings and three of the workspace’s 11 unguarded IO windows, all in the same file. `write_part`‘s sharded branch drops `part-.parquet` straight into the live directory ([`:73-74`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L73-L74)) that `iter_part_files` globs ([`:57`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L57)), with no tmp-then-rename; the code anticipates concurrency explicitly, since the nanosecond suffix exists to avoid collisions “when two part-writes land in the same millisecond under concurrency” ([`:70-72`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L70-L72)). `read_all` then hands every globbed part to `pl.read_parquet` with no size or completeness check ([`:100`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L100)), while `MIN_PARQUET_BYTES`’ own docstring states the size check “is what keeps a torn artifact from failing a pipeline that could skip it” ([`:26-31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L26-L31)) and the legacy single-file branch does apply it ([`:78`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L78)). A reader can therefore pick up a shard polars is still writing, and the guard the module documents protects only the path it is not on. `replace_sessions` compounds it by rewriting shards in place ([`:213`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L213)) and unlinking emptied ones ([`:209`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L209)). The coverage gap lands on the same two functions: of 33 uncovered units, 11 are in `replace_sessions` and 9 in `write_part`. It also owns the `max-returns = 5` ratchet through `_shard_may_hold` ([`pyproject.toml:268`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L268), [`:182`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L182); the function at [`packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py:145`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L145)).
### `atif_cli.app` — score 7.1
[Section titled “atif\_cli.app — score 7.1”](#atif_cliapp--score-71)
**What’s there.** The cyclopts composition root and the only module importing atif-converter, atif-corpus, and atif-duck together, wiring every cross-package seam — the `ConverterPort` adapter, the clock, version pins, the DuckDB connection ([`packages/atif-cli/src/atif_cli/app.py:3-15`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L3-L15)). Heavy imports are deferred into the command bodies so the `schema` / `--help` fast path stays lean, a property pinned by a fresh-interpreter test rather than by a lint ([`:22-26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L22-L26)).
**Recent activity.** 15 commits, the highest count in the repo, so `↑ rising` — expected of a composition root that gains a wiring line whenever any member changes.
**Owners.** `bgagent` at 100% — every commit touching the path, the 15 above plus 3 merges — a bot identity.
**Findings.** One `B608` at the `search` kNN query, whose `# noqa` records that `dim` is `len(vector)` while the session id, `k`, and the vector itself are `?`-bound ([`:925-937`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L925-L937)). It carries the `max-args = 19` ratchet through `search`, whose 19 parameters are the CLI flags cyclopts binds ([`pyproject.toml:264`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L264), [`:180`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L180); the function at [`packages/atif-cli/src/atif_cli/app.py:860`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L860)). The 56 uncovered units — the largest single-file gap in the workspace — concentrate in the three commands that reach outward: 18 in `analyze` ([`:627-725`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L627-L725)), 10 in `convert` ([`:225-291`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L225-L291)), and 9 in `status` ([`:412-488`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L412-L488)), with 4 in `search` ([`:828-948`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L828-L948)) and 4 in `main` ([`:1093-1103`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1093-L1103)). `analyze`, `embed`, and `search` are the three billable commands that call Bedrock, so the least-covered command body in the tree is also the one that spends money; the uncovered region inside `analyze` is the settings-override chain that applies the budget ceilings a crontab line depends on ([`:691-707`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L691-L707)).
### `atif_converter.domain.enrichment` — score 6.2
[Section titled “atif\_converter.domain.enrichment — score 6.2”](#atif_converterdomainenrichment--score-62)
**What’s there.** A pure function over a trajectory dict plus raw records — no harbor import, no IO — that repairs three of the seven named fidelity gaps by re-running harbor’s deterministic normalization order ([`packages/atif-converter/src/atif_converter/domain/enrichment.py:3-30`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L3-L30)). It exists because harbor 0.22.0 puts no record identity in `step.extra`: it reads `requestId` off the message dict and `id` off the event dict, while real transcripts carry both on the event, so nothing lands in extra and there is nothing to join on ([`:15-20`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L15-L20)).
**Recent activity.** 7 commits against a median of 3, so `↑ rising`.
**Owners.** `bgagent` at 100% — every commit touching the path — a bot identity.
**Findings.** Zero scanner findings, zero IO windows — this is a pure function — and both complexity ratchets in the workspace that a single function sets: `max-branches = 39` and `max-complexity = 38` are both pinned at `enrich_trajectory` ([`pyproject.toml:267`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L267), [`:181`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L181), [`:185-190`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L185-L190); the function at [`packages/atif-converter/src/atif_converter/domain/enrichment.py:198`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L198)). Each branch is one named fidelity gap, which is why the count is a ratchet rather than a target. Its 42 uncovered units split 18 inside `enrich_trajectory` ([`:198-412`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L198-L412)) and 10 inside `_visible_user_text` ([`:113-146`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L113-L146)), the helper that replicates harbor’s rules for which user records produce a visible text message. Both untested regions are alignment-failure paths: the user-step mismatch arm that logs a warning and truncates attribution from that step onward ([`:350-360`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L350-L360)) is the behavior a reader most needs pinned, since it silently narrows enrichment coverage rather than failing.
### `atif_analytics.application.use_cases.friction` — score 5.3
[Section titled “atif\_analytics.application.use\_cases.friction — score 5.3”](#atif_analyticsapplicationuse_casesfriction--score-53)
**What’s there.** A four-tier friction detector over short user-role messages: a pre-filter, a regex fast path at confidence 0.9, three deterministic stamp rules, and an LLM tier for everything else ([`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:3-27`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L3-L27)). The `source` column is the row’s provenance tier rather than the engine that computed it, and downstream atif-duck views bind to those literal values, so the vocabulary is a fixed contract ([`:29-33`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L29-L33)).
**Recent activity.** 7 commits against a median of 3, so `↑ rising`.
**Owners.** `bgagent` at 100% — every commit touching the path — a bot identity.
**Findings.** Zero scanner findings and the `max-statements = 119` ratchet, set at `_friction_async` ([`pyproject.toml:269`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L269), [`:183`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L183); the function at [`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:240`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L240)). The coverage gap and the complexity outlier are the same region: 35 of the file’s 43 uncovered units sit inside `_friction_async` ([`:240-528`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L240-L528)), with 5 in `detect_user_friction` ([`:531-635`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L531-L635)) and 3 in `deterministic_stamps` ([`:178-232`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L178-L232)). This is the file where the two signals coincide most tightly — the longest function in the workspace is also the least-exercised, and it is the function that decides which candidate messages cross into the billable LLM tier. The budget-guard posture that governs that decision assumes every candidate reaches the LLM even though roughly half survive the fast tiers ([`:583-585`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L583-L585)), and the third-party disclosure is explicit: short user message bodies leave the machine on tier 4 ([`:35-36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L35-L36)).
## See also
[Section titled “See also”](#see-also)
* [impact analysis](../insights/impact-analysis.md) — 6 shared source citations
* [tech debt](../insights/tech-debt.md) — 6 shared source citations
* [module map](../architecture/module-map.md) — 5 shared source citations
* [processes](../behavior/processes.md) — 5 shared source citations
* [components](../diagrams/architecture/components.md) — 5 shared source citations
# Data flow
# atif-sql · Data flow
[Section titled “atif-sql · Data flow”](#atif-sql--data-flow)
The distribution declares exactly one entry point, the console script `atif-sql = "atif_cli.app:main"` ([`packages/atif-cli/pyproject.toml:42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L42)), and the `main` it names installs a WARNING-and-up loguru sink and hands control to cyclopts — [`packages/atif-cli/src/atif_cli/app.py:1125`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1125). Every process below therefore begins as a CLI invocation; there is no HTTP, RPC, or queue surface to enter through.
Two of the three flows below produce the corpus and one consumes it: `materialize` fills `/sessions/` ([`:339`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L339)), `analyze` fills `/analytics/` ([`:627`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L627)), and `query` binds both and runs caller SQL over them ([`:497`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L497)). The other commands are subsets of these three — `convert` ([`:225`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L225)) is one iteration of flow 1’s inner loop, `search` ([`:828`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L828)) re-enters flow 2’s registration at [`:886`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L886) and adds one kNN statement, `embed` ([`:734`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L734)) writes the vector store flows 2 and 3 read, and `schema` ([`:1055`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1055)), `examples` ([`:963`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L963)), and `status` ([`:412`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L412)) answer from static data or `stat` calls with no downstream participant.
## Flow 1: corpus materialization (`atif-sql materialize`)
[Section titled “Flow 1: corpus materialization (atif-sql materialize)”](#flow-1-corpus-materialization-atif-sql-materialize)
1. The `materialize` command resolves `CorpusSettings` (pydantic-settings, env prefix `ATIF_SQL_`), then injects the three things the pure use case will not own: the `ConverterPort` adapter, the wall-clock instant, and the harbor / converter version pins stamped into every `meta.json` — [`packages/atif-cli/src/atif_cli/app.py:352`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L352).
2. One pass through the corpus use case runs scan, plan, convert, and write in that order and returns a `MaterializationReport` — [`packages/atif-corpus/src/atif_corpus/application/materialize.py:476`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L476).
3. The scanner discovers every session under the raw transcript root and separates unreadable sessions from absent ones, so a `stat` failure is never mistaken for a deletion — [`packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py:143`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py#L143).
4. The pure planner partitions the scan into to-materialize, up-to-date, and skipped-live using the previous watermark and the quiescence policy; `force` overrides staleness but never liveness — [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:159`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L159).
5. For each planned session the use case calls `converter.convert` across the port; one session’s exception is caught and recorded so a single bad transcript cannot abort the sync — [`packages/atif-corpus/src/atif_corpus/application/materialize.py:599`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L599).
6. The adapter that satisfies `ConverterPort` lives in atif-cli because the independence contract forbids atif-corpus from importing atif-converter; it raises rather than returning an invalid trajectory — [`packages/atif-cli/src/atif_cli/converter_adapter.py:51`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L51).
7. `convert_and_audit` snapshots the source files, stages the session into harbor’s expected directory shape and calls harbor’s pinned private method ([`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:178`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L178)), then builds the loss report and edges from the raw records, enriches the trajectory, and refuses the result if any source moved mid-pass — [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:105`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L105).
8. The four artifacts are written under `.staging/` with `meta.json` last, then the whole directory swaps into `sessions//` so a reader sees one complete generation or the other ([`packages/atif-corpus/src/atif_corpus/application/materialize.py:240`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L240)); the watermark advances only for sessions that succeeded — [`:608`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L608).
## Flow 2: SQL read path (`atif-sql query ''`)
[Section titled “Flow 2: SQL read path (atif-sql query '\')”](#flow-2-sql-read-path-atif-sql-query-sql)
1. The `query` command resolves the corpus root and reads the active embedder’s `(model_id, dim)` without constructing the embedder, so the vector store’s stamped identity can be checked before anything binds over it — [`packages/atif-cli/src/atif_cli/app.py:529`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L529).
2. A DuckDB connection is opened with no path or URI, so the engine runs in-process against memory — [`:591`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L591).
3. Registration builds the whole catalog on that connection in a fixed order — raw TEMP tables, base views, VSS, macros, analytics views, analytics macros — because each later stage binds against the earlier one at `CREATE` time — [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1212`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1212).
4. The raw readers materialize the four corpus artifact kinds as TEMP TABLEs over globs into `/sessions/`, which is where the cost of a `query` invocation lives: O(corpus) per connection — [`:196`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L196).
5. Trajectory, edges, and loss readers are semi-joined against the meta table, so a session directory missing its `meta.json` contributes nothing rather than a partial artifact set. This is the read-side half of flow 1’s meta-last write ordering — [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:221`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L221).
6. The VSS step `ATTACH`es the LanceDB store through the lance extension ([`packages/atif-duck/src/atif_duck/infrastructure/registry.py:887`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L887)) and reads the store’s stamped `model` and `dim` back out ([`packages/atif-duck/src/atif_duck/infrastructure/registry.py:921`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L921)) before creating any view over it; a store written by a different provider or width raises instead of binding, because vectors from different models live in incompatible spaces and would return numerically valid but meaningless cosine scores — [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:46`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L46).
7. The fully-registered connection is then sandboxed: spill directory, memory cap, a directory allowlist holding only the spill area, a path allowlist holding the individual analytics parquets, `enable_external_access=false`, and `lock_configuration` last so caller SQL cannot widen any of it — [`packages/atif-cli/src/atif_cli/app.py:138`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L138).
8. The caller’s statement executes against the locked connection ([`:606`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L606)) and the cursor drains in batches to stdout — a JSON array of row objects on a pipe, a width-aligned table on a TTY — [`packages/atif-cli/src/atif_cli/output.py:154`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L154).
## Flow 3: analytics enrichment (`atif-sql analyze --no-dry-run`)
[Section titled “Flow 3: analytics enrichment (atif-sql analyze --no-dry-run)”](#flow-3-analytics-enrichment-atif-sql-analyze---no-dry-run)
1. The `analyze` command loads `AnalyticsSettings`, reuses the same corpus-root resolution the other commands share, and stamps explicit `--max-sessions` / `--max-cost-usd` ceilings over the env defaults so a crontab line carries its spend cap visibly — [`packages/atif-cli/src/atif_cli/app.py:659`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L659).
2. The pipeline runner builds one shared `CorpusReader` for every stage, runs the three structural stages, then loops the five LLM stages; the lane selectors and `skip_*` flags subtract stages from whichever lane runs — [`packages/atif-analytics/src/atif_analytics/application/analyze.py:36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L36).
3. Corpus rows are read with stdlib `json` over `/sessions//trajectory.json` behind a bounded memo — not through DuckDB, which the `forbidden` import contract puts out of this package’s reach — [`packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:269`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py#L269).
4. The structural lane (cluster, then terms, then community) reads vectors straight out of the Lance store and writes single parquet files, bypassing the sharded cache — [`packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py#L28).
5. Before the LLM lane starts, one `RunBudget` is constructed from the per-run dollar ceiling ([`packages/atif-analytics/src/atif_analytics/application/analyze.py:140`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L140)); all five LLM stages then go through one call site, each receiving the shared reader and that budget, and a stage that finds the budget exhausted is skipped with nothing stamped — [`:181`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L181).
6. A stage drops the sessions whose checkpoint row still matches their mtime and last step timestamp, so a re-run costs nothing for unchanged work — [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:136`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py#L136).
7. The concrete provider satisfies `LlmStructuredProvider` structurally, and its synchronous inner method is the only place a Bedrock `invoke_model` call is issued, under tenacity retry with token usage accumulated against the budget — [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:258`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L258).
8. Results land as sharded `part-.parquet` files under `/analytics/` ([`packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py:90`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L90)), and each completed session is upserted into the SQLite WAL checkpoint — [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:179`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py#L179).
## See also
[Section titled “See also”](#see-also)
* [processes](../behavior/processes.md) — 16 shared source citations
* [sequences](../diagrams/behavioral/sequences.md) — 16 shared source citations
* [module map](module-map.md) — 13 shared source citations
* [debugging guide](../insights/debugging-guide.md) — 13 shared source citations
* [components](../diagrams/architecture/components.md) — 12 shared source citations
# Module map
# atif-sql · Module map
[Section titled “atif-sql · Module map”](#atif-sql--module-map)
Seven uv workspace members live under `packages/*`, declared at [`pyproject.toml:100`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L100); the root carries no `[project]` table because it is a virtual workspace holding only the member list, the shared dev dependency-group, and the shared tool config ([`pyproject.toml:1`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L1)). The internal import graph is a star: atif-cli declares five siblings as `==`-pinned dependencies ([`packages/atif-cli/pyproject.toml:32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L32)), plus one further edge from atif-analytics to atif-models that a `forbidden` import-linter contract leaves open ([`pyproject.toml:520`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L520)), with every other pair closed by an `independence` contract ([`pyproject.toml:515`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L515)). Every cross-package import sits inside a function body or a `TYPE_CHECKING` block so the CLI’s fast path pulls in no duckdb, harbor, lancedb, boto3, or polars, which is why `PLC0415` is ignored workspace-wide ([`pyproject.toml:164`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L164)). Modules below are ordered by total source LOC, descending; LOC figures are `wc -l` over the file.
## atif-analytics
[Section titled “atif-analytics”](#atif-analytics)
`run_analyze` composes eight pipelines in a fixed stage order — cluster, terms, community, classify, trajectory, conflicts, friction, perceived ([`packages/atif-analytics/src/atif_analytics/application/analyze.py:36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L36)). The first three are structural math at zero LLM cost ([`:8`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L8)); the remaining five call a model and honor `dry_run`, which defaults to True as a cost guard so those stages return plan dicts instead of spending ([`:19`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L19)). Every classifier system prompt lives in one module, four of them assembled by concatenating a shared appendix ([`packages/atif-analytics/src/atif_analytics/application/prompts.py:1006`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/prompts.py#L1006)), and the pydantic v2 response schemas they bind against are pure domain models whose field descriptions are themselves part of the prompt surface ([`packages/atif-analytics/src/atif_analytics/domain/models.py:3`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/models.py#L3)). This is the one member permitted to import a sibling — atif-models and nothing else ([`pyproject.toml:520`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L520)).
* `packages/atif-analytics/src/atif_analytics/application/prompts.py` (1021 LOC) — the task-framing system prompts, public constants assembled at [`packages/atif-analytics/src/atif_analytics/application/prompts.py:1006`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/prompts.py#L1006).
* `packages/atif-analytics/src/atif_analytics/domain/structure/community.py` (643 LOC) — pure Leiden+CPM and mutual-kNN graph math, no I/O, with `graspologic-native` behind one solver seam ([`packages/atif-analytics/src/atif_analytics/domain/structure/community.py:261`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/community.py#L261)).
* `packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py` (638 LOC) — three friction tiers behind a message-length pre-filter: regex fast path, deterministic stamp rules, then the LLM ([`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:5`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L5)).
* `packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py` (523 LOC) — one window per text step, sent in chunks of at most 16 windows with a shared anchor turn ([`packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py:13`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py#L13)).
* `packages/atif-analytics/src/atif_analytics/domain/models.py` (516 LOC) — the response schemas, from `SessionClassification` ([`packages/atif-analytics/src/atif_analytics/domain/models.py:22`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/models.py#L22)) to `PerceivedErrorsResult` ([`:484`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/models.py#L484)).
* `packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py` (451 LOC) — LangSmith’s Perceived Error definition on the conflicts chassis; clean sessions produce zero rows ([`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:16`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L16)).
* `packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py` (426 LOC) — one row per detected stance-conflict pair, keyed on two turn uuids, with refusals routed to a sidecar ([`packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py:11`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py#L11)).
* `packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py` (404 LOC) — one row per session, anti-joined against the parquet cache and written in crash-resilient chunks ([`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:8`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L8)).
## atif-duck
[Section titled “atif-duck”](#atif-duck)
`register(con, corpus_root)` binds a DuckDB connection to a contract-shaped corpus tree and exposes 16 views plus 9 macros ([`packages/atif-duck/src/atif_duck/__init__.py:9`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/__init__.py#L9), [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1212`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1212)). Those names are not introspected at runtime: a static catalog answers `atif-sql schema` in under 50 ms with no DuckDB bind, and two drift tests assert it column-for-column against `DESCRIBE` and signature-for-signature against the DDL ([`packages/atif-duck/src/atif_duck/domain/catalog.py:3`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L3)). The 12 analytics views and 13 analytics macros register separately, each only when its backing parquet is populated, because a corpus with no `atif-sql analyze` run is the default state ([`packages/atif-duck/src/atif_duck/infrastructure/analytics.py:124`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/analytics.py#L124)). It is the one member with `domain/` and `infrastructure/` but no `application/`, a deliberate shape that is why it carries no layers contract among the seven ([`pyproject.toml:462`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L462)).
* `packages/atif-duck/src/atif_duck/infrastructure/registry.py` (1294 LOC) — the raw readers, the 16 core views, the 9 macros, and the Lance attach path ([`packages/atif-duck/src/atif_duck/infrastructure/registry.py:830`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L830)).
* `packages/atif-duck/src/atif_duck/infrastructure/analytics.py` (581 LOC) — the v2 views and macros over the analytics parquet outputs, gated on file presence ([`packages/atif-duck/src/atif_duck/infrastructure/analytics.py:124`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/analytics.py#L124)).
* `packages/atif-duck/src/atif_duck/domain/catalog.py` (417 LOC) — `VIEW_NAMES` ([`packages/atif-duck/src/atif_duck/domain/catalog.py:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L28)), `MACRO_SIGNATURES` ([`:247`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L247)), the analytics catalogs ([`:269`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L269)), and one `DESCRIPTIONS` entry per object ([`:326`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L326)).
* `packages/atif-duck/src/atif_duck/domain/examples.py` (251 LOC) — `build_examples` derives one runnable query per catalog object from `ARG_EXEMPLARS` rather than hardcoding strings ([`packages/atif-duck/src/atif_duck/domain/examples.py:178`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L178)).
* `packages/atif-duck/src/atif_duck/domain/embedding_guard.py` (83 LOC) — the read-side provider and dimension guard, a deliberate twin of atif-embed’s copy because the independence contract forbids sharing it ([`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:5`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L5)).
* `packages/atif-duck/src/atif_duck/__init__.py` (40 LOC) — re-exports the catalog constants and the four `register*` entry points ([`packages/atif-duck/src/atif_duck/__init__.py:30`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/__init__.py#L30)).
* `packages/atif-duck/src/atif_duck/domain/__init__.py` (32 LOC) — the pure catalog layer, no duckdb import ([`packages/atif-duck/src/atif_duck/domain/__init__.py:5`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/__init__.py#L5)).
* `packages/atif-duck/src/atif_duck/infrastructure/__init__.py` (17 LOC) — the registry layer’s re-export surface ([`packages/atif-duck/src/atif_duck/infrastructure/__init__.py:5`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/__init__.py#L5)).
## atif-cli
[Section titled “atif-cli”](#atif-cli)
The composition root: it declares five siblings as `==`-pinned dependencies ([`packages/atif-cli/pyproject.toml:32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L32)) and wires every cross-package seam — the `ConverterPort` adapter, the clock, version pins, the DuckDB connection ([`packages/atif-cli/src/atif_cli/app.py:5`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L5)). Ten commands hang off one cyclopts `App`: nine `@app.command` functions from `convert` ([`packages/atif-cli/src/atif_cli/app.py:226`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L226)) to `schema` ([`:1055`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1055)), plus the `cron` sub-app registered at [`:65`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L65), with `main` ([`:1093`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1093)) exposed as the single console script named `atif-sql` ([`packages/atif-cli/pyproject.toml:42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L42)). Heavy imports — duckdb, harbor through atif-converter, pydantic through atif-corpus — are deferred into the command bodies that use them so `schema`, `--help`, and `--version` stay on a lean import graph that a fresh-interpreter test pins ([`packages/atif-cli/src/atif_cli/app.py:22`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L22)). Failures resolve to stable exit codes, 64 for parse, 65 for catalog, 70 for runtime, split between a pure taxonomy module ([`packages/atif-cli/src/atif_cli/errors.py:25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L25)) and a driver-dependent classifier ([`packages/atif-cli/src/atif_cli/duck_errors.py:34`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L34)) so the lean path never imports duckdb.
* `packages/atif-cli/src/atif_cli/app.py` (1120 LOC) — the ten commands and every wiring decision between them ([`packages/atif-cli/src/atif_cli/app.py:52`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L52)).
* `packages/atif-cli/src/atif_cli/cron.py` (302 LOC) — `cron install` prints a crontab block and never writes one; `cron status` reports per-lane lock and last-run state from injected probes ([`packages/atif-cli/src/atif_cli/cron.py:7`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L7)).
* `packages/atif-cli/src/atif_cli/output.py` (293 LOC) — `--format auto` resolves to a table on a TTY and JSON on a pipe ([`packages/atif-cli/src/atif_cli/output.py:74`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L74)); `emit_cursor` streams `fetchmany` batches so the client holds one batch ([`:154`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L154)).
* `packages/atif-cli/src/atif_cli/duck_errors.py` (96 LOC) — the duckdb exception classifier, plus a widened caught set for the registration path since `EmbeddingProviderMismatch` is not a `duckdb.Error` ([`packages/atif-cli/src/atif_cli/duck_errors.py:31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L31)).
* `packages/atif-cli/src/atif_cli/converter_adapter.py` (72 LOC) — `RealConverter` adapts atif-converter’s use case to atif-corpus’s port, the one place allowed to import both ([`packages/atif-cli/src/atif_cli/converter_adapter.py:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L44)).
* `packages/atif-cli/src/atif_cli/errors.py` (62 LOC) — `EXIT_CODES` and the `ClassifiedError` shape, pure and free of any `atif_*` import ([`packages/atif-cli/src/atif_cli/errors.py:52`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L52)).
* `packages/atif-cli/src/atif_cli/__main__.py` (8 LOC) — `python -m atif_cli` reaching the same `main` ([`packages/atif-cli/src/atif_cli/__main__.py:5`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/__main__.py#L5)).
* `packages/atif-cli/src/atif_cli/__init__.py` (3 LOC) — package docstring only ([`packages/atif-cli/src/atif_cli/__init__.py:3`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/__init__.py#L3)).
## atif-embed
[Section titled “atif-embed”](#atif-embed)
The vector-search write path: Cohere Embed v4 on Bedrock behind `EmbeddingProvider`, a local LanceDB store behind `VectorStorePort`, and a corpus reader behind `TextRowsPort` ([`packages/atif-embed/src/atif_embed/domain/ports.py:31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L31)). `run_backfill` anti-joins step text against the store’s uuid-to-text-hash map, embeds the misses, and bounds loss three ways — chunked discovery, mid-run checkpoints, and per-batch isolation inside a chunk ([`packages/atif-embed/src/atif_embed/application/embed.py:61`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L61)). Two stamps keep the vector space honest: `model_id` and `dimension` on every row, checked on both the write and the read path so a provider switch fails loud instead of corrupting kNN ([`packages/atif-embed/src/atif_embed/domain/embedding_guard.py:3`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/embedding_guard.py#L3)), and `text_hash` of the exact text a row was built from, so a re-conversion under a stable uuid reads as stale ([`packages/atif-embed/src/atif_embed/domain/text_stamp.py:3`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/text_stamp.py#L3)). Additive schema columns migrate online through a metadata-only `add_columns`, keyed on a `SCHEMA_VERSION` sidecar file ([`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:61`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L61)).
* `packages/atif-embed/src/atif_embed/infrastructure/lance_store.py` (444 LOC) — connect, open or create, online-migrate, delete by predicate, append, index, and compact ([`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:195`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L195)).
* `packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py` (432 LOC) — the `invoke_model` adapter under a tenacity retry with botocore retries off, and the document-`int8` / query-`float` asymmetry ([`packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:284`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py#L284)).
* `packages/atif-embed/src/atif_embed/application/embed.py` (277 LOC) — `discover_unembedded` ([`packages/atif-embed/src/atif_embed/application/embed.py:43`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L43)), `run_backfill` ([`:61`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L61)), and the sync `embed_query` the search command calls ([`:265`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L265)).
* `packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py` (250 LOC) — reads the contract corpus layout with its own DuckDB connection, since importing atif-duck is forbidden ([`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:155`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L155)).
* `packages/atif-embed/src/atif_embed/domain/ports.py` (119 LOC) — the three Protocols: `EmbeddingProvider` ([`packages/atif-embed/src/atif_embed/domain/ports.py:31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L31)), `VectorStorePort` ([`:59`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L59)), `TextRowsPort` ([`:87`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L87)).
* `packages/atif-embed/src/atif_embed/domain/embedding_guard.py` (83 LOC) — `ensure_store_matches`, the fail-loud provider and dimension rule ([`packages/atif-embed/src/atif_embed/domain/embedding_guard.py:46`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/embedding_guard.py#L46)).
* `packages/atif-embed/src/atif_embed/domain/errors.py` (81 LOC) — the `DomainError` taxonomy the CLI catches, with `terminal` separating operator-needed states from retryable ones ([`packages/atif-embed/src/atif_embed/domain/errors.py:14`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/errors.py#L14)).
* `packages/atif-embed/src/atif_embed/domain/text_stamp.py` (71 LOC) — `text_hash` ([`packages/atif-embed/src/atif_embed/domain/text_stamp.py:38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/text_stamp.py#L38)) and the `MAX_EMBEDDABLE_CHARS` head-only cap ([`:35`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/text_stamp.py#L35)).
## atif-corpus
[Section titled “atif-corpus”](#atif-corpus)
One materialization pass is sweep, scan, plan, convert, write, advance watermark, and `materialize` is that pass ([`packages/atif-corpus/src/atif_corpus/application/materialize.py:476`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L476)). The decision half is pure — the domain never stats a file and never reads a clock, so `build_plan` partitions scanned sessions into to-materialize, up-to-date, and skipped-live deterministically from mtimes in epoch nanoseconds plus an injected now ([`packages/atif-corpus/src/atif_corpus/domain/sessions.py:159`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L159)). The write half is atomic by construction: artifacts land in a temp session directory that is renamed into place, and every writer fsyncs the tmp file before the rename and the directory after, because atif-duck reads the corpus with no locks and no journal ([`packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py:5`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py#L5)). Conversion arrives through the `ConverterPort` Protocol, typed to the contract’s artifact shapes rather than converter internals since this member may never import atif-converter ([`packages/atif-corpus/src/atif_corpus/domain/ports.py:41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L41)).
* `packages/atif-corpus/src/atif_corpus/application/materialize.py` (634 LOC) — the pass itself ([`packages/atif-corpus/src/atif_corpus/application/materialize.py:476`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L476)), the report value object ([`:118`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L118)), and the public `read_watermark` its consumers call ([`:160`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L160)).
* `packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py` (222 LOC) — the only place this member stats the source corpus, keeping a genuinely absent transcript separate from a failed `stat` so a transient error never deletes live artifacts ([`packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py:15`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py#L15)).
* `packages/atif-corpus/src/atif_corpus/domain/sessions.py` (211 LOC) — `SessionSource` ([`packages/atif-corpus/src/atif_corpus/domain/sessions.py:42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L42)), `QuiescencePolicy` ([`:71`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L71)), `MaterializationPlan` ([`:108`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L108)), and `build_plan` ([`:159`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L159)).
* `packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py` (158 LOC) — tmp-sibling write, fsync, rename, for files ([`packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py:64`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py#L64)) and whole directories ([`:98`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py#L98)).
* `packages/atif-corpus/src/atif_corpus/domain/watermark.py` (78 LOC) — `diff_source_mtimes` partitions two mtime maps into added, modified, and removed ([`packages/atif-corpus/src/atif_corpus/domain/watermark.py:58`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/watermark.py#L58)).
* `packages/atif-corpus/src/atif_corpus/domain/layout.py` (75 LOC) — `CorpusLayout` is the single writer-side computation of every contract path ([`packages/atif-corpus/src/atif_corpus/domain/layout.py:26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/layout.py#L26)).
* `packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py` (69 LOC) — a scriptable `ConverterPort` fake shipped in `infrastructure` so the type checker holds it to the port on every run ([`packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py:3`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py#L3)).
* `packages/atif-corpus/src/atif_corpus/infrastructure/settings.py` (58 LOC) — `ATIF_SQL_`-prefixed settings whose default factories read env at call time, not import time ([`packages/atif-corpus/src/atif_corpus/infrastructure/settings.py:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/settings.py#L44)).
## atif-converter
[Section titled “atif-converter”](#atif-converter)
`convert_and_audit` returns a conversion result paired with a loss report — the trajectory plus an accounting of what upstream dropped ([`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:105`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L105)). The conversion is one private harbor call, `ClaudeCode._convert_events_to_trajectory`, confined to a single adapter module behind a startup assertion ([`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:112`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L112)), which is why the dependency is ceilinged at `harbor>=0.22.0,<0.23` ([`packages/atif-converter/pyproject.toml:23`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/pyproject.toml#L23)). The seven known conversion gaps are types rather than prose — `FidelityGap` enumerates them ([`packages/atif-converter/src/atif_converter/domain/fidelity.py:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L44)) and a pure enrichment pass repairs three by re-running harbor’s deterministic normalization order over the raw records ([`packages/atif-converter/src/atif_converter/domain/enrichment.py:198`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L198)). A fingerprint snapshot is taken before harbor reads and re-checked afterwards, so a session that resumes writing mid-conversion fails instead of yielding mutually inconsistent artifacts ([`packages/atif-converter/src/atif_converter/infrastructure/raw_records.py:110`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/raw_records.py#L110)).
* `packages/atif-converter/src/atif_converter/domain/enrichment.py` (412 LOC) — `enrich_trajectory` restores source uuids, the compact-summary flag, and the cache-creation total ([`packages/atif-converter/src/atif_converter/domain/enrichment.py:198`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L198)).
* `packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py` (195 LOC) — the only module importing harbor, which ships no `py.typed`, so the untyped surface stays contained ([`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:11`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L11)).
* `packages/atif-converter/src/atif_converter/infrastructure/raw_records.py` (164 LOC) — the one raw reader the census, edges emitter, and enrichment share, with `mutated_files` as the race check ([`packages/atif-converter/src/atif_converter/infrastructure/raw_records.py:146`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/raw_records.py#L146)).
* `packages/atif-converter/src/atif_converter/application/convert_and_audit.py` (155 LOC) — the use case ([`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:105`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L105)) and its refusal on any mid-window mutation ([`:93`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L93)).
* `packages/atif-converter/src/atif_converter/domain/fidelity.py` (137 LOC) — `RecordType` with 12 members ([`packages/atif-converter/src/atif_converter/domain/fidelity.py:18`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L18)), `FidelityGap` with 7 ([`:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L44)), and the `LossReport` value object ([`:83`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L83)).
* `packages/atif-converter/src/atif_converter/domain/edges.py` (101 LOC) — one `edges.jsonl` line per raw record, key order fixed by the contract ([`packages/atif-converter/src/atif_converter/domain/edges.py:22`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/edges.py#L22)).
* `packages/atif-converter/src/atif_converter/infrastructure/census.py` (89 LOC) — the raw-side counts that form the input half of a loss report, derived from an already-parsed snapshot ([`packages/atif-converter/src/atif_converter/infrastructure/census.py:57`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/census.py#L57)).
* `packages/atif-converter/src/atif_converter/domain/errors.py` (70 LOC) — six typed exceptions under one `DomainError` base, with exit-code mapping left to the CLI ([`packages/atif-converter/src/atif_converter/domain/errors.py:14`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/errors.py#L14)).
## atif-models
[Section titled “atif-models”](#atif-models)
A deliberately narrow seam: a system prompt, a user prompt, and a pydantic schema in; a validated instance of that schema out ([`packages/atif-models/src/atif_models/domain/ports.py:116`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L116)). Its only in-repo consumer is atif-analytics — the single edge the `forbidden` contract leaves open ([`pyproject.toml:520`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L520)) — and the registry is the only place in the workspace where a Bedrock model id is written down, so a pipeline names a family and a size alias and lets `resolve` pick the id ([`packages/atif-models/src/atif_models/domain/registry.py:6`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L6), [`:109`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L109)). The default adapter posts an OpenAI chat-completions body to `invoke_model` in strict `json_schema` mode, dispatching the blocking call through `anyio.to_thread` under a capacity limiter with tenacity owning the retry loop and botocore’s own retries disabled ([`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:142`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L142)). Strict mode demands `additionalProperties: false` at every object level and every property listed in `required`, which one pure transform enforces before the schema reaches the wire ([`packages/atif-models/src/atif_models/domain/schema.py:40`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/schema.py#L40)).
* `packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py` (314 LOC) — `OpenAiBedrockProvider` ([`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:142`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L142)), the retryable-code set ([`:63`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L63)), and usage extraction ([`:124`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L124)).
* `packages/atif-models/src/atif_models/domain/registry.py` (151 LOC) — `ModelSpec` ([`packages/atif-models/src/atif_models/domain/registry.py:39`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L39)), `resolve` ([`:109`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L109)), and `estimate_cost` over per-1M-token rates ([`:125`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L125)).
* `packages/atif-models/src/atif_models/domain/ports.py` (143 LOC) — the `LlmStructuredProvider` Protocol ([`packages/atif-models/src/atif_models/domain/ports.py:116`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L116)), the terminal-versus-retryable error split ([`:43`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L43)), and lock-guarded usage accumulation ([`:78`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L78)).
* `packages/atif-models/src/atif_models/domain/schema.py` (109 LOC) — `to_openai_strict` keeps `$defs` and `$ref`, drops `default` and `title` ([`packages/atif-models/src/atif_models/domain/schema.py:40`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/schema.py#L40)).
* `packages/atif-models/src/atif_models/infrastructure/settings.py` (84 LOC) — `LlmSettings` carries the family, region, concurrency, and per-pipeline size overrides ([`packages/atif-models/src/atif_models/infrastructure/settings.py:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/settings.py#L28)).
* `packages/atif-models/src/atif_models/__init__.py` (14 LOC) — states the ownership rule: no other member hardcodes a model id ([`packages/atif-models/src/atif_models/__init__.py:9`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/__init__.py#L9)).
* `packages/atif-models/src/atif_models/domain/__init__.py` (9 LOC) — the pure layer: no boto3, no env, no clock ([`packages/atif-models/src/atif_models/domain/__init__.py:7`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/__init__.py#L7)).
* `packages/atif-models/src/atif_models/infrastructure/__init__.py` (8 LOC) — adapters and env settings, importing downward into `domain` only ([`packages/atif-models/src/atif_models/infrastructure/__init__.py:7`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/__init__.py#L7)).
## See also
[Section titled “See also”](#see-also)
* [processes](../behavior/processes.md) — 42 shared source citations
* [business logic](../insights/business-logic.md) — 39 shared source citations
* [impact analysis](../insights/impact-analysis.md) — 38 shared source citations
* [contract map](../insights/contract-map.md) — 37 shared source citations
* [debugging guide](../insights/debugging-guide.md) — 24 shared source citations
# System overview
# atif-sql · System overview
[Section titled “atif-sql · System overview”](#atif-sql--system-overview)
## What it does
[Section titled “What it does”](#what-it-does)
`atif-sql` is a command-line analytics tool over Claude Code agent trajectories. It reads the session transcripts Claude Code leaves at `~/.claude/projects/**/*.jsonl`, converts each one to ATIF — Harbor’s Agent Trajectory Interchange Format — materializes the results as an on-disk corpus, and answers SQL against that corpus through DuckDB ([`README.md:12`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/README.md#L12)). The design bet is stated in the README itself: converting once at the boundary, with an explicit and tested fidelity policy for what the upstream converter drops, beats re-deriving trajectory semantics inside every SQL view ([`README.md:15`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/README.md#L15)). The reader it serves is an engineer or an agent asking how sessions actually went — which tools ran, where tokens went, where a session turned into friction.
Users get one installable distribution and one console script, `atif-sql = "atif_cli.app:main"` ([`packages/atif-cli/pyproject.toml:42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L42)), installed with `uv tool install atif-sql` ([`README.md:26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/README.md#L26)). The command surface is ten commands: nine registered with `@app.command` in `packages/atif-cli/src/atif_cli/app.py` (1120 LOC) plus the `cron` sub-app attached at [`:65`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L65). Nothing here is a server — DuckDB is embedded ([`packages/atif-duck/pyproject.toml:20`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/pyproject.toml#L20)), as are the SQLite analytics state file and the LanceDB vector store. Three commands, `analyze` / `embed` / `search`, call Amazon Bedrock and spend money per invocation, and each is dry-run by default ([`README.md:37`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/README.md#L37)).
## How the pieces fit
[Section titled “How the pieces fit”](#how-the-pieces-fit)
The seven directories under `packages/` are internal module boundaries, not seven installs ([`README.md:43`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/README.md#L43)); they are uv workspace members ([`pyproject.toml:100`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L100)). `atif-converter` wraps Harbor’s `ClaudeCode` adapter, pinned `harbor>=0.22.0,<0.23` ([`packages/atif-converter/pyproject.toml:23`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/pyproject.toml#L23)) because it calls a private upstream method verified against 0.22.0 only ([`:20`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/pyproject.toml#L20)), and owns the fidelity policy as types: `FidelityGap` enumerates the seven known upstream conversion gaps ([`packages/atif-converter/src/atif_converter/domain/fidelity.py:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L44), 137 LOC). `atif-corpus` drives materialization, writing per-session artifacts plus a corpus watermark ([`docs/CONTRACT.md:21`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L21)). Its `ConverterPort` Protocol lets an implementation raise anything: the materialize use case records the failure against that session and continues, so one broken transcript never aborts a sync ([`packages/atif-corpus/src/atif_corpus/domain/ports.py:41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L41)).
`atif-duck` reads the corpus root and never imports the packages that wrote it ([`docs/CONTRACT.md:52`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L52)). Its one public `register` registers raw readers, views, vector search, and macros in that order, because views bind against the raw TEMP tables at CREATE time ([`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1222`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1222)); the raw readers are `CREATE TEMP TABLE` over `read_json` ([`:13`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L13)), and every view and macro name is declared in a static catalog ([`packages/atif-duck/src/atif_duck/domain/catalog.py:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L28), 417 LOC). `atif-analytics` is the largest module at 35 source files; it writes parquet plus one SQLite WAL `state.db` under `/analytics/` ([`packages/atif-analytics/src/atif_analytics/domain/layout.py:6`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/layout.py#L6)). `atif-embed` backfills Cohere Embed v4 vectors into LanceDB ([`packages/atif-embed/pyproject.toml:4`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/pyproject.toml#L4)), which `atif-duck` reads back through DuckDB’s lance extension rather than by importing lancedb ([`packages/atif-duck/src/atif_duck/infrastructure/registry.py:830`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L830)). `atif-models` is the single owner of model ids, so no other package hardcodes one ([`packages/atif-models/pyproject.toml:4`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/pyproject.toml#L4)).
The direction of those edges is enforced rather than conventional. `[tool.importlinter]` ([`pyproject.toml:462`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L462)) declares a layer contract per member, an independence contract forbidding converter / corpus / duck / models / embed from importing each other ([`:417`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L417)), and a forbidden contract limiting `atif-analytics` to `atif-models` alone among the seven ([`:422`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L422)). `atif-cli` is the sole composition root, and it imports its five siblings lazily inside command bodies ([`packages/atif-cli/src/atif_cli/app.py:388`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L388), [`:583`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L583), [`:688`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L688)) so that `atif-sql schema` pays for none of the analytics or vector stack. Failures surface as typed exceptions mapped to stable process exit codes at the CLI edge ([`:38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L38)).
## Stack
[Section titled “Stack”](#stack)
| Layer | Technology | Source |
| --------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Language | Python, `requires-python = ">=3.13"` | [`packages/atif-cli/pyproject.toml:14`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L14) |
| Toolchain pin | mise, `python = "3.13"` | [`mise.toml:32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L32) |
| Packaging | uv workspace, `members = ["packages/*"]` | [`pyproject.toml:100`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L100) |
| Build backend | `uv_build>=0.11.14,<0.12` | [`packages/atif-cli/pyproject.toml:48`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L48) |
| CLI framework | `cyclopts>=4.10.2` | [`packages/atif-cli/pyproject.toml:37`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L37) |
| Query engine | `duckdb>=1.5.2,<2` | [`packages/atif-duck/pyproject.toml:20`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/pyproject.toml#L20) |
| Trajectory conversion | `harbor>=0.22.0,<0.23` | [`packages/atif-converter/pyproject.toml:23`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/pyproject.toml#L23) |
| Vector store | `lancedb>=0.30,<0.38` | [`packages/atif-embed/pyproject.toml:22`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/pyproject.toml#L22) |
| Model access | `boto3>=1.42.91` for Bedrock | [`packages/atif-models/pyproject.toml:24`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/pyproject.toml#L24) |
| Dataframes | `polars>=1.40.0` | [`packages/atif-embed/pyproject.toml:24`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/pyproject.toml#L24) |
| Validation | `pydantic>=2.13.2` | [`packages/atif-converter/pyproject.toml:25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/pyproject.toml#L25) |
| Logging | `loguru>=0.7.3` | [`packages/atif-corpus/pyproject.toml:19`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/pyproject.toml#L19) |
| Lint and format | ruff, `select = ["ALL"]` | [`pyproject.toml:146`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L146) |
| Architecture gate | import-linter contracts | [`pyproject.toml:462`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L462) |
| Tests | pytest, `testpaths = ["packages/*/tests"]` | [`pyproject.toml:389`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L389) |
| Definition of done | `mise run check`, nine gates | [`mise.toml:197`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L197) |
## Module map
[Section titled “Module map”](#module-map)
Nodes are the seven uv workspace members. Every edge is an import confirmed at an import site. There is no `atif-corpus` to `atif-converter` edge — the independence contract forbids it, and `RealConverter` in [`packages/atif-cli/src/atif_cli/converter_adapter.py:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L44) (72 LOC) satisfies `ConverterPort` by importing both from the composition root ([`:36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L36), [`:38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L38)). `atif-duck`, `atif-analytics`, and `atif-embed` exchange data through corpus files on disk, never through an import.
## See also
[Section titled “See also”](#see-also)
* [contract map](../insights/contract-map.md) — 11 shared source citations
* [dependency graph](../diagrams/structural/dependency-graph.md) — 10 shared source citations
* [impact analysis](../insights/impact-analysis.md) — 10 shared source citations
* [module map](module-map.md) — 9 shared source citations
* [processes](../behavior/processes.md) — 8 shared source citations
# Processes
# atif-sql · Processes
[Section titled “atif-sql · Processes”](#atif-sql--processes)
Every process in this system starts as a CLI invocation. There is one console script, `atif-sql = "atif_cli.app:main"` ([`packages/atif-cli/pyproject.toml:42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L42)), and its ten commands are the complete initiator set — nine `@app.command` sites plus the `cron` sub-App registered at [`packages/atif-cli/src/atif_cli/app.py:66`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L66). No HTTP route, RPC tool, message handler, or job queue exists to initiate anything else; the only scheduled initiator is a crontab line into `scripts/atif-sql-refresh.sh`. Long-running work is in-process async under `anyio` and `asyncio.run`, not a worker.
## materialize — sync the materialized corpus
[Section titled “materialize — sync the materialized corpus”](#materialize--sync-the-materialized-corpus)
Entry point: [`packages/atif-cli/src/atif_cli/app.py:352`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L352)
1. Resolve `CorpusSettings` and wire the pass: `--source-root` given without `--corpus-root` re-derives the corpus root from the overridden source’s slug so re-pointing the source cannot overwrite another corpus, and the CLI hands in the wall clock plus the harbor and converter version pins alongside `RealConverter` behind `ConverterPort` — [`:190`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L190), [`packages/atif-cli/src/atif_cli/converter_adapter.py:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L44).
2. Sweep `.staging/`: an entry whose `tmp-` owner is a dead pid is crash debris and is removed, while a live pid belongs to a concurrent pass and is left alone — [`packages/atif-corpus/src/atif_corpus/application/materialize.py:274`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L274).
3. Read `watermark.json` as a path-to-mtime map; an unreadable or wrongly shaped file degrades to empty, costing one full re-materialization rather than refusing to sync — [`packages/atif-corpus/src/atif_corpus/application/materialize.py:160`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L160).
4. Scan the source root for main transcripts and every side-file, then fold sessions living under a directory that would not list into the unreadable set using the watermark as the only record of what lived there — [`packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py:143`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py#L143), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:342`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L342).
5. Guard, then collect ghosts: zero scanned sessions over a non-empty corpus raises `SuspiciousEmptyScanError` instead of deleting everything as ghosts, and ghost removal is skipped entirely when any source directory failed to list, because absence is then not evidence of deletion — [`:542`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L542), [`:379`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L379).
6. Build the pure `MaterializationPlan` from quiescence and the watermark, force-replanning sessions the watermark calls current whose artifact directory is missing — the one state reachable by a kill inside the swap window — [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:159`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L159), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:313`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L313).
7. Per planned session: convert, write trajectory, loss report, edges, and `meta.json` last into a staging directory, then rename the whole directory into `sessions//` so a reader observes only a complete generation; a session that raises is recorded and the pass continues — [`packages/atif-corpus/src/atif_corpus/application/materialize.py:190`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L190), [`packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py:98`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py#L98).
8. Advance the watermark for succeeded sessions only, retaining every entry of a failed, unplanned, or unreadable session so staleness still signals a retry, and write it atomically before emitting the report — [`packages/atif-corpus/src/atif_corpus/application/materialize.py:428`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L428), [`packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py:64`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py#L64).
### Related
[Section titled “Related”](#related)
* [`packages/atif-corpus/src/atif_corpus/domain/ports.py:41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L41)
* [`packages/atif-corpus/src/atif_corpus/domain/layout.py:26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/layout.py#L26)
* [`packages/atif-corpus/src/atif_corpus/infrastructure/settings.py:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/settings.py#L44)
* [`packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py:43`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py#L43)
* [`packages/atif-cli/src/atif_cli/app.py:312`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L312)
## convert — one session to ATIF plus a loss audit
[Section titled “convert — one session to ATIF plus a loss audit”](#convert--one-session-to-atif-plus-a-loss-audit)
Entry point: [`packages/atif-cli/src/atif_cli/app.py:226`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L226)
1. Reject a path that is not an existing `.jsonl` file before any harbor work, which the CLI maps to exit 64 — [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:163`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L163).
2. Fingerprint every source file — the main transcript and each discovered side-file — before harbor reads anything — [`packages/atif-converter/src/atif_converter/infrastructure/raw_records.py:110`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/raw_records.py#L110).
3. Assert harbor still exposes the pinned private method, then stage the session as symlinks into the directory shape `ClaudeCode` expects — [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:112`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L112), [`:54`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L54).
4. Call `ClaudeCode._convert_events_to_trajectory` inside a temporary directory and dump the result to a JSON-mode dict; a `None` return means no convertible events and raises `EmptySessionError` — [`:178`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L178).
5. Re-check the fingerprints. Any movement raises `SourceMutatedDuringConversion` rather than emitting a census, a trajectory, and an edges file that describe different bytes of one session — [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:93`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L93).
6. Parse the raw records from the snapshot and build the loss report: four `FidelityGap` members are structural for every harbor 0.22.0 conversion, and the rest are added from what the census found — [`packages/atif-converter/src/atif_converter/infrastructure/raw_records.py:118`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/raw_records.py#L118), [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:62`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L62).
7. Derive the `edges.jsonl` lines from the raw records, then enrich the trajectory in place with `source_uuids`, `is_compact_summary`, and `cache_creation_total` — the harbor trajectory has no other consumer, so no deep copy is taken — [`packages/atif-converter/src/atif_converter/domain/edges.py:99`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/edges.py#L99), [`packages/atif-converter/src/atif_converter/domain/enrichment.py:198`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L198).
8. Re-check the fingerprints a second time, re-validate the enriched trajectory, and write it plus `edges.jsonl` beside it or stream the trajectory to stdout; a validation error exits 65 — [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:148`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L148), [`packages/atif-cli/src/atif_cli/app.py:272`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L272).
### Related
[Section titled “Related”](#related-1)
* [`packages/atif-converter/src/atif_converter/infrastructure/census.py:57`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/census.py#L57)
* [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:97`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L97)
* [`packages/atif-converter/src/atif_converter/domain/errors.py:57`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/errors.py#L57)
* [`packages/atif-converter/src/atif_converter/domain/fidelity.py:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L44)
* [`packages/atif-cli/src/atif_cli/errors.py:25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L25)
## analyze — orchestrate the eight analytics stages
[Section titled “analyze — orchestrate the eight analytics stages”](#analyze--orchestrate-the-eight-analytics-stages)
Entry point: [`packages/atif-cli/src/atif_cli/app.py:659`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L659)
1. Resolve `AnalyticsSettings`, reusing the corpus-root resolution the other commands share, then let `--max-sessions` and `--max-cost-usd` override the env ceilings so a crontab line carries its spend cap visibly — [`:691`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L691).
2. Reject `--structural-only` together with `--llm-only` and derive the two lane booleans — [`packages/atif-analytics/src/atif_analytics/application/analyze.py:61`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L61).
3. Build one `CorpusReader` shared by every stage: the parsed-steps memo is the expensive part and all five LLM stages walk the same sessions — [`:72`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L72), [`packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:138`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py#L138).
4. Structural lane, zero LLM cost — cluster the Lance store with UMAP plus HDBSCAN, skipping when the mtime sidecar says the input is unchanged and `--force-cluster` is absent — [`packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py:50`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py#L50).
5. Label those clusters with c-TF-IDF terms, then detect communities over session centroids with Leiden and CPM — [`packages/atif-analytics/src/atif_analytics/application/use_cases/terms.py:54`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/terms.py#L54), [`packages/atif-analytics/src/atif_analytics/application/use_cases/community.py:94`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/community.py#L94).
6. Construct the run-wide `RunBudget` from `llm_max_cost_usd_per_run`, priced from the providers’ running actual usage rather than estimates — [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:90`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L90).
7. Walk the five LLM stages in declaration order. A stage entered with the budget already exhausted is skipped with nothing stamped, its consecutive-skip streak persisted, and the log escalates to ERROR at three consecutive runs — [`packages/atif-analytics/src/atif_analytics/application/analyze.py:161`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L161), [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:217`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py#L217).
8. Emit the per-stage summary carrying `budget_exhausted` and, on a real run, `llm_spent_usd` — [`packages/atif-analytics/src/atif_analytics/application/analyze.py:202`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L202), [`packages/atif-cli/src/atif_cli/app.py:757`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L757).
### Related
[Section titled “Related”](#related-2)
* [`packages/atif-analytics/src/atif_analytics/infrastructure/settings.py:68`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/settings.py#L68)
* [`packages/atif-analytics/src/atif_analytics/domain/layout.py:45`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/layout.py#L45)
* [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L32)
* [`packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py:253`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L253)
* [`packages/atif-analytics/src/atif_analytics/infrastructure/freshness.py:77`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/freshness.py#L77)
## classify — the LLM analytics stage shape
[Section titled “classify — the LLM analytics stage shape”](#classify--the-llm-analytics-stage-shape)
Entry point: [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:342`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L342)
`trajectory`, `conflicts`, `friction`, and `perceived` are entered the same way from the stages table and share this shape through `_shared.py`.
1. Resolve the layout, the reader, and the sharded parquet cache, then resolve the model through the atif-models registry by pipeline size — no pipeline writes down a model id — [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:368`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L368), [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L32).
2. Under `dry_run`, the default, return a plan dict whose input tokens are measured from the actually rendered transcripts and whose session count is the same newest-first cap the real run applies — [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:293`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L293).
3. Anti-join the cache for session ids already written, then drop sessions whose last-step timestamp and mtime bounds are unchanged since the last run — [`:74`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L74), [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:136`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py#L136).
4. Drain the retry queue back into the admission set, then subtract units with a live entry — inside backoff or past the attempt cap — because the retry queue is the single re-admission gate and the checkpoint path would re-bill them — [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py:145`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L145), [`:159`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L159).
5. Walk newest-first and stop admitting at `llm_max_sessions_per_run` so a deferred session is never rendered; a session that renders to nothing is checkpointed at current bounds instead of re-rendered every tick — [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:135`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L135).
6. Dispatch each write chunk in budget-checked sub-batches of `BUDGET_CHECK_BATCH`, advancing the cursor by what was actually sent so a mid-chunk budget stop leaves the remainder unstamped rather than silently skipped — [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:155`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L155), [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:217`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L217).
7. Route each result: a `RefusalError` is terminal and writes the documented `goal='[refused]'` sentinel row so the refusal is queryable and never re-billed, while any other exception enqueues a retry — [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:219`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L219).
8. Write the chunk as a parquet part, checkpoint the completed sessions at current bounds, and clear them from the retry queue — [`packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py:268`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L268), [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:272`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L272).
### Related
[Section titled “Related”](#related-3)
* [`packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py:421`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py#L421)
* [`packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py:337`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py#L337)
* [`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:531`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L531)
* [`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:357`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L357)
* [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:191`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L191)
* [`packages/atif-models/src/atif_models/domain/ports.py:116`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L116)
## embed — backfill step embeddings into LanceDB
[Section titled “embed — backfill step embeddings into LanceDB”](#embed--backfill-step-embeddings-into-lancedb)
Entry point: [`packages/atif-cli/src/atif_cli/app.py:766`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L766)
1. Refuse a real run with no scope: without `--limit`, `--all`, or `--dry-run` the command exits 64, so a mistyped invocation cannot start a full backfill — [`:778`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L778).
2. Resolve the Lance URI from `EmbedSettings`, defaulting to `/embeddings_lance` — [`packages/atif-embed/src/atif_embed/infrastructure/settings.py:51`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/settings.py#L51).
3. Read the store’s uuid-to-text-hash map once, then stream candidates whose hash is absent or stale through `TextRowsPort`; the staleness comparison happens before the limit cap so `--limit N` always makes N rows of progress — [`packages/atif-embed/src/atif_embed/application/embed.py:43`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L43), [`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:158`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L158).
4. Under `--dry-run`, count candidates and return the plan dict, returning before boto3 is ever imported — [`packages/atif-embed/src/atif_embed/application/embed.py:120`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L120).
5. Build the Cohere-on-Bedrock provider once, then refuse to append when the store’s stamped model and dimension differ from the live embedder’s, because mixing vector spaces corrupts kNN silently — [`:157`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L157), [`packages/atif-embed/src/atif_embed/domain/embedding_guard.py:38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/embedding_guard.py#L38).
6. Per chunk, a multiple of the batch size so a checkpoint boundary never splits a Bedrock batch: embed the texts and drop rows the provider failed, leaving them pending for the next run — [`packages/atif-embed/src/atif_embed/application/embed.py:147`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L147), [`packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:309`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py#L309).
7. Delete stale rows under the same uuid before appending the fixed-size float32 array frame, so text that changed cannot leave two rows fanning the kNN join out — [`packages/atif-embed/src/atif_embed/application/embed.py:207`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L207), [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:402`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L402).
8. Compact every eight chunks to bound fragment count, then optimize and ensure the HNSW index on the way out so `search` pays no brute-force scan — [`packages/atif-embed/src/atif_embed/application/embed.py:243`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L243), [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:414`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L414).
### Related
[Section titled “Related”](#related-4)
* [`packages/atif-embed/src/atif_embed/domain/ports.py:31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L31)
* [`packages/atif-embed/src/atif_embed/domain/ports.py:59`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L59)
* [`packages/atif-embed/src/atif_embed/domain/ports.py:87`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L87)
* [`packages/atif-embed/src/atif_embed/domain/errors.py:29`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/errors.py#L29)
* [`packages/atif-cli/src/atif_cli/app.py:833`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L833)
## query — one SQL statement over the catalog
[Section titled “query — one SQL statement over the catalog”](#query--one-sql-statement-over-the-catalog)
Entry point: [`packages/atif-cli/src/atif_cli/app.py:529`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L529)
1. Short-circuit `--examples` to the examples listing without opening DuckDB; a missing statement emits a classified parse error and exits 64 — [`:564`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L564).
2. Resolve the corpus root and the expected embedder identity, then open an in-memory DuckDB connection — [`:586`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L586).
3. Register the four raw readers as TEMP tables over the contract layout, gating trajectory, edges, and loss on `meta.json` presence so a torn session directory contributes nothing to any view — [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:180`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L180).
4. Create the core views, then bind `message_embeddings` over the Lance store guard-before-bind: a store written by another provider raises rather than returning numerically valid garbage cosine scores — [`:333`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L333), [`:846`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L846).
5. Create the macros, then the analytics views and analytics macros over the analytics parquets, which bind against both the parquets and the base views — [`:1005`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1005), [`packages/atif-duck/src/atif_duck/infrastructure/analytics.py:124`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/analytics.py#L124).
6. Harden the connection in a fixed order: temp directory, memory cap, a directory allowlist holding only the spill area, a file allowlist of the analytics parquets, the config exemption list, then `enable_external_access=false` and `lock_configuration=true` last — [`packages/atif-cli/src/atif_cli/app.py:138`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L138).
7. Execute the caller’s statement and stream the cursor: a plain table on a TTY, a JSON array of row objects on a pipe — [`:606`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L606), [`packages/atif-cli/src/atif_cli/output.py:154`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L154).
8. Classify any DuckDB failure into parse, catalog, or runtime — or an embedding-provider mismatch — and exit 64, 65, or 70 with a JSON error envelope — [`packages/atif-cli/src/atif_cli/duck_errors.py:34`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L34), [`:66`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L66).
### Related
[Section titled “Related”](#related-5)
* [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1212`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1212)
* [`packages/atif-duck/src/atif_duck/domain/catalog.py:51`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L51)
* [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:46`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L46)
* [`packages/atif-cli/src/atif_cli/output.py:202`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L202)
* [`packages/atif-cli/src/atif_cli/errors.py:25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L25)
## search — semantic top-k over step embeddings
[Section titled “search — semantic top-k over step embeddings”](#search--semantic-top-k-over-step-embeddings)
Entry point: [`packages/atif-cli/src/atif_cli/app.py:860`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L860)
1. Resolve the corpus root, the Lance URI, and the expected model and dimension from `EmbedSettings` — [`:878`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L878), [`packages/atif-embed/src/atif_embed/infrastructure/settings.py:57`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/settings.py#L57).
2. Register the full catalog on a fresh in-memory connection; a registration failure or a provider mismatch leaves through the classified-error path with exit 65 — [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1212`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1212), [`packages/atif-cli/src/atif_cli/duck_errors.py:66`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L66).
3. Count `message_embeddings` first: an empty or absent store exits 2 with the backfill hint instead of returning an empty result that reads like “no matches” — [`packages/atif-cli/src/atif_cli/app.py:930`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L930).
4. Embed the query text in Cohere `search_query` float mode — Cohere forces float for queries even when documents were stored int8 — [`packages/atif-embed/src/atif_embed/application/embed.py:265`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L265), [`packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:400`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py#L400).
5. Bind the query vector, the optional session filter, and `k` as parameters rather than interpolating them — [`packages/atif-cli/src/atif_cli/app.py:945`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L945).
6. Order by `array_cosine_distance` ascending: that is what triggers the cosine HNSW index lookup, and cosine is the only magnitude-invariant choice against int8-cast document vectors whose magnitudes run into the thousands — [`:919`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L919).
7. Join back to `steps` on the first entry of `source_uuids` for a 200-character snippet, selecting cosine similarity as the reported score — [`:933`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L933).
8. Emit uuid, session id, similarity, and snippet; a DuckDB error classifies to its own exit code — [`:946`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L946), [`packages/atif-cli/src/atif_cli/output.py:129`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L129).
### Related
[Section titled “Related”](#related-6)
* [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:830`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L830)
* [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:46`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L46)
* [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:414`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L414)
* [`packages/atif-cli/src/atif_cli/errors.py:25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L25)
## refresh lane — the scheduled driver
[Section titled “refresh lane — the scheduled driver”](#refresh-lane--the-scheduled-driver)
Entry point: [`scripts/atif-sql-refresh.sh:109`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L109)
1. Pin the identity trio once from the invoking environment and unset `AWS_PROFILE` and `AWS_DEFAULT_PROFILE`, because an inherited profile name with no matching config entry makes botocore raise at credential resolution before Bedrock is reached — [`:84`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L84), [`:96`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L96).
2. Normalize the lane argument before it names a lock file, so two spellings of one lane cannot take two locks; an unknown mode exits 64 rather than defaulting to a plane — [`:109`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L109).
3. Read the rotated Bedrock bearer token at run time and export it only when non-empty, because botocore treats an empty value as a real broken credential and skips the default chain — [`:122`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L122).
4. Resolve the corpus list — the primary config directory first, then each `ATIF_SQL_EXTRA_CONFIG_DIRS` entry — and export both `CLAUDE_CONFIG_DIR` and `ATIF_SQL_SOURCE_ROOT` per corpus so the source root and the corpus slug stay coherent — [`:134`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L134), [`:312`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L312).
5. Resolve the CLI through the override, then a user install, then the workspace venv script, and take a nonblocking per-lane `flock`: a busy lane skips this tick rather than queueing behind it — [`:151`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L151), [`:166`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L166).
6. On the two analytics lanes, probe `atif-sql --help` for `analyze` and exit 0 when it is absent, so an armed crontab line against an older CLI is a no-op instead of an hourly error — [`:181`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L181).
7. Run the lane per corpus: `materialize` plus a bounded `embed --limit 500` piggyback, `analyze --structural-only`, or `analyze --no-dry-run --llm-only --max-sessions 50 --max-cost-usd 25.0` — the only spending line in the file — [`:255`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L255), [`:274`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L274), [`:286`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L286).
8. On embed exit 78, write a terminal marker keyed on the store path and its mtime and suppress further embeds for that corpus until the mtime changes; every other nonzero exit stays transient and retries next tick — [`:217`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L217), [`:244`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L244).
### Related
[Section titled “Related”](#related-7)
* [`scripts/atif-sql-refresh-selftest.sh:97`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh-selftest.sh#L97)
* [`scripts/atif-sql-refresh-selftest.sh:110`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh-selftest.sh#L110)
* [`packages/atif-cli/src/atif_cli/cron.py:47`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L47)
* [`packages/atif-cli/src/atif_cli/app.py:66`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L66)
## Minor flows
[Section titled “Minor flows”](#minor-flows)
* status — entry at [`packages/atif-cli/src/atif_cli/app.py:444`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L444). Read-only freshness report: scans source mtimes and replays the same pure plan `materialize` would build without converting anything, so its stale, up-to-date, and live counts are exactly what a pass would do ([`packages/atif-corpus/src/atif_corpus/domain/sessions.py:159`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L159)).
* examples — entry at [`packages/atif-cli/src/atif_cli/app.py:995`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L995). Derives one runnable example per view and macro from the static catalog and filters by `--category` and `--requires`; an unknown value exits 64 ([`packages/atif-duck/src/atif_duck/domain/examples.py:178`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L178)).
* schema — entry at [`packages/atif-cli/src/atif_cli/app.py:1087`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1087). Dumps the view schemas and macro signatures from the static catalog with no DuckDB import and no connection ([`packages/atif-duck/src/atif_duck/domain/catalog.py:51`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L51)).
* cron install — entry at [`packages/atif-cli/src/atif_cli/cron.py:179`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L179). Renders the three-lane crontab block for a human to paste and never writes the crontab itself ([`:79`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L79)).
* cron status — entry at [`packages/atif-cli/src/atif_cli/cron.py:198`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L198). Probes each lane’s flock nonblocking, so the probe perturbs nothing, and parses the last completion and last skip per lane out of the refresh log ([`:91`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L91), [`:109`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L109)).
* main — entry at [`packages/atif-cli/src/atif_cli/app.py:1125`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1125). Replaces loguru’s default DEBUG sink with WARNING-and-up so routine reads keep stderr quiet, then hands control to cyclopts.
## See also
[Section titled “See also”](#see-also)
* [module map](../architecture/module-map.md) — 42 shared source citations
* [business logic](../insights/business-logic.md) — 36 shared source citations
* [contract map](../insights/contract-map.md) — 35 shared source citations
* [impact analysis](../insights/impact-analysis.md) — 35 shared source citations
* [debugging guide](../insights/debugging-guide.md) — 29 shared source citations
# State machines
# atif-sql · State machines
[Section titled “atif-sql · State machines”](#atif-sql--state-machines)
No entity in this workspace carries a `status` enum with declared transitions. The workspace declares exactly three enums. `RecordType` ([`packages/atif-converter/src/atif_converter/domain/fidelity.py:18`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L18)) and `FidelityGap` ([`:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L44)) are closed classification vocabularies whose members never move into one another. `OutputFormat` ([`packages/atif-cli/src/atif_cli/output.py:58`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L58)) has one resolution — `resolve_format` maps `AUTO` to `TABLE` on a TTY and `JSON` otherwise ([`:79-81`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L79-L81)) — but that is an idempotent pure function evaluated per emit, a no-op for every explicit format ([`:74-80`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L74-L80)), whose result is never stored and never advances again. One irreversible resolution with no persisted state is not a lifecycle.
The three machines below are durable-state lifecycles instead: the state lives in a row, a sidecar file, or the presence of a directory, and named functions move an entity between named states. State names and transition labels are verbatim source text from each machine’s `Defined at:` file.
## Corpus session materialization
[Section titled “Corpus session materialization”](#corpus-session-materialization)
Every pass over the raw transcript corpus reclassifies each discovered session from scratch, so the state is not a stored field — it is the pair “what the watermark records” and “whether the session’s artifact directory exists”. The classification is the `if / elif / else` at [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:200-206`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L200-L206), and the outcome states are the counters and id tuples of `MaterializationReport` ([`packages/atif-corpus/src/atif_corpus/application/materialize.py:117-157`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L117-L157)).
* `skipped_live` is the entry state, not an error state. A transcript is appended to many times per turn, and a session qualifies to convert only once its newest source mtime is at least `quiesce_seconds` old — 300 by default ([`packages/atif-corpus/src/atif_corpus/domain/sessions.py:81`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L81)). `force` overrides staleness but never quiescence, because converting a half-written transcript produces a wrong artifact rather than a stale one ([`:170-174`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L170-L174)).
* `failures --> to_materialize` is the retry, and it works only because `_advance_watermark` retains the failed session’s entries. Staleness is “recorded set differs from scanned set”, so the retained entry *is* the retry signal; dropping it would classify the session `up_to_date` forever ([`packages/atif-corpus/src/atif_corpus/application/materialize.py:435-448`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L435-L448)).
* `up_to_date --> to_materialize: _unmaterialized_session_ids` is the crash-recovery edge. It is reachable exactly one way — a pass killed inside the directory swap, after the previous generation was renamed aside and before the new one landed. The watermark records source mtimes only and cannot express a missing artifact directory, so the directory check is what force-replans it ([`:302-328`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L302-L328)).
* `unreadable_session_ids` is a parking state, and the only state a session can starve in. A transient stat failure clears next pass; a permanent one (a side-file left at mode 000) leaves the session never materialized, never `up_to_date`, never `skipped_live`, and deliberately never ghosted, which is why the report carries the ids at all ([`:135-142`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L135-L142)).
* `removed_session_ids` is the sole terminal state: `shutil.rmtree` deletes the corpus session directory ([`:411`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L411)) and `_advance_watermark` drops its entries, because the raw source is authoritative and there are no tombstones ([`:386-392`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L386-L392)). A session whose sources merely could not be read is never ghosted ([`:404-410`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L404-L410)), and a scan that found zero sessions over a non-empty corpus raises `SuspiciousEmptyScanError` instead of removing everything ([`:542-548`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L542-L548)).
* Transition sites: `scan_sources` at [`:522`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L522); `QuiescencePolicy` at [`:575`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L575); `_write_session` at [`:588`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L588); `MaterializationFailure` at [`:599`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L599); `_remove_ghost_sessions` at [`:562`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L562); `_advance_watermark` at [`:610`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L610).
Defined at: [`packages/atif-corpus/src/atif_corpus/application/materialize.py:476`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L476)
## Embed store schema version
[Section titled “Embed store schema version”](#embed-store-schema-version)
The vector store’s state is a schema generation recorded in a `schema_version.json` sidecar next to the Lance table, written on create and checked on open. A sidecar rather than a table column, because the version describes the schema and reading it must not require the schema to be readable ([`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:63-67`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L63-L67)). `SCHEMA_VERSION` is `2` ([`:59`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L59)).
* `v1` and `unrecorded` have no incoming edge on purpose. This code never writes either state; it discriminates a store it finds on disk. `v1` is the five-column shape lacking `text_hash` and `truncated`; `unrecorded` is a store whose columns are already current but whose sidecar is absent, which `read_schema_version` reports as `None` ([`:141-154`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L141-L154)).
* `v1 --> v2` is online and metadata-only. `migrate_pre_stamp_table` adds the two columns through Lance schema evolution with SQL default expressions — no rows dropped, no vectors re-embedded, and readers keep working throughout ([`:164-190`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L164-L190)). `text_hash` backfills to `_PRE_STAMP_SENTINEL`, the literal `` ([`:71`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L71)), whose angle brackets make it impossible to equal a real blake2b digest; every migrated row therefore mismatches its corpus hash in the discovery anti-join and re-embeds incrementally through the ordinary staleness path ([`:67-70`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L67-L70)).
* `v2 --> v2: _has_table` is idempotent reopening. `migrate_pre_stamp_table` returns early when nothing is missing ([`:180-181`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L180-L181)) and the sidecar stamp is left alone.
* The machine has no terminal state in source. `v2` is absorbing; the store is never dropped here. A breaking change — a provider or dimension switch — is refused rather than migrated, and stays fail-loud through `embedding_guard` ([`:25-27`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L25-L27)).
Defined at: [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:195`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L195)
## Retry-queue row
[Section titled “Retry-queue row”](#retry-queue-row)
One row per `(pipeline, unit_id)` in the `retry_queue` table of the analytics `state.db`. The state is the triple “is `completed_at` NULL”, “`attempts` against `max_attempts`”, and “`next_attempt_at` against now”. The transition table is written as source at [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py:12-16`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L12-L16), and the columns holding the state are declared at [`:45-56`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L45-L56).
* The first `enqueue` lands in `backoff`, never `due`: it stamps `next_attempt_at` at now plus `_backoff_delta(1)`, which is two minutes ([`:78-81`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L78-L81), [`:109`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L109)). Backoff is exponential in minutes — 2, 4, 8, 16, 32 — and `_BACKOFF_CAP_MIN` clamps it at 60 ([`:43`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L43), [`:79`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L79)). That clamp is defensive rather than reachable at the shipped default: `drain` admits a unit only while `attempts < max_attempts` ([`:145`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L145)), so with `MAX_ATTEMPTS_DEFAULT` of 5 ([`:42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L42)) the largest value `_backoff_delta` receives is 5 and the longest real wait is 32 minutes.
* `backoff` and `exhausted` together are exactly what `blocked_units` returns, and `due` is what `drain` returns; the two are complementary partitions of the live rows ([`:159-190`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L159-L190), [`:128-156`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L128-L156)). Together they make the queue the single re-admission gate for a failed unit: a failed unit is not checkpointed, so without the `blocked_units` subtraction the checkpoint path would re-admit and re-bill it on every run ([`:166-176`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L166-L176)).
* `exhausted` is terminal in effect and has no outgoing edge. `drain` never returns it, so the pipeline never dispatches it, so it never fails again and is never marked done. The guard is `keep -= blocked` at [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:123`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L123), spelled `active_sessions -= blocked` in [`packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py:193`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py#L193) and `already |= blocked` in [`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:280`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L280), whose unit is a message uuid rather than a session id. `exhausted` is a parking state rather than a deleted row: the module issues no `DELETE`, and raising `max_attempts` on a `drain` call re-admits it.
* `completed --> backoff` is real, and the attempt counter survives it. `enqueue` clears `completed_at` back to NULL on conflict ([`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py:128-133`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L128-L133)) while reading the prior `attempts` with no filter on `completed_at` ([`:103-108`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L103-L108)), so a unit that failed four times, succeeded, then failed again re-enters at `attempts = 5` and is `exhausted` immediately under the default `MAX_ATTEMPTS_DEFAULT` of 5 ([`:42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L42)). Every state name here is a row predicate, not a stored string; the table has no status column ([`:45-56`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L45-L56)).
All five pipelines named by `PIPELINE_NAMES` ([`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:35-41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py#L35-L41)) fire the identical four-call sequence — `drain`, then `blocked_units`, then `enqueue` on failure and `mark_done` on success:
| pipeline | `drain` | `blocked_units` | `enqueue` | `mark_done` |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `classify` | [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:108`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L108) | [`:117`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L117) | [`:249`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L249) | [`:276`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L276) |
| `trajectory` | [`packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py:180`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py#L180) | [`:187`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py#L187) | [`:319`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py#L319) | [`:415`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py#L415) |
| `conflicts` | [`packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py:126`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py#L126) | [`:133`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py#L133) | [`:251`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py#L251) | [`:318`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py#L318) |
| `user_friction` | [`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:266`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L266) | [`:274`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L274) | [`:480`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L480) | [`:500`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L500) |
| `perceived` | [`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:160`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L160) | [`:167`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L167) | [`:287`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L287) | [`:336`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L336) |
Defined at: [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py:12`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L12)
## See also
[Section titled “See also”](#see-also)
* [processes](processes.md) — 12 shared source citations
* [module map](../architecture/module-map.md) — 10 shared source citations
* [business logic](../insights/business-logic.md) — 10 shared source citations
* [contract map](../insights/contract-map.md) — 8 shared source citations
* [debugging guide](../insights/debugging-guide.md) — 8 shared source citations
# Components
# atif-sql · Components
[Section titled “atif-sql · Components”](#atif-sql--components)
## Legend
[Section titled “Legend”](#legend)
### Classes
[Section titled “Classes”](#classes)
Each node is one uv workspace member, the unit the seven import-linter contracts at [`pyproject.toml:462-523`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L462-L523) constrain. Every use case and registration entry point is a module-level function, so the `+` entries are functions rather than methods. `AtifCliApp`’s five entries each carry an `@app.command` decorator on the line above; `app` is the `cyclopts.App` at [`packages/atif-cli/src/atif_cli/app.py:52`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L52) and `main` at [`packages/atif-cli/src/atif_cli/app.py:1125`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1125) is the `atif-sql` console script declared at [`packages/atif-cli/pyproject.toml:42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L42).
| Class | Workspace member | Method entry | Declared at |
| -------------------- | ---------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AtifCliApp` | atif-cli | `materialize` | [`packages/atif-cli/src/atif_cli/app.py:352`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L352) |
| `AtifCliApp` | atif-cli | `query` | [`packages/atif-cli/src/atif_cli/app.py:529`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L529) |
| `AtifCliApp` | atif-cli | `analyze` | [`packages/atif-cli/src/atif_cli/app.py:659`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L659) |
| `AtifCliApp` | atif-cli | `embed` | [`packages/atif-cli/src/atif_cli/app.py:766`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L766) |
| `AtifCliApp` | atif-cli | `search` | [`packages/atif-cli/src/atif_cli/app.py:860`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L860) |
| `AtifConverter` | atif-converter | `convert_and_audit` | [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:105`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L105) |
| `AtifConverter` | atif-converter | `convert_session` | [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:142`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L142) |
| `AtifConverter` | atif-converter | `enrich_trajectory` | [`packages/atif-converter/src/atif_converter/domain/enrichment.py:198`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L198) |
| `AtifConverter` | atif-converter | `validate_trajectory` | [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:97`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L97) |
| `CorpusMaterializer` | atif-corpus | `materialize` | [`packages/atif-corpus/src/atif_corpus/application/materialize.py:476`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L476) |
| `CorpusMaterializer` | atif-corpus | `read_watermark` | [`packages/atif-corpus/src/atif_corpus/application/materialize.py:160`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L160) |
| `CorpusMaterializer` | atif-corpus | `scan_source_root` | [`packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py:211`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py#L211) |
| `CorpusMaterializer` | atif-corpus | `build_plan` | [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:159`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L159) |
| `DuckRegistry` | atif-duck | `register` | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1212`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1212) |
| `DuckRegistry` | atif-duck | `register_views` | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:317`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L317) |
| `DuckRegistry` | atif-duck | `register_vss` | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:830`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L830) |
| `DuckRegistry` | atif-duck | `register_macros` | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:989`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L989) |
| `DuckRegistry` | atif-duck | `build_examples` | [`packages/atif-duck/src/atif_duck/domain/examples.py:178`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L178) |
| `EmbedBackfill` | atif-embed | `run_backfill` | [`packages/atif-embed/src/atif_embed/application/embed.py:61`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L61) |
| `EmbedBackfill` | atif-embed | `embed_query` | [`packages/atif-embed/src/atif_embed/application/embed.py:265`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L265) |
| `EmbedBackfill` | atif-embed | `discover_unembedded` | [`packages/atif-embed/src/atif_embed/application/embed.py:43`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L43) |
| `AnalyticsPipelines` | atif-analytics | `run_analyze` | [`packages/atif-analytics/src/atif_analytics/application/analyze.py:36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L36) |
| `AnalyticsPipelines` | atif-analytics | `run_clustering` | [`packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py:50`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py#L50) |
| `AnalyticsPipelines` | atif-analytics | `classify_sessions` | [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:342`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L342) |
| `AnalyticsPipelines` | atif-analytics | `detect_conflicts` | [`packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py:337`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py#L337) |
| `AnalyticsPipelines` | atif-analytics | `build_provider` | [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L32) |
| `ModelRegistry` | atif-models | `resolve` | [`packages/atif-models/src/atif_models/domain/registry.py:109`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L109) |
| `ModelRegistry` | atif-models | `spec_for` | [`packages/atif-models/src/atif_models/infrastructure/settings.py:79`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/settings.py#L79) |
| `ModelRegistry` | atif-models | `estimate_cost` | [`packages/atif-models/src/atif_models/domain/registry.py:125`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L125) |
| `ModelRegistry` | atif-models | `classify_structured` | [`packages/atif-models/src/atif_models/domain/ports.py:123`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L123), the sole method of the `LlmStructuredProvider` Protocol at [`packages/atif-models/src/atif_models/domain/ports.py:116`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L116), implemented by `OpenAiBedrockProvider` at [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:217`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L217) |
`register` calls the other three registrars in binding order at [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1267-1276`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1267-L1276); `run_analyze` dispatches the five LLM stages from a table at [`packages/atif-analytics/src/atif_analytics/application/analyze.py:154-160`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L154-L160).
### Relationships
[Section titled “Relationships”](#relationships)
Every edge is one import plus one call. **Every cross-package import in this workspace is indented into the function body that needs it**, never at module top. A fresh-interpreter test asserts that a bare `import atif_cli.app` pulls in none of `duckdb`, `harbor`, `lancedb`, `boto3`, or `polars` — `_FORBIDDEN_EAGER_IMPORTS` at [`packages/atif-cli/tests/test_lean_import.py:17-31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_lean_import.py#L17-L31) — and [`packages/atif-cli/src/atif_cli/app.py:22-25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L22-L25) records that as the reason the heavy imports sit in the command bodies. A line-anchored grep for `^from atif_` therefore finds none of these six edges.
Five edges originate at `AtifCliApp`, which [`packages/atif-cli/src/atif_cli/app.py:3-7`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L3-L7) names the workspace’s composition root. Those five are the whole of its outbound surface: an unanchored grep for `from atif_(converter|corpus|duck|models|embed|analytics)` across `packages/atif-cli/src` returns 26 hits spanning exactly those five members, and **atif-cli never imports atif-models**.
| Edge | Verb | Import site | Call site |
| ------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AtifCliApp -> AtifConverter` | converts | [`packages/atif-cli/src/atif_cli/app.py:253`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L253) | [`packages/atif-cli/src/atif_cli/app.py:261`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L261) |
| `AtifCliApp -> CorpusMaterializer` | materializes | [`packages/atif-cli/src/atif_cli/app.py:388`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L388) | [`packages/atif-cli/src/atif_cli/app.py:396`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L396) |
| `AtifCliApp -> DuckRegistry` | registers | [`packages/atif-cli/src/atif_cli/app.py:615`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L615), [`packages/atif-cli/src/atif_cli/app.py:906`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L906) | [`packages/atif-cli/src/atif_cli/app.py:626`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L626), [`packages/atif-cli/src/atif_cli/app.py:918`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L918) |
| `AtifCliApp -> AnalyticsPipelines` | dispatches | [`packages/atif-cli/src/atif_cli/app.py:720`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L720) | [`packages/atif-cli/src/atif_cli/app.py:739`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L739) |
| `AtifCliApp -> EmbedBackfill` | embeds | [`packages/atif-cli/src/atif_cli/app.py:806`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L806) | [`packages/atif-cli/src/atif_cli/app.py:826`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L826) |
| `AnalyticsPipelines -> ModelRegistry` | resolves | [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L41) | [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:45`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L45) |
### Relationships the import contracts forbid
[Section titled “Relationships the import contracts forbid”](#relationships-the-import-contracts-forbid)
[`pyproject.toml:514-517`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L514-L517) declares an `independence` contract over `atif_converter`, `atif_corpus`, `atif_duck`, `atif_models`, and `atif_embed`, so no edge may connect any two of those five. [`pyproject.toml:519-523`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L519-L523) declares a `forbidden` contract admitting exactly one analytics edge — to `atif_models`. Both are checked by `lint:imports`, the fifth of the nine gates `mise run check` depends on ([`mise.toml:204`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L204), in the list at [`mise.toml:199-211`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L199-L211)).
A Protocol declared in one member and implemented in another is **not** an edge between them: the implementation satisfies it structurally, and the composition root supplies the instance. Neither is a permission comment an edge — [`pyproject.toml:509`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L509) reads “ONLY atif-cli and atif-analytics may import atif-models”, which grants reach that atif-cli does not take.
| Absent edge | How the same work reaches across the boundary |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CorpusMaterializer -> AtifConverter` | `materialize` takes a `ConverterPort` parameter ([`packages/atif-corpus/src/atif_corpus/application/materialize.py:480`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L480)), the Protocol at [`packages/atif-corpus/src/atif_corpus/domain/ports.py:41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L41). `AtifCliApp` constructs the `RealConverter` adapter ([`packages/atif-cli/src/atif_cli/converter_adapter.py:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L44)) and injects it at [`packages/atif-cli/src/atif_cli/app.py:399`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L399); that adapter’s docstring names the independence contract as its reason to live in atif-cli ([`packages/atif-cli/src/atif_cli/converter_adapter.py:5-9`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L5-L9)). |
| `EmbedBackfill -> DuckRegistry` | `EmbedBackfill` reads the corpus through its own DuckDB `TextRowsPort` adapter, `DuckDbTextRows` ([`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:155`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L155)) against the Protocol at [`packages/atif-embed/src/atif_embed/domain/ports.py:87`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L87), over the `docs/CONTRACT.md` corpus layout — stated at [`pyproject.toml:458-460`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L458-L460). |
| `AnalyticsPipelines -> CorpusMaterializer` | `AnalyticsPipelines` reads the materialized corpus directly through its own `CorpusReader` ([`packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:138`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py#L138)), constructed once per run at [`packages/atif-analytics/src/atif_analytics/application/analyze.py:74`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L74). |
| `AtifCliApp -> ModelRegistry` | `AtifCliApp` never reaches atif-models. Model selection happens inside `AnalyticsPipelines`, whose `build_provider` resolves a spec through `spec_for` ([`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L44)) — so no model id is written down outside atif-models. |
| Absent edge | How the same work reaches across the boundary |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CorpusMaterializer -> AtifConverter` | `materialize` takes a `ConverterPort` parameter ([`packages/atif-corpus/src/atif_corpus/application/materialize.py:480`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L480)), the Protocol at [`packages/atif-corpus/src/atif_corpus/domain/ports.py:41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L41). `AtifCliApp` constructs the `RealConverter` adapter ([`packages/atif-cli/src/atif_cli/converter_adapter.py:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L44)) and injects it at [`packages/atif-cli/src/atif_cli/app.py:399`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L399); that adapter’s docstring names the independence contract as its reason to live in atif-cli ([`packages/atif-cli/src/atif_cli/converter_adapter.py:5-9`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L5-L9)). |
| `EmbedBackfill -> DuckRegistry` | `EmbedBackfill` reads the corpus through its own DuckDB `TextRowsPort` adapter, `DuckDbTextRows` ([`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:155`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L155)) against the Protocol at [`packages/atif-embed/src/atif_embed/domain/ports.py:87`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L87), over the `docs/CONTRACT.md` corpus layout — stated at [`pyproject.toml:458-460`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L458-L460). |
| `AnalyticsPipelines -> CorpusMaterializer` | `AnalyticsPipelines` reads the materialized corpus directly through its own `CorpusReader` ([`packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:138`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py#L138)), constructed once per run at [`packages/atif-analytics/src/atif_analytics/application/analyze.py:74`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L74). |
## See also
[Section titled “See also”](#see-also)
* [module map](../../architecture/module-map.md) — 23 shared source citations
* [processes](../../behavior/processes.md) — 23 shared source citations
* [impact analysis](../../insights/impact-analysis.md) — 23 shared source citations
* [contract map](../../insights/contract-map.md) — 20 shared source citations
* [business logic](../../insights/business-logic.md) — 18 shared source citations
# Sequences
# atif-sql · Sequences
[Section titled “atif-sql · Sequences”](#atif-sql--sequences)
## materialize
[Section titled “materialize”](#materialize)
Sources, in dispatch order:
* CLI materialize — [`packages/atif-cli/src/atif_cli/app.py:352`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L352); report rendered at [`:299`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L299).
* materialize UC — [`packages/atif-corpus/src/atif_corpus/application/materialize.py:476`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L476); `read_watermark` at [`:160`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L160) called from [`:521`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L521), `scan_sources` called at [`:522`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L522), `build_plan` called at [`:572`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L572), `_write_session` at [`:179`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L179) called from [`:588`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L588), watermark advanced at [`:610`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L610), report built at [`:613`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L613).
* scanner — [`packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py:143`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py#L143).
* build\_plan — [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:159`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L159).
* RealConverter — [`packages/atif-cli/src/atif_cli/converter_adapter.py:51`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L51), calling [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:105`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L105).
* harbor ClaudeCode — the pinned private `ClaudeCode._convert_events_to_trajectory` invoked at [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:178`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L178).
* corpus artifacts — path arithmetic in [`packages/atif-corpus/src/atif_corpus/domain/layout.py:26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/layout.py#L26); the four artifacts written and the directory swapped at [`packages/atif-corpus/src/atif_corpus/application/materialize.py:222-240`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L222-L240) through [`packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py:64`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py#L64) and [`:98`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py#L98).
## query
[Section titled “query”](#query)
Sources, in dispatch order:
* CLI query — [`packages/atif-cli/src/atif_cli/app.py:529`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L529); `duckdb.connect()` at [`:591`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L591), `register` called at [`:594`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L594), `_harden_query_connection` at [`:137`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L137) called from [`:601`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L601), caller SQL executed at [`:606`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L606), emit at [`:612`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L612).
* atif\_duck register — [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1212`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1212), whose documented order is fixed at [`:1283-1294`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1283-L1294): `register_raw` [`:196`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L196), `register_views` [`:333`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L333), `register_vss` [`:846`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L846), `register_macros` [`:1005`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1005), then the analytics pair.
* DuckDB in-process — the connection opened by the CLI; the sandbox settings and `lock_configuration` are issued against it at [`packages/atif-cli/src/atif_cli/app.py:180-188`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L180-L188).
* corpus artifacts — session globs read through `read_json` at [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:210-213`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L210-L213); the analytics parquets bind through [`packages/atif-duck/src/atif_duck/infrastructure/analytics.py:124`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/analytics.py#L124), with the analytics macros at [`:225`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/analytics.py#L225).
* LanceDB store — reached through DuckDB’s lance extension: [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:878-887`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L878-L887) installs, loads, and ATTACHes it; the stamped `(model, dim)` identity is read at [`:937`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L937) and checked by [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:46`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L46).
* stdout emitter — [`packages/atif-cli/src/atif_cli/output.py:154`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L154); a registration failure routes through [`packages/atif-cli/src/atif_cli/duck_errors.py:66`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L66) instead.
## analyze
[Section titled “analyze”](#analyze)
Sources, in dispatch order:
* CLI analyze — [`packages/atif-cli/src/atif_cli/app.py:659`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L659); settings resolved at [`:691`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L691), `run_analyze` called at [`:707`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L707).
* run\_analyze — [`packages/atif-analytics/src/atif_analytics/application/analyze.py:36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L36); lane selectors at [`:65-66`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L65-L66), the five-entry LLM `stages` list at [`:154-160`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L154-L160), each stage invoked at [`:181`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L181).
* CorpusReader — [`packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:138`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py#L138), constructed once per run at [`packages/atif-analytics/src/atif_analytics/application/analyze.py:74`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L74); `session_text` at [`packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:284`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py#L284).
* structural stages — `run_clustering` [`packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py:50`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py#L50), `run_terms` [`packages/atif-analytics/src/atif_analytics/application/use_cases/terms.py:54`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/terms.py#L54), `run_communities` [`packages/atif-analytics/src/atif_analytics/application/use_cases/community.py:94`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/community.py#L94).
* LanceDB store — `load_embeddings` [`packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py#L28), called from [`packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py:62`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py#L62); `lancedb.connect` at [`packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py:40`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py#L40).
* LLM stages — the classify exemplar at [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:342`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L342); provider built by [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L32), structured call dispatched at [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:194`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L194).
* Bedrock runtime — `OpenAiBedrockProvider.classify_structured` [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:217`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L217), blocking `invoke_model` at [`:261`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L261) on the `bedrock-runtime` client built at [`:195`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L195).
* analytics dir — `/analytics/`, defined at [`packages/atif-analytics/src/atif_analytics/domain/layout.py:51`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/layout.py#L51), holding both the sharded parquet caches (`write_part` [`packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py:90`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L90), called at [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:264`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L264)) and the single SQLite WAL `state.db` ([`packages/atif-analytics/src/atif_analytics/domain/layout.py:106`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/layout.py#L106)).
* analytics dir, SQLite arm — `filter_unchanged` [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:136`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py#L136) called at [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:99`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L99); `mark_completed` [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:299`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py#L299) called at [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:272`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L272); retry drain [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py:265`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L265) called at [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:108`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L108).
## See also
[Section titled “See also”](#see-also)
* [processes](../../behavior/processes.md) — 26 shared source citations
* [debugging guide](../../insights/debugging-guide.md) — 18 shared source citations
* [module map](../../architecture/module-map.md) — 17 shared source citations
* [business logic](../../insights/business-logic.md) — 17 shared source citations
* [data flow](../../architecture/data-flow.md) — 16 shared source citations
# Dependency graph
# atif-sql · Dependency graph
[Section titled “atif-sql · Dependency graph”](#atif-sql--dependency-graph)
Seven internal modules and their thirteen highest-frequency external dependencies. Internal nodes are the uv workspace members declared by `[tool.uv.workspace] members = ["packages/*"]` ([`pyproject.toml:99-100`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L99-L100)); external nodes are first-order distributions taken from a member’s own `[project.dependencies]`, never from the installed transitive closure.
The internal direction is enforced rather than conventional. [`pyproject.toml:462`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L462) declares `[tool.importlinter]` over all seven root packages ([`:366`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L366)), and `mise run lint:imports` ([`mise.toml:159-162`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L159-L162)) is one of the nine gates `mise run check` depends on ([`mise.toml:197-211`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L197-L211)). Two of those contracts fix the shape drawn here: the `independence` contract at [`pyproject.toml:514-517`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L514-L517) forbids any import edge among atif\_converter, atif\_corpus, atif\_duck, atif\_models, and atif\_embed, and the `forbidden` contract at [`:422-426`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L422-L426) allows atif\_analytics to import atif\_models and nothing else among the seven.
## Legend (overflow)
[Section titled “Legend (overflow)”](#legend-overflow)
Six declared external distributions are elided to hold the 20-node cap. Edge count is the number of internal modules whose `src/` imports the distribution, measured by grepping every `*.py` under `packages/*/src` for a `from X` or `import X` line at any indentation.
| elided node | edges | importing module | declared at | import site |
| ------------------ | ----- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| scipy | 1 | atif-analytics | [`packages/atif-analytics/pyproject.toml:37`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L37) | [`packages/atif-analytics/src/atif_analytics/domain/structure/community.py:82`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/community.py#L82) |
| umap-learn | 1 | atif-analytics | [`packages/atif-analytics/pyproject.toml:38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L38) | [`packages/atif-analytics/src/atif_analytics/domain/structure/cluster.py:48`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/cluster.py#L48) |
| scikit-learn | 1 | atif-analytics | [`packages/atif-analytics/pyproject.toml:36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L36) | [`packages/atif-analytics/src/atif_analytics/domain/structure/terms.py:39`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/terms.py#L39) |
| hdbscan | 1 | atif-analytics | [`packages/atif-analytics/pyproject.toml:26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L26) | [`packages/atif-analytics/src/atif_analytics/domain/structure/cluster.py:47`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/cluster.py#L47) |
| graspologic-native | 1 | atif-analytics | [`packages/atif-analytics/pyproject.toml:25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L25) | [`packages/atif-analytics/src/atif_analytics/domain/structure/community.py:261`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/community.py#L261) |
| pytz | 0 | none | [`packages/atif-duck/pyproject.toml:25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/pyproject.toml#L25) | no import site; DuckDB’s own client imports it to materialize TIMESTAMPTZ, per the comment at [`:22-24`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/pyproject.toml#L22-L24) |
All five clustering and community-detection libraries hang off atif-analytics alone, so the elision costs the diagram no structural information: it drops five leaves from one node that already carries four drawn external edges.
## Internal edges
[Section titled “Internal edges”](#internal-edges)
Exactly six ordered pairs of members import each other. Counts are importing-file counts under the source member’s `src/`.
| edge | files | contract that permits it |
| ----------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| atif-cli to atif-analytics | 1 | atif-cli is the composition root; it is absent from both restrictive contracts ([`pyproject.toml:517`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L517), [`:426`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L426)) |
| atif-cli to atif-converter | 2 | as above; declared [`packages/atif-cli/pyproject.toml:33`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L33) |
| atif-cli to atif-corpus | 2 | as above; declared [`packages/atif-cli/pyproject.toml:34`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L34) |
| atif-cli to atif-duck | 2 | as above; declared [`packages/atif-cli/pyproject.toml:35`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L35) |
| atif-cli to atif-embed | 1 | as above; declared [`packages/atif-cli/pyproject.toml:36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L36) |
| atif-analytics to atif-models | 7 | the `forbidden` contract’s single permitted edge ([`pyproject.toml:519-523`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L519-L523)); declared [`packages/atif-analytics/pyproject.toml:24`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L24) |
Two absences carry meaning. atif-cli imports atif\_models in zero source files even though it composes everything else — it reaches the model registry through atif-analytics, and its `[project.dependencies]` list omits atif-models accordingly ([`packages/atif-cli/pyproject.toml:32-39`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L32-L39)). And no edge exists in either direction between atif-converter, atif-corpus, atif-duck, atif-embed, or atif-models: they communicate only by writing and reading the corpus on disk. Adding any such edge fails gate 5 of `mise run check`.
The five inter-member dependencies are `==0.1.0`-pinned rather than bare ([`packages/atif-cli/pyproject.toml:32-36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L32-L36)) because `[tool.uv.sources]` at [`:59-64`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L59-L64) is a local source an external installer never sees; a bare name would ship as a `Requires-Dist` resolved from the public PyPI namespace.
## External edges and where each is sourced
[Section titled “External edges and where each is sourced”](#external-edges-and-where-each-is-sourced)
One edge per external distribution, drawn from the member whose files import it most often. Ties break on import-site count, then on the member’s own src line count, descending. `files` counts importing files across all seven members’ `src/`.
| external node | files | edge drawn from | declared at | import site |
| ----------------- | ----- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| loguru | 33 | atif-analytics | [`packages/atif-analytics/pyproject.toml:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L28) | [`packages/atif-analytics/src/atif_analytics/application/analyze.py:30`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L30) |
| polars | 13 | atif-analytics | [`packages/atif-analytics/pyproject.toml:33`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L33) | [`packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py:18`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py#L18) |
| pydantic | 7 | atif-models | [`packages/atif-models/pyproject.toml:26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/pyproject.toml#L26) | [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L41) |
| numpy | 6 | atif-analytics | [`packages/atif-analytics/pyproject.toml:32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L32) | [`packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py:17`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py#L17) |
| duckdb | 5 | atif-duck | [`packages/atif-duck/pyproject.toml:20`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/pyproject.toml#L20) | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:60`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L60) |
| pydantic-settings | 4 | atif-embed | [`packages/atif-embed/pyproject.toml:27`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/pyproject.toml#L27) | [`packages/atif-embed/src/atif_embed/infrastructure/settings.py:16`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/settings.py#L16) |
| tenacity | 2 | atif-embed | [`packages/atif-embed/pyproject.toml:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/pyproject.toml#L28) | [`packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py#L36) |
| pyarrow | 2 | atif-analytics | [`packages/atif-analytics/pyproject.toml:34`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L34) | [`packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py:137`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/parquet_cache.py#L137) |
| lancedb | 2 | atif-embed | [`packages/atif-embed/pyproject.toml:22`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/pyproject.toml#L22) | [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L42) |
| anyio | 2 | atif-models | [`packages/atif-models/pyproject.toml:19`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/pyproject.toml#L19) | [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:29`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L29) |
| cyclopts | 2 | atif-cli | [`packages/atif-cli/pyproject.toml:37`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L37) | [`packages/atif-cli/src/atif_cli/app.py:35`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L35) |
| boto3 | 2 | atif-embed | [`packages/atif-embed/pyproject.toml:20`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/pyproject.toml#L20) | [`packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:136`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py#L136) |
| harbor | 1 | atif-converter | [`packages/atif-converter/pyproject.toml:23`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/pyproject.toml#L23) | [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:161`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L161) |
Three readings the drawn edge deliberately compresses:
* **loguru is universal.** All seven members declare it — [`packages/atif-analytics/pyproject.toml:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L28), [`packages/atif-cli/pyproject.toml:38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L38), [`packages/atif-converter/pyproject.toml:24`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/pyproject.toml#L24), [`packages/atif-corpus/pyproject.toml:19`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/pyproject.toml#L19), [`packages/atif-duck/pyproject.toml:21`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/pyproject.toml#L21), [`packages/atif-embed/pyproject.toml:23`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/pyproject.toml#L23), [`packages/atif-models/pyproject.toml:25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/pyproject.toml#L25) — and the edge is drawn from atif-analytics only because 17 of the 33 importing files are its. The edge label states the real fan-out.
* **atif-corpus has no drawn external edge.** It declares three externals — loguru ([`packages/atif-corpus/pyproject.toml:19`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/pyproject.toml#L19)), pydantic ([`:20`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/pyproject.toml#L20)), pydantic-settings ([`:21`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/pyproject.toml#L21)) — and each is imported more often, or in more sites, by another member, so the attribution rule sources all three elsewhere. Its own import sites are real: [`packages/atif-corpus/src/atif_corpus/infrastructure/settings.py:19`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/settings.py#L19) and [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:29`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L29).
* **harbor’s single importing file is the highest-consequence external edge in the graph.** [`packages/atif-converter/pyproject.toml:19-23`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/pyproject.toml#L19-L23) ceilings the pin at `harbor>=0.22.0,<0.23` because atif-converter calls the private `ClaudeCode._convert_events_to_trajectory`, bound to `_CONVERT_METHOD` at [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L32) and imported at [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:161`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L161); `assert_harbor_private_api` at [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:112`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L112) raises a located error if a version bump removes it. Everything harbor touches is confined to that one module because harbor ships no `py.typed` marker ([`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:11-12`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L11-L12)).
## Declared dependencies are not the installed closure
[Section titled “Declared dependencies are not the installed closure”](#declared-dependencies-are-not-the-installed-closure)
The diagram’s external nodes are what a member asks for, and that is a strictly smaller set than what `uv sync` installs. The gap includes a web stack this system never runs: harbor’s own dependency block at [`uv.lock:1139-1164`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/uv.lock#L1139-L1164) lists `fastapi` ([`:1082`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/uv.lock#L1082)), `supabase` ([`:1097`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/uv.lock#L1097)), and `uvicorn` ([`:1101`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/uv.lock#L1101)), so all three are in the installed closure. No member declares any of them, and `grep -rnE "^[[:space:]]*(from|import) +(fastapi|uvicorn|starlette|supabase)(\.| |$)" --include='*.py' packages/` returns zero matches across every source and test file. atif-sql exposes no HTTP surface; it is one console script, `atif-sql = "atif_cli.app:main"` ([`packages/atif-cli/pyproject.toml:42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L42)), over a set of libraries.
Every storage engine in the graph is embedded, so no server node belongs here either: duckdb runs in-process ([`packages/atif-duck/pyproject.toml:20`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/pyproject.toml#L20)) and lancedb is a local vector store ([`packages/atif-embed/pyproject.toml:22`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/pyproject.toml#L22)). The only edge that leaves the machine is boto3 to Amazon Bedrock, from [`packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:136`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py#L136) and [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L31).
## See also
[Section titled “See also”](#see-also)
* [module map](../../architecture/module-map.md) — 13 shared source citations
* [processes](../../behavior/processes.md) — 13 shared source citations
* [contract map](../../insights/contract-map.md) — 12 shared source citations
* [impact analysis](../../insights/impact-analysis.md) — 11 shared source citations
* [tech debt](../../insights/tech-debt.md) — 11 shared source citations
# Business logic
# atif-sql · Business logic
[Section titled “atif-sql · Business logic”](#atif-sql--business-logic)
This file indexes the domain rules `atif-sql` enforces: input validations, invariants the code holds across a boundary, derived-value calculations, and the policy gates that decide whether work runs at all.
**Scope.** Application-layer and domain-layer rules across the seven workspace members, plus the SQL surface `atif-duck` registers into DuckDB. There is no database server, no migration directory, and no HTTP surface in this repo, so there are no DDL constraints or request-validation middlewares to survey — the DuckDB views and macros are the closest thing to a “schema”, and the rules encoded in their DDL are in scope and captured under Calculations and Invariants. LLM-output schemas count as validations here, because the provider adapter re-validates every response with pydantic before it reaches a parquet row. Ruff/pyright/import-linter rules are toolchain policy, not domain logic, and are out of scope.
**Test provenance.** Where a rule is pinned by a test, the test is cited beside the implementation. A row with no test citation is a rule read out of the implementation and not covered by a named test — that difference is stated, never smoothed over.
**Units.** Every `*_ns` value is epoch nanoseconds from `os.stat().st_mtime_ns`; every `*_chars` value counts Python string characters, not bytes or tokens; pricing is USD per 1,000,000 tokens; backoff is minutes and tenacity waits are seconds. Scope (per session, per run, per pipeline) is stated per row.
## Validations
[Section titled “Validations”](#validations)
| Rule | Domain | Citation | Failure mode |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A session path must end in `.jsonl` AND be an existing file | Conversion | [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:163-165`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L163-L165); test [`packages/atif-converter/tests/test_convert_and_audit.py:224`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_convert_and_audit.py#L224) | raise `InvalidSessionInput`; CLI exits 64 ([`packages/atif-cli/src/atif_cli/app.py:262-264`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L262-L264)) |
| harbor’s private `_convert_events_to_trajectory` must exist and be callable | Conversion | [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:112-139`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L112-L139); tests [`packages/atif-converter/tests/test_snapshot_and_drift.py:296`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_snapshot_and_drift.py#L296) and [`packages/atif-converter/tests/test_snapshot_and_drift.py:319`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_snapshot_and_drift.py#L319) | raise `HarborPrivateApiMissing`; CLI exits 127 ([`packages/atif-cli/src/atif_cli/errors.py:38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L38)) |
| harbor must return a trajectory for the session | Conversion | [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:187`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L187) | raise `EmptySessionError`; CLI exits 2 ([`packages/atif-cli/src/atif_cli/app.py:266-267`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L266-L267)) |
| The trajectory must pass harbor’s `TrajectoryValidator` AFTER enrichment mutates `extra` | Conversion | [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:97-102`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L97-L102), [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:150-154`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L150-L154); test [`packages/atif-converter/tests/test_enrichment.py:472`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_enrichment.py#L472) | errors collected onto `ConversionResult.validation_errors`; `convert` exits 65 when non-empty ([`packages/atif-cli/src/atif_cli/app.py:303-304`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L303-L304)) |
| No source file may change, vanish, or appear between the pre-read snapshot and the end of the raw parse | Conversion | [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:93-102`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L93-L102), checked twice at [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:135`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L135) and [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:148`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L148); tests [`packages/atif-converter/tests/test_snapshot_and_drift.py:193`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_snapshot_and_drift.py#L193), [`packages/atif-converter/tests/test_snapshot_and_drift.py:217`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_snapshot_and_drift.py#L217), [`packages/atif-converter/tests/test_snapshot_and_drift.py:274`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_snapshot_and_drift.py#L274) | raise `SourceMutatedDuringConversion`; the materialize pass records the session as failed and continues |
| A JSONL line that is not valid JSON, or parses to a non-dict, is not a record | Conversion | [`packages/atif-converter/src/atif_converter/infrastructure/raw_records.py:136-142`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/raw_records.py#L136-L142) | silent drop, DEBUG log (same policy as harbor) |
| An agent step and an assistant-record group pair up only when their tool ids intersect, or when BOTH sides carry no tool ids | Conversion | [`packages/atif-converter/src/atif_converter/domain/enrichment.py:304-318`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L304-L318); test [`packages/atif-converter/tests/test_enrichment.py:331`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_enrichment.py#L331) | refuse: attribution stops at that step, `enrichment_truncated_at_step` recorded, WARNING logged |
| A user step and a user text record pair up only while the record’s harbor-derived text equals the step’s message | Conversion | [`packages/atif-converter/src/atif_converter/domain/enrichment.py:347-360`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L347-L360); test [`packages/atif-converter/tests/test_enrichment.py:372`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_enrichment.py#L372) | refuse: same truncation marker, WARNING logged |
| A session materializes only once its newest source mtime is at least `quiesce_seconds` old (default 300 seconds of source silence, per session) | Corpus | [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:83-104`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L83-L104) and [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:201-202`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L201-L202); tests [`packages/atif-corpus/tests/test_domain.py:40`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L40) (inclusive boundary) and [`packages/atif-corpus/tests/test_domain.py:98`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L98) | deferred into `skipped_live`; revisited next pass |
| A source mtime in the FUTURE never satisfies the quiescence threshold | Corpus | [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:96-104`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L96-L104); test [`packages/atif-corpus/tests/test_domain.py:45`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L45) | stays skipped, plus a WARNING every pass so the starvation is visible |
| `watermark.json` must parse as a mapping of path to int | Corpus | [`packages/atif-corpus/src/atif_corpus/application/materialize.py:160-176`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L160-L176) | degrade to empty (one full re-materialization pass), WARNING logged — never a refusal to sync |
| A scan finding zero sessions while the corpus holds materialized ones is not a deletion | Corpus | [`packages/atif-corpus/src/atif_corpus/application/materialize.py:97-104`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L97-L104) and [`packages/atif-corpus/src/atif_corpus/application/materialize.py:553-559`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L553-L559); test [`packages/atif-corpus/tests/test_materialize.py:292`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L292) | raise `SuspiciousEmptyScanError`; nothing is removed |
| Only a genuine `FileNotFoundError` proves a source vanished; any other `OSError` means unreadable | Corpus | [`packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py:83-96`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py#L83-L96) and [`packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py:176-186`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py#L176-L186); test [`packages/atif-corpus/tests/test_materialize.py:819`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L819) | session lands in `SourceScan.unreadable`, is never ghosted, and its watermark entries are retained |
| `structural_only` and `llm_only` are mutually exclusive | Analytics | [`packages/atif-analytics/src/atif_analytics/application/analyze.py:61-63`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L61-L63) | raise `ValueError` |
| A retry-queue or checkpoint `pipeline` must be one of the five contract names | Analytics | [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py:96-98`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L96-L98), [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:35-41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py#L35-L41); tests [`packages/atif-analytics/tests/test_state.py:145`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_state.py#L145) and [`packages/atif-analytics/tests/test_state.py:150`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_state.py#L150) | raise `ValueError` |
| Every LLM-output `confidence` is bounded to the closed interval 0.0 to 1.0 (dimensionless, per emitted row) | Analytics | [`packages/atif-analytics/src/atif_analytics/domain/models.py:82-85`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/models.py#L82-L85), [`packages/atif-analytics/src/atif_analytics/domain/models.py:171-174`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/models.py#L171-L174), [`packages/atif-analytics/src/atif_analytics/domain/models.py:275-278`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/models.py#L275-L278), [`packages/atif-analytics/src/atif_analytics/domain/models.py:367-370`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/models.py#L367-L370), [`packages/atif-analytics/src/atif_analytics/domain/models.py:471-474`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/models.py#L471-L474) | pydantic `ValidationError` at `model_validate`, translated to `ProviderUnavailable` ([`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:307-311`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L307-L311)); the unit goes to the retry queue |
| Every categorical LLM-output field is a closed `Literal` union, and free text is length-capped (goal 280 chars, rationale 200 chars, evidence 280 chars, summary 280 chars, a turn uuid 64 chars) | Analytics | [`packages/atif-analytics/src/atif_analytics/domain/models.py:31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/models.py#L31), [`packages/atif-analytics/src/atif_analytics/domain/models.py:72-75`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/models.py#L72-L75), [`packages/atif-analytics/src/atif_analytics/domain/models.py:357-360`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/models.py#L357-L360), [`packages/atif-analytics/src/atif_analytics/domain/models.py:393-396`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/models.py#L393-L396), [`packages/atif-analytics/src/atif_analytics/domain/models.py:449-452`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/models.py#L449-L452), [`packages/atif-analytics/src/atif_analytics/domain/models.py:460-463`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/models.py#L460-L463) | same pydantic rejection path |
| A returned `turn_uuid` must be in the session’s USER-role main-chain text-step header uuid set | Analytics | [`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:123-134`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L123-L134) and [`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:303-312`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L303-L312); tests [`packages/atif-analytics/tests/test_perceived.py:161`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_perceived.py#L161), [`packages/atif-analytics/tests/test_resource_guards.py:617`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_resource_guards.py#L617) | row dropped with a WARNING; an EMPTY universe drops every row (fails closed) |
| A returned conflict pair’s two uuids must BOTH be real `edges.jsonl` uuids for that session | Analytics | [`packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py:266-296`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py#L266-L296); test [`packages/atif-analytics/tests/test_resource_guards.py:590`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_resource_guards.py#L590) | pair dropped; an unreadable universe drops every pair (fails closed) |
| An untrusted step body may not present itself as a turn header | Analytics | [`packages/atif-analytics/src/atif_analytics/domain/transcript.py:64-73`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/transcript.py#L64-L73); tests [`packages/atif-analytics/tests/test_resource_guards.py:561`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_resource_guards.py#L561) and [`packages/atif-analytics/tests/test_resource_guards.py:576`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_resource_guards.py#L576) | coerce: `[uuid=` is rewritten to `(uuid=` case-insensitively, length preserved, before any header is built |
| A friction candidate is a user-role main-chain text step with a uuid, 1 to `friction_max_chars` characters (default 300, per message), CLI bookkeeping text excluded | Analytics | [`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:154-175`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L154-L175), [`packages/atif-analytics/src/atif_analytics/domain/transcript.py:81-86`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/transcript.py#L81-L86); test [`packages/atif-analytics/tests/test_friction_tiers.py:98`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_friction_tiers.py#L98) | filtered out at the candidate boundary; no LLM call is ever made for it |
| `ATIF_SQL_LLM_FAMILY` must name a family with a wired provider adapter | Models | [`packages/atif-models/src/atif_models/infrastructure/settings.py:25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/settings.py#L25) and [`packages/atif-models/src/atif_models/infrastructure/settings.py:41-54`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/settings.py#L41-L54); tests [`packages/atif-models/tests/test_settings.py:59`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_settings.py#L59) and [`packages/atif-models/tests/test_settings.py:64`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_settings.py#L64) | raise `ValueError` at settings load — a startup refusal instead of a Bedrock 400 on every call |
| `size_for` accepts only a pipeline with an `llm_size_` field | Models | [`packages/atif-models/src/atif_models/infrastructure/settings.py:67-77`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/settings.py#L67-L77); test [`packages/atif-models/tests/test_settings.py:88`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_settings.py#L88) | raise `KeyError` |
| A schema with a schema-valued `additionalProperties` (a `dict[str, X]` field) cannot be expressed in OpenAI strict mode | Models | [`packages/atif-models/src/atif_models/domain/schema.py:63-69`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/schema.py#L63-L69); test [`packages/atif-models/tests/test_schema.py:71`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_schema.py#L71) | raise `ValueError` at body-build time |
| `finish_reason` of `content_filter` or `refusal`, or a non-empty `message.refusal`, is a decline | Models | [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:77`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L77), [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:284-290`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L284-L290); tests [`packages/atif-models/tests/test_openai_provider.py:182`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_openai_provider.py#L182) and [`packages/atif-models/tests/test_openai_provider.py:190`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_openai_provider.py#L190) | raise `RefusalError` — terminal, never retried |
| `finish_reason` of `length` gets exactly ONE retry, at `reasoning_effort` degraded one step | Models | [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:85-90`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L85-L90), [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:217-249`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L217-L249), [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:291-297`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L291-L297); tests [`packages/atif-models/tests/test_openai_provider.py:129`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_openai_provider.py#L129), [`packages/atif-models/tests/test_openai_provider.py:144`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_openai_provider.py#L144), [`packages/atif-models/tests/test_openai_provider.py:163`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_openai_provider.py#L163) | raise `ProviderUnavailable` when still truncated or already at the floor; the unit goes to the retry queue |
| Response content must be a non-empty string that parses as JSON and validates against the schema | Models | [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:298-311`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L298-L311); tests [`packages/atif-models/tests/test_openai_provider.py:198`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_openai_provider.py#L198), [`packages/atif-models/tests/test_openai_provider.py:206`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_openai_provider.py#L206), [`packages/atif-models/tests/test_openai_provider.py:212`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_openai_provider.py#L212) | raise `ProviderUnavailable` |
| A store’s stamped `(model, dim)` must match the active embedder before any vector is read or appended | Embeddings | [`packages/atif-embed/src/atif_embed/domain/embedding_guard.py:38-80`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/embedding_guard.py#L38-L80), [`packages/atif-embed/src/atif_embed/application/embed.py:166-174`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L166-L174); tests [`packages/atif-embed/tests/test_embed_use_case.py:153`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_embed_use_case.py#L153), [`packages/atif-embed/tests/test_lance_store.py:321`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_lance_store.py#L321) and [`packages/atif-embed/tests/test_lance_store.py:330`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_lance_store.py#L330) | raise `EmbeddingProviderMismatch` (`terminal = True`); `query`/`search` exit 65, `embed` exits 78 |
| `ensure_index` accepts only `cosine`, `l2`, or `dot` | Embeddings | [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:414-419`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L414-L419); test [`packages/atif-embed/tests/test_lance_store.py:110`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_lance_store.py#L110) | raise `ValueError` |
| A text over 50,000 characters (per embeddable text) cannot be embedded whole | Embeddings | [`packages/atif-embed/src/atif_embed/domain/text_stamp.py:35`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/text_stamp.py#L35), [`packages/atif-embed/src/atif_embed/domain/text_stamp.py:43-47`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/text_stamp.py#L43-L47), [`packages/atif-embed/src/atif_embed/domain/text_stamp.py:65-68`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/text_stamp.py#L65-L68); tests [`packages/atif-embed/tests/test_embed_use_case.py:369`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_embed_use_case.py#L369) and [`packages/atif-embed/tests/test_embed_use_case.py:386`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_embed_use_case.py#L386) | coerce: sent head-only, and the row stamps `truncated = true` so the search miss is attributable |
| Every macro parameter name must have an `ARG_EXEMPLARS` entry | SQL surface | [`packages/atif-duck/src/atif_duck/domain/examples.py:58-75`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L58-L75), [`packages/atif-duck/src/atif_duck/domain/examples.py:148-158`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L148-L158); test [`packages/atif-duck/tests/test_examples.py:188`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_examples.py#L188) | `build_examples()` raises; the drift test fails with it |
| Every catalog object must yield a runnable example or carry a documented `EXCLUSIONS` entry | SQL surface | [`packages/atif-duck/src/atif_duck/domain/examples.py:94-99`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L94-L99); test [`packages/atif-duck/tests/test_examples.py:160`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_examples.py#L160) | drift test fails |
| A materialized session dir with no `meta.json` is incomplete | SQL surface | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:160-177`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L160-L177), [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:188-192`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L188-L192), [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:213-221`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L213-L221); test [`packages/atif-duck/tests/test_duck_views.py:630`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L630) | excluded from every view, plus one WARNING per skipped dir so the exclusion is observable |
| A real `embed` run needs an explicit scope: `--limit N` or `--all` | CLI | [`packages/atif-cli/src/atif_cli/app.py:810-820`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L810-L820) | exit 64 with the hint naming both flags and `--dry-run` |
| `search` against an empty embeddings store is not a zero-result query | CLI | [`packages/atif-cli/src/atif_cli/app.py:930-941`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L930-L941) | exit 2 with the hint `atif-sql embed --all --no-dry-run` |
Notes on two rows above. The strict-JSON transform in [`packages/atif-models/src/atif_models/domain/schema.py:6-11`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/schema.py#L6-L11) is a *rewriting* validation rather than a rejecting one: it forces `additionalProperties: false` on every object level and moves every property into `required`, expressing optionality by making the field’s type nullable. Only the open-mapping shape has no representation and raises. Second: the provider’s structured-output contract and pydantic are two gates in series — the wire schema constrains shape, and `model_validate` re-applies the `ge`/`le` bounds the model may have ignored ([`packages/atif-models/src/atif_models/domain/schema.py:18-21`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/schema.py#L18-L21)).
## Invariants
[Section titled “Invariants”](#invariants)
### Conversion
[Section titled “Conversion”](#conversion)
| Invariant | Where enforced | Citation |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The seven `FidelityGap` members are the complete, typed statement of what harbor 0.22.0’s conversion loses; four are structural and observed on every session, three are session-conditional | Application code | [`packages/atif-converter/src/atif_converter/domain/fidelity.py:44-79`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L44-L79); [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:52-59`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L52-L59) (structural set) and [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:70-76`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L70-L76) (conditional set); test [`packages/atif-converter/tests/test_convert_and_audit.py:163`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_convert_and_audit.py#L163) |
| Only `RecordType.USER` and `RecordType.ASSISTANT` are convertible; every other record type is dropped upstream | Application code | [`packages/atif-converter/src/atif_converter/domain/fidelity.py:41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L41); test [`packages/atif-converter/tests/test_convert_and_audit.py:121`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_convert_and_audit.py#L121) |
| `LossReport.records_total` equals the `edges.jsonl` line count for any session, because the census and the edges emitter read one snapshot | Application code | [`packages/atif-converter/src/atif_converter/domain/fidelity.py:91-98`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L91-L98), [`packages/atif-converter/src/atif_converter/infrastructure/census.py:57-67`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/census.py#L57-L67), [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:137-139`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L137-L139); test [`packages/atif-converter/tests/test_snapshot_and_drift.py:122`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_snapshot_and_drift.py#L122) |
| `records_converted` counts raw user/assistant RECORDS, not ATIF steps — harbor bundles several assistant events sharing one `message.id` into one agent step, so the two numbers differ by design | Application code | [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:122-126`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L122-L126), [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:62-68`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L62-L68) |
| `subagent_files_convertible` equals `subagent_files_found`: the staging layer flattens workflow-nested side-files into harbor’s visible directory, so `WORKFLOW_SUBAGENTS_MISSED` names the UPSTREAM gap rather than a local loss | Application code | [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:83-88`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L83-L88), [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:71-92`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L71-L92); tests [`packages/atif-converter/tests/test_convert_and_audit.py:127`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_convert_and_audit.py#L127) and [`packages/atif-converter/tests/test_convert_and_audit.py:136`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_convert_and_audit.py#L136) |
| A truncation marker or a non-zero leftover count means enrichment attribution was REFUSED, never guessed | Application code | [`packages/atif-converter/src/atif_converter/domain/enrichment.py:45-62`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L45-L62), [`packages/atif-converter/src/atif_converter/domain/enrichment.py:379-399`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L379-L399); tests [`packages/atif-converter/tests/test_enrichment.py:272`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_enrichment.py#L272), [`packages/atif-converter/tests/test_enrichment.py:287`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_enrichment.py#L287), [`packages/atif-converter/tests/test_enrichment.py:302`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_enrichment.py#L302) |
| Staged side-files are per-FILE symlinks under real directories, flattened with `__` joins, because Python 3.13’s `rglob` does not descend a symlinked directory and a flat namespace can collide | Application code | [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:71-92`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L71-L92) |
| `edges.jsonl` line order is `(ts, uuid)` with missing values sorting first as empty strings, and the key order is contract-fixed | Application code | [`packages/atif-converter/src/atif_converter/domain/edges.py:22-32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/edges.py#L22-L32), [`packages/atif-converter/src/atif_converter/domain/edges.py:82-90`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/edges.py#L82-L90); tests [`packages/atif-converter/tests/test_edges.py:43`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_edges.py#L43) and [`packages/atif-converter/tests/test_edges.py:47`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_edges.py#L47) |
| A renamed harbor private method and a bad transcript must not look alike in a materialization log | Application code | [`packages/atif-converter/src/atif_converter/domain/errors.py:47-54`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/errors.py#L47-L54); test [`packages/atif-converter/tests/test_snapshot_and_drift.py:331`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_snapshot_and_drift.py#L331) |
### Corpus
[Section titled “Corpus”](#corpus)
| Invariant | Where enforced | Citation |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A reader never observes a torn artifact SET: all four artifacts are written into `/.staging/` and the whole directory is swapped in by rename | Application code | [`packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py:98-137`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py#L98-L137), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:190-244`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L190-L244); tests [`packages/atif-corpus/tests/test_atomic.py:85`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_atomic.py#L85), [`packages/atif-corpus/tests/test_materialize.py:346`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L346) |
| `meta.json` is written LAST inside the staging directory, and it is the marker `atif-duck` gates every reader on | Application code, both sides | [`packages/atif-corpus/src/atif_corpus/application/materialize.py:207-212`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L207-L212), [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:188-192`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L188-L192); test [`packages/atif-duck/tests/test_duck_views.py:630`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L630) |
| Persistence order matches write order: each artifact is fsynced before the rename that publishes it, and the parent directory after | Application code | [`packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py:64-95`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py#L64-L95), [`packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py:140-158`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py#L140-L158); tests [`packages/atif-corpus/tests/test_atomic.py:131`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_atomic.py#L131), [`packages/atif-corpus/tests/test_atomic.py:149`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_atomic.py#L149), [`packages/atif-corpus/tests/test_atomic.py:194`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_atomic.py#L194) |
| A kill inside the swap window leaves the session dir MISSING rather than torn, and the next pass force-replans it | Application code | [`packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py:113-123`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/atomic.py#L113-L123), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:313-339`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L313-L339); tests [`packages/atif-corpus/tests/test_materialize.py:791`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L791), [`packages/atif-corpus/tests/test_domain.py:216`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L216) |
| Only sessions that materialized successfully advance their watermark entries, so a failed session stays stale and is retried | Application code | [`packages/atif-corpus/src/atif_corpus/application/materialize.py:428-473`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L428-L473), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:615-622`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L615-L622); test [`packages/atif-corpus/tests/test_materialize.py:333`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L333) |
| A VANISHED source path keeps its watermark entry unless the owning session succeeded this pass or left the scan entirely — the retained entry IS the staleness signal | Application code | [`packages/atif-corpus/src/atif_corpus/application/materialize.py:455-473`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L455-L473); tests [`packages/atif-corpus/tests/test_materialize.py:191`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L191) and [`packages/atif-corpus/tests/test_materialize.py:227`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L227) |
| `owns_path` is the single definition of a session’s watermark scope, so staleness and retention can never disagree about which entries belong to a session | Application code | [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:129-140`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L129-L140); tests [`packages/atif-corpus/tests/test_domain.py:191`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L191), [`packages/atif-corpus/tests/test_domain.py:202`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L202), [`packages/atif-corpus/tests/test_domain.py:207`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L207) |
| Only an exactly-equal `mtime_ns` counts as unchanged; an mtime that moved BACKWARDS is `modified` | Application code | [`packages/atif-corpus/src/atif_corpus/domain/watermark.py:61-76`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/watermark.py#L61-L76); test [`packages/atif-corpus/tests/test_domain.py:86`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L86) |
| A staging entry is removed only when EVERY pid its name carries is proven gone; only `ProcessLookupError` proves that | Application code | [`packages/atif-corpus/src/atif_corpus/application/materialize.py:256-310`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L256-L310); tests [`packages/atif-corpus/tests/test_materialize.py:429`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L429), [`packages/atif-corpus/tests/test_materialize.py:457`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L457), [`packages/atif-corpus/tests/test_materialize.py:481`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L481) |
| One broken transcript never aborts a corpus sync — the port may raise anything, and the pass records the failure and continues | Application code | [`packages/atif-corpus/src/atif_corpus/domain/ports.py:41-47`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L41-L47), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:597-616`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L597-L616); test [`packages/atif-corpus/tests/test_materialize.py:316`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L316) |
| The domain reads no clock: `now_ns` is always passed in, so identical inputs always yield an identical plan | Application code | [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:18-21`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L18-L21), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:48-52`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L48-L52), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:528`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L528); test [`packages/atif-corpus/tests/test_domain.py:145`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L145) |
| All three plan partitions are sorted by session id, so the same scan yields the same work order, log output, and failure ordering | Application code | [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:110-121`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L110-L121), [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:195`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L195); test [`packages/atif-corpus/tests/test_domain.py:145`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L145) |
| The corpus root `~/.claude` maps to the reserved slug `default`; every other root is `-<8 hex of sha256 of the resolved path>` | Application code | [`packages/atif-corpus/src/atif_corpus/domain/slug.py:23`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/slug.py#L23), [`packages/atif-corpus/src/atif_corpus/domain/slug.py:45-50`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/slug.py#L45-L50); tests [`packages/atif-corpus/tests/test_slug.py:19`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_slug.py#L19), [`packages/atif-corpus/tests/test_slug.py:24`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_slug.py#L24), [`packages/atif-corpus/tests/test_slug.py:32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_slug.py#L32) |
| The per-session artifact filenames and the watermark filename are fixed by the contract | Application code | [`packages/atif-corpus/src/atif_corpus/domain/layout.py:17-22`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/layout.py#L17-L22); test [`packages/atif-corpus/tests/test_domain.py:245`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L245) |
### Analytics
[Section titled “Analytics”](#analytics)
| Invariant | Where enforced | Citation |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The retry queue is the SINGLE re-admission gate for a failed unit: a live entry blocks dispatch until `drain` says it is due, because a failed unit is never checkpointed and would otherwise be re-admitted fresh every run forever | Application code | [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py:176-193`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L176-L193), [`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:167-173`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L167-L173); tests [`packages/atif-analytics/tests/test_state.py:124`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_state.py#L124), [`packages/atif-analytics/tests/test_perceived.py:231`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_perceived.py#L231) |
| A FAILED unit is not checkpointed; a REFUSED unit is (terminal), and its refusal is durable — an in-band sentinel row for session-keyed `classify`, a `analytics/refusals` sidecar row for the uuid-keyed pipelines | Application code | [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:222-246`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L222-L246), [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:191-229`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L191-L229), [`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:277-289`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L277-L289); test [`packages/atif-analytics/tests/test_perceived.py:252`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_perceived.py#L252) |
| Nothing is stamped — no checkpoint, no cache row, no retry entry — for a unit that was never dispatched | Application code | [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:155-183`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L155-L183), [`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:248-266`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L248-L266); test [`packages/atif-analytics/tests/test_perceived.py:280`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_perceived.py#L280) |
| Budget overshoot is bounded by `BUDGET_CHECK_BATCH` units (8, per dispatch batch) on the priciest watched model, and does not scale with the session ceiling or the write chunk size | Application code | [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:100-112`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L100-L112), [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:146-152`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L146-L152); test [`packages/atif-analytics/tests/test_resource_guards.py:362`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_resource_guards.py#L362) (over both dispatch shapes) |
| A checkpoint skip requires BOTH `last_ts` and `last_mtime` to be non-advancing; either bound moving forward re-admits the session | Application code | [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:136-176`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py#L136-L176); tests [`packages/atif-analytics/tests/test_state.py:48`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_state.py#L48) and [`packages/atif-analytics/tests/test_state.py:57`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_state.py#L57) |
| `state.db` is corpus-scoped at `/analytics/state.db`, so one corpus’s completions can never skip another’s re-scoring | Application code | [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:22-24`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py#L22-L24) |
| Sidechain steps are excluded from every rendered transcript, because the prompts are calibrated on transcripts with no subagent content and harbor inlines sidechains | Application code | [`packages/atif-analytics/src/atif_analytics/domain/transcript.py:24-27`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/transcript.py#L24-L27), [`packages/atif-analytics/src/atif_analytics/domain/transcript.py:132-134`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/transcript.py#L132-L134), [`packages/atif-analytics/src/atif_analytics/domain/transcript.py:162`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/transcript.py#L162); test [`packages/atif-analytics/tests/test_transcript.py:50`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_transcript.py#L50) |
| A compact-summary step is synthetic text: it never participates in a trajectory window and never counts toward a human-AI pair | Application code | [`packages/atif-analytics/src/atif_analytics/domain/transcript.py:199-217`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/transcript.py#L199-L217), [`packages/atif-analytics/src/atif_analytics/domain/transcript.py:253`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/transcript.py#L253) |
| Only `render_session_text` writes a real `[uuid=` header; every body is escaped first | Application code | [`packages/atif-analytics/src/atif_analytics/domain/transcript.py:33-38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/transcript.py#L33-L38), [`packages/atif-analytics/src/atif_analytics/domain/transcript.py:160-161`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/transcript.py#L160-L161), [`packages/atif-analytics/src/atif_analytics/domain/transcript.py:173-183`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/transcript.py#L173-L183); test [`packages/atif-analytics/tests/test_resource_guards.py:561`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_resource_guards.py#L561) |
| `CLI_BOOKKEEPING_TEXTS` is defined once in the domain and shared by the friction candidate filter and the perceived-eligibility pair counter | Application code | [`packages/atif-analytics/src/atif_analytics/domain/transcript.py:78-86`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/transcript.py#L78-L86), [`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:94-100`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L94-L100); test [`packages/atif-analytics/tests/test_perceived.py:80`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_perceived.py#L80) |
| The `source` column vocabulary `regex` / `sql` / `llm` / `refused` names the row’s PROVENANCE TIER, not the engine that computed it, and downstream views bind to those literals | Application code | [`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:29-33`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L29-L33), [`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:86-89`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L86-L89) |
| Two friction stamp rules matching one uuid resolve by higher confidence, ties keeping the first stamp, with rules applied in the fixed order 1 to 3 | Application code | [`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:183-195`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L183-L195) |
| Structural hyperparameter defaults are pinned by contract and `seed = 42` threads into every UMAP and Leiden call, so a same-seed rerun reproduces the clustering | Application code | [`packages/atif-analytics/src/atif_analytics/domain/config.py:10-12`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/config.py#L10-L12), [`packages/atif-analytics/src/atif_analytics/domain/config.py:20-40`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/config.py#L20-L40), [`packages/atif-analytics/src/atif_analytics/domain/config.py:43-60`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/config.py#L43-L60) |
| A session’s community id is `-1` when it is a singleton or unclusterable — an out-of-band sentinel that survives `Int32` serialization | Application code | [`packages/atif-analytics/src/atif_analytics/domain/structure/community.py:84-86`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/community.py#L84-L86) |
| A community-detection resolution `γ` must be strictly positive: CPM at `γ = 0` has no null term and collapses every graph to one community | Application code | [`packages/atif-analytics/src/atif_analytics/domain/structure/community.py:100-102`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/community.py#L100-L102) |
### Models and embeddings
[Section titled “Models and embeddings”](#models-and-embeddings)
| Invariant | Where enforced | Citation |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| The registry is the ONLY place in the workspace where a model id is written down, and it is total over family times size (6 entries) | Application code | [`packages/atif-models/src/atif_models/domain/registry.py:5-8`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L5-L8), [`packages/atif-models/src/atif_models/domain/registry.py:60-106`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L60-L106); test [`packages/atif-models/tests/test_registry.py:61`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_registry.py#L61) |
| `estimate_cost` returns `None`, never `0.0`, when either rate is unknown — callers must render “pricing unavailable” | Application code | [`packages/atif-models/src/atif_models/domain/registry.py:125-137`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L125-L137); tests [`packages/atif-models/tests/test_registry.py:85`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_registry.py#L85) and [`packages/atif-models/tests/test_registry.py:95`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_registry.py#L95) |
| Usage is accumulated BEFORE any `finish_reason` gate, because a length-truncated call still billed tokens | Application code | [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:269-277`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L269-L277); test [`packages/atif-models/tests/test_openai_provider.py:249`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_openai_provider.py#L249) |
| `RETRY_CODES` (8 Bedrock codes) is a deliberate TWIN of atif-embed’s set, pinned against the same literal in each package’s own suite because the two may not import each other | Application code | [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:59-74`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L59-L74); tests [`packages/atif-models/tests/test_openai_provider.py:318`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_openai_provider.py#L318) and [`packages/atif-models/tests/test_openai_provider.py:322`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_openai_provider.py#L322) |
| Every embeddings row stamps `model`, `dim`, `text_hash`, and `truncated`, so a re-conversion that changes a step’s text is detectable under the same uuid | Application code | [`packages/atif-embed/src/atif_embed/domain/text_stamp.py:6-18`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/text_stamp.py#L6-L18), [`packages/atif-embed/src/atif_embed/application/embed.py:216-235`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L216-L235); test [`packages/atif-embed/tests/test_embed_use_case.py:89`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_embed_use_case.py#L89) |
| A uuid never fans out to two vectors: a stale row under the same uuid is DELETED before the new vector is appended | Application code | [`packages/atif-embed/src/atif_embed/application/embed.py:203-210`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L203-L210), [`packages/atif-embed/src/atif_embed/domain/text_stamp.py:50-63`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/text_stamp.py#L50-L63); tests [`packages/atif-embed/tests/test_embed_use_case.py:211`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_embed_use_case.py#L211), [`packages/atif-embed/tests/test_lance_store.py:129`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_lance_store.py#L129) |
| `_PRE_STAMP_SENTINEL` (``) can never equal a real blake2b hex digest, so every pre-stamp row mismatches its corpus hash and re-embeds through the ordinary staleness path | Application code | [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:69-73`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L69-L73); test [`packages/atif-embed/tests/test_lance_store.py:192`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_lance_store.py#L192) |
| ADDITIVE schema drift migrates ONLINE (metadata-only `add_columns`, no re-embedding, search stays up); a BREAKING change — provider or dimension switch — stays fail-loud | Application code | [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:16-32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L16-L32), [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:166-192`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L166-L192); tests [`packages/atif-embed/tests/test_embed_use_case.py:293`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_embed_use_case.py#L293) and [`packages/atif-embed/tests/test_embed_use_case.py:314`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_embed_use_case.py#L314) |
| The write chunk is a multiple of `batch_size`, so a checkpoint boundary never splits a Bedrock batch | Application code | [`packages/atif-embed/src/atif_embed/application/embed.py:145-147`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L145-L147) |
| The provider/dimension guard exists as two copies (atif-embed and atif-duck) because the independence contract forbids the import; a behavioral change in either must be ported | Application code | [`packages/atif-embed/src/atif_embed/domain/embedding_guard.py:15-18`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/embedding_guard.py#L15-L18), [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:46`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L46); tests [`packages/atif-embed/tests/test_guard_twin_pin.py:76`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_guard_twin_pin.py#L76), [`packages/atif-embed/tests/test_guard_twin_pin.py:79`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_guard_twin_pin.py#L79), [`packages/atif-embed/tests/test_guard_twin_pin.py:85`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_guard_twin_pin.py#L85) |
| The recovery hint names the real store locations (`ATIF_SQL_LANCE_URI` or `/embeddings_lance`), never a fixed home-directory path | Application code | [`packages/atif-embed/src/atif_embed/domain/embedding_guard.py:25-35`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/embedding_guard.py#L25-L35); tests [`packages/atif-embed/tests/test_guard_twin_pin.py:97`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_guard_twin_pin.py#L97) and [`packages/atif-embed/tests/test_guard_twin_pin.py:103`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_guard_twin_pin.py#L103) |
### SQL surface and CLI
[Section titled “SQL surface and CLI”](#sql-surface-and-cli)
| Invariant | Where enforced | Citation |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Registration order is raw TEMP tables, then views, then VSS, then macros, then the v2 analytics views and macros — each layer binds against the previous at CREATE time | Application code | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1228-1234`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1228-L1234); test [`packages/atif-duck/tests/test_duck_views.py:615`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L615) |
| `DESCRIPTIONS` covers the catalog exactly (16 views plus 9 macros plus 12 analytics views plus 13 analytics macros equals 50) | Application code | [`packages/atif-duck/src/atif_duck/domain/catalog.py:326`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L326); test [`packages/atif-duck/tests/test_examples.py:178`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_examples.py#L178) |
| `cost_estimate`’s `est_cost_usd` covers PRICED steps only and is meaningful only when `unpriced_steps = 0`; an inner join would return a partial number indistinguishable from a complete one | Application code | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1053-1058`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1053-L1058), [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1066-1077`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1066-L1077); tests [`packages/atif-duck/tests/test_duck_views.py:407`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L407) and [`packages/atif-duck/tests/test_duck_views.py:482`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L482) |
| Both `cost_estimate` counters filter on `model_name IS NOT NULL`, because user steps carry no model and would otherwise make every session look like a pricing gap | Application code | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1060-1063`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1060-L1063); test [`packages/atif-duck/tests/test_duck_views.py:533`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L533) |
| An absent or unattachable Lance store degrades to an empty `message_embeddings` TABLE with the right schema, so `semantic_search` always binds | Application code | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:884-905`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L884-L905); tests [`packages/atif-duck/tests/test_vss.py:112`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_vss.py#L112) and [`packages/atif-duck/tests/test_vss.py:248`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_vss.py#L248) |
| Exit codes are a stable wire contract shared with `atif-converter`’s taxonomy on every common number (64, 65, 70, 127) | Application code | [`packages/atif-cli/src/atif_cli/errors.py:5-11`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L5-L11), [`packages/atif-cli/src/atif_cli/errors.py:23-39`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L23-L39) |
| `terminal` versus transient decides the `embed` exit code, so an unattended lane can stop retrying instead of burning identical ticks | Application code | [`packages/atif-embed/src/atif_embed/domain/errors.py:17-26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/errors.py#L17-L26), [`packages/atif-cli/src/atif_cli/app.py:833-847`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L833-L847) |
## Calculations
[Section titled “Calculations”](#calculations)
| Calculation | Inputs | Output | Citation |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Dry-run dollar projection for a planned batch | measured `input_tokens`, `output_tokens`, and a `(in_rate, out_rate)` pair in USD per 1M tokens | USD for the batch, flat linear, no minimums or tiers | [`packages/atif-analytics/src/atif_analytics/domain/costs.py:33-47`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/costs.py#L33-L47) |
| Characters to tokens | character count | token estimate `max(1, chars // 4)`, floor 1 when non-empty | [`packages/atif-analytics/src/atif_analytics/domain/costs.py:23-30`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/costs.py#L23-L30) |
| Accumulated-usage dollar estimate for one `ModelSpec` | `ModelSpec.pricing_in`, `pricing_out` (USD per 1M tokens), accumulated `input_tokens` and `output_tokens` | USD, or `None` when a rate is unknown | [`packages/atif-models/src/atif_models/domain/registry.py:125-137`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L125-L137); test [`packages/atif-models/tests/test_registry.py:76`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_registry.py#L76) |
| Running actual spend for a run | every watched provider’s `UsageAccumulator` summary plus its `ModelSpec` | total USD spent this run, across all LLM pipelines | [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:123-139`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L123-L139) |
| `cost_estimate(sid)` SQL macro | a session’s `steps` rows (`prompt_tokens`, `cached_tokens`, `completion_tokens`, `model_name`) LEFT JOINed against `DEFAULT_PRICING` | `(est_cost_usd, priced_steps, unpriced_steps)` | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1066-1083`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1066-L1083); test [`packages/atif-duck/tests/test_duck_views.py:368`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L368) |
| Retry backoff delay | attempt counter | `min(2 ** attempts, 60)` MINUTES, per `(pipeline, unit_id)`: 2, 4, 8, 16, 32, capped at 60 | [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py:78-81`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L78-L81); test [`packages/atif-analytics/tests/test_state.py:102`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_state.py#L102) |
| Bedrock call retry schedule | the raised exception | up to 10 attempts, exponential wait with multiplier 2, minimum 2 SECONDS and maximum 60 SECONDS, only for `RETRY_CODES` and network errors | [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:251-257`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L251-L257), [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:102-109`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L102-L109); test [`packages/atif-models/tests/test_openai_provider.py:284`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_openai_provider.py#L284) |
| Parquet write chunk size | `batch_size` (default 96 units) | `max(batch_size * 4, 256)` ROWS per part — bounds crash loss, not spend | [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:242-250`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L242-L250) |
| Quiescence age test | newest source `mtime_ns`, `now_ns`, `quiesce_seconds` | boolean: `now_ns - newest_mtime_ns >= quiesce_seconds * 1_000_000_000` | [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:96-104`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L96-L104); test [`packages/atif-corpus/tests/test_domain.py:40`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L40) |
| Source delta between two scans | two `path` to `mtime_ns` maps | added / modified / removed sorted tuples; `touched` is added then modified | [`packages/atif-corpus/src/atif_corpus/domain/watermark.py:57-76`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/watermark.py#L57-L76); test [`packages/atif-corpus/tests/test_domain.py:76`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L76) |
| Corpus slug | a corpus root path | `default` for `~/.claude`, else `-<8 hex of sha256>` | [`packages/atif-corpus/src/atif_corpus/domain/slug.py:45-50`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/slug.py#L45-L50); test [`packages/atif-corpus/tests/test_slug.py:24`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_slug.py#L24) |
| Loss accounting for one session | raw record counts by `RecordType`, side-file classification | `records_converted` (user plus assistant records), `records_dropped` (total minus converted), `gaps_observed` | [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:62-90`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L62-L90), [`packages/atif-converter/src/atif_converter/domain/fidelity.py:109-112`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L109-L112) |
| Completed human-to-AI exchange count | a session’s `StepEvent` list | integer pair count, the perceived-error eligibility input | [`packages/atif-analytics/src/atif_analytics/domain/transcript.py:236-262`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/transcript.py#L236-L262); test [`packages/atif-analytics/tests/test_perceived.py:63`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_perceived.py#L63) |
| Sentiment delta for a trajectory window | `prev_sentiment`, `curr_sentiment` over the encoding negative equals -1, neutral equals 0, positive equals 1 | `curr - prev` as a float in the closed interval -2.0 to 2.0, or `None` when there is no previous turn | [`packages/atif-analytics/src/atif_analytics/domain/trajectory.py:31-32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/trajectory.py#L31-L32), [`packages/atif-analytics/src/atif_analytics/domain/trajectory.py:130-134`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/trajectory.py#L130-L134), [`packages/atif-analytics/src/atif_analytics/domain/trajectory.py:171-183`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/trajectory.py#L171-L183) |
| Content stamp for an embeddable text | the exact text sent to the embedder | blake2b digest, 16 bytes hex-encoded (128 bits) | [`packages/atif-embed/src/atif_embed/domain/text_stamp.py:26-40`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/text_stamp.py#L26-L40) |
| c-TF-IDF term weights | one pseudo-document per cluster plus a frozen `TermsConfig` | `(cluster_id, term, weight, rank)` rows, ranks 1-based, non-positive weights dropped, top 10 per cluster | [`packages/atif-analytics/src/atif_analytics/domain/structure/terms.py:44-74`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/terms.py#L44-L74) |
| CPM partition quality | a weighted graph, a label vector, and `γ` | scalar objective in the same units as the stored `quality` column | [`packages/atif-analytics/src/atif_analytics/domain/structure/community.py:288-308`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/community.py#L288-L308) |
| Resolution-profile call budget | the configured `γ` range | maximum distinct `γ` the bisection can evaluate, clamped by `_PROFILE_MAX_CALLS` (512 Leiden calls) | [`packages/atif-analytics/src/atif_analytics/domain/structure/community.py:134-170`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/community.py#L134-L170) |
| `friction_rate(since_days)` | `user_friction` label counts per session; user-role main-chain non-empty `steps` as denominator | per-session `rate` plus seven label counters | [`packages/atif-duck/src/atif_duck/infrastructure/analytics.py:344-384`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/analytics.py#L344-L384) |
| `success_rate_by_work(since_days)` | `session_classifications` rows | `unknown_fraction` over ALL sessions; success, failure and partial rates over KNOWN outcomes only | [`packages/atif-duck/src/atif_duck/infrastructure/analytics.py:271-293`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/analytics.py#L271-L293) |
| `semantic_search(query_vec, k)` | a unit-norm query vector and `k` | top-`k` `(uuid, sim, distance)` ordered by cosine distance | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1145-1152`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1145-L1152); test [`packages/atif-duck/tests/test_vss.py:195`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_vss.py#L195) |
| `todo_velocity(sid)` | `todo_state_current` rows for one session | completed count divided by distinct subject count, `NULL` when there are no subjects | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1101-1107`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1101-L1107); test [`packages/atif-duck/tests/test_duck_views.py:566`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L566) |
| `subagent_fanout(sid)` | `subagent_spawns` rows for one session | count of Task/Agent launch INTENTS, not side-transcript files | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1116-1124`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1116-L1124); test [`packages/atif-duck/tests/test_duck_views.py:572`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L572) |
Three of these need the formula spelled out.
**`cost_estimate(sid)`.** Per step, uncharged-cache base input is `prompt_tokens - cached_tokens`, because ATIF’s `prompt_tokens` is the TOTAL (input plus cache-read plus cache-creation); the charge is `(prompt_tokens - cached_tokens) * in_rate + completion_tokens * out_rate`, summed over the session’s steps and divided by `1e6`. Cache reads are uncharged, and the pricing join strips a dated model suffix (`claude-haiku-4-5-20251001` matches `claude-haiku-4-5`) via `regexp_replace(model_name, '-\d{8}$', '')`. The pricing table itself is `DEFAULT_PRICING`, 11 entries of `(in_rate, out_rate)` in **USD per 1,000,000 tokens**, base rates only — the prompt-cache write and read multipliers (1.25x, 2x, 0.1x of base input) are deliberately not modeled, matching the macro ([`packages/atif-duck/src/atif_duck/domain/catalog.py:395-417`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L395-L417), pinned by [`packages/atif-duck/tests/test_duck_views.py:459`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L459) against published list rates and by [`packages/atif-duck/tests/test_duck_views.py:470`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L470) for sanity bounds).
**`human_ai_pair_count`.** Walk the step list in materialized order. Skip any step that is sidechain, compact-summary, or has empty text. A user-role step arms a pending pair — unless its stripped text is one of the two `CLI_BOOKKEEPING_TEXTS` strings, which are Claude Code’s own user-role injections and are skipped outright. An assistant-role step completes the pending pair and disarms it. Consecutive user turns therefore collapse into one pending pair, because judging a perceived error requires the human RESPONDING to AI output ([`packages/atif-analytics/src/atif_analytics/domain/transcript.py:236-262`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/transcript.py#L236-L262)). The perceived pipeline admits a session only at 2 or more pairs ([`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:101-103`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L101-L103), [`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:118-120`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L118-L120)).
**c-TF-IDF.** `CountVectorizer` (lowercased, unicode-stripped accents, `min_df = 2`, `max_df = 0.95`, ngram range 1 to 2) produces a clusters-by-vocabulary count matrix. Term frequency is L1-normalized per cluster row. The IDF factor is `log(1 + sum(avg) / max(col_sum, 1e-9))` where `avg = col_sum / total`, and the weight is the row-normalized TF times that IDF. Terms are ranked descending per cluster and the top 10 kept, with non-positive weights dropped ([`packages/atif-analytics/src/atif_analytics/domain/structure/terms.py:44-74`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/terms.py#L44-L74)).
## Policy and gates
[Section titled “Policy and gates”](#policy-and-gates)
### Cost and spend
[Section titled “Cost and spend”](#cost-and-spend)
* **Dry-run by default:** every LLM analytics stage and the embed backfill treat `dry_run=True` as the default, returning a plan dict instead of spending; a real run is an explicit opt-out. [`packages/atif-analytics/src/atif_analytics/application/analyze.py:41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L41), [`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:362`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L362).
* **Session ceiling:** each LLM pipeline admits at most `llm_max_sessions_per_run` sessions per run (default 50, newest-first so fresh sessions win), enforced DURING the admission walk so a deferred session is never rendered or eligibility-probed. [`packages/atif-analytics/src/atif_analytics/infrastructure/settings.py:88-92`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/settings.py#L88-L92), [`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:176-193`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L176-L193); tests [`packages/atif-analytics/tests/test_resource_guards.py:84`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_resource_guards.py#L84) and [`packages/atif-analytics/tests/test_resource_guards.py:113`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_resource_guards.py#L113).
* **Cost ceiling:** one `RunBudget` of `llm_max_cost_usd_per_run` (default 25.0 USD per `analyze` run, shared across all five LLM pipelines) is priced from running actuals and checked at every dispatch batch boundary; when crossed, remaining LLM work stops and nothing is stamped for unstarted units. [`packages/atif-analytics/src/atif_analytics/infrastructure/settings.py:93-97`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/settings.py#L93-L97), [`packages/atif-analytics/src/atif_analytics/application/analyze.py:122-144`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L122-L144), [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:90-143`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L90-L143); test [`packages/atif-analytics/tests/test_analyze.py:75`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_analyze.py#L75).
* **Budget-skip starvation escalation:** a stage skipped for budget records a durable `budget_skips` row, and the log escalates from WARNING to ERROR once the SAME stage has been starved 3 consecutive runs; a stage that actually runs clears its rows, so the row count IS the streak. [`packages/atif-analytics/src/atif_analytics/application/analyze.py:148-180`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L148-L180), [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:54-62`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py#L54-L62), [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:217-246`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py#L217-L246); test [`packages/atif-analytics/tests/test_analyze.py:99`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_analyze.py#L99).
* **Retry attempt cap:** a `(pipeline, unit_id)` stops being drained at 5 attempts, which is what closes the uncapped-retry money leak. [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py:42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L42), [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py:176-193`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L176-L193); test [`packages/atif-analytics/tests/test_state.py:118`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_state.py#L118).
* **LLM-free tiers run first:** the friction pipeline pays no LLM call for a regex fast-path hit (flat 0.9 confidence over the first 512 characters of a message) or for a deterministic stamp-rule hit, and ambiguous phrasings deliberately fall through so one mis-tuned pattern cannot poison the corpus. [`packages/atif-analytics/src/atif_analytics/domain/friction.py:100-114`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/friction.py#L100-L114), [`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:178-232`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L178-L232); tests [`packages/atif-analytics/tests/test_friction_tiers.py:47`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_friction_tiers.py#L47), [`packages/atif-analytics/tests/test_friction_tiers.py:79`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_friction_tiers.py#L79), [`packages/atif-analytics/tests/test_friction_tiers.py:87`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_friction_tiers.py#L87), [`packages/atif-analytics/tests/test_friction_tiers.py:92`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/test_friction_tiers.py#L92).
* **Zero-cost visualization is off:** `compute_viz_coords` defaults to False because the 2-d UMAP projection measured 66% of the cluster stage’s wall clock and nothing consumes the coordinates. [`packages/atif-analytics/src/atif_analytics/domain/config.py:37-40`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/config.py#L37-L40).
### Model selection
[Section titled “Model selection”](#model-selection)
* **No package hardcodes a model id:** every pipeline names a `(family, size)` pair and the registry resolves it; that is itself the policy. [`packages/atif-models/src/atif_models/domain/registry.py:5-8`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L5-L8).
* **Runnable-family gate:** only families with a wired provider adapter may be selected (`RUNNABLE_FAMILIES` is `openai` alone); the anthropic column records ids and pricing for a future adapter and is rejected at settings load until one exists. [`packages/atif-models/src/atif_models/infrastructure/settings.py:20-25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/settings.py#L20-L25), [`packages/atif-models/src/atif_models/infrastructure/settings.py:41-54`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/settings.py#L41-L54); tests [`packages/atif-models/tests/test_settings.py:69`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_settings.py#L69) and [`packages/atif-models/tests/test_settings.py:72`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_settings.py#L72).
* **Per-pipeline size assignment:** classify, trajectory and perceived take `medium`, conflicts takes `large` (the hardest judgment task), friction takes `small` (a per-message enum) — each overridable via `ATIF_SQL_LLM_SIZE_`. [`packages/atif-models/src/atif_models/infrastructure/settings.py:56-65`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/settings.py#L56-L65); test [`packages/atif-models/tests/test_settings.py:26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/tests/test_settings.py#L26).
### Freshness and data safety
[Section titled “Freshness and data safety”](#freshness-and-data-safety)
* **Force overrides staleness only, never quiescence:** a live session is not converted even under `--force`, because converting a half-written transcript produces a WRONG artifact rather than a stale one. [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:170-174`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L170-L174), [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:200-206`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L200-L206); tests [`packages/atif-corpus/tests/test_domain.py:117`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L117), [`packages/atif-corpus/tests/test_materialize.py:148`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L148).
* **Ghost removal is all-or-nothing per pass:** when ANY source directory could not be listed, ghost removal is skipped entirely for the pass, because absence is not evidence of deletion without a complete picture of what exists. [`packages/atif-corpus/src/atif_corpus/application/materialize.py:560-577`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L560-L577); tests [`packages/atif-corpus/tests/test_materialize.py:609`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L609) and [`packages/atif-corpus/tests/test_materialize.py:647`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L647).
* **A `--sessions` filter never widens deletion:** ghost removal keys off the FULL scan, so an unplanned session is never removed just because it was not planned. [`packages/atif-corpus/src/atif_corpus/application/materialize.py:511-519`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L511-L519), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:573-577`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L573-L577); test [`packages/atif-corpus/tests/test_materialize.py:280`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L280).
* **Staging sweep errs toward keeping:** a signal delivered, a `PermissionError`, or any unexpected `OSError` all answer “do not delete”, and a recycled pid only means the sweep skips debris a later pass collects. [`packages/atif-corpus/src/atif_corpus/application/materialize.py:256-271`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L256-L271); test [`packages/atif-corpus/tests/test_materialize.py:481`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L481).
* **Corrupt state degrades, never refuses:** an unreadable or wrong-shaped `watermark.json` is treated as unmaterialized, costing one full pass, which is always safe. [`packages/atif-corpus/src/atif_corpus/application/materialize.py:160-176`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L160-L176).
### Embeddings and the SQL surface
[Section titled “Embeddings and the SQL surface”](#embeddings-and-the-sql-surface)
* **A provider switch is fail-loud, not silent:** the guard runs before any read or append and raises rather than mixing incompatible vector spaces; the error is `terminal` so an unattended lane exits 78 and suppresses retries instead of burning identical ticks. [`packages/atif-embed/src/atif_embed/domain/embedding_guard.py:3-18`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/embedding_guard.py#L3-L18), [`packages/atif-embed/src/atif_embed/domain/errors.py:29-41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/errors.py#L29-L41), [`packages/atif-cli/src/atif_cli/app.py:833-847`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L833-L847).
* **Embed requires an explicit scope:** a bare `atif-sql embed --no-dry-run` exits 64 so a full backfill cannot happen from a mistyped command; `--dry-run` needs no scope because it spends nothing. [`packages/atif-cli/src/atif_cli/app.py:778-783`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L778-L783), [`packages/atif-cli/src/atif_cli/app.py:810-820`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L810-L820).
* **`skip_vss` escape hatch:** registration can skip both the `message_embeddings` view and the `semantic_search` macro, because the backfill writes the store the view reads and binding it first would be circular. [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1244-1250`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1244-L1250); test [`packages/atif-duck/tests/test_vss.py:258`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_vss.py#L258).
* **Register-or-fail-loud:** any DuckDB error during view or macro registration is logged with `logger.exception` and re-raised — an empty or absent corpus fails registration rather than yielding empty views. [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:201-207`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L201-L207), [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1201-1204`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1201-L1204); test [`packages/atif-duck/tests/test_duck_views.py:623`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L623).
* **A new view or macro cannot land silently:** the drift tests require a `DESCRIPTIONS` entry, an `ARG_EXEMPLARS` entry for any new parameter name, `TABLE_MACRO_NAMES` membership when the DDL is `AS TABLE`, and an example that EXECUTES — or a documented `EXCLUSIONS` entry. [`packages/atif-duck/src/atif_duck/domain/examples.py:94-99`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L94-L99); tests [`packages/atif-duck/tests/test_examples.py:160`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_examples.py#L160), [`packages/atif-duck/tests/test_examples.py:178`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_examples.py#L178), [`packages/atif-duck/tests/test_examples.py:188`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_examples.py#L188), [`packages/atif-duck/tests/test_examples.py:197`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_examples.py#L197).
### Scheduling
[Section titled “Scheduling”](#scheduling)
* **Three cron lanes, split by cost:** `materialize` every 10 minutes (cheap incremental), `structural` hourly at minute 17 (zero LLM cost), `llm` once daily at 10:20 (the lane that spends). [`packages/atif-cli/src/atif_cli/cron.py:43-51`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L43-L51).
* **`cron install` never writes a crontab:** it prints the block for a human to paste, after checking `crontab -l`. [`packages/atif-cli/src/atif_cli/cron.py:180-197`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L180-L197).
## See also
[Section titled “See also”](#see-also)
* [module map](../architecture/module-map.md) — 39 shared source citations
* [processes](../behavior/processes.md) — 36 shared source citations
* [impact analysis](impact-analysis.md) — 34 shared source citations
* [contract map](contract-map.md) — 33 shared source citations
* [debugging guide](debugging-guide.md) — 22 shared source citations
# Contract map
# atif-sql · Contract map
[Section titled “atif-sql · Contract map”](#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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L514-L523) 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:
1. a `typing.Protocol` declared in one package’s `domain/ports.py` and satisfied by an adapter elsewhere (there are exactly five, listed below);
2. 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;
3. a build-enforced dependency rule (import-linter), which is a stronger fact than a convention;
4. an upstream API this workspace depends on and pins;
5. 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L44)) implements `ConverterPort` ([`packages/atif-corpus/src/atif_corpus/domain/ports.py:41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L41)) 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L92) (the module that CALLS it) and [`packages/atif-cli/tests/test_converter_adapter.py:17`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_converter_adapter.py#L17) (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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L514-L517) 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”](#the-materialized-corpus-artifact-layout)
**Producer:** [`packages/atif-corpus/src/atif_corpus/domain/layout.py:18-22`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/layout.py#L18-L22)
**Consumer(s):**
* [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:210-213`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L210-L213) — inlines the four filenames as SQL glob literals for its `read_json` readers.
* [`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:120-121`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L120-L121) — opens `trajectory.json` and gates on `meta.json` with its own DuckDB connection.
* [`packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:53-55`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py#L53-L55) — declares `TRAJECTORY_FILENAME` / `EDGES_FILENAME` / `META_FILENAME` a third time as its own constants.
* [`packages/atif-corpus/src/atif_corpus/application/materialize.py:222-238`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L222-L238) — the writer side, the only place the artifacts are produced.
* [`docs/CONTRACT.md:21-39`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L21-L39) — the hand-written specification all four agree to.
**Shape:**
```plaintext
/ # default: ~/.atif-sql/corpus//
sessions//
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 corpus
```
**Assumptions consumers make:**
* **`meta.json` present 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-192`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L188-L192) restricts the trajectory/edges/loss readers to dirs where `v_raw_meta` has a row; [`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:121-124`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L121-L124) skips a dir with no `meta.json`; [`packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:171-180`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py#L171-L180) does the same and logs a warning. Nothing in the file layout expresses this — it is a write-ORDER promise made at [`packages/atif-corpus/src/atif_corpus/application/materialize.py:209-212`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L209-L212).
* **No reader ever sees a partially-written directory**, because the writer stages all four artifacts under `/.staging/` and swaps the whole dir with `os.replace` ([`packages/atif-corpus/src/atif_corpus/application/materialize.py:201-212`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L201-L212)), and `.staging` is deliberately outside `sessions/` so a DuckDB glob cannot reach it ([`packages/atif-corpus/src/atif_corpus/domain/layout.py:50-54`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/layout.py#L50-L54)).
* **`trajectory.json` is ONE JSON document, `edges.jsonl` is newline-delimited.** atif-duck encodes that split in its reader choice at [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:23-25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L23-L25); atif-embed re-derives it at [`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:7-8`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L7-L8).
* **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-80`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L74-L80) cites 436 MB observed, [`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:47-49`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L47-L49) cites 85 MB. Same constant, two independent justifications.
* **`source_mtime_ns` and every `watermark.json` value are epoch NANOSECONDS** from `os.stat().st_mtime_ns` — unit stated in the identifier and again at [`packages/atif-corpus/src/atif_corpus/domain/watermark.py:12-13`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/watermark.py#L12-L13). atif-duck types the column `BIGINT` at [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:136`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L136), which preserves the magnitude but drops the unit from the schema.
* **`materialized_at` is an ISO-8601 UTC string, not a timestamp.** atif-duck projects it as `VARCHAR` ([`packages/atif-duck/src/atif_duck/infrastructure/registry.py:140`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L140)); the CLI supplies it ([`packages/atif-cli/src/atif_cli/app.py:400`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L400)).
**Where the hand-written contract disagrees with the code, the code wins — and it does disagree in two places.** [`docs/CONTRACT.md:61`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L61) 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L32-L36)) and the CLI imports all five. [`docs/CONTRACT.md:16-17`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L16-L17) lists VSS/`semantic_search` and the v2 LLM-analytics pipelines as out of scope and then reverses itself at [`docs/CONTRACT.md:17-19`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L17-L19); both are shipped commands ([`packages/atif-cli/pyproject.toml:42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L42) plus the `embed` / `search` / `analyze` commands). Read [`docs/CONTRACT.md:21-39`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L21-L39) 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/layout.py#L18-L22) 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”](#the-static-sql-catalog)
**Producer:** [`packages/atif-duck/src/atif_duck/domain/catalog.py:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L28) (`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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1099-L1118) — the `schema` command reads `VIEW_SCHEMA` and `MACRO_SIGNATURES` and answers with no DuckDB connection.
* [`packages/atif-duck/src/atif_duck/domain/examples.py:36-42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L36-L42) — the examples generator imports all seven catalogs and derives one runnable query per object.
* [`packages/atif-cli/src/atif_cli/app.py:1025-1046`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1025-L1046) — the `examples` command calls `build_examples()`.
* [`packages/atif-duck/tests/test_duck_views.py:31-33`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L31-L33) — the `DESCRIBE`-vs-`VIEW_SCHEMA` and DDL-vs-`MACRO_SIGNATURES` drift tests.
* [`packages/atif-duck/tests/test_examples.py:27-35`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_examples.py#L27-L35) — `DESCRIPTIONS` coverage, `ARG_EXEMPLARS` coverage, and the `AS TABLE` set drift test.
* [`packages/atif-duck/tests/test_analytics_views.py:20-21`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_analytics_views.py#L20-L21) — the analytics-side drift test.
* [`packages/atif-cli/tests/test_app.py:78-84`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_app.py#L78-L84) — asserts the CLI’s JSON payload keys equal the catalog keys.
**Shape:**
```plaintext
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 `DESCRIBE` output, stated at [`packages/atif-duck/src/atif_duck/domain/catalog.py:47-50`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L47-L50); reordering a view’s `SELECT` list fails CI even though every column still exists.
* **`DESCRIPTIONS` covers every object in every catalog, exactly.** [`packages/atif-duck/tests/test_examples.py:181-185`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_examples.py#L181-L185) fails 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_EXEMPLARS` literal**, or example generation raises rather than emitting a broken query — [`packages/atif-duck/src/atif_duck/domain/examples.py:151-157`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L151-L157) fails loud, and [`packages/atif-duck/tests/test_examples.py:189-194`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_examples.py#L189-L194) pre-empts it.
* **`TABLE_MACRO_NAMES` decides the SQL call shape.** [`packages/atif-duck/src/atif_duck/domain/examples.py:159-162`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L159-L162) emits `SELECT * FROM name(args)` for a member and `SELECT name(args)` otherwise, so a macro whose DDL gains or loses `AS TABLE` breaks every derived example — pinned by the regex test at [`packages/atif-duck/tests/test_examples.py:198-210`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_examples.py#L198-L210).
* **The catalog is answerable without a corpus.** [`packages/atif-cli/src/atif_cli/app.py:1094-1098`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1094-L1098) states the sub-50 ms, no-DuckDB-bind guarantee the `schema` command rests on; a runtime `DESCRIBE` would violate it.
* **The four raw readers are deliberately NOT in `VIEW_NAMES`** ([`packages/atif-duck/src/atif_duck/infrastructure/registry.py:65-68`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L65-L68)), so a consumer enumerating `VIEW_NAMES` does not see `v_raw_trajectories` and 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L218-L224) 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”](#the-seven-import-linter-architecture-contracts)
**Producer:** [`pyproject.toml:462-523`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L462-L523)
**Consumer(s):**
* [`mise.toml:197`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L197) — `lint:imports` is gate 5 of the nine `mise run check` gates, so the build is the consumer.
* [`packages/atif-corpus/src/atif_corpus/domain/ports.py:5-8`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L5-L8) — the `ConverterPort` docstring cites the independence contract as the reason the Protocol exists at all.
* [`packages/atif-embed/src/atif_embed/domain/ports.py:11-15`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L11-L15) — cites it as the reason `TextRowsPort` exists.
* [`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:5-10`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L5-L10) — cites it as the reason atif-embed carries its own corpus reader.
* [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:5-9`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L5-L9) — cites it as the reason the guard is duplicated.
* [`packages/atif-duck/src/atif_duck/infrastructure/analytics.py:13-15`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/analytics.py#L13-L15) — cites it as the reason the analytics artifact names are pinned by hand.
* [`packages/atif-analytics/src/atif_analytics/domain/layout.py:8-10`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/layout.py#L8-L10) — cites it as the reason its layout is computed in one place.
**Shape:**
```plaintext
[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 `independence` module list ([`pyproject.toml:517`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L517)) and the `forbidden` source list ([`pyproject.toml:522`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L522)). The one module that imports two independent packages is [`packages/atif-cli/src/atif_cli/converter_adapter.py:36-38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L36-L38), and its docstring names that privilege explicitly at [`packages/atif-cli/src/atif_cli/converter_adapter.py:5-9`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L5-L9).
* **atif-analytics is absent from the independence list on purpose** so it can compose atif-models, with the `forbidden` contract pinning its other six edges shut — the reasoning is inline at [`pyproject.toml:509-513`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L509-L513). 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 `layers` contract.** Five members do ([`pyproject.toml:465-508`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L465-L508)); atif-duck has `domain/` and `infrastructure/` but no `application/`, so there is no third layer to order.
* **Every layered member’s `domain/` is the innermost layer**, which is why all five Protocols live in `domain/ports.py` and none in an `application/ports.py` — no such file exists in this workspace.
* **The comment at [`pyproject.toml:509-510`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L509-L510) grants 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 in [`packages/atif-cli/pyproject.toml:32-36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L32-L36) nor 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 by [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:28-29`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L28-L29).
* **Every cross-package import in this workspace is INDENTED** — inside a function body or a `TYPE_CHECKING` block — because `PLC0415` is ignored workspace-wide to satisfy the lean-import test ([`pyproject.toml:164`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L164)). 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-38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L36-L38) and [`packages/atif-cli/src/atif_cli/duck_errors.py:26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L26). 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L463)) 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”](#llmstructuredprovider--the-structured-output-port)
**Producer:** [`packages/atif-models/src/atif_models/domain/ports.py:116`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L116)
**Consumer(s):**
* [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:55`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L55) (annotated at [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:86`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L86), [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:349`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L349))
* [`packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py:79`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py#L79) ([`packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py:127`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py#L127), [`packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py:163`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py#L163), [`packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py:428`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py#L428))
* [`packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py:73`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py#L73) ([`packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py:107`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py#L107), [`packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py:344`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py#L344))
* [`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:76`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L76) ([`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:244`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L244), [`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:538`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L538))
* [`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:87`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L87) ([`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:141`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L141), [`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:364`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L364))
* [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L28) — the provider factory and the usage/budget plumbing.
* [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:143`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L143) — the one production implementation.
* [`packages/atif-analytics/tests/analytics_fixtures.py:237`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/analytics_fixtures.py#L237) — the deterministic double.
**Shape:**
```plaintext
@runtime_checkable
class 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.** `RefusalError` is terminal and `ProviderUnavailable` is retryable ([`packages/atif-models/src/atif_models/domain/ports.py:43-58`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L43-L58)), 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:70`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L70) and [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:129`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L129) both reach for `getattr(provider, "usage", None)` because `usage` is not on the port; a conforming adapter without that attribute silently reports zero spend and the `RunBudget` ceiling never trips.
* **`CallUsage` counts are PER CALL, and two of the four are subsets of the other two.** `reasoning_tokens` is a subset of `output_tokens` and `cached_tokens` a subset of `input_tokens` ([`packages/atif-models/src/atif_models/domain/ports.py:64-70`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L64-L70)), so `estimate_cost` at [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:73-77`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L73-L77) prices only `input_tokens` and `output_tokens` — adding the other two would double-charge.
* **`UsageAccumulator` must be thread-safe, not task-safe.** The lock is a `threading.Lock` ([`packages/atif-models/src/atif_models/domain/ports.py:87`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L87)) because adapters dispatch blocking `invoke_model` through `anyio.to_thread`, reasoning stated at [`packages/atif-models/src/atif_models/domain/ports.py:16-22`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L16-L22).
* **The budget is a stop-dispatch trigger, not a cap.** [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:101-112`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L101-L112) documents the overshoot bound as `max_cost_usd` plus at most `BUDGET_CHECK_BATCH` units in flight; a consumer treating `max_cost_usd` as a hard ceiling is wrong.
* **`pricing_in` / `pricing_out` on `ModelSpec` are USD per 1,000,000 tokens**, stated at [`packages/atif-models/src/atif_models/domain/registry.py:42-43`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L42-L43), and `None` when unknown — which `estimate_cost` ([`packages/atif-models/src/atif_models/domain/registry.py:125`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L125)) turns into a `None` cost rather than a zero.
* **`OpenAiBedrockProvider` never 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:34`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L34) declares `tuple[LlmStructuredProvider, ModelSpec]` and [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:45-57`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L45-L57) returns 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L123-L125) is the contract, and the factory’s annotated return at [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:34`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L34) is where a mismatched adapter fails the typecheck gate.
## The `edges.jsonl` line shape
[Section titled “The edges.jsonl line shape”](#the-edgesjsonl-line-shape)
**Producer:** [`packages/atif-converter/src/atif_converter/domain/edges.py:22-32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/edges.py#L22-L32)
**Consumer(s):**
* [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:107-117`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L107-L117) — `_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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py#L300-L301) — reads the `uuid` field for the conflicts pipeline’s returned-uuid validity guard.
* [`packages/atif-corpus/src/atif_corpus/application/materialize.py:224-227`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L224-L227) — writes the lines and owns line termination.
* [`packages/atif-duck/src/atif_duck/domain/catalog.py:330`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L330) — the `messages` view is defined as exactly this surface.
**Shape:**
```plaintext
#: 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_lines` field at [`packages/atif-corpus/src/atif_corpus/domain/ports.py:30-33`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L30-L33) and honoured at [`packages/atif-corpus/src/atif_corpus/application/materialize.py:224-227`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L224-L227); a producer that terminated its own lines would double them.
* **`parent_uuid` is a nullable string, and atif-duck declares it `VARCHAR` outright** rather than letting JSON union inference decide — the reason is recorded at [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:103-106`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L103-L106).
* **`tool_use_ids` means two different things by record type**: tool\_use block ids for assistant records, the `tool_use_id` of tool\_result blocks for user records ([`packages/atif-converter/src/atif_converter/domain/edges.py:11-13`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/edges.py#L11-L13)). One field, two semantics, discriminated by the sibling `type` column.
* **`edges.jsonl` is the only source of raw-record identity.** The trajectory cannot supply uuids ([`packages/atif-duck/src/atif_duck/infrastructure/registry.py:38-40`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L38-L40), fidelity gap 7 `UUID_NOT_PRESERVED`), which is why the `messages` view is reconstructed from edges rather than from steps.
* **`records_total` in the loss report equals this file’s line count.** Asserted at [`packages/atif-converter/src/atif_converter/domain/fidelity.py:91-95`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L91-L95) — 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L107-L117) in the same commit.
## `EmbeddingProvider`, `VectorStorePort`, `TextRowsPort`
[Section titled “EmbeddingProvider, VectorStorePort, TextRowsPort”](#embeddingprovider-vectorstoreport-textrowsport)
**Producer:** [`packages/atif-embed/src/atif_embed/domain/ports.py:31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L31), [`packages/atif-embed/src/atif_embed/domain/ports.py:59`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L59), [`packages/atif-embed/src/atif_embed/domain/ports.py:87`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L87)
**Consumer(s):**
* [`packages/atif-embed/src/atif_embed/application/embed.py:35`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L35) — imports all three; annotated at [`packages/atif-embed/src/atif_embed/application/embed.py:46-47`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L46-L47) and [`packages/atif-embed/src/atif_embed/application/embed.py:65-67`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L65-L67).
* [`packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:285`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py#L285) — the `EmbeddingProvider` adapter.
* [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:377`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L377) — the `VectorStorePort` adapter.
* [`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:156`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L156) — the `TextRowsPort` adapter.
* [`packages/atif-embed/tests/embed_fixtures.py:171`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/embed_fixtures.py#L171) — the deterministic `EmbeddingProvider` double.
* [`packages/atif-embed/tests/test_embed_use_case.py:458`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_embed_use_case.py#L458) — a recording `TextRowsPort` double.
**Shape:**
```plaintext
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_documents` returns one slot per input text in input order, and the caller enforces it.** [`packages/atif-embed/src/atif_embed/application/embed.py:191`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L191) zips with `strict=True`, so a provider returning a different length raises `ValueError` instead of misattributing vectors to texts.
* **A `None` slot means “not embedded this run”, not “failed the run”.** The port documents bounded loss at [`packages/atif-embed/src/atif_embed/domain/ports.py:47-51`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L47-L51); [`packages/atif-embed/src/atif_embed/application/embed.py:192-198`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L192-L198) filters the `None`s, 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L166-L175), using the identity `table_identity()` returns — `None` there means empty and any provider may claim it.
* **`add_chunk` accepts a 7-column polars frame with a fixed-size `pl.Array`, not a `pl.List`.** The schema is built at [`packages/atif-embed/src/atif_embed/application/embed.py:216-238`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L216-L238) and the reason is stated at [`packages/atif-embed/src/atif_embed/application/embed.py:212-215`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L212-L215): 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 at [`packages/atif-embed/src/atif_embed/domain/ports.py:111-114`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L111-L114) — peak resident text inside an implementation is each adapter’s own problem, and [`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:54-60`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L54-L60) establishes 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_hash` is yielded with `replaces_existing=True` ([`packages/atif-embed/src/atif_embed/domain/ports.py:105-109`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L105-L109)), 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L203-L209).
* **The CLI does not inject these ports.** [`packages/atif-cli/src/atif_cli/app.py:826-831`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L826-L831) calls `run_backfill` with only `corpus_root`, `settings`, `limit`, and `dry_run`; the use case constructs each default adapter itself under a deferred import ([`packages/atif-embed/src/atif_embed/application/embed.py:103-110`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L103-L110), [`packages/atif-embed/src/atif_embed/application/embed.py:157-159`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L157-L159)) 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 in `packages/atif-embed/tests/`; the only static link is the defaulting assignment inside `run_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-110`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L103-L110) and [`packages/atif-embed/src/atif_embed/application/embed.py:157-159`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L157-L159) against the annotations at [`packages/atif-embed/src/atif_embed/application/embed.py:65-67`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L65-L67)). 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L161-L162)), 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py#L305-L307) reads it from [`packages/atif-embed/src/atif_embed/infrastructure/settings.py:31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/settings.py#L31), never from a response.
## `ConverterPort` and `ConversionOutput`
[Section titled “ConverterPort and ConversionOutput”](#converterport-and-conversionoutput)
**Producer:** [`packages/atif-corpus/src/atif_corpus/domain/ports.py:41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L41) (`ConverterPort`) and [`packages/atif-corpus/src/atif_corpus/domain/ports.py:21`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L21) (`ConversionOutput`)
**Consumer(s):**
* [`packages/atif-corpus/src/atif_corpus/application/materialize.py:92`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L92) — imports the port; annotated at [`packages/atif-corpus/src/atif_corpus/application/materialize.py:193`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L193) and [`packages/atif-corpus/src/atif_corpus/application/materialize.py:480`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L480).
* [`packages/atif-cli/src/atif_cli/converter_adapter.py:38-69`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L38-L69) — `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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py#L16-L65) — the test adapter that ships in `src/`, not in `tests/`.
* [`packages/atif-cli/tests/test_converter_adapter.py:17`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_converter_adapter.py#L17) — asserts the mapping and binds `RealConverter` to the port annotation at [`packages/atif-cli/tests/test_converter_adapter.py:91`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_converter_adapter.py#L91).
* [`packages/atif-cli/src/atif_cli/app.py:399`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L399) — wires `RealConverter()` into the use case.
**Shape:**
```plaintext
@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:**
* **`RealConverter` never names the Protocol it implements.** It imports only the return-value dataclass ([`packages/atif-cli/src/atif_cli/converter_adapter.py:38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L38)), so nothing in either package links the class to the port. The single static link in the workspace is the annotated assignment `converter: ConverterPort = RealConverter()` at [`packages/atif-cli/tests/test_converter_adapter.py:91`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_converter_adapter.py#L91), whose own comment ([`packages/atif-cli/tests/test_converter_adapter.py:89-90`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_converter_adapter.py#L89-L90)) states that the assignment is what ty verifies. Delete that test and a signature change on either side becomes a runtime `AttributeError` at materialize time.
* **The port is typed to `docs/CONTRACT.md`’s artifact shapes, not to converter internals** — three loosely-typed `dict[str, Any]` / `list[str]` fields instead of the converter’s own `ConversionResult`. The docstring at [`packages/atif-corpus/src/atif_corpus/domain/ports.py:3-10`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L3-L10) names 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-47`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L44-L47) says an implementation may raise anything and the use case records the failure and continues. `RealConverter` relies on that: it raises `TrajectoryValidationError` for an invalid trajectory rather than materializing it ([`packages/atif-cli/src/atif_cli/converter_adapter.py:63-64`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L63-L64), reasoning at [`packages/atif-cli/src/atif_cli/converter_adapter.py:21-25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L21-L25)).
* **`trajectory_dict` is the ENRICHED trajectory and is NOT yet compact-serialized.** The writer owns `separators=(",", ":")` — the mapping decision is stated at [`packages/atif-cli/src/atif_cli/converter_adapter.py:13-14`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L13-L14) and the writer applies it at [`packages/atif-corpus/src/atif_corpus/application/materialize.py:222`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L222).
* **`loss_report_dict` is `LossReport.to_json()`-shaped with enum members flattened to strings** ([`packages/atif-cli/src/atif_cli/converter_adapter.py:15-16`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L15-L16)), which is what makes it readable by atif-duck’s `_LOSS_REPORT_COLUMNS` projection at [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:122-131`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L122-L131).
* **Importing the adapter drags harbor.** [`packages/atif-cli/src/atif_cli/converter_adapter.py:27-29`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L27-L29) forbids importing it at `atif_cli.app` module scope, and [`packages/atif-cli/src/atif_cli/app.py:387`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L387) obeys by importing inside the command body — enforced by the fresh-interpreter lean-import test named at [`pyproject.toml:164`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L164).
**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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_converter_adapter.py#L88-L92) 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_converter_adapter.py#L17-L60) 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”](#token-semantics-and-the-pricing-table)
**Producer:** [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:41-44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L41-L44) (the semantics) and [`packages/atif-duck/src/atif_duck/domain/catalog.py:405`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L405) (`DEFAULT_PRICING`)
**Consumer(s):**
* [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1066-1083`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1066-L1083) — the `cost_estimate` macro, which consumes both.
* [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1020`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1020) — `register_macros` resolves the pricing override against `DEFAULT_PRICING`.
* [`packages/atif-duck/src/atif_duck/domain/catalog.py:72-75`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L72-L75) — the `steps` view schema publishes `prompt_tokens`, `completion_tokens`, `cached_tokens`, `cache_creation` to every SQL consumer.
* [`packages/atif-duck/src/atif_duck/domain/catalog.py:347-350`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L347-L350) — the agent-facing `cost_estimate` description carries the trust condition.
**Shape:**
```plaintext
# 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_tokens` is a CUMULATIVE TOTAL, per step: non-cached + cache\_read + cache\_creation.** Stated at [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:41-44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L41-L44) and read from `metrics.prompt_tokens` at [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:376-377`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L376-L377). The identifier does not say so, so a consumer summing `prompt_tokens + cached_tokens` double-counts every cache read.
* **`cached_tokens` is the cache-READ subset of `prompt_tokens`**, not an additional quantity ([`packages/atif-duck/src/atif_duck/infrastructure/registry.py:380-381`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L380-L381)). `cost_estimate` therefore prices `prompt_tokens - cached_tokens` and documents that identity at [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1043-1049`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1043-L1049); cache reads are charged nothing.
* **`in_rate` / `out_rate` are USD per 1,000,000 tokens.** The unit appears in neither name — only the comment at [`packages/atif-duck/src/atif_duck/domain/catalog.py:395`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L395) and the `/ 1e6` at [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1071`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1071) carry it.
* **`est_cost_usd` is USD and covers the PRICED steps only.** It is meaningful only when `unpriced_steps = 0`, stated three times: [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1053-1058`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1053-L1058), [`packages/atif-duck/src/atif_duck/domain/catalog.py:347-350`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L347-L350), and the `LEFT JOIN` shape itself at [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1079`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1079).
* **`unpriced_steps` counts only steps with a non-NULL `model_name`**, because user steps carry no model and cost nothing — [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1060-1063`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1060-L1063). 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-404`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L402-L404) states that the 1.25x / 2x / 0.1x tiers are out of scope, so `est_cost_usd` under-reports a cache-heavy session.
* **A step’s `model_name` matches a pricing row by dated-suffix-stripping prefix** ([`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1080`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1080)), so `claude-haiku-4-5-20251001` prices as `claude-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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L380)) and atif-models’ per-call `CallUsage.cached_tokens` ([`packages/atif-models/src/atif_models/domain/ports.py:75`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L75)) — 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L41-L44) or [`packages/atif-models/src/atif_models/domain/ports.py:64-70`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L64-L70)) before using either in arithmetic.
## The `EmbeddingProviderMismatch` guard, declared twice
[Section titled “The EmbeddingProviderMismatch guard, declared twice”](#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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/errors.py#L29) with the rule at [`packages/atif-embed/src/atif_embed/domain/embedding_guard.py:38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/embedding_guard.py#L38), and [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:33`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L33) with the rule at [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:46`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L46)
**Consumer(s):**
* [`packages/atif-embed/src/atif_embed/application/embed.py:166-175`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L166-L175) — the WRITE path calls `ensure_store_matches` before appending.
* [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:830-861`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L830-L861) — the READ/bind path calls it before binding `message_embeddings` (guard-before-bind).
* [`packages/atif-cli/src/atif_cli/duck_errors.py:26-31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L26-L31) — puts atif-duck’s copy in `REGISTRATION_ERRORS` and maps it to exit 65 at [`packages/atif-cli/src/atif_cli/duck_errors.py:74-81`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L74-L81).
* [`packages/atif-embed/tests/test_guard_twin_pin.py:93`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_guard_twin_pin.py#L93) — reads both twin modules as source text and requires the recovery hint to appear after the `raise` keyword.
**Shape:**
```plaintext
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-11`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L5-L11) and [`packages/atif-embed/src/atif_embed/domain/embedding_guard.py:15-18`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/embedding_guard.py#L15-L18) — 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’s `EM102` / `TRY003` and defeats the twin pin, so both rules are suppressed on purpose at [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:67-74`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L67-L74).
* **A `None` on 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L61-L62)).
* **`expected_dim=None` trusts `model_id` alone**, 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L57-L59)).
* **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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L33)) while atif-embed’s derives from that package’s `DomainError` ([`packages/atif-embed/src/atif_embed/domain/errors.py:29`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/errors.py#L29)), so a bare `except duckdb.Error` on the registration path would let atif-duck’s escape as exit 1 — the reason `REGISTRATION_ERRORS` widens the caught set, stated at [`packages/atif-cli/src/atif_cli/duck_errors.py:14-18`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L14-L18).
* **The recovery hint must not name a fixed home directory.** The store lives at `/embeddings_lance` by 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-24`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L21-L24) and [`packages/atif-embed/src/atif_embed/domain/embedding_guard.py:25-29`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/embedding_guard.py#L25-L29)).
**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”](#harbors-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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/pyproject.toml#L23)
**Consumer(s):**
* [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L32) — the method name lives in one named module constant, `_CONVERT_METHOD`.
* [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:178`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L178) — the sole call site, reached via `getattr`.
* [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:112`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L112) — `assert_harbor_private_api()`, the pre-flight probe, invoked at [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:167`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L167).
* [`packages/atif-converter/tests/test_snapshot_and_drift.py:303-329`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_snapshot_and_drift.py#L303-L329) — the drift alarm: deletes and then corrupts the attribute and asserts the probe raises.
* [`packages/atif-converter/tests/test_convert_and_audit.py:153`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_convert_and_audit.py#L153) — calls the private method directly to pin observed 0.22.0 behavior.
* [`packages/atif-converter/src/atif_converter/domain/fidelity.py:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L44) — `FidelityGap`, the seven known conversion gaps, is the typed record of what this method loses.
**Shape:**
```plaintext
#: 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 `.jsonl` plus optional `subagents/*.jsonl` under a `/` subdirectory — reconstructed by symlink staging at [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:54-94`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L54-L94).
* **harbor’s own subagent discovery cannot see workflow-nested side-files**, so the adapter stages every `*.jsonl` FLAT into the harbor-visible `subagents/` dir with `__`-joined collision-safe names ([`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:71-92`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L71-L92)). This is fidelity gap 1, and it is why the parity oracle expected a superset ([`docs/CONTRACT.md:74-76`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L74-L76)).
* **Directory symlinks are not enough.** Python 3.13’s `Path.rglob` does 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L74-L77)).
* **A missing attribute is a FLEET failure, not a session failure.** Without the probe an upstream rename surfaces as one `AttributeError` per session swallowed into `ConversionError` ([`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:114-118`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L114-L118)), which is exactly the shape `HarborPrivateApiMissing` exists to distinguish ([`packages/atif-converter/src/atif_converter/domain/errors.py:47-54`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/errors.py#L47-L54)).
* **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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L11-L12)).
* **`None` from the method means “no convertible events”, not “error”** — mapped to `EmptySessionError` and to exit code 2 ([`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:157`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L157), [`packages/atif-cli/src/atif_cli/errors.py:27`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L27)).
**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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/pyproject.toml#L19-L23)), 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”](#the-atif-sql-distributions-010-sibling-pins)
**Producer:** [`packages/atif-cli/pyproject.toml:32-36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L32-L36) and [`packages/atif-analytics/pyproject.toml:24`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L24)
**Consumer(s):**
* [`pyproject.toml:446-452`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L446-L452) — commitizen’s `version_files`, which rewrites every pin on each bump.
* [`packages/atif-cli/pyproject.toml:56-60`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L56-L60) — `[tool.uv.sources]`, the local-workspace resolution that these pins deliberately do NOT express.
* [`packages/atif-analytics/pyproject.toml:56`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L56) — the same pairing for its single sibling dependency.
* [`pyproject.toml:399-406`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L399-L406) — `[tool.commitizen] version = "0.1.0"`, the one version this repo publishes.
* [`pyproject.toml:17-18`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L17-L18) — the distribution is named `atif-sql` at the workspace root, while the console script’s module stays `atif_cli` in a member.
**Shape:**
```plaintext
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",
]
```
```plaintext
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 as `Requires-Dist: atif-duck` and resolve from PyPI to whatever the newest release of that name is, owned by whoever owns it — the reasoning is inline at [`packages/atif-cli/pyproject.toml:24-31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L24-L31).
* **One version covers the whole repository.** commitizen reads its own `version` key rather than the PEP 621 field ([`pyproject.toml:401-403`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L401-L403)), and `version_files` propagates it to the published `[project] version` and to all seven member manifests; seven numbers for one artifact is the shape being refused.
* **The `version_files` entries are per-file rather than globbed on purpose**, because `--check-consistency` requires a hit in every matched file and five of the seven members carry no dev pin between members ([`pyproject.toml:436-439`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L436-L439)).
* **The lockfile must land in the same commit as the versions it resolves.** `pre_bump_hooks` run `uv lock` and `git add uv.lock` after the rewrite and before the commit ([`pyproject.toml:422-433`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L422-L433)), because `uv.lock` records every member’s version and `mise run lock:check` would 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L416)) — 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L434-L436).
## Other contracts
[Section titled “Other contracts”](#other-contracts)
* **`LossReport.to_json()`** — producer [`packages/atif-converter/src/atif_converter/domain/fidelity.py:114`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L114), whose docstring calls the keys wire contract; consumed by [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:122-131`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L122-L131) as `_LOSS_REPORT_COLUMNS` and by [`packages/atif-cli/src/atif_cli/converter_adapter.py:67`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L67). `record_counts` and `gaps_observed` stay `JSON` because they are an enum-keyed dict and a sorted list of enum values.
* **The `meta.json` provenance record** — written at [`packages/atif-corpus/src/atif_corpus/application/materialize.py:230-237`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L230-L237), projected at [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:134-141`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L134-L141). `harbor_version` and `converter_version` are supplied by the CLI ([`packages/atif-cli/src/atif_cli/app.py:401-402`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L401-L402)), 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L115-L121), of which the `message_embeddings` view exposes 5 ([`packages/atif-duck/src/atif_duck/domain/catalog.py:218-224`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L218-L224)). `text_hash` and `truncated` are unreachable from SQL, so a query cannot distinguish a head-only-embedded row from a complete one.
* **The Lance schema version sidecar** — `SCHEMA_VERSION = 2` and `schema_version.json` at [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:61-67`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L61-L67), 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/layout.py#L22-L41), of which atif-duck pins 9 as `_ANALYTICS_SOURCES` ([`packages/atif-duck/src/atif_duck/infrastructure/analytics.py:53-64`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/analytics.py#L53-L64)). `REFUSALS_DIRNAME` ([`packages/atif-analytics/src/atif_analytics/domain/layout.py:32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/layout.py#L32)) has no view, so the refusal audit its docstring calls queryable is not reachable from SQL.
* **The CLI exit-code taxonomy** — `EXIT_CODES` at [`packages/atif-cli/src/atif_cli/errors.py:25-39`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L25-L39), consumed at 12 sites in `packages/atif-cli/src/atif_cli/app.py` and mapped from DuckDB exceptions at [`packages/atif-cli/src/atif_cli/duck_errors.py:34-63`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L34-L63). 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L35-L37)).
* **`PendingText` and the text stamp** — [`packages/atif-embed/src/atif_embed/domain/text_stamp.py:50`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/text_stamp.py#L50), with `MAX_EMBEDDABLE_CHARS = 50_000` at [`packages/atif-embed/src/atif_embed/domain/text_stamp.py:35`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/text_stamp.py#L35) in CHARACTERS per text; the same constant governs both the row’s `truncated` flag 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/text_stamp.py#L31-L34)).
* **The `steps`-view rendering semantics, mirrored without a shared symbol** — [`packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:23-33`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py#L23-L33) reproduces four of atif-duck’s `steps` rules by hand (message-union flattening, `extra.source_uuids[0]` as the step key, `agent` → `assistant`, error recovery from `observation.results[].extra.tool_result_metadata.is_error`), and [`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:12-25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L12-L25) reproduces 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:31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/settings.py#L31) pins `output_dimension: Literal[256, 512, 1024, 1536] = 1024`, which is the value the static catalog hardcodes as `FLOAT[1024]`.
* **The lean-import contract** — [`pyproject.toml:164`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L164) and [`pyproject.toml:181`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L181) record that `atif_cli.app` must not import duckdb, harbor, lancedb, boto3, or polars at module scope, asserted in a fresh interpreter by `packages/atif-cli/tests/test_lean_import.py`; 182 deferred-import sites exist because of it.
## See also
[Section titled “See also”](#see-also)
* [impact analysis](impact-analysis.md) — 49 shared source citations
* [module map](../architecture/module-map.md) — 37 shared source citations
* [processes](../behavior/processes.md) — 35 shared source citations
* [business logic](business-logic.md) — 33 shared source citations
* [components](../diagrams/architecture/components.md) — 20 shared source citations
# Debugging guide
# atif-sql · Debugging guide
[Section titled “atif-sql · Debugging guide”](#atif-sql--debugging-guide)
Something is broken. Where do you look first?
atif-sql is a CLI plus six libraries with no server, no job queue, and no observability platform. That narrows the search surface to four places, and this guide is organized around them:
1. **The process exit code.** `EXIT_CODES` is one dict of 11 keys ([`packages/atif-cli/src/atif_cli/errors.py:25-39`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L25-L39)) and it is the primary diagnostic. Nine of the eleven are reachable.
2. **The classified error envelope on stderr** — one readable line on a TTY, one JSON object on a pipe ([`packages/atif-cli/src/atif_cli/output.py:274-281`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L274-L281)).
3. **The loguru sink.** Exactly one, added in `main()`, stderr, WARNING and up ([`packages/atif-cli/src/atif_cli/app.py:1133-1134`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1133-L1134)).
4. **The cron refresh log and its marker files**, for anything that failed unattended ([`scripts/atif-sql-refresh.sh:102-103`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L102-L103)).
The exit-code contract, read from source and confirmed by running the CLI:
| code | keys | meaning |
| ---- | --------------------------------------------------------- | ----------------------------------------------------------- |
| 0 | `ok` | success |
| 2 | `empty_session`, `no_embeddings` | nothing to work on; not a fault |
| 64 | `invalid_input`, `parse_error` | the caller’s input is malformed |
| 65 | `catalog_error`, `validation_error`, `embedding_mismatch` | the named object cannot be bound |
| 70 | `runtime_error` | everything else the adapter raises |
| 78 | `terminal_state`, `suspicious_scan` | an operator has to act; retrying cannot succeed |
| 127 | `harbor_missing` | the private harbor method the converter is built on is gone |
| 1 | *(none)* | an unhandled exception — outside the taxonomy |
Two of these codes exist to be told apart from a crash rather than from each other. **`harbor_missing: 127`** is raised only by `convert`, ahead of its generic `DomainError` clause ([`packages/atif-cli/src/atif_cli/app.py:285-295`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L285-L295)), because a vanished private method condemns every session in the corpus rather than the one in hand — a driver should stop, not walk the corpus collecting the same failure. **`suspicious_scan: 78`** is raised only by `materialize` ([`packages/atif-cli/src/atif_cli/app.py:429-443`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L429-L443)), when the source scan finds zero sessions over a non-empty corpus and ghost removal is refused; it shares 78 with `terminal_state` because both mean an operator has to act, and carries its own `kind` string because the remedy is a path rather than a store.
**Exit 1 is still a reachable outcome**, and it means an unhandled exception: `analyze` maps nothing into the taxonomy ([`packages/atif-cli/src/atif_cli/app.py:755-773`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L755-L773) has no `except`), and neither does a registration error outside `REGISTRATION_ERRORS` ([`packages/atif-cli/src/atif_cli/duck_errors.py:14-18`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L14-L18)). Read a 1 as a bug report, never as a diagnosis.
On a pipe the envelope is the last line on stderr, in this shape:
```plaintext
{"error": {"kind": "invalid_input", "message": "a real embed run needs an explicit scope", "hint": "pass --limit N (bounded) or --all (full backfill), or preview with --dry-run"}}
```
## Failure-mode index
[Section titled “Failure-mode index”](#failure-mode-index)
| Symptom | Likely surface | First check | Citation |
| ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `query` exits 64, kind `parse_error` | `duckdb.ParserException`, or no SQL argument at all | Run `atif-sql schema --format json` for the real view and column names; a bare `atif-sql query` also exits 64 | [`packages/atif-cli/src/atif_cli/duck_errors.py:37-43`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L37-L43), [`packages/atif-cli/src/atif_cli/app.py:599-610`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L599-L610) |
| `query` exits 65, kind `catalog_error` | `duckdb.CatalogException` — the view, macro, or column does not exist | `atif-sql schema` answers from the static catalog with no DuckDB bind, so it works even on an unmaterialized corpus | [`packages/atif-cli/src/atif_cli/duck_errors.py:44-50`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L44-L50), [`packages/atif-cli/src/atif_cli/app.py:1086-1122`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1086-L1122) |
| `query` exits 70 with `IO Error: No files found that match the pattern .../sessions/*/meta.json` | The corpus is not materialized; `register_raw`’s glob matches nothing | `atif-sql status`; a `watermark: never materialized` line means run `atif-sql materialize` | [`packages/atif-cli/src/atif_cli/duck_errors.py:51-57`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L51-L57), [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:306-309`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L306-L309) |
| `query` or `search` exits 65, kind `embedding_mismatch` | The Lance store’s stamped `(model, dim)` differs from the active embedder | The message names both sides; either point `ATIF_SQL_EMBED_MODEL_ID` at the model that wrote the store, or delete the store | [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:61-80`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L61-L80), [`packages/atif-cli/src/atif_cli/duck_errors.py:74-81`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L74-L81) |
| `search` exits 2, kind `no_embeddings` | The store is empty or absent, so `register_vss` bound an empty fallback table | `atif-sql query 'SELECT count(*) FROM message_embeddings'`; zero rows confirms it | [`packages/atif-cli/src/atif_cli/app.py:930-941`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L930-L941), [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:896-914`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L896-L914) |
| A bare `atif-sql embed` exits 64 | The scope guard, which runs before any Bedrock client is built | Pass `--limit N`, `--all`, or `--dry-run`; this is the accidental-full-backfill guard, not a fault | [`packages/atif-cli/src/atif_cli/app.py:810-820`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L810-L820) |
| `embed` exits 78 and the cron lane stops trying | A `DomainError` with `terminal = True` — the store or its config needs an operator | Read the terminal marker file the refresh lane drops: three lines, store path, mtime, reason | [`packages/atif-embed/src/atif_embed/domain/errors.py:14-26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/errors.py#L14-L26), [`scripts/atif-sql-refresh.sh:244-249`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L244-L249) |
| A session directory exists under `sessions/` but no view returns its rows | Torn artifact set: the dir has no `meta.json`, so the meta gate excludes it from every reader | Grep the WARNING; the complete set is four files, and `meta.json` is written last | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:173-177`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L173-L177), [`docs/CONTRACT.md:22-31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L22-L31) |
| `atif-sql status` and `SELECT count(*) FROM sessions` disagree | `status` counts directories with no meta check; the view applies the meta gate | The difference is exactly the number of incomplete dirs — use it as the torn-set count | [`packages/atif-cli/src/atif_cli/app.py:482-486`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L482-L486), [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:221`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L221) |
| `materialize` prints all zeroes and reads as an idle, complete corpus | Every session was unreadable; an unreadable session lands in no other counter | Read the `unreadable` field, not just `materialized` | [`packages/atif-cli/src/atif_cli/app.py:312-346`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L312-L346), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:136-142`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L136-L142) |
| `materialize` aborts with a traceback naming `SuspiciousEmptyScanError`, exit 1 | The scan found zero sessions over a non-empty corpus — almost always a wrong `source_root` | Compare the `source_root` line in `atif-sql status` against where the transcripts actually are | [`packages/atif-corpus/src/atif_corpus/application/materialize.py:97-104`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L97-L104), raise at [`packages/atif-corpus/src/atif_corpus/application/materialize.py:553-559`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L553-L559) |
| A changed session never materializes | Quiescence: the newest source mtime is younger than `quiesce_seconds` (default 300) | `atif-sql status` reports it under `live`; `--force` overrides staleness but never quiescence | [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:81-104`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L81-L104), [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:172-173`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L172-L173), [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:201-203`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L201-L203) |
| A session stays `live` forever and warns about a FUTURE mtime | The writing host’s clock is ahead, so the age is negative and never meets the threshold | Fix the clock on the host writing the transcript | [`packages/atif-corpus/src/atif_corpus/domain/sessions.py:96-103`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L96-L103) |
| `materialize` or `status` aborts inside `read_watermark` with `ValueError` or `TypeError` | A watermark whose values are not numbers; the `int(value)` coercion sits outside the try | Delete `watermark.json`; a missing watermark costs one full re-materialization pass and is always safe | [`packages/atif-corpus/src/atif_corpus/application/materialize.py:160-176`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L160-L176) |
| Every session fails conversion at once, with `harbor … has no ClaudeCode._convert_events_to_trajectory` | harbor moved past the pin; this is upstream drift, not a data problem | Compare the installed harbor against the `>=0.22.0,<0.23` pin before touching any transcript | [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:112-139`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L112-L139) |
| One session fails with `N source file(s) changed while converting …` | The session is still being written; the snapshot re-check refused to publish inconsistent artifacts | Retry once the session goes quiet — this is self-clearing | [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:93-102`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L93-L102), [`packages/atif-converter/src/atif_converter/domain/errors.py:57-70`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/errors.py#L57-L70) |
| `convert` prints a trajectory and then exits 65 | The enriched trajectory failed harbor’s `TrajectoryValidator` | Read `validation_errors` in the JSON it already printed | [`packages/atif-cli/src/atif_cli/app.py:283-304`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L283-L304), [`packages/atif-cli/src/atif_cli/converter_adapter.py:62-64`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L62-L64) |
| `loss_report.json` shows `records_dropped > 0` | Fidelity gap 2: harbor 0.22.0 converts only user and assistant records | Check `gaps_observed` against the seven named gaps — these are documented losses, not defects | [`packages/atif-converter/src/atif_converter/domain/fidelity.py:44-79`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L44-L79) |
| `analyze` summary carries `budget_exhausted: true` and a stage `{"skipped": "budget_exhausted"}` | The dollar ceiling was crossed against running actual usage | Read `llm_spent_usd` in the same summary; raise `--max-cost-usd` or wait for the next run | [`packages/atif-analytics/src/atif_analytics/application/analyze.py:164-179`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L164-L179), [`packages/atif-analytics/src/atif_analytics/application/analyze.py:202-204`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L202-L204) |
| An LLM stage skips the same sessions every run, forever | The retry queue is exhausted at 5 attempts; `attempts >= 5` with `completed_at IS NULL` is permanent and nothing resets it | `sqlite3 /analytics/state.db 'SELECT * FROM retry_queue'` — note the pipeline is `user_friction`, never `friction` | [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py:176-193`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L176-L193), [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:35-41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py#L35-L41) |
| An LLM run stalls for minutes with no output | Bedrock throttling under tenacity: 10 attempts, exponential 2 s to 60 s | The per-backoff WARNING names the attempt number and the sleep | [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:251-257`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L251-L257), [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:112-121`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L112-L121) |
| A classify row reads `[refused]`, or a refusals sidecar row appears | `RefusalError` — a content filter or a refusal `finish_reason` | Nothing to fix here: a refusal is terminal, checkpointed, and never retried | [`packages/atif-models/src/atif_models/domain/ports.py:43-48`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L43-L48), [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:284-290`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L284-L290) |
| An LLM unit lands in the retry queue with a truncation error | `finish_reason=length` twice, or once already at the effort floor | Raise `max_completion_tokens` or shrink the prompt; the automatic ladder has only one reachable rung | [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:291-297`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L291-L297), [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:229-249`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L229-L249) |
| `analyze` crashes with a raw traceback and exit 1 | The `analyze` command body has no `except`, so nothing maps analytics errors into `EXIT_CODES` | Read the traceback’s innermost frame; the exit code carries no information here | [`packages/atif-cli/src/atif_cli/app.py:739-757`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L739-L757) |
| `atif-sql examples` raises a bare `KeyError` naming `ARG_EXEMPLARS` or `DESCRIPTIONS` | A catalog drift tripwire firing at example-build time, not query time | Add the missing exemplar or description entry named in the message | [`packages/atif-duck/src/atif_duck/domain/examples.py:150-158`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L150-L158), [`packages/atif-duck/src/atif_duck/domain/examples.py:165-175`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L165-L175) |
| A cron lane logs `analytics not yet installed, skipping` and exits 0 | The resolved CLI predates the `analyze` subcommand; `~/.local/bin/atif-sql` wins over the workspace venv | Check which binary the lane resolved, in the CLI-resolution order the script documents | [`scripts/atif-sql-refresh.sh:180-185`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L180-L185), [`scripts/atif-sql-refresh.sh:150-158`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L150-L158) |
## Log and error surfaces
[Section titled “Log and error surfaces”](#log-and-error-surfaces)
There is no structured-logging platform, no log file the application writes, and no observability bootstrap. The whole surface is loguru on stderr plus the shell-side refresh log.
| Surface | Where it emits | What to grep for | Citation |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The one loguru sink | stderr, WARNING and above; `logger.remove()` deletes loguru’s default handler first | `WARNING` / `ERROR`; nothing at INFO or DEBUG is emitted at all | [`packages/atif-cli/src/atif_cli/app.py:1125-1135`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1125-L1135) |
| Classified error envelope, pipe form | stderr, one JSON object | `"error"`, `"kind"`, `"hint"` | [`packages/atif-cli/src/atif_cli/errors.py:60-68`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L60-L68) |
| Classified error, TTY form | stderr, one line plus an optional hint line | `[]` at line start, then `hint:` | [`packages/atif-cli/src/atif_cli/output.py:274-281`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L274-L281) |
| Process exit code | the shell | the nine reachable `EXIT_CODES` values | [`packages/atif-cli/src/atif_cli/errors.py:25-39`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L25-L39) |
| Per-session materialize failures | stderr, table format only | `FAILED :` and `UNREADABLE ` | [`packages/atif-cli/src/atif_cli/app.py:343-346`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L343-L346) |
| Materialize report | stdout, JSON on a pipe | `unreadable`, `failures`, `removed_session_ids` | [`packages/atif-cli/src/atif_cli/app.py:320-332`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L320-L332) |
| Corpus freshness | `atif-sql status` stdout | `watermark_age_seconds`, `staleness`, `source_root` | [`packages/atif-cli/src/atif_cli/app.py:508-520`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L508-L520) |
| DDL failure narration | stderr via `logger.exception`, with loguru’s decorated traceback, then re-raised | `Failed to register raw readers over`, `Failed to register derived views`, `Failed to register macros` | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:308`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L308), [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:802`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L802), [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1203`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1203) |
| Torn-session exclusion | stderr WARNING, one line per excluded dir | `Skipping incomplete session dir` | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:173-177`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L173-L177) |
| Empty-store fallback | stderr WARNING at VSS bind | `No Lance embeddings table at` | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:897-902`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L897-L902) |
| Unreadable-source warnings | stderr WARNING during the scan | `NOT treated as deleted`, `NOT treating it as deleted` | [`packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py:133-138`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py#L133-L138), [`packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py:179-184`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py#L179-L184) |
| Ghost-removal suppression | stderr WARNING | `skipping ghost removal`, `keeping session` | [`packages/atif-corpus/src/atif_corpus/application/materialize.py:566-570`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L566-L570), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:416-420`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L416-L420) |
| Watermark degradation | stderr WARNING | `treating corpus as unmaterialized` | [`packages/atif-corpus/src/atif_corpus/application/materialize.py:171`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L171), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:174`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L174) |
| Bedrock retry narration | stderr WARNING, one per backoff | `bedrock invoke retry` (LLM), `Retrying` … `seconds as it raised` (embeddings) | [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:112-121`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L112-L121), [`packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:94-101`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py#L94-L101) |
| Degraded-effort retry | stderr WARNING | `finish_reason=length at effort=` | [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:237-243`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L237-L243) |
| Cost-ceiling events | stderr, WARNING or ERROR at 3 consecutive skips | `cost ceiling hit` | [`packages/atif-analytics/src/atif_analytics/application/analyze.py:169-178`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L169-L178), [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:205`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L205) |
| Terminally failed embed batch | stderr ERROR | `failed terminally`, `the next run re-picks these rows` | [`packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:377-384`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py#L377-L384) |
| Clipped embedding input | stderr WARNING | `Clipping text at position`, `content past the cap will not match a search` | [`packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:332-338`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py#L332-L338) |
| Refresh-script log | a file appended under the script’s `.run/` sibling, one `date -Is`-stamped line per event; the directory is gitignored, so the path is named in prose only | `refresh complete (mode=`, `skip[`, `TERMINAL:`, `FATAL:` | [`scripts/atif-sql-refresh.sh:100-103`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L100-L103), [`scripts/atif-sql-refresh.sh:337`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L337) |
| Refresh log, machine-read subset | `atif-sql cron status` stdout | only two line shapes are parsed: completion and skip | [`packages/atif-cli/src/atif_cli/cron.py:55-56`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L55-L56) |
| Lane lock state | `atif-sql cron status`, from a nonblocking flock probe plus the pidfile | `RUNNING (pid …)` versus `idle` | [`packages/atif-cli/src/atif_cli/cron.py:109-124`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L109-L124), [`packages/atif-cli/src/atif_cli/cron.py:127-144`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L127-L144) |
| Embed terminal marker | one file per corpus beside the refresh log; three lines — store path, store mtime, reason | the reason string, parsed out of the exit-78 envelope | [`scripts/atif-sql-refresh.sh:238-249`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L238-L249) |
| Analytics durable state | `/analytics/state.db`, sqlite in WAL mode; three tables, no read command | `retry_queue`, `budget_skips`, `session_checkpoint` | [`packages/atif-analytics/src/atif_analytics/domain/layout.py:106-108`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/layout.py#L106-L108), [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:43-62`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py#L43-L62) |
| Analyze summary | stdout JSON | `budget_exhausted`, `llm_spent_usd`, `consecutive_skips` | [`packages/atif-analytics/src/atif_analytics/application/analyze.py:179`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L179), [`packages/atif-analytics/src/atif_analytics/application/analyze.py:202-204`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L202-L204) |
| Per-session loss accounting | `loss_report.json` inside each corpus session dir | `gaps_observed`, `records_dropped`, `records_total` | [`packages/atif-converter/src/atif_converter/domain/fidelity.py:114-137`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L114-L137) |
Two properties of this surface change how you read it.
**Everything below WARNING is unreachable through the CLI.** `main()` hardcodes the level and there is no `--verbose`, no `--log-level`, and no `LOGURU_LEVEL` support — `logger.remove()` deletes the default handler that would honor the env var ([`packages/atif-cli/src/atif_cli/app.py:1133-1134`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1133-L1134)). The INFO lines that would narrate a materialize pass ([`packages/atif-corpus/src/atif_corpus/application/materialize.py:634-644`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L634-L644)) or an embed backfill ([`packages/atif-embed/src/atif_embed/application/embed.py:255-261`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L255-L261)) never appear. Reaching them means importing `atif_cli.app` and calling `app()` directly after adding your own sink, instead of going through `main()`.
**A `logger.exception` traceback on stderr is not a crash.** The registration path logs and re-raises, so the decorated traceback is followed by the JSON envelope and a classified exit code ([`packages/atif-duck/src/atif_duck/infrastructure/registry.py:306-309`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L306-L309)). Read the last stderr line, not the first.
## First-checks ladder
[Section titled “First-checks ladder”](#first-checks-ladder)
Cheapest first. Steps 1 through 6 are free and read-only; step 10 spends money.
1. **Read the exit code.** It partitions the problem before you read any text: 64 means your input, 65 means the object cannot be bound, 70 means the adapter, 78 means an operator is required, and 1 means the taxonomy did not cover it. [`packages/atif-cli/src/atif_cli/errors.py:25-39`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L25-L39)
2. **Read the last line on stderr.** On a pipe it is the JSON envelope with `kind`, `message`, and `hint`; on a TTY it is `[kind] message` plus a `hint:` line. The hint names the recovery command in most cases. [`packages/atif-cli/src/atif_cli/output.py:267-281`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L267-L281)
3. **Run `atif-sql status`.** It replays the exact planning decision `materialize` would make, without converting anything, so its `stale / up-to-date / live` split answers “would a pass do anything right now?” — and it prints the resolved `source_root` and `corpus_root`, which is how a wrong-root problem becomes visible. [`packages/atif-cli/src/atif_cli/app.py:475-481`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L475-L481), [`packages/atif-cli/src/atif_cli/app.py:495-496`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L495-L496)
4. **Run `atif-sql schema` or `atif-sql examples`.** Both answer from the static catalog with no DuckDB connection, so they work on an unmaterialized corpus and separate “my SQL is wrong” from “the corpus is not there”. Every example is executed by the test suite against a fixture corpus, so a verbatim copy is known to parse. [`packages/atif-cli/src/atif_cli/app.py:1022-1026`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1022-L1026), [`packages/atif-cli/src/atif_cli/app.py:1099`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1099), [`packages/atif-cli/src/atif_cli/app.py:987-991`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L987-L991)
5. **Cross-check `status`’s session count against the view.** `atif-sql query 'SELECT count(*) FROM sessions'` applies the meta gate; `status` counts directories. A gap is the torn-dir count, and each torn dir also emits a WARNING naming itself. [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:173-177`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L173-L177)
6. **Run `atif-sql cron status`.** For anything that failed unattended: per lane it reports whether the flock is held and by which pid, the last completion with its exit code, the last skip, and a log tail. The lock probe acquires and releases nonblocking, so it perturbs no running lane. [`packages/atif-cli/src/atif_cli/cron.py:205-208`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L205-L208), [`packages/atif-cli/src/atif_cli/cron.py:226-245`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L226-L245)
7. **Look for a terminal marker file** beside the refresh log. Its presence is why an embed stopped being attempted; it holds the store path, the mtime it was keyed on, and the reason. Any operator touch that changes the store directory’s mtime clears it on the next tick. [`scripts/atif-sql-refresh.sh:217-236`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L217-L236)
8. **Query `state.db` directly** at `/analytics/state.db`. There is no CLI or SQL surface over it. `retry_queue` rows with `attempts >= 5` and `completed_at IS NULL` are permanently blocked and need manual clearing; `budget_skips` rows are the consecutive-skip streak. The pipeline name is `user_friction`. [`packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py:42-56`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py#L42-L56)
9. **Re-run the failing library call in-process with your own loguru sink** if the WARNING lines were not enough. This is the only way to see the DEBUG narration of the staging sweep, the mid-scan races, the atomic-write tmp cleanups, and every view and macro registration. [`packages/atif-cli/src/atif_cli/app.py:1133-1134`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1133-L1134)
10. **Only now re-run a billable command**, and bound it: `analyze` defaults to a dry run and needs `--no-dry-run` to spend, and `embed` refuses to run without `--limit N` or `--all`. `atif-sql search` always calls Bedrock, once, to embed the query. [`packages/atif-cli/src/atif_cli/app.py:743`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L743), [`packages/atif-cli/src/atif_cli/app.py:810`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L810), [`packages/atif-cli/src/atif_cli/app.py:943`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L943)
## Known incident patterns
[Section titled “Known incident patterns”](#known-incident-patterns)
* **`EmbeddingStoreSchemaStale` and the silent retry storm:** a store state no retry can clear once retried 400+ times across 3 days of 10-minute ticks with zero escalation, because every failure exited alike. Signal: an embed failing identically every tick with no operator ever paged. Mitigation: the terminal flag on the error, exit 78 as its own code, and a marker keyed on the store path plus its mtime that suppresses retries until an operator touch changes it. [`packages/atif-embed/src/atif_embed/domain/errors.py:14-26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/errors.py#L14-L26), [`scripts/atif-sql-refresh.sh:187-194`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L187-L194)
* **Destroy-and-rebuild on a missing column:** raising a rebuild-demanding error for a store that merely predates the `text_hash` stamp rebuilt both fleet corpora, roughly 3.4M vectors of Cohere spend. Signal: a full re-embed triggered by a schema read rather than by a provider change. Mitigation: additive drift now migrates online via `Table.add_columns` and a sentinel that re-embeds incrementally through the ordinary staleness path, so search stays online throughout. [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:344-351`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L344-L351), [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:166-192`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L166-L192)
* **Corpus-slug collapse:** setting only `ATIF_SQL_SOURCE_ROOT` points one corpus’s source at another root’s transcripts while the corpus root still slugs from `CLAUDE_CONFIG_DIR`, so two corpora collapse onto one slug (`projects-73b00a87`, verified live 2026-08-23). Signal: two config roots, one corpus directory, and sessions from both interleaved. Mitigation: every tick exports both `CLAUDE_CONFIG_DIR` and `ATIF_SQL_SOURCE_ROOT` per corpus. [`scripts/atif-sql-refresh.sh:52-59`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L52-L59), [`scripts/atif-sql-refresh.sh:312-313`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L312-L313)
* **`HarborPrivateApiMissing`:** the converter is built on a private harbor method, so an upstream rename makes every session fail for a reason unrelated to any transcript. Signal: a materialize pass where the failure count equals the session count. Mitigation: an explicit callable probe before any conversion raises a distinct error naming the installed harbor version, so the log line cannot be mistaken for one bad transcript among many. [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:112-139`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L112-L139)
* **`SourceMutatedDuringConversion`:** a session that resumes writing mid-conversion would yield a census, a trajectory, and an `edges.jsonl` each describing different bytes. Signal: one session failing with a `N source file(s) changed` message while its siblings succeed. Mitigation: a fingerprint snapshot taken before harbor reads and re-checked twice, refusing rather than publishing; retrying once the session goes quiet succeeds. [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:11-22`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L11-L22), [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:93-102`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L93-L102)
* **`SuspiciousEmptyScanError`:** a wrong `source_root` — a typo, an unmounted disk, a stale env var — makes the scan empty, and ghost collection would then delete the entire corpus. Signal: a traceback naming the error, with the scanned root and the materialized session count in the message, and nothing removed. Mitigation: the guard runs before ghost removal and refuses the pass; an operator who genuinely emptied the source tree deletes the corpus directory explicitly. [`packages/atif-corpus/src/atif_corpus/application/materialize.py:97-104`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L97-L104), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:553-559`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L553-L559)
* **Absence mistaken for deletion:** `Path.glob` swallows `PermissionError` and yields nothing, which would make every session under an unlistable project directory a ghost. Signal: a WARNING naming the directory count, and a pass that removed nothing. Mitigation: only `FileNotFoundError` on `stat` counts as gone; an unlistable directory disables ghost collection for the whole pass, and the affected session ids are resolved from the watermark so their entries are retained. [`packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py:83-95`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py#L83-L95), [`packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py:119-140`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py#L119-L140), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:560-571`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L560-L571)
* **Lane-lock inheritance through fd 9:** the refresh lane’s flock is held by an open descriptor, not by a pid, so a descendant that inherits it keeps the lane locked for as long as it lives — a lane that stops doing work while every log line still reads like a clean single-flight. Signal: `cron status` reporting a held lock with no corresponding work in the log. Mitigation: every `atif-sql` invocation in the script closes fd 9 with `9>&-`, while the parent keeps its own. [`scripts/atif-sql-refresh.sh:160-176`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L160-L176)
* **`AWS_PROFILE` reaching a Bedrock call:** an inherited profile name with no matching `~/.aws/config` entry makes botocore raise `FileNotFoundError` at credential resolution, killing the lane before it reaches Bedrock at all. Signal: an llm lane failing instantly with a credential-resolution error rather than a Bedrock one. Mitigation: the script unsets `AWS_PROFILE` and `AWS_DEFAULT_PROFILE`, and authenticates from a run-time-read bearer token or the default credential chain. [`scripts/atif-sql-refresh.sh:91-96`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L91-L96), [`scripts/atif-sql-refresh.sh:116-128`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L116-L128)
* **The seven `FidelityGap` members:** these are documented, verified upstream losses, not defects — workflow-nested subagents missed by harbor’s own discovery, non-message records dropped, the parent chain flattened by timestamp sort, subagents inlined rather than embedded, compaction summaries unhandled, the cache split only partially preserved, and the event uuid not preserved. Signal: `records_dropped > 0` or a non-empty `gaps_observed` in a `loss_report.json`. Mitigation: none is intended — the converter’s job is honest loss accounting, and unit tests pin each gap so a harbor bump that changes behavior trips the suite. [`packages/atif-converter/src/atif_converter/domain/fidelity.py:44-79`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L44-L79)
* **Budget overshoot and the streak that never escalates:** the cost ceiling stops dispatch at an 8-unit batch boundary rather than capping spend exactly, and the consecutive-skip streak is cleared whenever a stage ran at all — including a run that aborted mid-chunk on the ceiling. Signal: repeated `cost ceiling hit` WARNINGs that never become the ERROR line. Mitigation: read `llm_spent_usd` in the summary rather than trusting the streak, and treat a mid-stage abort as leaving nothing durable behind: no checkpoint, no cache row, no retry entry for the unstarted units. [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:141-152`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L141-L152), [`packages/atif-analytics/src/atif_analytics/application/analyze.py:164-198`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L164-L198)
* **Deterministic truncation and the one-rung ladder:** resending a `finish_reason=length` request with identical parameters truncates identically and doubles the bill, so the single retry degrades `reasoning_effort` instead. Signal: a `finish_reason=length at effort=high` WARNING followed by a retry queue entry. Mitigation: the degrade ladder — but note every spec resolves at `high`, so only `high` to `medium` is reachable in practice, and a second truncation raises `ProviderUnavailable` for the retry queue. Usage is accumulated before every gate, so a truncated call still counts against the budget. [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:79-99`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L79-L99), [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:229-249`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L229-L249), [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:277`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L277)
* **The `EmbeddingProviderMismatch` twin:** the class exists independently in two packages with different bases — a `DomainError` in atif-embed, a bare `Exception` in atif-duck — because the independence contract forbids the import. Signal: an unhandled traceback with exit 1 instead of the classified exit 65. Mitigation: `REGISTRATION_ERRORS` widens the caught tuple to cover both, and a test reads both twin modules as source text to require the shared recovery hint appear after the `raise` keyword in each. [`packages/atif-cli/src/atif_cli/duck_errors.py:14-31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L14-L31), [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:5-16`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L5-L16)
## See also
[Section titled “See also”](#see-also)
* [processes](../behavior/processes.md) — 29 shared source citations
* [module map](../architecture/module-map.md) — 24 shared source citations
* [business logic](business-logic.md) — 22 shared source citations
* [impact analysis](impact-analysis.md) — 21 shared source citations
* [contract map](contract-map.md) — 20 shared source citations
# Impact analysis
# atif-sql · Impact analysis
[Section titled “atif-sql · Impact analysis”](#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. Seven import-linter contracts forbid five of the seven packages from importing each other ([`pyproject.toml:514-517`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L514-L517)), so the surfaces that cost the most to change are exactly the ones with the *fewest* inbound imports — a corpus filename string has four independent readers and zero import edges. Ranking by import count would put `atif_analytics`’ 92 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. Three properties of this codebase make that confirmation step load-bearing rather than pedantic:
* **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:22-26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L22-L26)). A line-anchored `^from atif_` grep returns zero cross-package consumers. The census behind this file allowed leading whitespace and then read each enclosing scope.
* **Names collide across packages.** `DomainError` is declared independently in three packages and `EmbeddingProviderMismatch` in two — [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:33`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L33) derives from bare `Exception`, [`packages/atif-embed/src/atif_embed/domain/errors.py:29`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/errors.py#L29) from atif-embed’s own `DomainError`. Near-names compound it: [`packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py#L44) imports `EmbeddingProviderUnavailable`, which is not the `EmbeddingProvider` Protocol.
* **A Protocol implementation imports nothing.** Structural satisfaction leaves no edge in either direction, so the adapters are invisible to any import-graph query. See the Protocols section.
The internal graph is a star: `atif-cli` to its five declared siblings, plus exactly one `atif-analytics → atif-models` edge. atif-cli neither declares nor imports atif-models ([`packages/atif-cli/pyproject.toml:32-36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L32-L36) lists five, none of them atif-models). 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”](#the-static-duckdb-catalog)
Defined at: [`packages/atif-duck/src/atif_duck/domain/catalog.py:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L28)
Four catalogs (`VIEW_NAMES` 16, `MACRO_NAMES` 9, `ANALYTICS_VIEW_NAMES` 12, `ANALYTICS_MACRO_SIGNATURES` 13) plus `VIEW_SCHEMA`, `TABLE_MACRO_NAMES`, `DESCRIPTIONS`, and `DEFAULT_PRICING`. Adding one view or macro is a five-place edit, and each place has a gate.
| Downstream | Type | Touch on change | Citation |
| ------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `atif_duck.domain.examples` — derives every example from all four catalogs | direct import | yes | [`packages/atif-duck/src/atif_duck/domain/examples.py:35-43`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L35-L43) |
| `DESCRIPTIONS` — one entry per object; `_description()` raises `KeyError` without it | direct import | yes | [`packages/atif-duck/src/atif_duck/domain/catalog.py:326`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L326) and [`packages/atif-duck/src/atif_duck/domain/examples.py:166-175`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L166-L175) |
| `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:75-92`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L75-L92) and [`packages/atif-duck/src/atif_duck/domain/examples.py:148-158`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L148-L158) |
| `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:308-317`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L308-L317) |
| `atif_duck.infrastructure.registry` — owns the real DDL and imports `DEFAULT_PRICING` | direct import | yes | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:53`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L53) |
| `atif_duck.infrastructure.analytics` — `register_analytics` and `register_analytics_macros` own the DDL the two analytics catalogs describe | indirect | yes | [`packages/atif-duck/src/atif_duck/infrastructure/analytics.py:124`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/analytics.py#L124) and [`packages/atif-duck/src/atif_duck/infrastructure/analytics.py:221`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/analytics.py#L221) |
| `atif_cli.app.schema` — deferred import of `VIEW_SCHEMA` + `MACRO_SIGNATURES` | direct import | no | [`packages/atif-cli/src/atif_cli/app.py:1099`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1099) |
| `atif_cli.app.examples` — deferred import of `build_examples` + the two value tuples | direct import | no | [`packages/atif-cli/src/atif_cli/app.py:1022-1026`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1022-L1026) |
| `test_examples.py::test_every_example_executes` — parametrized over every derived example, `fetchall()` forces materialization | test | yes | [`packages/atif-duck/tests/test_examples.py:73-79`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_examples.py#L73-L79) |
| `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:160-211`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_examples.py#L160-L211) |
| `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:50-59`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L50-L59) |
| `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:66-87`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L66-L87) |
| `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:313-328`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_analytics_views.py#L313-L328) |
| `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:459-467`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L459-L467) |
| `test_app.py::TestSchema` / `TestExamples` — assert the CLI payload against the catalog, not literals | test | no | [`packages/atif-cli/tests/test_app.py:77-84`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_app.py#L77-L84) and [`packages/atif-cli/tests/test_app.py:94-111`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_app.py#L94-L111) |
### Blast-radius notes
[Section titled “Blast-radius notes”](#blast-radius-notes)
* **`EXCLUSIONS` is 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:99`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L99)), and a stale key — or a key that still emits an example — fails `test_exclusions_reference_real_catalog_objects` ([`packages/atif-duck/tests/test_examples.py:170-175`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_examples.py#L170-L175)).
* **`atif-sql schema` prints only the two CORE catalogs.** It reads `VIEW_SCHEMA` and `MACRO_SIGNATURES` and nothing else ([`packages/atif-cli/src/atif_cli/app.py:1099`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1099)), so a new analytics view appears in `atif-sql examples` output ([`packages/atif-duck/src/atif_duck/domain/examples.py:200-211`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L200-L211)) and never in `atif-sql schema` output. This asymmetry is pinned, not accidental: [`packages/atif-cli/tests/test_app.py:82`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_app.py#L82) asserts set equality against `VIEW_SCHEMA` alone.
* **Column ORDER in `VIEW_SCHEMA` is load-bearing.** The drift test asserts tuple equality against `DESCRIBE`, so reordering a `SELECT` list in the DDL without reordering the catalog entry fails CI ([`packages/atif-duck/src/atif_duck/domain/catalog.py:47-50`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L47-L50)).
## harbor’s private `_convert_events_to_trajectory`
[Section titled “harbor’s private \_convert\_events\_to\_trajectory”](#harbors-private-_convert_events_to_trajectory)
Defined at: [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L32)
The whole converter package is built on one private upstream method, pinned `harbor>=0.22.0,<0.23` ([`packages/atif-converter/pyproject.toml:23`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/pyproject.toml#L23)). Every `from harbor...` import in `src/` lives inside that one module by policy ([`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:11-12`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L11-L12), with the three sites at [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:103`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L103), [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:124`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L124), and [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:161`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L161)).
| Downstream | Type | Touch on change | Citation |
| ------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `assert_harbor_private_api()` — the preflight `callable(getattr(...))` check | direct import | yes | [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:112-137`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L112-L137) |
| `convert_session()` — the only caller of the private method | direct import | yes | [`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:161-182`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L161-L182) |
| `FidelityGap` — 7 members, each a behavior probed against 0.22.0 | indirect | yes | [`packages/atif-converter/src/atif_converter/domain/fidelity.py:44-79`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L44-L79) |
| `atif_converter.domain.enrichment` — replicates harbor’s dedup + timestamp-sort + `turn_by_msgid` bundling order to re-derive step identity | indirect | yes | [`packages/atif-converter/src/atif_converter/domain/enrichment.py:15-51`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L15-L51) |
| `atif_converter.application.convert_and_audit` — composes census + adapter, declares the always-present gap set | direct import | likely | [`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:39-51`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L39-L51) |
| `HarborPrivateApiMissing` — distinct from `ConversionError` so one bad transcript and a renamed method do not look alike in a log | direct import | likely | [`packages/atif-converter/src/atif_converter/domain/errors.py:47-54`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/errors.py#L47-L54) |
| `EXIT_CODES["harbor_missing"] = 127` | config | likely | [`packages/atif-cli/src/atif_cli/errors.py:38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L38) |
| `atif_cli.app.materialize` — stamps `_version_of("harbor")` into every `meta.json` | direct import | likely | [`packages/atif-cli/src/atif_cli/app.py:401`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L401) |
| `atif_duck.infrastructure.registry` — SQL comments encode gap 6 and gap 7 as facts the DDL depends on | indirect | likely | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:348`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L348) and [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:427-428`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L427-L428) |
| `atif_duck.infrastructure.analytics` — treats `root_session_id = session_id` because harbor inlines subagents (gap 4) | indirect | likely | [`packages/atif-duck/src/atif_duck/infrastructure/analytics.py:405`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/analytics.py#L405) |
| `test_convert_and_audit.py` — the drift alarm; the gap-1 test calls the private method DIRECTLY | test | yes | [`packages/atif-converter/tests/test_convert_and_audit.py:136-159`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_convert_and_audit.py#L136-L159) |
| `test_snapshot_and_drift.py` — `monkeypatch.delattr` on the private method, three scenarios | test | yes | [`packages/atif-converter/tests/test_snapshot_and_drift.py:301-329`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_snapshot_and_drift.py#L301-L329) |
| `test_lean_import.py` — `harbor` is on the forbidden-eager-import list | test | no | [`packages/atif-cli/tests/test_lean_import.py:19`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_lean_import.py#L19) |
### Blast-radius notes
[Section titled “Blast-radius notes”](#blast-radius-notes-1)
* **A harbor bump that FIXES a gap fails the suite on purpose.** The gap-1 canary asserts harbor’s own `rglob("subagents/*.jsonl")` still misses workflow-nested files and fails with “harbor now discovers workflow-nested subagent files — gap 1 closed” ([`packages/atif-converter/tests/test_convert_and_audit.py:159`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_convert_and_audit.py#L159)); the gap-7 canary does the same for event-uuid preservation ([`packages/atif-converter/tests/test_convert_and_audit.py:104`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/tests/test_convert_and_audit.py#L104)). Read a red converter suite after a bump as upstream drift to re-verify, not as a regression to patch around.
* **harbor ships no `py.typed`.** Every import from it carries `# type: ignore[import-untyped]`, and the quarantine to one module is what keeps the untyped surface from spreading past the adapter ([`packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py:11-12`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/infrastructure/harbor_adapter.py#L11-L12)).
* **63 of the 113 runtime packages reach this project only through harbor** — `fastapi`, `uvicorn`, `starlette`, `litellm`, `openai`, `tiktoken` — for this one private call: 57% of the roster and 155 MiB ([`RELEASING.md:215-222`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/RELEASING.md#L215-L222)). None of them is imported by this code. Retargeting this method is therefore also the only lever on install weight, and it is what pins the Python floor at 3.13 ([`pyproject.toml:124`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L124)).
## The materialized corpus artifact layout
[Section titled “The materialized corpus artifact layout”](#the-materialized-corpus-artifact-layout)
Defined at: [`packages/atif-corpus/src/atif_corpus/domain/layout.py:18-22`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/layout.py#L18-L22), specified by [`docs/CONTRACT.md:21-31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L21-L31)
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 four 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:75-76`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L75-L76) and [`packages/atif-corpus/src/atif_corpus/application/materialize.py:530`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L530) |
| `atif_duck.infrastructure.registry` — builds four globs under `corpus_root / "sessions"` | indirect | yes | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:209-213`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L209-L213) |
| `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:112-123`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L112-L123) |
| `atif_analytics.infrastructure.corpus_reader` — re-declares three filename constants | indirect | yes | [`packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:53-55`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py#L53-L55) and [`packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:163-164`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py#L163-L164) |
| `atif_cli.app.convert` — writes `edges.jsonl` beside a `--out` trajectory on the one-shot path | indirect | likely | [`packages/atif-cli/src/atif_cli/app.py:276`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L276) |
| `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:460-470`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L460-L470) |
| [`docs/CONTRACT.md:21-31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L21-L31) — the specification; the file declares itself orchestrator-owned and changeable only via itself | config | yes | [`docs/CONTRACT.md:3`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L3) |
| `test_domain.py::TestCorpusLayout.test_contract_paths` — pins all five contract strings | test | yes | [`packages/atif-corpus/tests/test_domain.py:245-249`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_domain.py#L245-L249) |
| Four independent fixture-corpus builders, one per consuming package | test | yes | [`packages/atif-duck/tests/duck_fixtures.py:482`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/duck_fixtures.py#L482), [`packages/atif-embed/tests/embed_fixtures.py:120`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/embed_fixtures.py#L120), [`packages/atif-analytics/tests/analytics_fixtures.py:218`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/analytics_fixtures.py#L218), [`packages/atif-corpus/tests/corpus_fixtures.py:77`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/corpus_fixtures.py#L77) |
### Blast-radius notes
[Section titled “Blast-radius notes”](#blast-radius-notes-2)
* **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 duplicated verbatim in all three readers — evidence of the duplication cost, and three places to edit if the completion marker ever moves ([`packages/atif-duck/src/atif_duck/infrastructure/registry.py:188`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L188), [`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L28), [`packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:14`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py#L14)).
* **`.staging` sits outside `sessions/` for a reader-visibility reason, not tidiness.** DuckDB’s `read_json` glob matches dot-dirs, so moving staging under `sessions/` exposes half-written session dirs to every reader and to the ghost-removal walk ([`packages/atif-corpus/src/atif_corpus/domain/layout.py:47-55`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/layout.py#L47-L55)).
* **`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 the `loss_reports` view with no type error anywhere ([`packages/atif-converter/src/atif_converter/domain/fidelity.py:114-137`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L114-L137), [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:122`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L122)).
## The five domain Protocols
[Section titled “The five domain Protocols”](#the-five-domain-protocols)
Defined at: [`packages/atif-corpus/src/atif_corpus/domain/ports.py:41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L41) (`ConverterPort`), [`packages/atif-embed/src/atif_embed/domain/ports.py:31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L31), [`packages/atif-embed/src/atif_embed/domain/ports.py:59`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L59), [`packages/atif-embed/src/atif_embed/domain/ports.py:87`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L87) (`EmbeddingProvider`, `VectorStorePort`, `TextRowsPort`), [`packages/atif-models/src/atif_models/domain/ports.py:116`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L116) (`LlmStructuredProvider`)
Five Protocols, all under `domain/`, all satisfied structurally — there is no `abstractmethod` and no DI container in this workspace. Two 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 other three are ordinary seams — atif-analytics is permitted to import atif-models, and the two atif-embed adapters live in the same package as their ports.
**Every adapter satisfies its Protocol without importing it.** 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` ← six atif-analytics modules, every one importing it under `if TYPE_CHECKING:` | direct import | yes | [`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L28), [`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:55`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L55), [`packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py:73`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/conflicts.py#L73), [`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:76`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L76), [`packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:87`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py#L87), [`packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py:79`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/trajectory.py#L79) |
| `OpenAiBedrockProvider` — the one real adapter; 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:49-55`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L49-L55) and [`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:143`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L143) |
| `FakeProvider` — the deterministic double, named in prose only | test | yes | [`packages/atif-analytics/tests/analytics_fixtures.py:237`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/tests/analytics_fixtures.py#L237) |
| `ConverterPort` ← `materialize` use case, three signature positions | direct import | yes | [`packages/atif-corpus/src/atif_corpus/application/materialize.py:92`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L92), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:193`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L193), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:480`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L480) |
| `RealConverter` — the production adapter; imports `ConversionOutput` only, names `ConverterPort` in its docstring | indirect | yes | [`packages/atif-cli/src/atif_cli/converter_adapter.py:38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L38) and [`packages/atif-cli/src/atif_cli/converter_adapter.py:44-45`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L44-L45) |
| `FakeConverter` — shipped in `src/` rather than `tests/`; same shape, same non-import | indirect | yes | [`packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py:16`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py#L16) and [`packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py:37`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py#L37) |
| `ConversionOutput` — the three-field frozen dataclass that DOES cross the boundary as an import | direct import | yes | [`packages/atif-corpus/src/atif_corpus/domain/ports.py:21-38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L21-L38) |
| `EmbeddingProvider` / `VectorStorePort` / `TextRowsPort` ← the one embed use case, under `if TYPE_CHECKING:` | direct import | yes | [`packages/atif-embed/src/atif_embed/application/embed.py:35`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L35), [`packages/atif-embed/src/atif_embed/application/embed.py:46-47`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L46-L47), [`packages/atif-embed/src/atif_embed/application/embed.py:65-67`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L65-L67) |
| Three production adapters, one per embed port, none importing its Protocol | indirect | yes | [`packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py:285`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/cohere_bedrock.py#L285), [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:377`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L377), [`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:156`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L156) |
| `test_converter_adapter.py` — the one place a Protocol name is imported and bound to an implementation | test | yes | [`packages/atif-cli/tests/test_converter_adapter.py:17`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_converter_adapter.py#L17) and [`packages/atif-cli/tests/test_converter_adapter.py:91`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_converter_adapter.py#L91) |
| `[tool.coverage.report] exclude_also` — a bare `...` line is excluded because a Protocol body is a signature | config | no | [`pyproject.toml:561-564`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L561-L564) |
### Blast-radius notes
[Section titled “Blast-radius notes”](#blast-radius-notes-3)
* **Adding a method to a Protocol breaks implementations that name it nowhere.** Because no adapter imports its Protocol, the only mechanical signal is a `pyright`/`ty` error where the implementation is passed into a port-typed parameter — and for `RealConverter` that call site is in a different package from both the Protocol and the class ([`packages/atif-cli/src/atif_cli/converter_adapter.py:44`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/converter_adapter.py#L44), [`packages/atif-corpus/src/atif_corpus/application/materialize.py:193`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L193)). `FakeConverter` lives in `src/`, so a missed update fails the type gate rather than only a test ([`packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py:37`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py#L37)).
* **`ConverterPort` deliberately 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:41-47`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L41-L47)).
* **`EmbeddingProvider.embed_documents` returns one slot per input, `None` for 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:44-52`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L44-L52)).
## The seven import-linter contracts
[Section titled “The seven import-linter contracts”](#the-seven-import-linter-contracts)
Defined at: [`pyproject.toml:462-523`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L462-L523)
Five `layers` contracts, one `independence` contract over converter / corpus / duck / models / embed, one `forbidden` contract pinning atif-analytics to atif-models alone. These are the reason four 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`, entry 5 of the 9 in `[tasks.check].depends` | config | yes | [`mise.toml:162`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L162) and [`mise.toml:204`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L204) |
| `root_packages` — all seven import packages must be listed, or a new member goes unchecked | config | yes | [`pyproject.toml:463`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L463) |
| `atif_cli` — the composition root, absent from `independence.modules` and listed in `forbidden_modules`; it declares five siblings and NOT atif-models, so the comment granting it that permission describes an unused allowance | indirect | likely | [`pyproject.toml:517`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L517), [`pyproject.toml:523`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L523), [`packages/atif-cli/pyproject.toml:32-36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L32-L36) |
| `ConverterPort` — exists because atif-corpus may not import atif-converter | indirect | yes | [`packages/atif-corpus/src/atif_corpus/domain/ports.py:5-9`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L5-L9) |
| `TextRowsPort` — exists because atif-embed may not import atif-duck | indirect | yes | [`packages/atif-embed/src/atif_embed/domain/ports.py:11-15`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L11-L15) |
| The `embedding_guard` twins — one rule, two copies, in atif-duck and atif-embed | direct import | yes | [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:33`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L33) and [`packages/atif-embed/src/atif_embed/domain/embedding_guard.py:38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/embedding_guard.py#L38) |
| `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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_guard_twin_pin.py#L41) and [`packages/atif-embed/tests/test_guard_twin_pin.py:55-72`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_guard_twin_pin.py#L55-L72) |
| The three independent corpus readers | indirect | yes | [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:209`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L209), [`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:113`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L113), [`packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py:53`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/corpus_reader.py#L53) |
| `[tool.coverage.run] source` — the same seven import packages, named rather than pathed | config | likely | [`pyproject.toml:537-545`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L537-L545) |
### Blast-radius notes
[Section titled “Blast-radius notes”](#blast-radius-notes-4)
* **A new cross-package import fails gate 5 of 9 and nothing else.** `lint:imports` runs `uv run lint-imports` over `packages/*/src/**/*.py` plus `pyproject.toml` ([`mise.toml:161-162`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L161-L162)) — 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:517`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L517)).
* **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: two 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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_guard_twin_pin.py#L3-L14)).
## `EXIT_CODES` — the CLI wire contract
[Section titled “EXIT\_CODES — the CLI wire contract”](#exit_codes--the-cli-wire-contract)
Defined at: [`packages/atif-cli/src/atif_cli/errors.py:25-39`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L25-L39)
Eleven keys mapping to seven distinct codes (0, 2, 64, 65, 70, 78, 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:38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L38) |
| `atif_cli.app.convert` — four exits: `invalid_input`, `empty_session`, `runtime_error`, `validation_error` | direct import | yes | [`packages/atif-cli/src/atif_cli/app.py:264-304`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L264-L304) |
| `atif_cli.app.query` — `parse_error`, raised after the classified envelope is emitted | direct import | yes | [`packages/atif-cli/src/atif_cli/app.py:603-610`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L603-L610) |
| `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:814-847`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L814-L847) |
| `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:935-941`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L935-L941) |
| `atif_cli.app.examples` — `invalid_input` on an unknown `--category` / `--requires` value | direct import | yes | [`packages/atif-cli/src/atif_cli/app.py:1036-1042`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1036-L1042) |
| `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:25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L25) and [`packages/atif-cli/src/atif_cli/duck_errors.py:40-86`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L40-L86) |
| `atif_cli.cron` — the unattended lane, which suppresses retries on code 78 | direct import | likely | [`packages/atif-cli/src/atif_cli/cron.py:32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L32) and [`packages/atif-cli/src/atif_cli/cron.py:175`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L175) |
| `ClassifiedError.to_payload()` — the JSON error envelope on non-TTY stderr | indirect | likely | [`packages/atif-cli/src/atif_cli/errors.py:60-68`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L60-L68) |
| `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:17`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_app.py#L17) and [`packages/atif-cli/tests/test_app.py:744`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_app.py#L744) |
| `test_cron.py`, `test_integration.py`, `test_vss_commands.py` — three more suites keyed off the dict | test | yes | [`packages/atif-cli/tests/test_cron.py:27`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_cron.py#L27), [`packages/atif-cli/tests/test_integration.py:99`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_integration.py#L99), [`packages/atif-cli/tests/test_vss_commands.py:20`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_vss_commands.py#L20) |
| [`docs/CONTRACT.md:62-66`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L62-L66) §CLI — the documented command surface these codes are raised from | config | likely | [`docs/CONTRACT.md:62-66`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L62-L66) |
### Blast-radius notes
[Section titled “Blast-radius notes”](#blast-radius-notes-5)
* **Two keys share code 2 and three share 65 on purpose.** `empty_session` and `no_embeddings` both exit 2; `catalog_error`, `validation_error`, and `embedding_mismatch` all exit 65. Renaming a key is safe for callers reading numbers and breaks every internal `EXIT_CODES["..."]` lookup, which is the opposite of the usual polarity ([`packages/atif-cli/src/atif_cli/errors.py:26-38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L26-L38)).
* **Exit 78 is the only code with retry semantics attached.** `terminal_state` means 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:35-37`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L35-L37), [`packages/atif-cli/tests/test_app.py:744`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_app.py#L744)).
* **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 two tests assert that a lazily-bound Lance scan tripping mid-stream still classifies instead of exiting 1 ([`packages/atif-cli/src/atif_cli/duck_errors.py:15-17`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L15-L17), [`packages/atif-cli/tests/test_app.py:657`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_app.py#L657) and [`packages/atif-cli/tests/test_app.py:1039`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_app.py#L1039)).
## The published `atif-sql` distribution
[Section titled “The published atif-sql distribution”](#the-published-atif-sql-distribution)
Defined at: [`pyproject.toml:108-109`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L108-L109)
One distribution carries every capability, as a single wheel bundling all seven module trees. 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:132-152`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L132-L152) |
| `[tool.hatch.build.targets.wheel] packages` — the seven module trees the wheel carries; an omission ships a wheel missing a module | config | yes | [`pyproject.toml:173-182`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L173-L182) |
| `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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_distribution.py#L3-L22) |
| Five dev pins in the CLI manifest and one in the analytics manifest, resolved locally through `[tool.uv.sources]` and never published | config | likely | [`packages/atif-cli/pyproject.toml:32-36`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L32-L36), [`packages/atif-analytics/pyproject.toml:24`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L24) |
| `[tool.commitizen] version_files` — four glob:regex entries rewriting the published version, all seven manifests, and both dev pin blocks | config | yes | [`pyproject.toml:446-451`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L446-L451) |
| `[tool.commitizen] version` — the single source of truth, because `version_provider` defaults to reading it | config | yes | [`pyproject.toml:403-406`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L403-L406) |
| `cz bump --check-consistency` in `release.yml` — fails when the current version is absent from any listed file | config | yes | [`.github/workflows/release.yml:193`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/.github/workflows/release.yml#L193) |
| `pre_bump_hooks` — `uv lock` then `git add uv.lock`, so the re-resolved lockfile lands in the bump commit | config | yes | [`pyproject.toml:428-431`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L428-L431) |
| `uv lock --check` — enforced as a lefthook pre-commit step and as a CI step | config | likely | [`mise.toml:84`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L84), [`lefthook.yml:95`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/lefthook.yml#L95), [`.github/workflows/check.yml:51`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/.github/workflows/check.yml#L51) |
| The console script `atif-sql = "atif_cli.app:main"` | config | likely | [`packages/atif-cli/pyproject.toml:42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L42) |
| `tag_format = "v$version"` — `publish.yml` strips the leading `v` and `gh release create --verify-tag` fails if the formats diverge | config | likely | [`pyproject.toml:411`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L411) |
### Blast-radius notes
[Section titled “Blast-radius notes”](#blast-radius-notes-6)
* **The pin entries are per-file rather than globbed because `--check-consistency` requires a hit in every matched file**, and five of the seven 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:432-451`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L432-L451)).
* **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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_distribution.py#L92)).
* **`major_version_zero = true`, so a breaking change moves 0.1.0 to 0.2.0.** Reaching 1.0.0 is a decision, not a side effect of a `!` in a commit subject ([`pyproject.toml:412-414`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L412-L414)).
## The lean import path of `atif_cli.app`
[Section titled “The lean import path of atif\_cli.app”](#the-lean-import-path-of-atif_cliapp)
Defined at: [`packages/atif-cli/src/atif_cli/app.py:22-26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L22-L26)
A bare `import atif_cli.app` must not pull duckdb, harbor, lancedb, boto3, polars, or umap. The fast path — `schema`, `examples`, `--help`, `--version` — needs none of them, and each costs hundreds of milliseconds (lancedb alone \~2.6 s). Every heavy import is deferred into the command body that uses it.
| Downstream | Type | Touch on change | Citation |
| --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `test_lean_import.py` — an 11-module forbidden list checked in a FRESH interpreter via `subprocess` | test | yes | [`packages/atif-cli/tests/test_lean_import.py:17-32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_lean_import.py#L17-L32) and [`packages/atif-cli/tests/test_lean_import.py:43-49`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_lean_import.py#L43-L49) |
| ruff `PLC0415` (import-outside-top-level) ignored workspace-wide, 182 measured sites | config | yes | [`pyproject.toml:164`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L164) |
| `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:13-15`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L13-L15) |
| `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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L26) and [`packages/atif-cli/src/atif_cli/duck_errors.py:31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/duck_errors.py#L31) |
| `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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L26-L27) |
| Nine 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:615`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L615), [`packages/atif-cli/src/atif_cli/app.py:906`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L906), [`packages/atif-cli/src/atif_cli/app.py:1099`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1099) |
| `atif_cli.cron` — imported at module scope, so it must itself stay lean | direct import | yes | [`packages/atif-cli/src/atif_cli/app.py:37`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L37) |
| `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:33`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L33) |
### Blast-radius notes
[Section titled “Blast-radius notes”](#blast-radius-notes-7)
* **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 12 workspace-wide ruff ignores and carries its measured site count inline ([`pyproject.toml:164`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L164)).
* **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`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_lean_import.py#L21)).
* **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-32`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_lean_import.py#L17-L32)).
## Other notable surfaces
[Section titled “Other notable surfaces”](#other-notable-surfaces)
* **`DEFAULT_PRICING`** ([`packages/atif-duck/src/atif_duck/domain/catalog.py:405`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L405)) — 11 models with published list rates. A model absent here lands in `cost_estimate`’s `unpriced_steps` rather than being dropped, and two tests pin the table: an exact oracle ([`packages/atif-duck/tests/test_duck_views.py:459`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L459)) and a corpus-coverage gate ([`packages/atif-duck/tests/test_duck_views.py:533`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/tests/test_duck_views.py#L533)).
* **The model alias registry** ([`packages/atif-models/src/atif_models/domain/registry.py:60`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L60)) — six `(family, size)` entries resolved through `resolve()` at [`packages/atif-models/src/atif_models/domain/registry.py:109`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L109). `ModelSpec` reaches five atif-analytics pipelines through the shared provider builder ([`packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:34`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py#L34)), and `spec.model_id` is written into every pipeline’s output row, so a re-alias changes parquet content ([`packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py:335`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/classify.py#L335)).
* **`RECOVERY_HINT`** ([`packages/atif-embed/src/atif_embed/domain/embedding_guard.py:23`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/embedding_guard.py#L23) and its atif-duck twin) — one operator instruction, four 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:97-106`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_guard_twin_pin.py#L97-L106)).
* **The 15 `# noqa: S608` sites** — `S608` is enforced, and each suppression carries its static-catalog reason inline; two 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:144`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L144), [`packages/atif-duck/src/atif_duck/domain/examples.py:161`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L161), [`pyproject.toml:211-212`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L211-L212)).
* **Watermark and quiescence** ([`packages/atif-corpus/src/atif_corpus/domain/watermark.py:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/watermark.py#L28)) — the freshness rule that decides what `materialize` re-converts, surfaced to `status` through the producer’s public `read_watermark` ([`packages/atif-cli/src/atif_cli/app.py:460`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L460)) rather than a private reader.
## See also
[Section titled “See also”](#see-also)
* [contract map](contract-map.md) — 49 shared source citations
* [module map](../architecture/module-map.md) — 38 shared source citations
* [processes](../behavior/processes.md) — 35 shared source citations
* [business logic](business-logic.md) — 34 shared source citations
* [tech debt](tech-debt.md) — 24 shared source citations
# Tech debt
# atif-sql · Tech debt
[Section titled “atif-sql · Tech debt”](#atif-sql--tech-debt)
**There are zero `TODO`, `FIXME`, `HACK`, and `XXX` markers in this repository.** Three scans confirm it: over `packages/*/src`, over `packages/` plus `scripts/`, and over every file `git ls-files` names. That means the marker channel carries no signal here. It does not mean the codebase is debt-free, and this page exists because the debt is real and lives elsewhere.
So the register below is assembled from five sources rather than from comments. Every citation on this page carries its full path, so no reference resolves through a nearby antecedent.
1. **The suppression config, read as the debt register someone already wrote.** `select = ["ALL"]` with exactly 12 ignores ([`pyproject.toml:153-184`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L153-L184)), four pyright rules switched off ([`pyproject.toml:668-679`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L668-L679)), ty at `all = "error"` with no rule disabled ([`pyproject.toml:370-371`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L370-L371)), and per-site `# noqa` / `# pyright: ignore` counts taken by grep. Almost every config entry carries its own measured count and its cost-of-removal analysis inline, which is a better debt record than a comment marker and is quoted here rather than re-derived.
2. **Version ceilings and the reason attached to each**, read at the pin in the member manifests, not inferred from the lockfile.
3. **Install weight and platform coverage**, computed independently from `uv.lock` by walking the `atif-sql` dependency graph twice — once whole, once with `harbor` blocked — following extras as well as direct dependencies.
4. **Asymmetric enforcement between sibling surfaces**: a coverage floor enforced in CI but not in the declared definition of done, so a local green run does not measure it.
5. **Coverage and test-shape gaps**, per module. Coverage figures are measured from the generated report, which is gitignored, so every figure is cited to the source module it describes.
Category and cost vocabularies are closed. Categories: `marker`, `wrong abstraction`, `error handling`, `dead code adjacent`, `deprecated pattern`, `version pin`, `duplicated logic`, `missing tests`. Cost: `S`, `M`, `L`. No row carries the `marker` category, for the reason in the first paragraph. Rank is `cost-to-fix × consequence-of-leaving`, so a cheap fix for a harmless problem sinks and an expensive fix for a structural problem rises.
## Ranked register
[Section titled “Ranked register”](#ranked-register)
| Rank | Debt item | Category | Cost to fix | Citation |
| ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1 | The entire Claude Code → ATIF conversion path runs through one PRIVATE upstream method, `ClaudeCode._convert_events_to_trajectory`, verified against harbor 0.22.0 only — so the manifest carries a minor-version ceiling and the unit tests are the drift alarm. Nothing upstream promises the method exists in 0.23. | `version pin` | L | [`packages/atif-converter/pyproject.toml:19-23`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/pyproject.toml#L19-L23) |
| 2 | Seven upstream conversion losses are catalogued as `FidelityGap` enum members rather than fixed. Gap 3 flattens the `parentUuid` tree by timestamp sort, losing branch and rewind structure; gap 7 drops the event `uuid`, making step-to-raw-record identity unrecoverable from the trajectory — recovered out of band through a parallel `edges.jsonl` sidecar the corpus layout mandates. | `wrong abstraction` | L | [`packages/atif-converter/src/atif_converter/domain/fidelity.py:44-79`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L44-L79), [`docs/CONTRACT.md:26-28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L26-L28) |
| 3 | 63 of the 113 runtime distributions reach this project only through `harbor` — `fastapi`, `uvicorn`, `starlette`, the whole `supabase` client stack, `litellm`, `openai`, `tiktoken`, `tokenizers`, `huggingface-hub`, `cryptography`, `aiohttp` — for exactly one private method call. A CLI that converts JSONL ships a web server and a database client. 57% of the roster, 155 MiB. | `version pin` | L | [`RELEASING.md:215-222`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/RELEASING.md#L215-L222) |
| 4 | 415 pyright findings sit behind three disabled Unknown-propagation rules, and the config names the fix it has not built: a `TypedDict` model of the `~/.claude` JSONL record and the ATIF trajectory would close 144 of them. Until that model exists, every JSON-shaped value in the hottest modules is `dict[str, Any]` narrowed by `isinstance`, with the type checker’s opinion switched off. The two heaviest concentrations are the converter’s enrichment module at 59 findings and the analytics corpus reader at 33. | `wrong abstraction` | L | [`pyproject.toml:641-671`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L641-L671) |
| 5 | 15 SQL statements are built by string interpolation through a hand-rolled quote-doubling escape, because DuckDB rejects prepared parameters as table-function arguments. ruff’s `# noqa: S608` silences ruff only — bandit reports the same 15 as B608 and those findings upload to code scanning, so the suppression is not portable across the two scanners that both implement the rule. | `deprecated pattern` | L | [`pyproject.toml:737-739`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L737-L739), [`packages/atif-duck/src/atif_duck/infrastructure/registry.py:172`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L172), [`packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py:108`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/corpus_text_rows.py#L108) |
| 6 | No test reads a real `~/.claude` corpus. The whole integration tier is two tests over two synthetic 4-event sessions, and parity oracles against the predecessor implementation are explicitly not part of this repo. The seven fidelity gaps are policy about real-world JSONL shapes that nothing real-world exercises. | `missing tests` | L | [`packages/atif-cli/tests/test_integration.py:3-9`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_integration.py#L3-L9), [`packages/atif-cli/tests/test_integration.py:24-34`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_integration.py#L24-L34), [`AGENTS.md:92-94`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/AGENTS.md#L92-L94) |
| 7 | `hdbscan 0.8.44` has never published an aarch64 wheel, so a first install on Graviton, an ARM CI runner, or a `linux/arm64` container compiles five Cython extensions and needs a C toolchain. musl is not merely slow but impossible: `lancedb 0.37.1` publishes no sdist at all, so there is nothing to build from. | `version pin` | L | [`RELEASING.md:203-213`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/RELEASING.md#L203-L213) |
| 8 | The four size ratchets and the complexity ratchet are set at the measured worst function in the tree — `max-statements = 119` for `_friction_async`, `max-complexity = 38` for `enrich_trajectory`, `max-args = 19` for the `search` command. The ratchet stops growth and permanently blesses the current outliers; nothing in the config plans their reduction. | `wrong abstraction` | L | [`pyproject.toml:258-281`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L258-L281), [`packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:240`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py#L240), [`packages/atif-converter/src/atif_converter/domain/enrichment.py:198`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/enrichment.py#L198) |
| 9 | `TC001`/`TC002`/`TC003` are ignored on the strength of a manual probe: moving 58 imports under `TYPE_CHECKING` broke `typing.get_type_hints` on 159 first-party functions and classes. The 182-site `PLC0415` ignore beside it has a regression test proving its property in a fresh interpreter; the `get_type_hints` property has none, so the argument that justifies the ignore is not locked. | `missing tests` | M | [`pyproject.toml:170-183`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L170-L183), [`packages/atif-cli/tests/test_lean_import.py:35`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_lean_import.py#L35) |
| 10 | Every one of the nine security scanners has its findings exit code swallowed at eight sites, by a stated contract: findings do not fail the job, only a scanner that produced no usable report does. Gating is delegated entirely to GitHub code-scanning alerts and branch protection, so nothing inside the repo fails on a new vulnerability. | `error handling` | M | [`mise.toml:217-223`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L217-L223), [`mise.toml:366`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L366), [`mise.toml:482`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L482) |
| 11 | The provider/dimension guard exists as two deliberate 83-line twins because the independence contract forbids either package importing the other. The drift pin reads the atif-duck twin as SOURCE TEXT, parses it with `ast`, and asserts substrings — including a raw `source.index("raise EmbeddingProviderMismatch")` that silently changes meaning if the raise site is renamed or a second raise appears. The two docstrings have already diverged; the pin covers the recovery string and the interpolation, not the rule. | `duplicated logic` | M | [`packages/atif-embed/tests/test_guard_twin_pin.py:3-13`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_guard_twin_pin.py#L3-L13), [`packages/atif-embed/tests/test_guard_twin_pin.py:93-95`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_guard_twin_pin.py#L93-L95), [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:5-16`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L5-L16) |
| 12 | The clustering path is the least-tested code in the tree and it is also the least portable: the structural clustering module at 22% (23 of 30 lines unexecuted), the Lance reader at 31%, the cluster use case at 51% — against an 89.98% whole-tree combined figure. This is exactly the UMAP/HDBSCAN code behind the aarch64 wheel gap. | `missing tests` | M | [`packages/atif-analytics/src/atif_analytics/domain/structure/cluster.py:88`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/cluster.py#L88), [`packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py:38-49`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py#L38-L49), [`packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py:50`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/use_cases/cluster.py#L50) |
| 13 | Both binding contract documents disclaim describing the code. CONTRACT-V2 states outright that when it disagrees with the code, the code wins and the file is design intent “not a description of current behavior”; CONTRACT v1 calls its own scope section historical. A binding document that pre-emptively surrenders authority cannot be used to detect drift, which is the only thing a contract document is for. *judgment-call* — the disclaimers are honest, and honesty about staleness is still staleness. | `dead code adjacent` | M | [`docs/CONTRACT-V2.md:3-8`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT-V2.md#L3-L8), [`docs/CONTRACT.md:5-8`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L5-L8) |
| 14 | One version constraint is unexplained where every other pin in the repo justifies itself: `lancedb>=0.30,<0.38` is declared twice, in two members, with no comment on either side. A single distribution declares one constraint per package, so `test_distribution.py` now fails on two members that disagree — but it cannot ask why a constraint exists, which is what a comment is for. | `version pin` | S | [`packages/atif-analytics/pyproject.toml:27`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/pyproject.toml#L27), [`packages/atif-embed/pyproject.toml:20-22`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/pyproject.toml#L20-L22) |
| 15 | `FakeConverter` — a scriptable test double — ships inside the installable `atif-corpus` wheel under `infrastructure/`, and it is the converter the end-to-end materialize suite runs against. A fake that both mutates and reads corpus state stands in for the real harbor adapter in exactly the tests that would catch a state-transition bug. | `wrong abstraction` | S | [`packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py:3-9`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py#L3-L9), [`packages/atif-corpus/tests/test_materialize.py:25`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/tests/test_materialize.py#L25) |
| 16 | Three independently declared `DomainError(Exception)` bases with the same name and no shared ancestor, so a caller composing two members cannot write one `except` clause and any symbol-name search cross-attributes all three. | `duplicated logic` | S | [`packages/atif-converter/src/atif_converter/domain/errors.py:14`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/errors.py#L14), [`packages/atif-embed/src/atif_embed/domain/errors.py:14`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/errors.py#L14), [`packages/atif-models/src/atif_models/domain/ports.py:39`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L39) |
| 17 | The 89% coverage floor is not part of the declared definition of done. `mise run check` depends on nine gates and `test:cov` is not among them, so a local green run can drop coverage below the floor and only the CI job notices. | `missing tests` | S | [`mise.toml:197-212`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L197-L212), [`pyproject.toml:560`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L560), [`.github/workflows/check.yml:78`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/.github/workflows/check.yml#L78) |
## Explicit markers
[Section titled “Explicit markers”](#explicit-markers)
**None.** This section is empty because the repository contains no comment markers, not because the search was narrow.
* Scanned `packages/*/src` for `\bTODO\b`, `\bFIXME\b`, `\bHACK\b`, `\bXXX\b`: 0 matches.
* Scanned `packages/` plus `scripts/` with the same pattern: 0 matches.
* Scanned every file `git ls-files` reports: 0 matches.
* No `@deprecated` decorator and no `# DEPRECATED` banner sits on any first-party symbol. Every `deprecat*` hit in the tree describes an UPSTREAM deprecation, and the two members reading the LanceDB store name the same one independently — `db.table_names()` at [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:90`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L90) and at [`packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py:43`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py#L43), because the independence contract means neither can read the other’s warning. The other two are a lancedb kwarg family at [`packages/atif-embed/src/atif_embed/infrastructure/lance_store.py:274`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/lance_store.py#L274) and an osv-scanner flag at [`mise.toml:362`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L362).
* The closest thing to a marker is [`RELEASING.md:215`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/RELEASING.md#L215), which names the harbor dependency subtree “the standing follow-up” in prose — a declined-scope note in the release record rather than a comment in code. It is register row 3.
The absence is enforced, not incidental. `select = ["ALL"]` at [`pyproject.toml:146`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L146) leaves flake8-fixme and flake8-todos on, and neither appears among the 12 ignores at [`pyproject.toml:153-184`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L153-L184). Probed 2026-08-28 by dropping one `# TODO: ...` comment into `packages/atif-duck/src/`: `ruff check` exits 2 with `TD002` (missing author), `TD003` (missing issue link), and `FIX002` (line contains TODO). A marker cannot reach `main` past `mise run lint`, so the debt lives in the five channels the register draws on instead of in markers. A sixth channel exists and contributes no row: a comment explaining the code by reference to an implementation no installer of this package can obtain. `grep -riE 'claude-sql|predecessor' packages/*/src` returns 0 lines. No lint can police that one, so it is empty by review rather than by gate, which is why it is worth naming here.
## Pattern-level smells
[Section titled “Pattern-level smells”](#pattern-level-smells)
### The toolchain config is the real debt register, and it is load-bearing
[Section titled “The toolchain config is the real debt register, and it is load-bearing”](#the-toolchain-config-is-the-real-debt-register-and-it-is-load-bearing)
Every suppression in `pyproject.toml` carries its measured count and its cost-of-removal analysis, which makes the config the most honest debt document in the repo — and also means a large amount of unresolved work is encoded as a rule that is off rather than as an issue anyone tracks. The three disabled Unknown-propagation rules cover 415 findings and the config names the two things that would let them back on, neither of which exists. Three `TC*` rules are off because a probe found that fixing them breaks `typing.get_type_hints` on 159 first-party functions. Nine per-site `# noqa: N818` mark error classes deliberately not named `*Error`. Five size limits are pinned at the current worst function. Reading the config top to bottom is a more complete account of what this codebase owes than reading its comments, and none of it appears in any tracker.
Shows up in:
* [`pyproject.toml:153-184`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L153-L184) — the 12 ignores, each with its site count.
* [`pyproject.toml:641-680`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L641-L680) — 415 findings traced to origin, plus the 23-warning stub gap.
* [`pyproject.toml:258-281`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L258-L281) — the five ratchets set at the measured worst.
* [`pyproject.toml:370-371`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L370-L371) — `[tool.ty.rules] all = "error"` with nothing disabled beneath it, which is what the pattern looks like when it holds: a suppression here would need the same measured argument every entry above carries.
Cost: L — the analysis is done and written; the work it defers is a `TypedDict` model of the JSONL record, four stub distributions, and three refactors of named functions.
### One private upstream method carries the whole product
[Section titled “One private upstream method carries the whole product”](#one-private-upstream-method-carries-the-whole-product)
`atif-converter` exists to call `ClaudeCode._convert_events_to_trajectory`, a private harbor method. Four consequences compound. The manifest carries a minor-version ceiling that only a manual re-audit can lift. Seven data losses are documented as enum members because the private method’s behavior cannot be changed, and two of them — flattened parent chains, dropped event uuids — are worked around by writing a parallel `edges.jsonl` sidecar for every session, so the corpus layout itself is shaped by an upstream defect. The dependency arrives with 63 other distributions including a web framework, an ASGI server, and a hosted-database client, none of which this code imports.
And it forecloses a supply-chain control the rest of the toolchain would take. semgrep’s `uv-missing-dependency-cooldown` asks for `exclude-newer` under `[tool.uv]`, so a freshly published version waits before it can be resolved — the standard mitigation for a compromised release. Probed 2026-08-28 with `uv lock --dry-run --exclude-newer "7 days"`: the workspace becomes UNSATISFIABLE, because no distribution inside `harbor>=0.22.0,<0.23` is older than the cutoff. The control is declined for that measured reason rather than overlooked, and `uv.lock` plus `uv sync --locked` remain what actually pins the resolution.
Shows up in:
* [`packages/atif-converter/pyproject.toml:19-23`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/pyproject.toml#L19-L23) — the ceiling and the reason.
* [`packages/atif-converter/src/atif_converter/domain/fidelity.py:44-79`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/fidelity.py#L44-L79) — the seven gaps.
* [`docs/CONTRACT.md:26-28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/docs/CONTRACT.md#L26-L28) — `edges.jsonl`, the sidecar that exists because of gaps 3 and 7.
* [`RELEASING.md:215-222`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/RELEASING.md#L215-L222) — 63 of 113 packages, 155 MiB, for one method.
Cost: L — either vendor the conversion the method performs and drop harbor entirely, or get a public entry point upstreamed. Both are real projects.
### The independence contract is paid for in copies, and the copies are pinned by text
[Section titled “The independence contract is paid for in copies, and the copies are pinned by text”](#the-independence-contract-is-paid-for-in-copies-and-the-copies-are-pinned-by-text)
Five packages may never import each other, so shared logic is duplicated instead of extracted. Three byte-identical SQL-literal escapers exist, one per package that inlines SQL, and the same pure provider/dimension guard exists as two 83-line twins. The escapers are the contract’s price paid honestly: each package owns one copy, in `domain/`, and no import would be legal. The guard is the price paid dangerously. The mechanism keeping the twins honest is a test that reads the other package’s file as text, parses it with `ast`, and asserts substrings — including one raw `str.index` on a `raise` keyword. That test is fragile in a way an import never is: it passes when the twins agree on a string and disagree on their logic, and it breaks on a rename that changes nothing.
Shows up in:
* [`packages/atif-duck/src/atif_duck/domain/sql_literal.py:18`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/sql_literal.py#L18) and [`packages/atif-embed/src/atif_embed/domain/sql_literal.py:24`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/sql_literal.py#L24) — two of the three escapers, each in its own package’s `domain/`, each reached by that package’s adapters.
* [`packages/atif-cli/src/atif_cli/app.py:106`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L106) — the third, in the composition root, which could legally import either of the two above and does not.
* [`packages/atif-embed/tests/test_guard_twin_pin.py:93-95`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/tests/test_guard_twin_pin.py#L93-L95) — the substring assertions that stand in for a shared import.
* [`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:5-16`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L5-L16) — the twin whose docstring has already drifted from its counterpart’s.
Cost: M — collapsing the guard twins needs a decision about a shared-kernel package the contract forbids. The three escapers need no decision: a shared kernel would absorb them, and short of one, one copy per package is the contract working rather than failing.
### Findings never fail anything
[Section titled “Findings never fail anything”](#findings-never-fail-anything)
Nine security scanners run, and every one has its findings exit code swallowed — eight sites use a trailing `|| true`, the rest an equivalent flag — under an explicit contract: a finding does not fail the task, a missing report does. The reasoning is sound, because a non-zero exit kills the CI step before the SARIF upload. The consequence is that no gate inside this repository fails on a new vulnerability, a new secret, or a new SAST finding, and enforcement lives entirely in GitHub code-scanning alerts and branch protection. The same asymmetry appears in the coverage floor: `fail_under = 89` exists, and the task that reads it is not among the nine `mise run check` depends on, so the declared definition of done does not measure coverage. The suppression ledger this tier generates is currently empty, which reads identically to “nothing needed suppressing” and to “nobody has looked”.
Shows up in:
* [`mise.toml:217-223`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L217-L223) — the contract, stated plainly.
* [`mise.toml:366`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L366) and [`mise.toml:482`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L482) — two of the eight swallow sites, one of them bandit’s, whose 15 B608 findings therefore reach code scanning permanently.
* [`mise.toml:197-212`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/mise.toml#L197-L212) — the nine gates, without `test:cov`.
* [`pyproject.toml:560`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L560) — the floor that only [`.github/workflows/check.yml:78`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/.github/workflows/check.yml#L78) enforces.
Cost: M — splitting each scanner into “run and upload” plus “assert the finding delta against a baseline” is mechanical; agreeing on the baseline is the work.
### The code the type checkers guard hardest is the code the tests reach least
[Section titled “The code the type checkers guard hardest is the code the tests reach least”](#the-code-the-type-checkers-guard-hardest-is-the-code-the-tests-reach-least)
Combined line-and-branch coverage is 89.98% and the misses are not spread evenly — they concentrate in one path, the numeric clustering one. [`packages/atif-analytics/src/atif_analytics/domain/structure/cluster.py:88`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/cluster.py#L88) is at 22%, and it is the module carrying a `pyright: ignore` on the UMAP call and depending on the one package with no aarch64 wheel. [`packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py:38-49`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py#L38-L49) is at 31%, so the store-shape branches deciding whether the structural stages run at all are unexecuted. Above all of it, the end-to-end materialize suite runs against a fake converter that ships in the wheel, so the state transitions that matter most are exercised against a double.
Shows up in:
* [`packages/atif-analytics/src/atif_analytics/domain/structure/cluster.py:88`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/domain/structure/cluster.py#L88) — 22% covered, and the site of the UMAP `pyright: ignore`.
* [`packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py:38-49`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/lance_reader.py#L38-L49) — 31% covered, including the `noqa: BLE001` that turns any store-shape surprise into a skip.
* [`packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py:3-9`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/fake_converter.py#L3-L9) — the double the materialize suite uses instead of the real adapter.
* [`packages/atif-cli/tests/test_integration.py:3-9`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/tests/test_integration.py#L3-L9) — the whole integration tier: two synthetic 4-event sessions.
Cost: M — a corpus fixture built from real, scrubbed `~/.claude` records is the whole of it, and it is what would exercise both the clustering path and the fidelity gaps nothing real-world currently touches.
## See also
[Section titled “See also”](#see-also)
* [impact analysis](impact-analysis.md) — 24 shared source citations
* [contract map](contract-map.md) — 19 shared source citations
* [module map](../architecture/module-map.md) — 18 shared source citations
* [processes](../behavior/processes.md) — 16 shared source citations
* [business logic](business-logic.md) — 14 shared source citations
# Documentation
# atif-sql · Documentation
[Section titled “atif-sql · Documentation”](#atif-sql--documentation)
Seventeen documents covering the architecture, reference surface, runtime behavior, and the things this codebase quietly assumes. Every factual claim carries a `path:LOC` citation into source.
Prose is generated; structure is mechanical. Cross-references are deterministic.
Measured 2026-08-28: 1,768 full-path citations plus 314 shorthands, resolving into 136 distinct source files. A validator rejects any citation naming a file that does not exist, a line outside that file’s range, or a shorthand with no resolvable antecedent, and it exits non-zero on the first one — so a citation here resolves or the tree does not ship.
## Start here
[Section titled “Start here”](#start-here)
| If you want to | Read |
| ------------------------------------------------ | -------------------------------------------------- |
| Understand what this is and how it fits together | [System overview](architecture/system-overview.md) |
| Run it | [CLI](reference/cli.md) |
| Know what happens when a command runs | [Processes](behavior/processes.md) |
| Change something without breaking it | [Impact analysis](insights/impact-analysis.md) |
| Debug a failure | [Debugging guide](insights/debugging-guide.md) |
## Architecture
[Section titled “Architecture”](#architecture)
* [System overview](architecture/system-overview.md) — the two-paragraph version, the stack, and one diagram of the seven workspace members.
* [Module map](architecture/module-map.md) — one section per member, ordered by source size, with the eight files in each that carry the weight.
* [Data flow](architecture/data-flow.md) — three flows (`materialize`, `query`, `analyze`) traced end to end, each as a sequence diagram.
## Reference
[Section titled “Reference”](#reference)
* [CLI](reference/cli.md) — all ten commands with verbatim usage, every flag, and the exit-code taxonomy. This is the public contract: atif-sql ships one installable distribution and the command is its supported entry point.
* [Public API](reference/public-api.md) — the internal module surface, ranked by the enforced inter-package seam. No symbol here is a supported import path or carries a compatibility promise.
## Behavior
[Section titled “Behavior”](#behavior)
* [Processes](behavior/processes.md) — eight processes with numbered, cited steps, plus six minor flows.
* [State machines](behavior/state-machines.md) — the three genuine multi-state lifecycles: corpus session materialization, embed-store schema version, and the retry-queue row.
## Analysis
[Section titled “Analysis”](#analysis)
* [Risk hotspots](analysis/risk-hotspots.md) — ranked by type-checker defects, scanner findings, complexity ratchets, unguarded IO windows, and uncovered units. Commit churn is deliberately excluded and the document says why.
* [Dead code](analysis/dead-code.md) — nothing in this workspace is deletable code. What the two populated tables record instead is export declarations with no consumer.
## Diagrams
[Section titled “Diagrams”](#diagrams)
* [Components](diagrams/architecture/components.md) — one class diagram over the seven members.
* [Dependency graph](diagrams/structural/dependency-graph.md) — internal members and external distributions on one page, with the enforced direction stated.
* [Sequences](diagrams/behavioral/sequences.md) — call order for the top three processes.
## Insights
[Section titled “Insights”](#insights)
* [Impact analysis](insights/impact-analysis.md) — eight high-impact surfaces, each with the downstream effects and the gate that catches you for missing one.
* [Debugging guide](insights/debugging-guide.md) — a failure-mode index, the log and error surfaces, and a first-checks ladder ordered cheapest first.
* [Contract map](insights/contract-map.md) — eleven contracts with producer, consumer, and shape, including the units, base, and scope of every numeric field crossing a seam.
* [Business logic](insights/business-logic.md) — the domain rules: validations, invariants, calculations, and policies, with the test that pins each one where a test exists.
* [Tech debt](insights/tech-debt.md) — a ranked register with cost of removal. The workspace carries zero `TODO`/`HACK`/`FIXME` markers, so the register is built from version ceilings, justified lint suppressions, install weight, and platform gaps instead.
## Two documents this tree deliberately omits
[Section titled “Two documents this tree deliberately omits”](#two-documents-this-tree-deliberately-omits)
* **`analysis/ownership.md`** — the history is 97 commits over six days under a single bot identity, with zero human authors. A per-person ranked table and a bus-factor list would be noise dressed as analysis.
* **`reference/rpc-tools.md`** — there is no RPC, MCP, or HTTP surface. `fastapi`, `uvicorn`, `starlette`, and the supabase client stack do appear in the installed dependency closure, arriving transitively through harbor, and no first-party module imports any of them.
An empty file in either slot would imply absence by omission rather than by silence, which is why neither exists.
## Hand-written documents that outrank inference
[Section titled “Hand-written documents that outrank inference”](#hand-written-documents-that-outrank-inference)
[`CONTRACT.md`](CONTRACT.md) and [`CONTRACT-V2.md`](CONTRACT-V2.md) are authored, not generated. Where a generated document disagrees with one of them, the generated document cites source and flags the disagreement rather than picking a side.
# CLI
# atif-sql · CLI
[Section titled “atif-sql · CLI”](#atif-sql--cli)
The `atif-sql` console script ([`packages/atif-cli/pyproject.toml:42`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/pyproject.toml#L42)) is the repo’s public contract: one cyclopts router ([`packages/atif-cli/src/atif_cli/app.py:52`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L52)) dispatches the ten subcommands below, and a root-level `--version` resolves the installed `atif-sql` distribution ([`:60`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L60)).
Three conventions hold across every subcommand. `--format` takes `auto`, `table`, `json`, or `csv` from the `OutputFormat` `StrEnum` at [`packages/atif-cli/src/atif_cli/output.py:58`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L58), and `auto` resolves to `table` when stdout is a TTY and `json` otherwise ([`packages/atif-cli/src/atif_cli/output.py:74`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/output.py#L74)). Exit codes come from one table, `EXIT_CODES` at [`packages/atif-cli/src/atif_cli/errors.py:28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/errors.py#L28): `0` ok, `2` empty-session / no-embeddings, `64` invalid input or SQL parse error, `65` catalog error / validation error / embedding mismatch, `70` runtime error, `78` terminal state or suspicious scan, `127` harbor missing. A number in that table is a wire contract: keys may be added, never renumbered. And stderr carries WARNING and above only, so a piped read emits data on stdout and nothing on stderr unless something is wrong; `ATIF_SQL_LOG_LEVEL` widens it (`INFO`, `DEBUG`) and is the only way to reach the INFO surface, because the entry point replaces loguru’s default handler and with it the `LOGURU_LEVEL` that would have parameterized it ([`packages/atif-cli/src/atif_cli/app.py:1141-1148`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1141-L1148)).
`analyze`, `embed`, and `search` call Amazon Bedrock and spend money; the other seven are offline.
## convert
[Section titled “convert”](#convert)
```plaintext
atif-sql convert [OPTIONS] SESSION-JSONL
```
Convert one Claude Code session JSONL to ATIF plus a loss report and edges. [`packages/atif-cli/src/atif_cli/app.py:226`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L226)
Flags:
* `SESSION-JSONL` / `--session-jsonl` — required path to the session transcript, `~/.claude/projects//.jsonl`. [`:226`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L226)
* `--include-subagents` / `--no-subagents` — stage `/subagents/**.jsonl` side-files alongside the main chain; default `True`, with the negative form named explicitly. [`:228`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L228)
* `--trajectory-out` — write the trajectory JSON here and `edges.jsonl` beside it, instead of stdout. [`:229`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L229)
Exit codes: `0` ok, `2` empty session, `64` invalid input, `65` validation, `70` conversion, `127` the pinned private harbor method is gone. [`:250`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L250)
## materialize
[Section titled “materialize”](#materialize)
```plaintext
atif-sql materialize [OPTIONS]
```
Sync the materialized corpus with the raw transcript corpus in one scan-plan-convert-write pass. [`packages/atif-cli/src/atif_cli/app.py:352`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L352)
Flags:
* `--force` / `--no-force` — re-materialize every quiescent session regardless of the watermark; default `False`. [`:341`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L341)
* `--quiesce-seconds` — source-silence threshold; defaults from settings (contract: 300). [`:342`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L342)
* `--source-root` — override the raw transcript root, otherwise `ATIF_SQL_SOURCE_ROOT` or `/projects`. [`:343`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L343)
* `--corpus-root` — override the materialized corpus root, otherwise env or `~/.atif-sql/corpus/`. [`:344`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L344)
* `--sessions` — comma-separated session-id filter; only these sessions are planned this pass. [`:345`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L345)
* `--format` — report format. [`:346`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L346)
Exit codes: `0` ok, `78` suspicious scan — the source scan found zero sessions while the corpus holds materialized ones, so ghost removal was refused and nothing was deleted. Check `--source-root`; a retry over the same root cannot succeed. [`packages/atif-cli/src/atif_cli/app.py:429-443`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L429-L443)
## status
[Section titled “status”](#status)
```plaintext
atif-sql status [OPTIONS]
```
Report corpus freshness: watermark age, counts, bytes, staleness. [`packages/atif-cli/src/atif_cli/app.py:444`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L444)
Flags:
* `--source-root` — override the raw transcript root. [`:414`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L414)
* `--corpus-root` — override the materialized corpus root. [`:415`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L415)
* `--quiesce-seconds` — source-silence threshold used to replay `materialize`’s planning decision. [`:416`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L416)
* `--format` — report format. [`:417`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L417)
Both roots resolve through `_corpus_settings` ([`:433`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L433)), so a `--source-root` given without `--corpus-root` re-derives the corpus root from the overridden source’s slug unless `ATIF_SQL_CORPUS_ROOT` is set ([`:206`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L206)).
## query
[Section titled “query”](#query)
```plaintext
atif-sql query [OPTIONS] [ARGS]
```
Run one SQL statement against the atif-duck catalog and emit results. [`packages/atif-cli/src/atif_cli/app.py:529`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L529)
Flags:
* `SQL` — positional-only statement; omitting it without `--examples` is a parse error. [`:498`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L498)
* `--examples` — short-circuit to the `examples` listing, honoring `--category` and `--requires`, without opening DuckDB. [`:501`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L501)
* `--category` — forwarded to the `examples` listing. [`:502`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L502)
* `--requires` — forwarded to the `examples` listing. [`:503`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L503)
* `--corpus-root` — override the materialized corpus root. [`:504`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L504)
* `--format` — `table` on a TTY, a JSON array of row objects on a pipe. [`:505`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L505)
The statement runs against a hardened connection: reads reach the registered views and nothing else, and the only writable path is the query engine’s own spill directory `/.duckdb_tmp`. [`:601`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L601)
Exit codes: `64` parse error, `65` catalog error, `65` embedding mismatch, `70` runtime error. [`:550`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L550)
## analyze
[Section titled “analyze”](#analyze)
```plaintext
atif-sql analyze [OPTIONS]
```
Run the analytics pipelines — cluster, terms, community, plus the LLM classify, trajectory, conflicts, friction, and perceived stages. [`packages/atif-cli/src/atif_cli/app.py:659`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L659)
Flags:
* `--since-days` — restrict LLM stages to sessions whose last step is within N days; default `30`, and structural stages always run over the full store. [`:629`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L629)
* `--limit` — cap the number of sessions, newest-first, per LLM stage. [`:630`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L630)
* `--max-sessions` — hard per-run session ceiling per LLM pipeline; overrides `ATIF_SQL_LLM_MAX_SESSIONS_PER_RUN`, default 50. [`:631`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L631)
* `--max-cost-usd` — hard per-run dollar ceiling across all LLM pipelines, checked against running actual usage; overrides `ATIF_SQL_LLM_MAX_COST_USD_PER_RUN`, default 25.0. [`:632`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L632)
* `--no-dry-run` — execute the LLM stages for real, which costs money; the default is a dry run emitting plan dicts and cost estimates. [`:633`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L633)
* `--structural-only` — run only cluster, terms, and community, the hourly cron lane that fires at minute 17. [`:634`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L634)
* `--llm-only` — run only classify, trajectory, conflicts, friction, and perceived, the nightly lane. [`:635`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L635)
* `--skip-cluster` — opt out of the cluster stage. [`:636`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L636)
* `--skip-terms` — opt out of the terms stage. [`:637`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L637)
* `--skip-community` — opt out of the community stage. [`:638`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L638)
* `--skip-classify` — opt out of the classify stage. [`:639`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L639)
* `--skip-trajectory` — opt out of the trajectory stage. [`:640`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L640)
* `--skip-conflicts` — opt out of the conflicts stage. [`:641`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L641)
* `--skip-friction` — opt out of the friction stage. [`:642`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L642)
* `--skip-perceived` — opt out of the perceived stage. [`:643`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L643)
* `--force-cluster` — recompute clustering even when the mtime sidecar says the input is unchanged. [`:644`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L644)
* `--force-community` — recompute community detection even when the mtime sidecar says the input is unchanged. [`:645`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L645)
* `--corpus-root` — override the materialized corpus root. [`:646`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L646)
* `--format` — summary format. [`:647`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L647)
## embed
[Section titled “embed”](#embed)
```plaintext
atif-sql embed [OPTIONS]
```
Embed unembedded corpus steps with Cohere Embed v4 and append them to LanceDB. [`packages/atif-cli/src/atif_cli/app.py:766`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L766)
Flags:
* `--limit` — cap the number of steps embedded this run. [`:736`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L736)
* `--all` — explicitly embed every unembedded step, a full backfill. [`:737`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L737)
* `--dry-run` — preview only; emit the plan JSON with keys `pipeline, candidates, batches, batch_size, concurrency, model, limit, dry_run` and make no embedding calls. [`:738`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L738)
* `--corpus-root` — override the materialized corpus root. [`:739`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L739)
* `--format` — output format. [`:740`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L740)
A real run requires an explicit scope: a bare `atif-sql embed` exits `64` with a hint rather than starting an unbounded backfill. [`:778`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L778)
Exit codes: `0` success, `64` missing `--limit` or `--all`, `70` runtime (Bedrock, DuckDB, or Lance failure — transient, safe to retry), `78` terminal state, where the store or its config needs operator action and unattended lanes suppress retries. [`:767`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L767)
## search
[Section titled “search”](#search)
```plaintext
atif-sql search [OPTIONS] QUERY_TEXT
```
Semantic top-k nearest-neighbor search over step embeddings. [`packages/atif-cli/src/atif_cli/app.py:860`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L860)
Flags:
* `QUERY_TEXT` — required positional-only text, embedded with Cohere Embed v4 in `search_query` mode. [`:829`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L829)
* `-k` / `--k` — top-k; default `10`. This is the CLI’s only short flag. [`:832`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L832)
* `--session-id` — confine the kNN to one session. [`:833`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L833)
* `--corpus-root` — override the materialized corpus root. [`:834`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L834)
* `--format` — output format. [`:835`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L835)
Output columns are `uuid`, `session_id`, `snippet`, and `sim` ([`:927`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L927)), ranked by cosine distance ascending so the highest similarity comes first ([`:935`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L935)).
Exit codes: `0` success, `2` no embeddings yet, `65` embedding mismatch when the store was written by another provider, `70` runtime. [`:864`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L864)
## examples
[Section titled “examples”](#examples)
```plaintext
atif-sql examples [OPTIONS]
```
List tested example queries for every view and macro, derived from the static atif-duck catalog. [`packages/atif-cli/src/atif_cli/app.py:995`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L995)
Flags:
* `--category` — filter to one of `view`, `table-macro`, `scalar-macro`, validated against `CATEGORY_VALUES`. [`:965`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L965)
* `--requires` — filter to one of `core`, `analytics`, `vss`, validated against `REQUIRES_VALUES`. [`:966`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L966)
* `--format` — a TTY table grouped by `requires`, or a JSON object carrying `note` and `examples`. [`:967`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L967)
Both value sets are `Literal` aliases in the producer package: `Requires` at [`packages/atif-duck/src/atif_duck/domain/examples.py:48`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L48) and `Category` at [`:53`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L53), exported as tuples at [`:55`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L55) and [`:56`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L56).
Exit codes: `0` ok, `64` unknown `--category` or `--requires` value. [`packages/atif-cli/src/atif_cli/app.py:1020`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1020)
## schema
[Section titled “schema”](#schema)
```plaintext
atif-sql schema [OPTIONS]
```
List every registered view with its columns and every macro signature. [`packages/atif-cli/src/atif_cli/app.py:1087`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1087)
Flags:
* `--format` — a TTY listing, or a JSON object carrying `views`, `macros`, and `examples_hint`. [`:1057`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1057)
The answer comes from the static `VIEW_SCHEMA` and `MACRO_SIGNATURES` dicts with no DuckDB import and no view registration. [`:1067`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1067)
## cron
[Section titled “cron”](#cron)
```plaintext
atif-sql cron COMMAND
```
Inspect and manually install the `atif-sql` refresh cron lanes. [`packages/atif-cli/src/atif_cli/cron.py:38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L38)
The group is attached to the root router by `app.command(cron_app)` at [`packages/atif-cli/src/atif_cli/app.py:66`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L66), and its three lanes — `materialize` on `*/10 * * * *`, `structural` on `17 * * * *`, `llm` on `20 10 * * *` — are declared once in `LANES` at [`packages/atif-cli/src/atif_cli/cron.py:47`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L47).
### cron install
[Section titled “cron install”](#cron-install)
```plaintext
atif-sql cron install [OPTIONS]
```
Print the crontab block for the three refresh lanes and never write it. [`packages/atif-cli/src/atif_cli/cron.py:180`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L180)
Flags:
* `--script` — path to `atif-sql-refresh.sh`. [`packages/atif-cli/src/atif_cli/cron.py:180`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L180)
Without the flag the script is located by walking up from this module ([`:147`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L147)); a tree where `scripts/atif-sql-refresh.sh` is unreachable exits `64` demanding `--script` ([`:175`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/scripts/atif-sql-refresh.sh#L175)).
### cron status
[Section titled “cron status”](#cron-status)
```plaintext
atif-sql cron status [OPTIONS]
```
Report each lane’s lock holder plus the last run and skip parsed from the refresh log. [`packages/atif-cli/src/atif_cli/cron.py:199`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L199)
Flags:
* `--script` — path to `atif-sql-refresh.sh`, whose `.run/` sibling holds the locks and the log. [`packages/atif-cli/src/atif_cli/cron.py:201`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L201)
* `--tail` — how many trailing log lines to include; `0` disables, default `10`. [`:202`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L202)
* `--format` — human lines on a TTY, JSON on a pipe. [`:203`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L203)
The lock probe acquires and releases nonblocking, so the command perturbs no running lane. [`:127`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/cron.py#L127)
## See also
[Section titled “See also”](#see-also)
* [processes](../behavior/processes.md) — 7 shared source citations
* [module map](../architecture/module-map.md) — 6 shared source citations
* [debugging guide](../insights/debugging-guide.md) — 6 shared source citations
* [dead code](../analysis/dead-code.md) — 5 shared source citations
* [impact analysis](../insights/impact-analysis.md) — 5 shared source citations
# Public API
# atif-sql · Public API
[Section titled “atif-sql · Public API”](#atif-sql--public-api)
**The supported entry point is the `atif-sql` command, not an import.** This repository ships exactly one installable distribution, `atif-sql`, whose only console script is `atif-sql = "atif_cli.app:main"` — [`pyproject.toml:64`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L64). All seven workspace members ship inside that one wheel rather than as install targets, and the published name is deliberately split from the `atif_cli` module that provides the entry point — [`pyproject.toml:16-18`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L16-L18). Install with `uvx atif-sql` or `uv tool install atif-sql`; `docs/reference/cli.md` is the primary reference for the surface a user actually calls.
What follows documents the **internal seam**: the 30 highest-traffic symbols that one workspace member imports from another. That seam is enforced rather than conventional — the root `pyproject.toml` declares `[tool.importlinter]` over all seven root packages at [`pyproject.toml:462-463`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L462-L463), with an `independence` contract at [`pyproject.toml:514-517`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L514-L517) forbidding atif-converter, atif-corpus, atif-duck, atif-models, and atif-embed from importing each other at all, and a `forbidden` contract at [`pyproject.toml:519-523`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/pyproject.toml#L519-L523) limiting atif-analytics to atif-models alone. Every symbol below is a real declaration read at the cited line; none of them is a supported import path for code outside this workspace, and no compatibility promise attaches to any of them.
Two facts about the seam shape are worth carrying into every entry below. First, the boundary abstractions are `typing.Protocol` classes and there are exactly five, all under a `domain/` package: `ConverterPort` at [`packages/atif-corpus/src/atif_corpus/domain/ports.py:41`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L41), `EmbeddingProvider`, `VectorStorePort`, and `TextRowsPort` at [`packages/atif-embed/src/atif_embed/domain/ports.py:31`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L31), [`:59`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L59), and [`:87`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/ports.py#L87), and `LlmStructuredProvider` at [`packages/atif-models/src/atif_models/domain/ports.py:116`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L116). No `abstractmethod` exists anywhere in the workspace, so adapters satisfy a port structurally. Second, the name `DomainError` is declared three independent times — one base class per erroring package, at [`packages/atif-converter/src/atif_converter/domain/errors.py:14`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/errors.py#L14), [`packages/atif-embed/src/atif_embed/domain/errors.py:14`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/errors.py#L14), and [`packages/atif-models/src/atif_models/domain/ports.py:39`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L39) — and the three are unrelated types that share only a spelling.
There is no HTTP or RPC surface: a grep for route decorators, `FastAPI(`, `APIRouter`, `add_route`, and `uvicorn` across all 100 source files under `packages/*/src` returns zero hits, and no module imports fastapi, starlette, flask, or uvicorn.
### AnalyticsSettings
[Section titled “AnalyticsSettings”](#analyticssettings)
```plaintext
class AnalyticsSettings(BaseSettings):
```
Env-driven configuration for the analytics pipelines, read under the `ATIF_SQL_` prefix with `.env` support, carrying the corpus root, the Lance URI, and every pipeline knob.
[`packages/atif-analytics/src/atif_analytics/infrastructure/settings.py:68-191`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/infrastructure/settings.py#L68-L191)
### build\_examples
[Section titled “build\_examples”](#build_examples)
```plaintext
def build_examples() -> tuple[ExampleQuery, ...]:
```
Derives the full example inventory from the static catalogs in a fixed order — core views, the VSS view, analytics views, then core macros, the VSS macro, analytics macros — so the CLI listing and the JSON array are deterministic across runs.
[`packages/atif-duck/src/atif_duck/domain/examples.py:178-239`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/examples.py#L178-L239)
### build\_plan
[Section titled “build\_plan”](#build_plan)
```plaintext
def build_plan(
sessions: Sequence[SessionSource],
*,
watermark: Mapping[str, int],
policy: QuiescencePolicy,
now_ns: int,
force: bool = False,
unmaterialized_session_ids: Collection[str] = (),
) -> MaterializationPlan:
```
Partitions scanned sessions into the three plan buckets — the pure decision at the centre of materialization, taking the watermark and the quiescence policy as data.
[`packages/atif-corpus/src/atif_corpus/domain/sessions.py:159-211`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L159-L211)
### ConversionOutput
[Section titled “ConversionOutput”](#conversionoutput)
```plaintext
@dataclass(frozen=True, slots=True)
class ConversionOutput:
```
Everything one conversion yields that the corpus writes to disk: the ATIF trajectory dict, the loss-report dict, and one already-serialized `edges.jsonl` line per raw record without trailing newlines, because the writer owns line termination.
[`packages/atif-corpus/src/atif_corpus/domain/ports.py:21-38`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/ports.py#L21-L38)
### convert\_and\_audit
[Section titled “convert\_and\_audit”](#convert_and_audit)
```plaintext
def convert_and_audit(
session_jsonl: Path,
*,
include_subagents: bool = True,
) -> tuple[ConversionResult, LossReport]:
```
Converts one Claude Code session JSONL to ATIF and produces its loss accounting in the same call, returning the enriched trajectory and a `LossReport` whose `records_dropped` is an upper bound on what the materialized trajectory is missing.
[`packages/atif-converter/src/atif_converter/application/convert_and_audit.py:105-155`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/application/convert_and_audit.py#L105-L155)
### CorpusLayout
[Section titled “CorpusLayout”](#corpuslayout)
```plaintext
@dataclass(frozen=True, slots=True)
class CorpusLayout:
```
Computes every contract path under one `corpus_root` as pure path arithmetic that never touches the filesystem, so the layout can be asserted against the contract without a tmpdir.
[`packages/atif-corpus/src/atif_corpus/domain/layout.py:25-75`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/layout.py#L25-L75)
### corpus\_slug
[Section titled “corpus\_slug”](#corpus_slug)
```plaintext
def corpus_slug(corpus_root: Path | str) -> str:
```
Maps one corpus root to a stable, human-legible directory key.
[`packages/atif-corpus/src/atif_corpus/domain/slug.py:32-50`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/slug.py#L32-L50)
### CorpusSettings
[Section titled “CorpusSettings”](#corpussettings)
```plaintext
class CorpusSettings(BaseSettings):
```
Env-driven settings for corpus materialization, read under the `ATIF_SQL_` prefix.
[`packages/atif-corpus/src/atif_corpus/infrastructure/settings.py:44-58`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/settings.py#L44-L58)
### DomainError
[Section titled “DomainError”](#domainerror)
```plaintext
class DomainError(Exception):
```
Base class for atif-embed domain errors, subclassed by the embed-specific failures declared beneath it in the same module.
[`packages/atif-embed/src/atif_embed/domain/errors.py:14-26`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/domain/errors.py#L14-L26)
### EmbedSettings
[Section titled “EmbedSettings”](#embedsettings)
```plaintext
class EmbedSettings(BaseSettings):
```
Env-driven settings for the embedding pipeline — model, batch size, concurrency, and the LanceDB URI.
[`packages/atif-embed/src/atif_embed/infrastructure/settings.py:19-64`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/infrastructure/settings.py#L19-L64)
### EmbeddingProviderMismatch
[Section titled “EmbeddingProviderMismatch”](#embeddingprovidermismatch)
```plaintext
class EmbeddingProviderMismatch(Exception): # noqa: N818 — names a store state, not an "*Error"
```
Raised on a stamped-versus-active `(model, dim)` mismatch, and terminal by design: vectors from two embedding models occupy incompatible spaces, so the store must be dropped and re-embedded rather than queried across the switch.
[`packages/atif-duck/src/atif_duck/domain/embedding_guard.py:33-43`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/embedding_guard.py#L33-L43)
### EmptySessionError
[Section titled “EmptySessionError”](#emptysessionerror)
```plaintext
class EmptySessionError(DomainError):
```
Harbor produced no trajectory because the session held no convertible events.
[`packages/atif-converter/src/atif_converter/domain/errors.py:27-28`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/errors.py#L27-L28)
### estimate\_cost
[Section titled “estimate\_cost”](#estimate_cost)
```plaintext
def estimate_cost(spec: ModelSpec, *, input_tokens: int, output_tokens: int) -> float | None:
```
USD estimate for one call or an accumulated pipeline against the prices carried on `spec`, returning `None` when either price is unknown — which a caller must render as pricing unavailable, never as zero.
[`packages/atif-models/src/atif_models/domain/registry.py:125-137`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L125-L137)
### InvalidSessionInput
[Section titled “InvalidSessionInput”](#invalidsessioninput)
```plaintext
class InvalidSessionInput(DomainError): # noqa: N818 — named as a terminal input verdict, not "*Error"
```
The supplied path is not a Claude Code session JSONL file.
[`packages/atif-converter/src/atif_converter/domain/errors.py:23-24`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/errors.py#L23-L24)
### LlmSettings
[Section titled “LlmSettings”](#llmsettings)
```plaintext
class LlmSettings(BaseSettings):
```
Env-driven model selection for the LLM analytics pipelines, and the only place a Bedrock model id is resolved from configuration.
[`packages/atif-models/src/atif_models/infrastructure/settings.py:28-81`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/settings.py#L28-L81)
### LlmStructuredProvider
[Section titled “LlmStructuredProvider”](#llmstructuredprovider)
```plaintext
@runtime_checkable
class LlmStructuredProvider(Protocol):
```
The port for one structured-output call, one adapter per backend, deliberately narrow: its single `classify_structured` method takes system text, a prompt, and a schema type, and raises `RefusalError` terminally or `ProviderUnavailable` retryably.
[`packages/atif-models/src/atif_models/domain/ports.py:115-132`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L115-L132)
### MACRO\_SIGNATURES
[Section titled “MACRO\_SIGNATURES”](#macro_signatures)
```plaintext
MACRO_SIGNATURES: dict[str, tuple[str, ...]] = {
"ago": ("interval_text",),
"model_used": ("sid",),
"cost_estimate": ("sid",),
"tool_rank": ("last_n_days",),
"todo_velocity": ("sid",),
"subagent_fanout": ("sid",),
"semantic_search": ("query_vec", "k"),
"skill_rank": ("last_n_days",),
"skill_source_mix": ("last_n_days",),
}
```
Hand-maintained parameter names for all nine core macros, kept static because DuckDB’s `duckdb_functions()` returns NULL `parameters` for table macros and cannot recover them at runtime.
[`packages/atif-duck/src/atif_duck/domain/catalog.py:242-257`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L242-L257)
### main
[Section titled “main”](#main)
```plaintext
def main() -> None:
```
The console-script entry point behind the `atif-sql` command; it replaces loguru’s default DEBUG sink with WARNING-and-up so routine reads keep stderr quiet, then hands off to the cyclopts app.
[`packages/atif-cli/src/atif_cli/app.py:1125-1135`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-cli/src/atif_cli/app.py#L1125-L1135)
### materialize
[Section titled “materialize”](#materialize)
```plaintext
def materialize(
*,
source_root: Path,
corpus_root: Path,
converter: ConverterPort,
materialized_at: str,
harbor_version: str,
converter_version: str,
quiesce_seconds: int = 300,
force: bool = False,
now_ns: int | None = None,
session_ids: Collection[str] | None = None,
) -> MaterializationReport:
```
Runs one materialization pass over the raw transcript corpus, taking conversion as an injected `ConverterPort` so tests substitute a fake and atif-cli injects the harbor-backed adapter.
[`packages/atif-corpus/src/atif_corpus/application/materialize.py:476-645`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L476-L645)
### MaterializationReport
[Section titled “MaterializationReport”](#materializationreport)
```plaintext
@dataclass(frozen=True, slots=True)
class MaterializationReport:
```
What one materialization pass did, for logs and the CLI status line.
[`packages/atif-corpus/src/atif_corpus/application/materialize.py:117-157`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/application/materialize.py#L117-L157)
### ModelSpec
[Section titled “ModelSpec”](#modelspec)
```plaintext
@dataclass(frozen=True, slots=True)
class ModelSpec:
```
One concrete model behind a `(family, size)` alias, carrying its Bedrock model id, USD-per-1M-token prices that may be `None`, and whether the family supports native strict-JSON structured output.
[`packages/atif-models/src/atif_models/domain/registry.py:38-57`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/registry.py#L38-L57)
### OpenAiBedrockProvider
[Section titled “OpenAiBedrockProvider”](#openaibedrockprovider)
```plaintext
class OpenAiBedrockProvider:
```
The default `LlmStructuredProvider` adapter, satisfying the port structurally rather than by inheritance.
[`packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:142-311`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py#L142-L311)
### QuiescencePolicy
[Section titled “QuiescencePolicy”](#quiescencepolicy)
```plaintext
@dataclass(frozen=True, slots=True)
class QuiescencePolicy:
```
The rule for when a session is settled enough to convert: quiescent once its newest source mtime is at least `quiesce_seconds` in the past, an observed rather than announced signal that the writer finished.
[`packages/atif-corpus/src/atif_corpus/domain/sessions.py:70-104`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/domain/sessions.py#L70-L104)
### RefusalError
[Section titled “RefusalError”](#refusalerror)
```plaintext
class RefusalError(DomainError):
```
Terminal error for a model refusal — a content filter or a refusal finish reason — as distinct from a retryable provider fault.
[`packages/atif-models/src/atif_models/domain/ports.py:43-48`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-models/src/atif_models/domain/ports.py#L43-L48)
### register
[Section titled “register”](#register)
```plaintext
def register(
con: duckdb.DuckDBPyConnection,
corpus_root: Path,
pricing: dict[str, tuple[float, float]] | None = None,
*,
skip_vss: bool = False,
lance_uri: Path | None = None,
expected_model: str | None = None,
expected_dim: int | None = None,
) -> None:
```
Registers raw readers, views, VSS, and macros over `corpus_root` in dependency order on one DuckDB connection; every call re-scans the whole corpus into TEMP tables, so the cost is O(corpus) per connection and a caller should reuse one connection per process.
[`packages/atif-duck/src/atif_duck/infrastructure/registry.py:1212-1278`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/infrastructure/registry.py#L1212-L1278)
### run\_analyze
[Section titled “run\_analyze”](#run_analyze)
```plaintext
def run_analyze(
settings: AnalyticsSettings,
*,
since_days: int | None = 30,
limit: int | None = None,
dry_run: bool = True,
structural_only: bool = False,
llm_only: bool = False,
skip_cluster: bool = False,
skip_terms: bool = False,
skip_community: bool = False,
skip_classify: bool = False,
skip_trajectory: bool = False,
skip_conflicts: bool = False,
skip_friction: bool = False,
skip_perceived: bool = False,
force_cluster: bool = False,
force_community: bool = False,
) -> dict[str, Any]:
```
Runs the analytics pipeline end to end, structure first and then the LLM stages, returning a per-stage summary dict; `structural_only` and `llm_only` are mutually exclusive lane selectors and the `skip_*` flags subtract individual stages from whichever lane runs.
[`packages/atif-analytics/src/atif_analytics/application/analyze.py:36-207`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-analytics/src/atif_analytics/application/analyze.py#L36-L207)
### run\_backfill
[Section titled “run\_backfill”](#run_backfill)
```plaintext
async def run_backfill(
*,
corpus_root: Path,
settings: EmbedSettings,
embedder: EmbeddingProvider | None = None,
text_rows: TextRowsPort | None = None,
store: VectorStorePort | None = None,
limit: int | None = None,
dry_run: bool = False,
) -> int | dict[str, Any]:
```
Discovers unembedded steps, embeds them, and appends to the Lance store; all three ports default to None and are constructed lazily so a dry run never loads boto3.
[`packages/atif-embed/src/atif_embed/application/embed.py:61-262`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-embed/src/atif_embed/application/embed.py#L61-L262)
### scan\_source\_root
[Section titled “scan\_source\_root”](#scan_source_root)
```plaintext
def scan_source_root(source_root: Path) -> tuple[SessionSource, ...]:
```
The sessions found under `source_root`, discarding the per-scan diagnostics that the fuller `scan_sources` returns.
[`packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py:211-222`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-corpus/src/atif_corpus/infrastructure/scanner.py#L211-L222)
### TrajectoryValidationError
[Section titled “TrajectoryValidationError”](#trajectoryvalidationerror)
```plaintext
class TrajectoryValidationError(DomainError):
```
The converted trajectory failed harbor’s `TrajectoryValidator`, and the exception carries the validator’s error list so a caller can report the exact schema violations.
[`packages/atif-converter/src/atif_converter/domain/errors.py:31-40`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-converter/src/atif_converter/domain/errors.py#L31-L40)
### VIEW\_SCHEMA
[Section titled “VIEW\_SCHEMA”](#view_schema)
```plaintext
VIEW_SCHEMA: dict[str, tuple[tuple[str, str], ...]] = {
```
The hand-maintained column schema for all 16 core views, where column order is load-bearing: a drift test asserts tuple equality against DuckDB `DESCRIBE` output, so editing view DDL without updating this dict fails CI instead of surfacing as a runtime mystery.
[`packages/atif-duck/src/atif_duck/domain/catalog.py:47-225`](https://github.com/theagenticguy/atif-sql/blob/5793d935bfb0117994ba04c75125151ba805598c/packages/atif-duck/src/atif_duck/domain/catalog.py#L47-L225)
## See also
[Section titled “See also”](#see-also)
* [module map](../architecture/module-map.md) — 23 shared source citations
* [processes](../behavior/processes.md) — 22 shared source citations
* [business logic](../insights/business-logic.md) — 20 shared source citations
* [contract map](../insights/contract-map.md) — 18 shared source citations
* [impact analysis](../insights/impact-analysis.md) — 18 shared source citations