Story 2169: teach the enrichment chain to interpret a document, not summarize it

WHY. The catastrophe this Story prevents is a citation that lies. The Document Page (app Story-2168) renders an insight brief per document — the founder’s real bar: “Para 30 — solvency admission locked in… if they later plead inability to pay, this order is the answer.” The one way a brief like that turns into “a hallucination machine wearing a citation costume” (desk RFD §2) is to blur the three context rings: to paraphrase a paragraph and pin the paraphrase, or to dress the model’s own legal knowledge as a fact in the record. A court strikes the evidence the instant a quote is not in the document. So this seat is built so that cannot happen: the model proposes claims and candidate quotes; a deterministic byte-verify gate builds and checks every span; and the three-source ring law is a hard contract in the type system, not a UI nicety.

🛰 Satellite — the one sentence

A new LLM-backed enrichment step inside the existing context-embed worker takes a registered document’s card + the matter’s assertions, asks a frontier model (through the OpenRouter adapter, ADR-120) for a document_insight_brief.v1 — headline, punch cards, positions/admissions, silences, authorities — then byte-verifies every record pin fail-closed (an unverifiable pin drops its item, never ships approximate) before upserting it into a new firm-scoped context.document_insight_briefs table that the app reads over one GET.

✈️ Airplane — a scribe who must point at the page, and a librarian who may not

Picture two people reading the order beside counsel. The scribe may only say things he can point at on the page — every claim he makes, his finger is on the exact words (this_document), or on the exact words of another filing in the matter (matter). The librarian knows the statute and the precedent by heart, but she has no page to point at — so whenever she speaks, her sentence is stamped “general law — verify” and she is forbidden from putting her finger on the record at all. This Story is the rule that keeps the scribe honest (the byte-verify gate: if his finger isn’t on those exact bytes, the claim is dropped) and keeps the librarian’s knowledge from ever wearing the scribe’s costume (the ring law: a general_law claim that carries a record pin cannot even be constructed).

The three rings (desk RFD §2), enforced in the type. InsightPin is the atom every claim carries: A @model_validator makes the wrong combination fail construction. The hard-contract test (test_insight_brief_contract.py) was written first.

🔬 Microscopic — the moving parts

1. Where the seat sits — inside the enrichment chain, no new runtime

The seat is a step in the existing context-embed worker (src/workers/context_embed/handler.py), run after process_embed for the outcomes that mean “a live record document with pages exists” (EMBEDDED / NO_NEW_CHUNKS). No new AgentCore runtime, no new queue, no new deploy: the backfill/enrich re-drive already re-kicks this worker, so freshness v1 (recompute on re-drive, desk RFD §5) comes for free — a re-drive after tomorrow’s upload re-runs the seat even when the document’s own chunks are unchanged, because the matter universe grew. The seat is isolated: a model/JSON failure is logged and swallowed (never a batch-item failure, never a poison-pill loop) — the embed already committed and the brief is re-drivable.

2. Citations are data flow + a gate, never a “citation agent”

The model never emits a SourceSpan. It returns a lean draft: for a this_document claim, {page_number, block_index, quote}; for a matter claim, an index into the numbered assertion list it was given; for general_law, nothing. The seat (src/shared/context/insight_brief.py) then builds each span (deterministic span_id, the real document_id/segment_kind) and verifies it against the confidence-floored page bytes via the live V-CITE resolver (span_resolution.resolve_span — one byte-verification semantic in the codebase). A quote that is not byte-present drops its item. matter pins reuse the assertion spans already byte-verified at their own ingest; general_law carries no span to verify.

draft pin {source:this_document, page_number:1, block_index:0, quote:"..."}
        → _build_span(...)  → resolve_span(span, admitted_pages).verified?
              → True:  keep, as InsightPin(source="this_document", span=span)
              → False: drop the whole item  (never approximate)

3. The contract — document_insight_brief.v1

A pure context_contracts DTO (no sqlalchemy/boto3/settings — the purity test guards it): headline (NonCitable display prose) · punch_cards[] (lead, consequence, a primary pin, corroborating extra_sources[]) · positions_admissions[] · silences[] · authorities_cited[], plus generated_at + matter_version for the counsel-facing “as of” freshness stamp, and an EnrichmentStamp (reused from the card) for provenance.

4. Storage + read door

Migration 0162 adds context.document_insight_briefs — one current brief per document, upserted whole per run, RLS + grants cloned from context.document_cards (firm isolation + staff-with-grant read, SELECT/INSERT/UPDATE only, DELETE revoked; registered in the RLS-isolation confidential-table registry). The app reads it over GET /app/documents/{doc_id}/insight-brief (appGetDocumentInsightBrief, matters:read), which re-validates the stored JSONB through the contract (HR-21) and 404s until the first enrich run produces it.

5. Ambient seat — no cache writes (S-9)

This is a background, non-interactive call. It sets no Anthropic cache_control, so it requests no prompt-cache creation — the S-9 platform-wallet guard has no cache-write spend to block. Provenance is recorded (ADR-120 D11) from the adapter result in the same write transaction as the brief upsert.

What was verified

CheckResult
Hard contract: general_law never carries a record pin (written first)green (unit)
Byte-verify fail-closed: fabricated quote drops its item; byte-exact survivesgreen (unit + golden eval)
Why-Cite golden: ≥3 punch cards, every shipped pin re-verifies byte-exactgreen (eval)
Contract round-trip + version-pin (extends CE-1 harness)green
Migration upgrade / downgrade / re-upgrade on real Postgres; table + 2 RLS policies + grantsgreen
System boundary: store + read as pramaan_app, incl. empty first-run 404 + idempotent upsertgreen (integration)
RLS isolation registry, embed-worker integration, observability guardrail, permission map, OpenAPI snapshotsgreen

Sequencing note. Build + tests are complete now. Live evidence on the Order_S.17 doc (3ee1dc28) follows #2162 (embed-worker deploy, in CI) landing + an enrich re-drive, and requires OPENROUTER_API_KEY in the embed worker’s Lambda env (a deploy-workflow env line, owned by #2162’s workflow — called out in the PR).

One-sentence recall: the Insight seat interprets a document in its matter’s context and lets counsel point at any claim — because the scribe’s finger is byte-verified on the page and the librarian’s knowledge is never allowed to wear the scribe’s costume.