Field States, Carry-Forward and Score-Blind Audit#
Decision#
Field states are derived, never stored. deriveFieldStates reads import
records, decisions, and optionally verifier records, carry-forward records and
supersessions, and returns a content state and a lifecycle state per claim
slot. A state appears only when the input that produces it is present.
- Content:
value,conflicting(two or more distinct values),unsupported(every candidate has a contradicted or not-addressed verifier record for its current value and none supported; precedence over the other two),excluded(proposals were made for the field but the import left every one out because its excerpt was not at its span: the field was read, so it is nevernot_covered),not_covered(no proposal at all, and the run was partial or failed). - Lifecycle:
pending,accepted,rejected,could_not_confirm,superseded(a supersession names the decision). - Signals, not states:
incompleteRun,excludedProposals,unresolvedImport.
not_found, stale, held, stated_none and not_applicable are not
shipped: no producer computes them. A field with no candidate in a complete run
has no content state, because chunk coverage does not show that one field's
proposal was not dropped. A value from a partial run stays value but carries
incompleteRun, so a run that lost content never reads as complete.
Unit. One import record is one source extraction; states are keyed by
(import, claim slot), where the slot is the importer's grouping identity
(subject, facet, claim type, field, claim id, path indices). A field with no
proposal appears only when the caller passes expectedFields. Merging slots
across imports of different sources is not done.
Surface projection. buildCanonicalReviewedTrustInput({ fieldStates })
derives the states from the imports and the results' own decisions and writes
them under claim metadata key survey.kontourai.io/field-state
({ schemaVersion: 1, content?, lifecycle?, decisionBasis?, signals? }).
Claim status is unchanged.
Carry-forward. splitRoundForCarryForward takes the new round's items with
producer slotId and per-candidate version ids (Survey validates presence and
never invents them) and the prior round's decisions. An item carries forward
only when exactly one live prior decision names its slot, the version-id sets
are equal, and each candidate's value, full locator (scheme, locator, excerpt),
source ref, source checksum and full claim target (subject, field, impact and
the rest) are unchanged. The last check means a decision never carries across
changed content even if a producer reuses a version id. By default only
accepted carries; rejected and could_not_confirm carry only under an
explicit policy.carry. The output is a content-addressed
DecisionCarryForward record naming the prior decision by name and digest.
buildDecisionSupersession records candidate-changed or re-reviewed.
The vocabulary is carried-forward versus affirmed, matching the round
receipts producers already keep. Field states expose it as decisionBasis.
Score-blind audit. A session's presentation: { scoreBlind } and
sampling sit in the snapshot, and every decision event is stamped with them
(data.sessionConditions). Replay refuses an event whose stamp differs from
the snapshot's, so sighted events cannot be replayed over a blind snapshot and
a snapshot's presentation cannot be flipped after decisions were recorded.
Full tamper evidence needs a ReviewQueueBinding: without one (the MCP and
console file sessions), an edit that changes the snapshot and every event
stamp together is not detectable. The session validates sampling (rate in
(0, 1], seed a stable identity). Calibration takes outcome metadata at its
word, so it should only read outcomes from Survey's apply path. In a score-blind session the
workbench, the MCP item text, data and card, and the recorded decision prompt
show no confidence and no verifier result, before or after a decision.
Excerpt-verification notes and excluded proposals stay visible: they are about
the evidence, not a verdict on the value. Decisions copy both fields into
ReviewDecision.spec and the canonical projection copies them into
ReviewOutcome.metadata. drawRandomAuditSample uses a per-item SHA-256
draw, so the same seed gives the same sample in any input order.
deriveCalibration({ auditSamplesOnly: true }) keeps only score-blind
random-audit outcomes. It is off by default.
Provenance. status.provenance is required on import records (breaking).
Left open#
- Field-slot and version identities from the extractor (kontourai/traverse#172); until then callers compute them.
not_found: needs per-field chunk coverage and a dropped-proposal count.- Carried-forward decisions are records, not
ReviewDecisions, so they do not yet project to Surface claims. A carried accept that had an edit points at the prior decision for the edited value. - The MCP server does not show field states. The workbench panel lists every
field, including unread ones, when the host passes
fieldStates. - A score-blind browser page still holds the item JSON in memory. The blind mode controls what is rendered, not what a determined reviewer could inspect.