feat(prepass): the excerpt's new fields reach the prompt, po stops dropping them twice

P2 measured that the producer's payload now carries title/req_number/sources/source_* on
every excerpt (14 members, was 9), but PrepassExcerpt ignored them (extra="ignore") and
_data_blocks rendered only concept_id/adjudication/trust_tier -- so (b') was a po verdict,
never a model verdict. title/req_number/sources are now named fields; source_* locators are
read via model_extra and a prefix scan (measured: the producer treats source_* as an
open-ended family, not a fixed allowlist), so a future producer's new source_foo key reaches
the prompt without a code change here. A P1-form payload renders byte-identical to before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-08 16:42:17 +02:00
commit 2453246d4b
5 changed files with 406 additions and 3 deletions

View file

@ -110,6 +110,16 @@ class PrepassDenominators(_Permissive):
delivered: int
class PrepassSource(_Permissive):
"""SS 8's declared address for an excerpt: where the underlying source document lives.
A fixed, singular member (never a growing family like ``source_*``) named on purpose.
"""
resource: str
title: str | None = None
class PrepassExcerpt(_Permissive):
"""One delivered unit of bundle content.
@ -117,8 +127,21 @@ class PrepassExcerpt(_Permissive):
names no content member at all a payload can be fully conformant and carry nothing to read,
and this seam's entire value is the content. Requiring them refuses that case by name instead
of discovering it as an empty prompt.
``title``, ``req_number`` and ``sources`` are named fields because they are SS-8-declared,
singular members every payload has at most one of each. ``source_*`` locators
(``source_element_id``, ``source_sha256``, and whatever a future producer adds) are NOT named
here: measured (P3, order 20260908T141941Z), the producer's own message calls that a PREFIX
RULE rather than a fixed allowlist a real corpus already carries five such members where
another carries two. Naming two of them would need a new field and a new render line for a
third; ``extra="allow"`` plus :meth:`source_locators`' prefix scan needs neither. Every field
here defaults to ``None`` (or, for ``sources``, absent) because most payloads in this
repository predate P2's producer change and carry none of them — a required field would
refuse every payload written before today.
"""
model_config = ConfigDict(extra="allow")
bundle_id: str
concept_id: str
sha256: str
@ -126,6 +149,26 @@ class PrepassExcerpt(_Permissive):
trust_tier: str
text: str
text_sha256: str
title: str | None = None
req_number: str | None = None
sources: tuple[PrepassSource, ...] | None = None
def source_locators(self) -> tuple[tuple[str, str], ...]:
"""Every ``source_*`` scalar the producer attached, sorted by key.
Reads ``model_extra`` rather than a named field for each one the prefix-rule reason
this class's docstring gives. ``sources`` itself is excluded: it is a named field (a list,
never a scalar) and does not land in ``model_extra`` in the first place, but the guard
is explicit rather than relying on that.
"""
extra = self.model_extra or {}
return tuple(
sorted(
(key, str(value))
for key, value in extra.items()
if key.startswith("source_") and key != "sources"
)
)
class PrepassWithheld(_Permissive):
@ -505,14 +548,39 @@ def _declaration_lines(payload: PrepassPayload, *, rest_reachable: bool = False)
]
def _excerpt_header(excerpt: PrepassExcerpt) -> str:
"""The BEGIN line for one excerpt: ``concept_id`` plus whatever the producer named it with.
**Known-negative, load-bearing:** a P1-form excerpt (none of the five new fields) renders
BYTE-IDENTICAL to before P3 the loop below appends nothing, and the line is exactly the
old ``(adjudication: ..., trust_tier: ...)`` form
(``test_a_p1_form_payload_renders_the_header_exactly_as_before``). Carrying a field through
must never change what an older payload renders as.
``req_number`` and ``title`` are what a person would cite; the address (``sources[0].resource``)
and every ``source_*`` locator are what lets a claim be traced back to the document that
produced it measured (P2 SS 4) to be the exact thing modelled proposals cited a UUID instead
of, because the UUID was the only identifier that reached the prompt.
"""
fields = [f"adjudication: {excerpt.adjudication}", f"trust_tier: {excerpt.trust_tier}"]
if excerpt.req_number is not None:
fields.append(f"req_number: {excerpt.req_number}")
if excerpt.title is not None:
fields.append(f"title: {excerpt.title}")
if excerpt.sources:
fields.append(f"source: {excerpt.sources[0].resource}")
for key, value in excerpt.source_locators():
fields.append(f"{key}: {value}")
return f"--- BEGIN DATA {excerpt.concept_id} ({', '.join(fields)}) ---"
def _data_blocks(payload: PrepassPayload) -> list[str]:
"""The delimited DATA blocks, one per delivered excerpt (SS 9.3), shared by both arms."""
lines: list[str] = []
for excerpt in payload.excerpts:
lines += [
"",
f"--- BEGIN DATA {excerpt.concept_id} "
f"(adjudication: {excerpt.adjudication}, trust_tier: {excerpt.trust_tier}) ---",
_excerpt_header(excerpt),
excerpt.text,
f"--- END DATA {excerpt.concept_id} ---",
]