Story-2272 Walkthrough

Change: Blake gains the SEGMENTATION command — the third act of Registration (RFD-DOC-004 D24: Identification → Collation → Segmentation) — plus the Collation hypothesis on the Identification (card) pass. Agent half only (B2a): the fan-out wiring, tools and queues are B2b; cut-point verification and child minting are B2d.

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

PieceFileWhat it does
Typed I/O + assemblyagents/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.
Promptagents/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).
Orchestrationagents/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 seamruntime.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 hypothesiscard_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

RuleEnforcement
Boundary = (page, block) range, byte-verifiedassemble_document_segmentation: resolve_quote_span on BOTH edges; reject on either miss, on unordered ranges, recorded with reasons
Document text is evidence, never instructionanchored 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-authoredkind_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 CIevals/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 slotcounsel_note input slot ships now, fenced "context, never evidence", content arrives in a later ticket

The golden corpus (evals/segment_golden_corpus.jsonl)

CaseProves
e5df47c7-arbitration-replythe 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-fallbackan unstructured scan yields an EMPTY section list (correct; the flat single-section fallback is B2b's, functions-side)
oversize-progressive-windows5 planned windows with 1-page overlap merge into the SAME artifact shape; read_mode="progressive" recorded; overlap duplicates deduped
fabricated-boundary-rejecteda 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)