Severity: p1
Area: engine-store
Slug: corruption-record-identity-omits-provenance
Claim
The corruption record's producer identity is (keyDigest, path, recordedDigest, measuredDigest) with sourceSeq: 0 and no recorded-run/event component, while the payload carries recordedRunId/recordedEventSeq outside the identity. Since #164 every detection evicts and the next dispatch re-records under new provenance; a host that corrupts the fresh row identically (same path, same digests — the plausible repeat-fault case) emits an exact producer duplicate that the journal collapses, so a repeatedly-corrupting host leaves exactly one durable corruption record per observing run, and that record names a recordedRunId/recordedEventSeq of a row eviction already destroyed.
Evidence
Failure scenario
Host re-corrupts the freshly healed row identically (same path, same recorded/measured digests — natural, since a hermetic re-capture reproduces the same recordedDigest). noteCorruption re-emits an exact producer duplicate; the journal collapses it (or fails with idempotency_conflict, swallowed). The second incident leaves no durable record, and the surviving record's recordedRunId/recordedEventSeq name a row generation already evicted.
Verifier confirmation
Code matches the claim at every cited line. One scope correction to the finding's framing: dedupe is keyed on (runId, sourceId), so it is one record per observing run, not literally one "ever" across runs — but the defect (re-corruption after healing within a run is unjournalled, and the record's provenance points at an evicted generation) is real, and the proposed fix of folding recordedEventSeq (or recordedRunId+recordedEventSeq) into the sourceId is the right shape, so each poisoned row generation journals once.
Found by the iterate-until-dry loop, round 15 review.
Severity: p1
Area: engine-store
Slug: corruption-record-identity-omits-provenance
Claim
The corruption record's producer identity is
(keyDigest, path, recordedDigest, measuredDigest)withsourceSeq: 0and no recorded-run/event component, while the payload carriesrecordedRunId/recordedEventSeqoutside the identity. Since #164 every detection evicts and the next dispatch re-records under new provenance; a host that corrupts the fresh row identically (same path, same digests — the plausible repeat-fault case) emits an exact producer duplicate that the journal collapses, so a repeatedly-corrupting host leaves exactly one durable corruption record per observing run, and that record names arecordedRunId/recordedEventSeqof a row eviction already destroyed.Evidence
packages/engine-store/src/Inconsistency.ts:166-168— sourceIdflows/engine-store/inconsistency:corruption:${keyDigest}:${path}:${recordedDigest}:${measuredDigest}, constantsourceSeq: 0.packages/engine-store/src/Inconsistency.ts:176-177—recordedRunId/recordedEventSeqare payload-only;:182—idempotency_conflictis swallowed.packages/journal/src/Journal.ts:184-185—sourceSeqis assigned per(runId, sourceId)and is the producer idempotency key, so the dedupe identity contains no recorded-provenance component.packages/engine-store/src/internal/ActivityPersistence.ts:475-495— the round-15: Cache-hit corruption never evicts the poisoned row #164 quarantine path: every cache-branch detection journals then evicts, so tolerant re-execution re-records under a new(recordedRunId, recordedEventSeq).Failure scenario
Host re-corrupts the freshly healed row identically (same path, same recorded/measured digests — natural, since a hermetic re-capture reproduces the same
recordedDigest).noteCorruptionre-emits an exact producer duplicate; the journal collapses it (or fails withidempotency_conflict, swallowed). The second incident leaves no durable record, and the surviving record'srecordedRunId/recordedEventSeqname a row generation already evicted.Verifier confirmation
Code matches the claim at every cited line. One scope correction to the finding's framing: dedupe is keyed on
(runId, sourceId), so it is one record per observing run, not literally one "ever" across runs — but the defect (re-corruption after healing within a run is unjournalled, and the record's provenance points at an evicted generation) is real, and the proposed fix of foldingrecordedEventSeq(orrecordedRunId+recordedEventSeq) into the sourceId is the right shape, so each poisoned row generation journals once.Found by the iterate-until-dry loop, round 15 review.