Story-2287 Walkthrough

Change: Blake's segmentation command (Story-2272 / B2a) learns the D26 amendment — a section kind is a ROLE, never a TOPIC — in three places: the coinage instructions, the output contract (a per-coinage self-check), and a deterministic generality gate in the eval corpus. Prompt + contract change ⇒ registrar-segment-v1 → registrar-segment-v2.

Why this exists. The first live UAT of EPIC-2270 coined lack of evidence of AI use and misapplication of AI case law. Both are true. Both are useless: no second document will ever carry "AI use", so each kind is a one-use label that fills the coinage caps, defeats dedupe, and degrades the catalog into per-case noise — destroying the one thing the vocabulary exists for, cross-document recall. The human ruling (RFD-DOC-004 D26 AMENDMENT, 2026-07-12): the kind names the section's function in the genre ("lack of evidence", "prayer"); the case's own subject — parties, technology, dates, exhibit numbers — belongs in heading_path and summary. The 100-documents test: would this exact kind apply verbatim to 100+ unrelated documents? If not, generalize.

The second half of the same ruling (mid-UAT): the same live run also dumped three recognizable grounds of opposition into other ("X. The request for Bar Council liberty is improper" — plainly an argument). other is a LAST RESORT, never a bucket. So coinage has TWO failure modes pulling in opposite directions — too specific (a topic) and too lazy (a cop-out) — and the right altitude sits between them: general enough to recur across matters, specific enough to mean something.

The three surfaces (exactly the issue's scope)

#SurfaceFileWhat lands
1Prompt contractagents/registrar/prompts/segment.py Two new sections. "A KIND IS A ROLE, NEVER A TOPIC": the rule, the 100-documents test, four BAD→GOOD rewrites from the live casualties, and the explicit push of party/technology/date/subject specifics into heading_path/summary. "other IS A LAST RESORT, NEVER A BUCKET": both failure modes named side by side, the nameable-genre-roles list (argument/ground, prayer, synopsis, cause title, verification), and the altitude between them. Self-check steps (4)+(5) and the ambiguity ladder carry both; the paired coinage example now teaches topic ✗ and cop-out ✗ against the role ✓.
2DTO self-checkagents/registrar/segment_models.py SectionGen.kind_generality_check: on every COINED label the model states, in one line, the genre ROLE the label names and that it passes the 100-documents test. Cheap chain-of-verification — no new tool, no extra model call. Null when an existing kind_key is reused (nothing coined, nothing to check).
3Evalagents/registrar/evals/segment_gate.py + corpus generality_violations() — deterministic, per-PR, no key — holds every ACCEPTED kind to the test. New golden case e5df47c7-arbitration-reply-coinage replays the live-UAT scenario so the gate is non-vacuous.

The gate has teeth (the fail-closed half)

A coined label with no kind_generality_check is rejected in assemble_document_segmentation — reason missing_generality_check — exactly like an instruction-shaped label or an unresolved boundary. An unchecked coinage is how the case-specific labels got in; a self-check that the code does not enforce is decoration. Check order is preserved so the SS-13 injection suite is untouched: instruction-shaped label → missing generality check → coinage cap.

kind claim → in vocabulary?           → reuse (no check needed)
            → instruction-shaped?       → REJECT instruction_shaped_label   (SS-13, unchanged)
            → no kind_generality_check?  → REJECT missing_generality_check   (NEW, D26 amendment)
            → over MAX_KIND_PROPOSALS?   → REJECT kind_proposal_cap          (unchanged)
            → else                       → coin

The generality gate — three deterministic checks per accepted kind

CheckFails onWhy
No case-specific tokenthe document's own subject words (forbidden_kind_tokens pinned per golden case: ai, annexure-a, cw-1, respondent, …)the exact live-UAT failure: lack of evidence of AI use
No digitsobjection under paragraph 14 of the replya number in a kind is always this case's fact
Role-shaped> 6 words — response to the six categories in Annexure-Aa kind is counsel's noun phrase, not a sentence about this document
No other bucket (whole-document)> MAX_OTHER_SECTIONS = 1 sections falling to other/miscellaneous/unclassifiedthe opposite failure mode: the live run dumped three grounds of opposition into other. One unclassifiable part is a last resort; three is a bucket.

The same function grades the live half (measure_live_golden), so the real model's coinage is held to the identical bar — the deterministic gate is the merge blocker, the live run is periodic (RFD-SEC-002 §3).

The corpus case that makes it non-vacuous

The existing e5df47c7-arbitration-reply case coins nothing — its vocabulary already contains every kind it needs, so a generality gate over it would be vacuously green. e5df47c7-arbitration-reply-coinage is the SAME 14pp reply, the SAME byte-verified boundaries, replayed against an empty-ish vocabulary — the live-UAT condition (first document, catalog nearly bare). Blake must coin, and what he coins is what the ruling demands:

Heading (this case's facts)Live UAT coined (TOPIC) ✗This corpus pins (ROLE) ✓
III. THE RESPONDENT HAS PRODUCED NO EVIDENCE OF AI USElack of evidence of AI uselack of evidence
IV. THE RESPONDENT'S RELIANCE ON AI CASE LAW IS…misapplication of AI case lawmisapplication of case law
I. PRELIMINARY SUBMISSIONS—preliminary submissions
XIV. PRAYER—prayer

All 17 sections still accept, zero rejects, and the (page, block) ranges are byte-identical to the reuse case — only the kind text differs. That is the point: the ruling changes the vocabulary, never the boundaries.

Pinning — every run stays attributable

_SEGMENT_PROMPT_VERSION = "registrar-segment-v2" (agent.py), mirrored in the eval fixture pin and the assembly-test pin. The D14 quadruple (model_id + prompt_version + input hash + decoding params) rides every document_sections.v1 artifact, so a section coined under the old rules and one coined under the amendment are never confused. Untouched: the pinning fields themselves, the injection suite (segment_injection_cases.jsonl, 6 cases, green), the boundary gate, D27 windowing.

Out of scope (per the issue, deliberately)

Verification

uv run --package registrar pytest agents/registrar/tests -q   → 334 passed
uv run --package registrar pytest agents/registrar/tests/test_registrar_segment_injection.py -q
                                                              → 6 passed (injection suite untouched)
uv run pytest tests -q                                        → 179 passed (governance)
uv run ruff check agents/ tests/ scripts/                     → All checks passed

New tests: test_every_golden_kind_passes_the_100_documents_test, test_coinage_case_coins_role_kinds_not_the_live_uat_topics, test_generality_gate_is_not_vacuous_the_live_uat_casualties_fail (the gate is fed the actual 2026-07-12 casualties and must reject them), test_coinage_without_the_generality_self_check_rejects, test_other_is_a_last_resort_never_a_bucket (one other passes, three fail), test_no_golden_case_dumps_its_grounds_into_other (the D26 eval bar: the golden reply's grounds coin an argument/ground-class kind — legal_submissions on the reuse case, coined roles on the coinage case — never other).

Refs: pramaan-io/pramaan-spec#2287 (this Story) · #2270 (EPIC) · #2272 (B2a) · RFD-DOC-004 D26 AMENDMENT (human-ruled 2026-07-12) · PROMPTING.md · RFD-SEC-002 §3.