Why this exists. The pipeline knows where ink sits but not
what it is: the evidence doc's cause title is 5 layout blocks, "prayer" exists nowhere. This
story makes Blake ABLE to name a filing's parts — cause title, preliminary submissions,
prayer — each with a boundary a court could re-read byte-for-byte.
The shape — same discipline as the card pass, deliberately
| Piece | File | What it does |
| Typed I/O + assembly | agents/registrar/segment_models.py |
Flat gen DTO (extra="forbid", identity-free) → document_sections.v1 DTO in CODE. Both boundary quotes byte-resolve via resolve_quote_span or the section is REJECTED — never fuzzy-matched. |
| Prompt | agents/registrar/prompts/segment.py |
PROMPTING.md contract style: recursive injection fence, reuse-first vocabulary, verbatim boundary quotes, ambient×structured output cell, self-check, ambiguity ladder, paired good/bad example. Delivered via instructions= (the locked PROMPTING.md rule; older passes predate it). |
| Orchestration | agents/registrar/agent.py::run_segmentation |
D27 deterministic switch (TOKENS, never pages): whole-read vs progressive windows (1-page overlap, absolute [p:b] addressing, merged then assembled ONCE against the full page index). Never raises (D13). read_mode + window_count recorded. |
| Runtime seam | runtime.py mode "segment" → events.document_sections_event |
Mirrors the card handler: parse-before-run.started, always completes with a document.sections effect. This is the DTO contract B2b consumes. |
| Collation hypothesis | card_models.py::assemble_bundle_hypothesis + card prompt § |
D25 agent half: on a bundle with an Index of Documents, the card TRANSCRIBES it — compound + bundle_hypothesis rows with claimed start pages, each row cited from the index block and byte-resolved. No surviving citation ⇒ compound=False. Card prompt bumped to registrar-card-v2. |
The non-negotiables, and where each is enforced
| Rule | Enforcement |
| Boundary = (page, block) range, byte-verified | assemble_document_segmentation: resolve_quote_span on BOTH edges; reject on either miss, on unordered ranges, recorded with reasons |
| Document text is evidence, never instruction | anchored fence (shared with the card) + SS-13 suite: evals/segment_injection_cases.jsonl (indirect · fence-escape · exfiltration · boundary-forgery) proven against the ASSEMBLY layer per-PR; prompt-extraction rides the live half's no-leak canary (measure_live_injection) |
| Kinds reuse-first; identity never model-authored | kind_key must be in the tool-provided vocabulary; coinage = label TEXT only, instruction-shaped labels rejected, D26 caps; gen DTOs carry no firm/matter/document ids and forbid extras (a smuggled firm_id is a ValidationError) |
| Pinning (D14) | SegmentationPinning: model_id + prompt_version + input hash + decoding params on every run |
| Golden diff gate in CI | evals/segment_gate.py::golden_range_diff gated by tests/test_registrar_segment_golden_corpus.py — ANY drift in (kind, page, block) ranges fails the PR |
| D23 note slot | counsel_note input slot ships now, fenced "context, never evidence", content arrives in a later ticket |
The golden corpus (evals/segment_golden_corpus.jsonl)
| Case | Proves |
e5df47c7-arbitration-reply | the real 14pp native reply segments into 17 sections — cause title, preliminary submissions, twelve legal-submission parts (II–XIII), prayer, without-prejudice, signature block — all boundaries byte-resolved, zero rejects |
scan-ocr-flat-fallback | an unstructured scan yields an EMPTY section list (correct; the flat single-section fallback is B2b's, functions-side) |
oversize-progressive-windows | 5 planned windows with 1-page overlap merge into the SAME artifact shape; read_mode="progressive" recorded; overlap duplicates deduped |
fabricated-boundary-rejected | a paraphrased boundary quote is rejected (boundary_unresolved) — the gate is not vacuous |
Verification
uv run --package registrar pytest agents/registrar/tests -q → 328 passed
uv run pytest tests -q → 179 passed (governance)
uv run ruff check agents/registrar/ → clean
python scripts/check_brain_egress.py → clean
python scripts/check_prompt_leakage.py → clean
New offline tests: test_registrar_segment_assembly.py (boundary/kind/cap/pinning
gates), test_registrar_segment_windows.py (D27 switch + windows + fail-closed),
test_registrar_segment_golden_corpus.py (the CI golden diff gate),
test_registrar_segment_injection.py (SS-13),
test_registrar_card_bundle.py (D25 hypothesis). Live halves
(measure_live_golden / measure_live_injection) are periodic,
key-gated, never the merge gate — the repo's RFD-SEC-002 grading contract.
Out of scope (owned downstream)
- B2b: fan-out sub-step,
list_section_kinds/propose_section_kind Lambda tools, registry/policy entries, server-side identity resolution, flat fallback + re-seg queue.
- B2d: deterministic cut-point verifier (±3 snap), logical child minting, auto-split undo.
- pramaan-docs ADR-165 lands with B1's ADR twin; act names used here per RFD D24.