atif-sql · Business logic
Section titled “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”Notes on two rows above. The strict-JSON transform in
packages/atif-models/src/atif_models/domain/schema.py:6-11 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).
Invariants
Section titled “Invariants”Conversion
Section titled “Conversion”Corpus
Section titled “Corpus”Analytics
Section titled “Analytics”Models and embeddings
Section titled “Models and embeddings”SQL surface and CLI
Section titled “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; test packages/atif-duck/tests/test_duck_views.py:615 |
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; test packages/atif-duck/tests/test_examples.py:178 |
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, packages/atif-duck/src/atif_duck/infrastructure/registry.py:1066-1077; tests packages/atif-duck/tests/test_duck_views.py:407 and packages/atif-duck/tests/test_duck_views.py:482 |
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; test packages/atif-duck/tests/test_duck_views.py:533 |
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; tests packages/atif-duck/tests/test_vss.py:112 and packages/atif-duck/tests/test_vss.py:248 |
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, packages/atif-cli/src/atif_cli/errors.py:23-39 |
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, packages/atif-cli/src/atif_cli/app.py:833-847 |
Calculations
Section titled “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 |
| 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 |
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; test packages/atif-models/tests/test_registry.py:76 |
| 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 |
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; test packages/atif-duck/tests/test_duck_views.py:368 |
| 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; test packages/atif-analytics/tests/test_state.py:102 |
| 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, packages/atif-models/src/atif_models/infrastructure/openai_bedrock.py:102-109; test packages/atif-models/tests/test_openai_provider.py:284 |
| 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 |
| 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; test packages/atif-corpus/tests/test_domain.py:40 |
| 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; test packages/atif-corpus/tests/test_domain.py:76 |
| Corpus slug | a corpus root path | default for ~/.claude, else <sanitized-dirname (<= 32 chars)>-<8 hex of sha256> |
packages/atif-corpus/src/atif_corpus/domain/slug.py:45-50; test packages/atif-corpus/tests/test_slug.py:24 |
| 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, packages/atif-converter/src/atif_converter/domain/fidelity.py:109-112 |
| 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; test packages/atif-analytics/tests/test_perceived.py:63 |
| 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, packages/atif-analytics/src/atif_analytics/domain/trajectory.py:130-134, packages/atif-analytics/src/atif_analytics/domain/trajectory.py:171-183 |
| 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 |
| 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 |
| 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 |
| 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 |
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 |
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 |
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; test packages/atif-duck/tests/test_vss.py:195 |
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; test packages/atif-duck/tests/test_duck_views.py:566 |
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; test packages/atif-duck/tests/test_duck_views.py:572 |
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, pinned by
packages/atif-duck/tests/test_duck_views.py:459 against published list rates and by
packages/atif-duck/tests/test_duck_views.py:470 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). 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,
packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:118-120).
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).
Policy and gates
Section titled “Policy and gates”Cost and spend
Section titled “Cost and spend”- Dry-run by default: every LLM analytics stage and the embed backfill treat
dry_run=Trueas 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,packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:362. - Session ceiling: each LLM pipeline admits at most
llm_max_sessions_per_runsessions 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,packages/atif-analytics/src/atif_analytics/application/use_cases/perceived.py:176-193; testspackages/atif-analytics/tests/test_resource_guards.py:84andpackages/atif-analytics/tests/test_resource_guards.py:113. - Cost ceiling: one
RunBudgetofllm_max_cost_usd_per_run(default 25.0 USD peranalyzerun, 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,packages/atif-analytics/src/atif_analytics/application/analyze.py:122-144,packages/atif-analytics/src/atif_analytics/application/use_cases/_shared.py:90-143; testpackages/atif-analytics/tests/test_analyze.py:75. - Budget-skip starvation escalation: a stage skipped for budget records a durable
budget_skipsrow, 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,packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:54-62,packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/checkpointer.py:217-246; testpackages/atif-analytics/tests/test_analyze.py:99. - 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,packages/atif-analytics/src/atif_analytics/infrastructure/sqlite_state/retry_queue.py:176-193; testpackages/atif-analytics/tests/test_state.py:118. - 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,packages/atif-analytics/src/atif_analytics/application/use_cases/friction.py:178-232; testspackages/atif-analytics/tests/test_friction_tiers.py:47,packages/atif-analytics/tests/test_friction_tiers.py:79,packages/atif-analytics/tests/test_friction_tiers.py:87,packages/atif-analytics/tests/test_friction_tiers.py:92. - Zero-cost visualization is off:
compute_viz_coordsdefaults 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.
Model selection
Section titled “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. - Runnable-family gate: only families with a wired provider adapter may be selected
(
RUNNABLE_FAMILIESisopenaialone); 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,packages/atif-models/src/atif_models/infrastructure/settings.py:41-54; testspackages/atif-models/tests/test_settings.py:69andpackages/atif-models/tests/test_settings.py:72. - Per-pipeline size assignment: classify, trajectory and perceived take
medium, conflicts takeslarge(the hardest judgment task), friction takessmall(a per-message enum) — each overridable viaATIF_SQL_LLM_SIZE_<PIPELINE>.packages/atif-models/src/atif_models/infrastructure/settings.py:56-65; testpackages/atif-models/tests/test_settings.py:26.
Freshness and data safety
Section titled “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,packages/atif-corpus/src/atif_corpus/domain/sessions.py:200-206; testspackages/atif-corpus/tests/test_domain.py:117,packages/atif-corpus/tests/test_materialize.py:148. - 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; testspackages/atif-corpus/tests/test_materialize.py:609andpackages/atif-corpus/tests/test_materialize.py:647. - A
--sessionsfilter 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,packages/atif-corpus/src/atif_corpus/application/materialize.py:573-577; testpackages/atif-corpus/tests/test_materialize.py:280. - Staging sweep errs toward keeping: a signal delivered, a
PermissionError, or any unexpectedOSErrorall 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; testpackages/atif-corpus/tests/test_materialize.py:481. - Corrupt state degrades, never refuses: an unreadable or wrong-shaped
watermark.jsonis treated as unmaterialized, costing one full pass, which is always safe.packages/atif-corpus/src/atif_corpus/application/materialize.py:160-176.
Embeddings and the SQL surface
Section titled “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
terminalso 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,packages/atif-embed/src/atif_embed/domain/errors.py:29-41,packages/atif-cli/src/atif_cli/app.py:833-847. - Embed requires an explicit scope: a bare
atif-sql embed --no-dry-runexits 64 so a full backfill cannot happen from a mistyped command;--dry-runneeds no scope because it spends nothing.packages/atif-cli/src/atif_cli/app.py:778-783,packages/atif-cli/src/atif_cli/app.py:810-820. skip_vssescape hatch: registration can skip both themessage_embeddingsview and thesemantic_searchmacro, 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; testpackages/atif-duck/tests/test_vss.py:258.- Register-or-fail-loud: any DuckDB error during view or macro registration is logged
with
logger.exceptionand 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,packages/atif-duck/src/atif_duck/infrastructure/registry.py:1201-1204; testpackages/atif-duck/tests/test_duck_views.py:623. - A new view or macro cannot land silently: the drift tests require a
DESCRIPTIONSentry, anARG_EXEMPLARSentry for any new parameter name,TABLE_MACRO_NAMESmembership when the DDL isAS TABLE, and an example that EXECUTES — or a documentedEXCLUSIONSentry.packages/atif-duck/src/atif_duck/domain/examples.py:94-99; testspackages/atif-duck/tests/test_examples.py:160,packages/atif-duck/tests/test_examples.py:178,packages/atif-duck/tests/test_examples.py:188,packages/atif-duck/tests/test_examples.py:197.
Scheduling
Section titled “Scheduling”- Three cron lanes, split by cost:
materializeevery 10 minutes (cheap incremental),structuralhourly at minute 17 (zero LLM cost),llmonce daily at 10:20 (the lane that spends).packages/atif-cli/src/atif_cli/cron.py:43-51. cron installnever writes a crontab: it prints the block for a human to paste, after checkingcrontab -l.packages/atif-cli/src/atif_cli/cron.py:180-197.
See also
Section titled “See also”- module map — 39 shared source citations
- processes — 36 shared source citations
- impact analysis — 34 shared source citations
- contract map — 33 shared source citations
- debugging guide — 22 shared source citations