sqlalchemy, every agent that reads a citation drags a database
driver it can never use. CE-1 is the one shared seam that forbids both by
construction β before any store, migration, or executor is built.
Everything in the engine β every card field, every assertion, every graph
node and edge, every bundle item, every cited answer β points at ONE shape:
source_span.v1. A span is a byte-exact address into
documents.document_pages (never the synthesised
document.md): a document, a page, a paragraph, a character
range, and the verbatim quote those bytes contain. Build on that
atom four layers of typed DTOs β cards (L1), evidence hits (L2), assertions
and graph edges (L3), bundles (L4) β plus the seven tool result envelopes and
the honesty types (EngineState, context_provenance).
All of it lives in src/shared/context_contracts/, a package with
no sqlalchemy, no boto3, no settings: brains import it as the
single source of contract truth; the executors that fill it in live
functions-side and arrive in sibling stories (CE-2β¦CE-24).
Think of a courtroom. Nothing is "in the record" until it carries an exhibit
tag pointing at the exact page and line it came from; an assertion with no tag
is inadmissible β the judge will not read it. SourceSpan
is that exhibit tag. CitedValue[T] is a fact that
cannot be filed without one: its spans field is
min_length=1, so a cited value with an empty span list fails to
construct β the "no naked strings" rule is enforced by the type system, not a
downstream reviewer who might forget. The single carve-out is
NonCitable (the card summary): display garnish,
explicitly typed as tag-free, and by that same typing barred from ever
entering a bundle item or a graph node as evidence.
documents.document_pages (the real record, byte-exact)
β²
β span_id = "sp1:{doc}:{page}:{block}:{start-end}"
β + verbatim quote (V-CITE byte match)
SourceSpan ββββββββββββ the exhibit tag
β² β² β² β² β²
CitedValue β β β β β spans: min_length=1 (LAW)
βββββββββββββ΄β ββ΄βββββ΄β ββ΄βββββββ΄ββ ββββββββββββ
βDocumentCardβ βAsser-β βChrono- β βEvidenceHitβ
β (L1) β βtion β βlogyEventβ β (L2) β
ββββββββββββββ βAssertionEdge (L3) β ββββββββββββ
βββββββββββββββββββββ
assembled into βΌ
ContextBundle (L4) ββ sections carry EngineState + truncated
The whole package is thirteen small files; there is no logic to run, only shape to freeze. Each freeze is one line of Pydantic:
# _base.py β the frozen wire model every DTO extends
model_config = ConfigDict(extra="forbid", populate_by_name=True)
# extra="forbid" β an unknown field is contract drift, raises on validate
# source_span.py β the version pin + the provenance law
schema_version: Literal["source_span.v1"] = "source_span.v1"
# a wrong version string ("source_span.v2") is rejected by the Literal
class CitedValue[T](ContractModel):
spans: list[SourceSpan] = Field(min_length=1) # empty list β ValidationError
# bundle.py β one section, mixed item types, still typed
BundleSectionItem = DocumentCard | ChronologyEvent | EvidenceHit | Assertion | ContradictionItem
# pydantic's smart union re-hydrates each item to its OWN type through JSON β
# distinct schema_version literals / required fields make discrimination safe
"Additive-only versioning" is not a comment, it is the shape: every carrier
pins a schema_version Literal; new fields land as
optional-with-default within a vN; a meaning change forks
vN+1 beside the old one. And purity is proven, not asserted: a
fresh interpreter imports the package and fails the test if
sqlalchemy or boto3 ever appears in
sys.modules β transitive leaks caught, not just direct imports.
| File | What it owns |
|---|---|
context_contracts/_base.py | The pure ContractModel base: extra="forbid", snake_case, no store imports. |
context_contracts/source_span.py | source_span.v1 β the provenance atom β plus CitedValue[T] (spans min_length=1) and the NonCitable alias (RFD Β§4, Β§5.1). |
context_contracts/engine_state.py | EngineState (stubbed/partial/live) and context_provenance.v1 β the honesty mechanism (RFD Β§7). |
context_contracts/enums.py | Closed vocabularies: date precision, assertion kind, confirmation status, edge type, support status, retrieval leg, profile/budget/role literals, section kind. |
context_contracts/document_card.py | document_card.v1 + sub-models (dates, parties, amounts, key_assertions, enrichment stamp) β L1 (RFD Β§5.1). |
context_contracts/assertion.py | assertion.v1 and the single assertion_edge.v1 view covering all five closed edge types β L3 (RFD Β§5.3). |
context_contracts/chronology.py | chronology_event.v1 β first-class timeline, span-required (RFD Β§5.3 redline item 2). |
context_contracts/views.py | EvidenceHit and ContradictionItem β shared by bundle sections and tool results (broke a bundleβtools import cycle). |
context_contracts/bundle.py | context_request.v1, context_bundle.v1, BundleSection, BuildWatermark β L4 assembly (RFD Β§5.4). |
context_contracts/tools.py | The seven tool result envelopes on a shared ToolResponse (EngineState on every envelope). |
context_contracts/__init__.py, AGENTS.md | Flat re-export surface + package doctrine (purity, additive versioning, provenance-is-law). |
| Criterion | Where it is proven |
|---|---|
| round-trip tests | test_contracts_roundtrip.py::test_roundtrips_through_json β every DTO (one representative each) survives model_dump_json β model_validate_json equal; test_bundle_section_union_rehydrates_each_item_to_its_own_type proves the mixed-type section union. |
| version-discipline tests | test_schema_version_default_is_the_pinned_literal + test_wrong_schema_version_is_rejected (every versioned carrier) + test_unknown_field_is_rejected (extra="forbid") + test_cited_value_requires_at_least_one_span / test_assertion_requires_at_least_one_span (provenance-is-law). |
| no sqlalchemy import (CI-checked) | test_purity.py β a fresh-interpreter import asserts no store module leaks into sys.modules, and an AST scan bans the direct import lines. Runs in CI's pytest. |
CE-1 is contracts only. The stores and RLS (CE-2), the chunker + embed worker
(CE-3), the retrieval + rerank service (CE-5), the Neptune graph substrate and
tenancy helper (CE-8), the normalizer/judge/chronology writers (CE-9β¦CE-11),
the bundle builder (CE-13), the broker pre-fetch (CE-14), and the
cite endpoint (CE-17) all consume these shapes and are their own
stories. A deliberate omission here: the span_id compose helper
and byte-exact resolution belong with the store (CE-2's V-CITE reuse), not the
pure DTO package.
uv run ruff check src/shared/context_contracts tests/unit/context_contracts uv run ruff format --check src/shared/context_contracts tests/unit/context_contracts uv run mypy src/shared/context_contracts # local-only per AGENTS.md uv run pytest tests/unit/context_contracts -q # 55 passed make openapi # no drift β DTOs wired to no route yet
Recall in one sentence: CE-1 freezes the Context Engine's one shared seam β every fact carries a byte-exact exhibit tag it cannot be constructed without, every carrier pins its version, and the package stays pure so a brain can read a citation without importing a database.