feat(inbox): surface adjudication state and its dwell time

This commit is contained in:
Kjell Tore Guttormsen 2026-09-02 14:49:45 +02:00
commit a60312a5f3
9 changed files with 219 additions and 9 deletions

View file

@ -194,6 +194,19 @@ def render_inbox_concept(
frontmatter[policy.offset_key] = _render_flow_list([str(offset) for offset in segment.span])
if segment.parent_id is not None:
frontmatter["parent"] = segment.parent_id
if policy.adjudication_key is not None:
# The per-entry verdict IS the discriminator. A plan-level
# `adjudicated: true` with no record for THIS entry leaves it
# `proposed`: B2 forbids the ratified state without the three keys
# beside it, so a flag alone cannot promote a segment.
verdict = segment.adjudication
if verdict is None:
frontmatter[policy.adjudication_key] = ADJUDICATION_PROPOSED
else:
frontmatter[policy.adjudication_key] = ADJUDICATION_ADJUDICATED
frontmatter["adjudicated_by"] = verdict.adjudicated_by
frontmatter["adjudicated_at"] = verdict.adjudicated_at
frontmatter["adjudication_dwell_s"] = str(verdict.adjudication_dwell_s)
return f"---\n{profile.frontmatter.emit(frontmatter)}\n---\n\n{_normalize_body(text)}"
@ -847,6 +860,23 @@ def process_inbox(
)
#: The adjudication states a segment concept may carry, and the whole set.
#: CLOSED on purpose (PM decision B2, `docs/plan/office-intake.md` § 5): a
#: value outside it is an error, not an extension point. The consumer's half of
#: the same contract is that ABSENCE of the key means `unknown` -- an older
#: bundle -- never a collapse to `absent`, so a producer emitting the key
#: inconsistently would make that distinction unmeasurable on their side.
ADJUDICATION_PROPOSED = "proposed"
ADJUDICATION_ADJUDICATED = "adjudicated"
ADJUDICATION_STATES = (ADJUDICATION_PROPOSED, ADJUDICATION_ADJUDICATED)
#: Written beside the state when, and only when, it is `adjudicated`. The dwell
#: time travels WITH the verdict: a ratified flag carrying no per-item time is
#: unfalsifiable, and it is the same number that makes adjudication throughput
#: measurable at all.
ADJUDICATION_COMPANION_KEYS = ("adjudicated_by", "adjudicated_at", "adjudication_dwell_s")
def _validate_facets(structure: DocumentStructure, profile: BundleProfile) -> None:
"""Refuse a document whose values cannot be rendered as index facets.
@ -859,8 +889,22 @@ def _validate_facets(structure: DocumentStructure, profile: BundleProfile) -> No
markers are this library's own tokens.
"""
assert profile.index.facets is not None
values = structure_frontmatter(structure, profile.index.facets.keys)
policy = profile.segmentation
key = None if policy is None else policy.adjudication_key
# Refused HERE rather than at reprojection, for the reason above: the only
# adjudication value that can be wrong is one the producer declared in the
# dropped document itself, and refusing it per file keeps a single bad
# document from failing the index for every other one.
if key is not None and key in values and values[key] not in ADJUDICATION_STATES:
raise MaterializationError(
f"frontmatter key {key!r} carries {values[key]!r}, which is outside the closed "
f"set {ADJUDICATION_STATES} — the adjudication state is a contract, not an "
"extension point",
code="index_facet_invalid",
)
try:
profile.index.facets.render(structure_frontmatter(structure, profile.index.facets.keys))
profile.index.facets.render(values)
except ValueError as exc:
raise MaterializationError(str(exc), code="index_facet_invalid") from exc

View file

@ -1233,6 +1233,12 @@ SEGMENTED_V1 = BundleProfile(
)
# Bound so the sixth profile's facet extension is typed: `IndexPolicy.facets`
# is `FacetPolicy | None`, and `SEGMENTED_V1` is known here to carry one.
_SEGMENTED_FACETS = SEGMENTED_V1.index.facets
assert _SEGMENTED_FACETS is not None
# The sixth profile. A segmented bundle could not declare which upstream spec
# it targets: `SEGMENTED_V1` names `bundle_id`, `OKF_V0_2` names `okf_version`,
# and the two never intersected. Additive, as upstream support always is here --
@ -1255,7 +1261,21 @@ SEGMENTED_OKF_V0_2 = BundleProfile(
types=OKF_V0_2.types,
frontmatter=OKF_V0_2.frontmatter,
paths=SEGMENTED_V1.paths,
index=replace(SEGMENTED_V1.index, root_frontmatter=("okf_version", "bundle_id")),
# The facet tuple is EXTENDED here rather than shared, and that is the
# discriminator doing its job: `SEGMENTED_V1.index.facets` is one object
# both profiles would otherwise point at, so appending `adjudication` to it
# would surface the state under the older profile too and move a
# byte-pinned golden. `FacetPolicy.render` refuses any key a policy does
# not name, which is why the key has to live here and cannot be added by
# the door at write time.
index=replace(
SEGMENTED_V1.index,
root_frontmatter=("okf_version", "bundle_id"),
facets=replace(
_SEGMENTED_FACETS,
keys=_SEGMENTED_FACETS.keys + ("adjudication",),
),
),
ownership=OKF_V0_2.ownership,
# Constructed rather than `replace`d off `SEGMENTED_V1.segmentation`: that
# attribute is typed `| None`, and the equality is asserted in the suite so