Skip to main content

Module chain_validator

Module chain_validator 

Source
Expand description

P7-S5 · the chain validator (P2 §5 rules C1–C4 + C7). Host-ops tooling — CI and incident triage run the same knife — never part of an SDK runtime path. P7-S5 · the chain validator, batch 1: rules C1–C4 with C7 degradation marking (P2 §5).

Host-ops tooling, not an SDK runtime path: CI gates and incident triage run the same knife, and C3 needs the deterministic transition (re-plan), which only the core can perform. The CLI half is src/bin/ds-chain-validator.rs; this module is the verdict logic.

Input is a journal prefix — a sequence of opaque record byte blobs. Records are grouped into per-operation chain segments and every segment is judged independently. Nothing here ever re-serializes a record: blobs pass through untouched, so a self-digest verdict is a verdict about the bytes the host durably wrote.

The rules, and where each one gets its teeth:

  • C1 · chain integrityrecord[i].previous_record_digest == digest(record[i-1]), step_seq strictly +1, genesis previous_record_digest = None. Complete segments go through [verify_record_chain]; a segment with degraded hops falls back to checking every link whose digests survived.
  • C2 · input idempotency — one input_id never yields two different records: a retry must reach the same record. Grouped per operation (the idempotency key’s namespace).
  • C3 · causal closure — every record’s resolved effect must be reproducible by re-planning the earlier records. This is the §12.2 restore ladder’s genesis leg ([restore_operation] with no checkpoint): chain verify + deterministic re-plan + per-step record-digest comparison. It doubles as the re-plan determinism regression gate — the direct gate for 0.2.62-class “this binary does not reproduce the history it is resuming” incidents. If C1 failed, C3 reports degraded rather than re-reporting the same break.
  • C4 · task lineage — the journal-direct half: every (task_id, attempt_id) launch pair appears at most once (the launch token is derived from that pair, so a repeated pair is a reused token), and a spawn resolution names an effect the same operation published at an earlier step. The parent chain itself is not journaled; it holds structurally under C3’s re-plan because an orphan spawn has no outstanding effect to resolve. The durable launch-token ledger lives in checkpoints — batch 2 territory. Both limits are named in [ValidationReport::deferred].
  • C7 · degradation — an old-format hop (strict decode fails but the identity fields survive) degrades the checks that need the missing fields instead of failing them. A proven digest mismatch fails C1 even when identity fields survive; every degraded hop is marked on its segment’s report. A blob that is not a record at all counts as unparseable input, which is an exit-code-2 condition (“evidence insufficient”), never a violation.

§Batch 3 · the SessionLog input plane (0.2.64 S4)

[validate_with_session_log] adds a second plane: SessionLog event streams. SessionLog is Evidence Truth (P6 §S) — never recovery authority, and never kernel input. Where the journal plane is order-independent blobs, a session log is one file’s append-ordered events, so the input is a list of streams (one per file) whose internal order is preserved.

The core has no typed SessionLog vocabulary (P6: the core treats SessionLog as opaque JSON), so events are classified leniently: the kind field picks the extraction shape, missing additive fields parse as absent, and both spellings of host-nested fields are accepted (route.routeId from node, route.route_id from python). Unknown kinds are parseable but ignored — the vocabulary evolves; only C6/C8-relevant kinds are extracted. An event that is not a JSON object at all counts as unparseable (exit-code-2), exactly like the journal plane.

C7 carries across planes: old logs simply lack provider_attempt / the additive fields — the rules that need them degrade, never fail.

§Batch 2 · the checkpoint input plane (0.2.65 S1)

[validate_with_checkpoint] adds the third plane: one or more logical checkpoints (§12). A checkpoint is a claim about the journal — “this logical state was captured at step N, anchored by these digests” — and C5 is the rule that makes the claim answer to the bytes. Without a checkpoint input, C5 is deferred, not red (the C7 philosophy: a plane nobody handed over cannot fail).

  • C5a · checkpoint anchoring — the checkpoint’s genesis_digest names the journal’s genesis record, its covered head names the record at through_step_seq, and every bounded-tail entry whose journal record survives carries that record’s digest. A present record with the wrong digest is a proven contradiction (fail); a pruned or missing record is unverifiable (degrade) — retention is not a crime.
  • C5b · the launch-token ledger — the durable half of C4: within one checkpoint the same launch token may not name two mints at different steps (reuse across TaskLaunch payloads), every pending SpawnTasks effect must carry tokens the ledger registered at the effect’s own step, and no ledger entry may sit beyond the covered boundary. Under --strict the re-plan fold must re-derive the exact ledger.
  • --strict · the re-plan replay — the journal is folded from genesis through the covered step through the same restore path C3 uses, and the re-derived checkpoint must carry the checkpoint’s state_digest; the checkpoint+tail restore ladder must also hold against the journal above the covered step. Cost is one full fold — explicit request only.

Structs§

DegradedHop
A hop whose strict record decode failed but whose identity fields survived — the C7 marking.
RuleReport
One rule’s verdict on one segment.
SegmentReport
One operation’s chain, judged independently.
SessionStream
One session-log file, classified: its events in append order plus the count of blobs that were not JSON objects at all.
ValidationReport

Enums§

EvidenceEvent
A SessionLog event, leniently classified. Only the kinds C6/C8 read are extracted; every other kind — known or future — is Other. Field absence is data (C7 degrades), not error.
Verdict

Constants§

UNATTRIBUTED_SEGMENT
The pseudo-segment for degraded hops whose operation_id did not survive. Kept obviously synthetic so a report reader never confuses it with a real operation.

Functions§

validate_journal
Validate a journal prefix: a sequence of opaque record byte blobs, in any order. Records group into per-operation segments, each judged independently; blob order never matters because the chain’s own step_seq/digest links define the order.
validate_with_checkpoint
Batch 2 entry point: the journal plane plus whichever evidence planes the caller holds. An empty session_streams means journal-only; an empty checkpoint_blobs means C5 is deferred, not run. strict arms the re-plan replay (C5’s --strict): each checkpoint’s journal prefix is folded from genesis through the covered step and both the state digest and the launch-token ledger must re-derive exactly. Strict costs one fold per checkpoint and is meaningless without checkpoint blobs.
validate_with_session_log
Batch 3 entry point: the journal plane plus SessionLog evidence streams. Each inner slice is one session-log file’s events in append order — unlike journal blobs, event order within a stream is meaningful (a run_started delimits the run its following attempts belong to). Streams never cross-join: fingerprint and route-stability checks are per-stream.