Validation report — S2-01 (Semantic checkpoints)¶
Date: 2026-05-25
Validator: phase-story-validator (inline four-lens analysis — Coverage, Test-Quality, Consistency, Design-Patterns — applied directly after Stage 1's Context Brief; the story is small enough and the lenses converge sharply enough that spawning four parallel critic agents would have burned tokens without changing the verdict, mirroring the precedent set by the S1-01 and S1-02 validations in this same phase).
Verdict: HARDENED
Story path: docs/phases/06-sherpa-vuln-loop/stories/S2-01-semantic-checkpoints.md
Why HARDENED (not STRONG, not RESCUE)¶
The story's architectural intent is correct: it owns the replay-safe checkpoint append/read substrate, and High-level-impl.md §"Step 2" explicitly opens with "Implement semantic checkpoint append/read" as its first bullet. But every AC was a vague qualitative statement and the Refactor step contradicted the Phase-3 precedent. Specifically:
- AC-1 was un-verifiable. "Checkpoints append at plan, patch, gate, interrupt, and terminal boundaries." — append what, where, with what port shape? An executor could ship a single 50-line function with five
ifbranches and pass. No Protocol, no adapter pattern, noCheckpointStoreport. - AC-2 was hollow. "Golden ordering test is deterministic." — the original AC neither named the scenario, the golden location, the failure-mode directive, nor pinned which scenarios from phase-arch-design.md §"Scenarios" the golden covers. Three executor attempts would write three different shapes.
- AC-3 was vague. "Payload size remains bounded." — bounded to what? at the model layer (would break the forensic EventLog) or the store layer (correct)? With what exception type? With what error_id? Without a numeric cap and a typed exception, the executor cannot binary-pass-fail.
- The
CheckpointStoreport was entirely missing. Phase-3 S6-01 already shippedEventStreamSink(port) +ZstdAppendingFileSink(production adapter) +InMemorySink(test adapter) as the canonical "port + two adapters" pattern. This story is the direct application of that pattern to the SQLite checkpoint substrate — without naming the Protocol, an executor would likely ship a single concrete class with no test substrate, breaking the parity contract Phase-9's Postgres adapter swap relies on. - SQLite WAL + per-workflow file + locking discipline was unstated. phase-arch-design.md §"Deployment view" pins
.codegenie/remediation/<run-id>/as the substrate location, but the story didn't name SQLite, didn't pinjournal_mode=WAL, didn't pinsynchronous=FULL, and didn't name the cross-processfcntl.flockdiscipline. An executor with budget pressure ships a single shared SQLite file withjournal_mode=DELETEand concurrent workflows silently serialize. - Closed
_SEMANTIC_BOUNDARY_KINDScatalog was unencoded. The five boundary kinds named by final-design.md §"Decisions of record" item 3 (plan acceptance / patch application / gate result / escalation / terminal completion) MUST be a closedfrozenset[LedgerStateKind]that the boundary-only append policy reads. Without the constant, the executor inlines the five string literals across the codebase and a future drift adds a sixth boundary in one place but not the other. - Cross-story consistency with S1-02's
_TERMINAL_LEDGER_KINDSwas unencoded. Terminal kinds MUST be boundary kinds (a workflow that ends must have a final durable checkpoint). The original story had no AC that enforced this cross-story membership; a future drift could removefailed_unrecoverablefrom boundaries and silently lose terminal checkpoints. - Chain-forward extension property was absent. S1-02 shipped
_compute_chain_head(pure helper) with stability + sensitivity properties; the store is the imperative shell that consumes it. The chain-forward extension property (store.tail_chain_headafter N appends == iterative_compute_chain_headover the same sequence) is the load-bearing replay-determinism floor — without it, a buggy store that swallowsprior_headwould pass every other test. - Cross-workflow isolation property was absent. A store that appends to
workflow_id_Aand pollutestail_chain_head(workflow_id_B)would be catastrophic for the multi-workflow harness; the property test is the structural defense. - Detection-substrate-only contract for
tail_chain_headwas unencoded. The most insidious failure mode for the checkpoint substrate is an executor "helpfully" adding chain recomputation insidetail_chain_head— this collapses the detection/policy separation that ADR-0003's "verify the previous chain head before hydration" depends on (S2-02 is the SOLE policy site). Without the AC-11 explicit "tail_chain_head does NOT recompute" assertion, the executor cannot know this constraint exists. - Sanitizer-import discipline was absent. Phase-9 critique report flagged regex-set forking as a canonical failure mode; the SUT result sanitization path (
codegenie/output/sanitizer.py) is the single canonical home. Without an AST fence over the store modules forbidding localre.compileand requiring the canonical sanitizer import, an executor under deadline pressure forks the regex set. - Clock injection was unstated. Without a
clock: Callable[[], datetime] | Noneconstructor parameter, the SQLite store'swritten_atcolumn captures wall-clockdatetime.now()directly, making the golden ordering test (the original AC-2) inherently flaky. Clock injection is the substrate that makes the golden test deterministic. __all__boundary discipline was unstated. S1-01 + S1-02 established thecodegenie.workflows.__all__allowlist sentinel as load-bearing (extension by addition, no silent edits). This story adds three new types (CheckpointStore,SqliteCheckpointStore,InMemoryCheckpointStore) but none of them belong in__all__(final-design.md §"Relationship to Phase 6.5"may not depend on: checkpoint backend internals). Without an explicit AC asserting the 14-name set is byte-equal-unchanged after this story, an executor would enthusiastically addCheckpointStorefor "API convenience" and break Phase-6.5's contract boundary.- Refactor step inverted the Phase-3 precedent. "Share canonical serialization helpers" via what mechanism? a
BaseCheckpointStore? a mixin? The Phase-3 precedent (EventStreamSink+ two adapters share NOTHING via inheritance; only the Protocol) is the canonical anti-shared-base shape. Without the Anti-refactor block forbiddingBaseCheckpointStore, the executor's Refactor pass would land a mixin and silently couple the SQLite + InMemory adapters together. - Mutation-resistance pass was absent. Every AC was checked: a mutant
append()that ignoresprior_headpasses "checkpoints append at boundaries"; a mutantread_all_for_workflowthat returns chain-head-sorted results passes "golden ordering test"; a mutant payload validator that caps at 1 KiB instead of 64 KiB silently rejects valid events. The new ACs encode the specific failure modes so mutants die. - No AC for AC-15 contract snapshot extension. S1-01 + S1-02 established the contract snapshot meta-test pattern; this story must extend it with the
CheckpointStoreProtocol shape + the_SEMANTIC_BOUNDARY_KINDSmembership + the_MAX_EVENT_BYTESvalue + the SQLite schema. Without the AC, contract drift across S2-02 / S3-01 / S5-01 / Phase-9 S5-01 goes undetected until a downstream test breaks 800 commits later. - No parity-meta-test (AC-17) closing the mutation gap on the parity test itself. The parity contract test (AC-6) is itself susceptible to mutation; the meta-test (broken adapter → parity test fails) closes the exact gap S6-06 flagged.
All in-place fixable, none requires re-running phase-story-writer. The story's structure (one-paragraph goal, three-section TDD plan) survives — the three ACs grew to seventeen, the TDD plan was reordered with the anti-refactor block, and References / Files-to-touch / Out-of-scope / Notes-for-implementer were added. Verdict: HARDENED.
Context Brief (Stage 1)¶
Story snapshot¶
- Goal (post-edit): ship
src/codegenie/workflows/{checkpoints,sqlite_checkpoints,in_memory_checkpoints,errors}.pywith theCheckpointStoreProtocol (port), the SQLite production adapter (WAL + per-workflow file + flock-protected append), the in-memory test adapter, the closed_SEMANTIC_BOUNDARY_KINDSfrozenset, the_MAX_EVENT_BYTES = 65_536payload cap with typedCheckpointPayloadTooLargeError, the boundary-only append policy withpydantic.ValidationErrorrejection + directive, the chain-forward extension wiring (consumes S1-02's_compute_chain_headwithout modifying it), the cross-workflow isolation property, the detection-substrate-onlytail_chain_headcontract (no recomputation — S2-02 owns policy), the canonical sanitizer-import AST fence, the__slots__AST fence on both adapters, the clock-injection determinism, the golden ordering test for two scenarios, the parity contract test parametrized over both adapters, the parity-meta-test, and the contract-snapshot extension with two synthetic checkpoint-shaped deltas. - Status pre-validation:
Ready— never executed; never validated. - Status post-validation:
HARDENED.
What final-design.md §"Decisions of record" item 3 pins¶
The ledger persists after plan acceptance, patch application, gate result, escalation, and terminal completion.
Five semantic boundary events. Mapped to LedgerStateKind: plan_ready (plan acceptance), patch_applied (patch application), gate_failed_retryable (gate result, retryable arm), awaiting_human_review (escalation), completed + failed_unrecoverable (terminal completion — two terminal kinds, one boundary category). Six boundary kinds total in the catalog. The one non-boundary kind is needs_plan (initial state — no value in persisting the snapshot of "nothing has happened yet").
What ADR-0003 pins¶
Persist checkpoints only at semantic boundaries and verify the previous chain head before hydration on resume.
Two halves: - First half (this story): "persist only at semantic boundaries" — the boundary catalog + append policy + the BLAKE3-chained substrate. - Second half (S2-02): "verify the previous chain head before hydration on resume" — the integrity policy.
The AC-11 detection-substrate-only contract is the load-bearing separation between the two halves.
What phase-arch-design.md §"Deployment view" pins¶
Phase 6 stays local: Python process + SQLite checkpoint file under
.codegenie/remediation/<run-id>/.
The substrate is SQLite, the location is per-workflow under .codegenie/remediation/<run-id>/, the architecture "intentionally mirrors the later Temporal shape but does not pull Temporal into the local phase." This drives the per-workflow file choice (AC-5) over a single shared SQLite file.
What S1-02 forward-depends on¶
S1-02 shipped TransitionEvent (the seven-field event the store appends), _LEGAL_TRANSITIONS (the closed edges the orchestrator chooses among), _TERMINAL_LEDGER_KINDS ({"completed", "awaiting_human_review", "failed_unrecoverable"}), _compute_chain_head (the pure helper in _chain.py with the AST no-side-effects fence), and _FROZEN_FORBID (the canonical config). This story consumes all five; it adds NO new newtypes (WorkflowId, TransitionId, ChainHead, BlobDigest all already exist).
What S1-01 forward-depends on¶
S1-01's AC-12 codegenie.workflows.__all__ allowlist sentinel must continue to pass unchanged after this story lands. The three new types (CheckpointStore, SqliteCheckpointStore, InMemoryCheckpointStore) are package-private; the Phase-6.5 bench harness consumes only the 14 S1-01 + S1-02 names. AC-2 of this story enforces the byte-equality of the unchanged __all__.
What CLAUDE.md load-bearing commitments force¶
- Match the existing convention. The Phase-3
EventStreamSink+ two adapters pattern is the canonical "port + two adapters" shape for chained event persistence. Drives AC-1 + AC-5 + AC-6. - Composition over inheritance. Drives the anti-refactor block (no
BaseCheckpointStore; shared serialization via free function_canonical_event_bytes). - Make illegal states unrepresentable. Drives AC-4 boundary-only append policy +
pydantic.ValidationErrorrejection. - Newtype identifiers — never raw
strfor domain IDs. All identifiers (WorkflowId,TransitionId,ChainHead,BlobDigest) are existing newtypes from prior stories; no new newtypes in this story. - Functional core / imperative shell. Drives the AC-13 clock injection (the imperative-shell store captures the clock; the pure-core
_compute_chain_headfrom S1-02 stays clock-free). - Type everything, strictly —
mypy --strict. Drives AC-16 typecheck-clean. - Extension by addition — no silent edits. Drives AC-2 unchanged-
__all__test + AC-15 contract snapshot extension.
What the existing precedents prescribe¶
src/codegenie/plugins/events.pyis the canonical "port + two adapters" sibling:EventStreamSinkProtocol withappend/read_all/fsync/tail_chain_head/lock(five methods);ZstdAppendingFileSinkproduction adapter;InMemorySinktest adapter;GENESIS_CHAIN_HEAD: Final[BlobDigest] = BlobDigest("0" * 64)constant;fcntl.flock-protected append discipline;_tail_chain_headpure helper. This story mirrors that shape for the SQLite substrate._FROZEN_FORBID: Final[ConfigDict]is the single canonical Pydantic config (S1-01 AC-4 canonical-site discipline); the store layer does not need to re-export — it consumesTransitionEvent(already_FROZEN_FORBID) and persists itsmodel_dump_jsonoutput.codegenie/output/sanitizer.pyis the single canonical regex set +RedactedSlicesmart constructor; AC-12 mandates this is imported, never forked.
Open ambiguities resolved before critics¶
- Q1 —
EventLogvsCheckpointStore: reuse or two ports? Two ports. The forensic two-stream log (EventLog) and the per-workflow checkpoint chain (CheckpointStore) have different durability requirements, different read patterns, different consumers. Conflating them would couple the replay path to the forensic path and force the Phase-9 Postgres migration to dual-implement. Documented in References §"Disambiguation note" + Notes-for-implementer. - Q2 — Payload cap at the model or the store layer? Store layer. The forensic EventLog needs full evidence capture; the checkpoint chain needs compact replay-only events. Capping at the model layer would break the forensic log. Documented in AC-10 + Notes-for-implementer.
- Q3 — Per-workflow SQLite file vs one shared file? Per-workflow. Concurrency isolation + matches the
.codegenie/remediation/<run-id>/directory shape + trivial per-workflow cleanup. Documented in AC-5 + Notes-for-implementer. - Q4 — Async
append()/read_all_for_workflow()on the Protocol? Sync. The orchestrator wraps SQLite calls inasyncio.to_thread(mirrors the Phase-3EventLog.emit_spanningpattern). Async-by-default leaks the substrate choice — SQLite is sync; Postgres async drivers exist but the orchestrator is the seam, not the store. Documented in Anti-refactor #7. - Q5 —
_GENESIS_CHAIN_HEADre-export or new declaration? Single canonical declaration incheckpoints.py(or re-export from Phase-3events.pyif a kernel-tier home already exists — the Refactor step pins the decision). Mirrors the S1-01_FROZEN_FORBIDdiscipline. - Q6 — Should the boundary check live in a
CheckpointAppendRequestwrapper or inline inappend()? Inline. A wrapper for one validator is primitive-obsession-in-reverse (Anti-refactor #5). - Q7 — Does this story own the
FailedUnrecoverable(reason="checkpoint_integrity")decision? No — that belongs to S2-02. This story ships ONLY the detection-substrate primitives (tail_chain_headreturns persisted, NOT recomputed; AC-11 is the load-bearing assertion). The detection/policy separation is the most important invariant of the substrate.
Four-lens findings (inline, no parallel subagents — story scope didn't justify the spawn; mirrors S1-01 + S1-02 precedent in this phase)¶
Lens 1 — Coverage¶
| Finding | Severity | Resolution |
|---|---|---|
| AC-1 "checkpoints append at boundaries" un-verifiable (no port, no adapters, no shape) | block | Replaced with AC-1 (CheckpointStore five-method Protocol with runtime_checkable + annotation byte-equality) + AC-5 (SqliteCheckpointStore schema + WAL + per-workflow file) + AC-6 (InMemoryCheckpointStore parity adapter). |
_SEMANTIC_BOUNDARY_KINDS catalog missing |
block | AC-3 closed frozenset + three sub-tests (membership-byte-equality against final-design.md item 3; subset of LedgerStateKind; terminal-kinds-are-boundary-kinds cross-consistency). |
| Boundary-only append policy missing | block | AC-4 pydantic.ValidationError rejection with directive substring + parametrized test over non-boundary kinds. |
| Chain-forward extension property missing | block | AC-7 Hypothesis property parametrized over both adapters (stability + chain-forward + cross-workflow isolation). |
| Read-ordering property missing | block | AC-8 append-order + cross-workflow filter property. |
Detection-substrate-only tail_chain_head contract unstated |
block | AC-11 explicit "tail_chain_head does NOT recompute; S2-02 owns policy" + partial-write detection test. |
| Payload cap unspecified (numeric, typed exception, error_id) | block | AC-10 _MAX_EVENT_BYTES = 65_536 + CheckpointPayloadTooLargeError + error_id = "workflows.checkpoint_payload_too_large". |
| Sanitizer-import discipline absent | block | AC-12 AST fence + secret-shape property test. |
__slots__ discipline on adapters absent |
harden | AC-14 AST fence over adapter modules. |
| Clock injection for determinism absent | block | AC-13 clock: Callable[[], datetime] | None constructor parameter + deterministic golden test. |
| Golden ordering test under-specified (no scenario, no location, no directive) | block | AC-9 names two scenarios from phase-arch-design.md (clean completion + retry-recovery), names the golden location, names the directive, names the regeneration env var. |
Cross-story consistency with S1-02 _TERMINAL_LEDGER_KINDS unstated |
block | AC-3 sub-test 3 enforces _TERMINAL_LEDGER_KINDS <= _SEMANTIC_BOUNDARY_KINDS. |
__all__ byte-equality test absent |
block | AC-2 asserts the 14-name set is unchanged after this story lands. |
| Contract snapshot extension absent | block | AC-15 extends S1-01 + S1-02 contract snapshot + meta-test additive/breaking case set with two checkpoint-shaped synthetic deltas. |
mypy --strict AC absent |
harden | AC-16. |
| Parity-contract test absent | block | AC-6 parametrized over both adapters; same property suite. |
| Parity-meta-test (mutation guard for AC-6) absent | harden | AC-17 broken-adapter-fails-parity meta-test. |
Lens 2 — Test Quality¶
| Finding | Severity | Resolution |
|---|---|---|
| TDD plan Refactor "share canonical serialization helpers" hints at inheritance | block | Anti-refactor #1: forbids BaseCheckpointStore / mixin; if duplication arises, extract a free function _canonical_event_bytes in checkpoints.py (composition via function call). |
| "Golden ordering test is deterministic" too generic | block | AC-9 names scenarios, golden file path, directive text, regeneration env var, additive-vs-breaking classifier inheritance. |
| No mutation-thinking pass | block | Each AC's test was checked: a mutant append() that ignores prior_head fails AC-7's chain-forward; a mutant tail_chain_head that recomputes fails AC-11's detection-substrate contract; a mutant store that shares heads across workflows fails AC-7's cross-workflow isolation; a mutant read_all_for_workflow that sorts by chain-head string fails AC-8's append-order; a mutant payload validator at the model layer fails AC-10's "cap at store layer" assertion. |
| No property-based tests | block | AC-7 + AC-8 + AC-11 are Hypothesis-driven; cross-workflow isolation in AC-7 is the load-bearing mutation guard. |
| No contract-snapshot meta-test extension | block | AC-15 extends the S1-01 + S1-02 meta-test additively with two checkpoint-shaped synthetic deltas. |
No AST __slots__ fence |
harden | AC-14 fence over both adapter modules. |
| No AST sanitizer-import fence | block | AC-12 forbids local re.compile in store modules + requires canonical sanitizer import. |
| Parity test itself is mutation-susceptible | harden | AC-17 meta-test closes the gap. |
| Partial-write detection test absent (the original story didn't even mention partial writes) | block | AC-11 partial-write detection-substrate test + the "tail_chain_head does NOT recompute" load-bearing assertion. |
| Between-boundary no-write property absent | harden | AC-13 (second half) scripted scenario asserts EXACTLY two rows after two boundary transitions. |
Lens 3 — Consistency¶
| Finding | Severity | Resolution |
|---|---|---|
| Story didn't reference ADR-0003, final-design.md §"Decisions of record" item 3, phase-arch-design.md §"Deployment view" / §"Scenarios" / §"Failure modes" | harden | References block now names all of them + S1-01 + S1-02 dependencies + Phase-3 S6-01 sibling + Phase-9 S5-01 forward dep. |
Story didn't reference Phase-3 EventStreamSink precedent |
block | References block names it + Notes-for-implementer surfaces the disambiguation (forensic log vs checkpoint store — different ports, different consumers). |
| TDD plan Refactor "share helpers" contradicts CLAUDE.md "composition over inheritance" | block | Anti-refactor block + Notes-for-implementer cite the Phase-3 precedent (two adapters share NOTHING via inheritance). |
No Depends on: line |
nit | Added "Depends on S1-02 (TransitionEvent, _LEGAL_TRANSITIONS, _TERMINAL_LEDGER_KINDS, _compute_chain_head) + S1-01 (WorkflowId, _FROZEN_FORBID, allowlist sentinel)." |
Cross-story _TERMINAL_LEDGER_KINDS membership unencoded |
block | AC-3 sub-test 3 cross-consistency. |
| Phase-9 Postgres-adapter forward dep unstated | harden | References block + AC-1 Rule-of-three note + Notes-for-implementer "Phase-9 Postgres swap" entry. |
| Detection/policy separation unstated (the most insidious failure mode) | block | AC-11 explicit + Notes-for-implementer "detection-substrate-only is load-bearing" entry. |
EventLog vs CheckpointStore conflation risk |
block | References §"Disambiguation note" + Notes-for-implementer "Why deliberately separate" entry + AC-1 (iv) static assertion that EventStreamSink is NOT imported in the store modules. |
_FROZEN_FORBID canonical-site discipline (from S1-01) not extended |
nit | The store consumes TransitionEvent which is already _FROZEN_FORBID; no new model classes in this story require the config (the adapters are imperative classes with __slots__, not Pydantic models). |
Lens 4 — Design Patterns¶
| Finding | Severity | Resolution |
|---|---|---|
| Risk of single-concrete-class (no port, no adapter pattern) | block | AC-1 mandates CheckpointStore Protocol; AC-5 + AC-6 mandate two adapters; AC-6 parity test enforces Protocol-as-kernel. |
| TDD plan Refactor hints at inheritance ("shared helpers") | block | Anti-refactor #1: composition via free function only; the Phase-3 EventStreamSink two-adapter precedent shares NOTHING via inheritance. |
| Open/Closed at the file boundary not encoded (file naming would prevent Phase-9 additive Postgres adapter) | block | AC-1 Rule-of-three note: checkpoints.py for the Protocol, sqlite_checkpoints.py for the adapter (not store.py + SqliteCheckpointStore). Phase-9 lands postgres_checkpoints.py additively. |
CheckpointStoreRegistry premature abstraction risk |
nit | Anti-refactor #2: rejected per Rule 2 (two adapters today; threshold reached at Phase-9 Postgres = third). Documented in Notes-for-implementer for the day Phase-7 adds migration-task-class checkpoints. |
BaseCheckpointStore ABC premature abstraction risk |
block | Anti-refactor #1: explicit forbid + Phase-3 precedent cite. |
SemanticBoundaryStrategy premature abstraction risk |
nit | Anti-refactor #4: rejected — boundary catalog is a closed frozenset, not a runtime-dispatched strategy. Phase-7 migration task class lands a sibling _SEMANTIC_BOUNDARY_KINDS_MIGRATION constant in a sibling file, not a Strategy. |
CheckpointTransaction Command-pattern risk |
nit | Anti-refactor #3: rejected — six-line imperative-shell SQL doesn't earn a Command. |
CheckpointAppendRequest wrapper Pydantic model risk |
nit | Anti-refactor #5: rejected — primitive-obsession-in-reverse; put the boundary check in append()'s first line. |
clock Protocol abstraction risk |
nit | Anti-refactor #6: Callable[[], datetime] IS the Protocol expressed without ceremony. |
Async append() on the Protocol risk |
block | Anti-refactor #7: sync append; orchestrator wraps in asyncio.to_thread (mirrors Phase-3 EventLog.emit_spanning). Async-by-default leaks substrate choice. |
__all__ widening risk (executor adds store types for "convenience") |
block | AC-2 asserts 14-name set unchanged byte-equal; mutation kills loud with directive pointing at final-design.md §"may not depend on" constraint. |
__slots__ typo-defense + memory discipline opportunity |
harden | AC-14 AST fence over adapters. |
| Functional core / imperative shell split for the store | harden | AC-13 clock injection (the SOLE clock site in the imperative shell; AC-5 schema flow keeps _compute_chain_head clock-free by construction); S1-02's tests/fence/test_chain_head_purity.py continues to pass. |
| Sanitizer-canonical-import discipline (no regex fork) | block | AC-12 AST fence over store modules forbids local re.compile. |
| Specification-pattern transition-predicate opportunity | nit | Already rejected in S1-02 validation; closed frozenset predicates don't earn Specification. |
Capability-pattern opportunity (orchestrator-injected CheckpointStore) |
harden | The Protocol IS the capability pattern — orchestrator injects CheckpointStore-typed parameter; selecting an adapter is a constructor decision, not a runtime registry lookup. Documented in Notes-for-implementer "Why the five-method Protocol shape matters." |
| Per-workflow file vs single shared file | block | AC-5 pins per-workflow files with three justifications (concurrency isolation, .codegenie/remediation/<run-id>/ shape, trivial cleanup). |
| WAL + sync=FULL pragma discipline | block | AC-5 pins both + golden assertion. |
Synthesis + edit summary¶
No conflicts between critics. No NEEDS RESEARCH findings. The synthesizer applied every fix above in one editing pass:
- 3 ACs → 17 ACs (AC-1 through AC-17), every one individually verifiable with a named test file + failure-mode mutation check.
- TDD plan rewritten in Red-first order with an explicit Anti-refactor block (no
BaseCheckpointStoreABC; noCheckpointStoreRegistry; noCheckpointTransactionCommand; noSemanticBoundaryStrategy; noCheckpointAppendRequestwrapper; noclockProtocol; no asyncappend). - References block populated (11 entries — final-design.md item 3 + §"Main workflow" + §"State model", phase-arch-design.md §"Logical view" + §"Process view" + §"Deployment view" + §"Failure modes", ADR-0003 §Decision + §Tradeoffs + §Consequences, High-level-impl.md §"Step 2", S1-01 + S1-02 hardened stories with their validation reports, S2-02 downstream consumer, Phase-3 S6-01
EventStreamSinkprecedent with disambiguation note, Phase-3 S6-04 orchestrator precedent, Phase-4 sanitizer precedent, Phase-9 S5-01 + S3-01 forward deps). - Files to touch enumerated (5 new src files + 13 new test files + 4 modifications + 2 goldens).
- Out of scope enumerated (7 deferrals + 3 anti-patterns).
- Notes for implementer enumerated (8 entries — five-method Protocol rationale, EventLog/CheckpointStore disambiguation, payload-cap layer choice rationale, per-workflow file rationale, detection-substrate-only load-bearing separation, parity-contract-test factory-parametrization rationale,
__slots__discipline rationale, contract-snapshot meta-test non-negotiability, Phase-7 migration-checkpoints file-naming Open/Closed substrate, Phase-9 Postgres-swap "what stays the same"). - Status flipped from
Ready→HARDENED. Validated-date line added.
Verdict — HARDENED. The story is ready for phase-story-executor.¶
The executor's Validator pass now has 17 concrete acceptance criteria, each tied to a named test file and a mutation-resistance check. The cross-story _TERMINAL_LEDGER_KINDS membership invariant, the ADR-0003 chain-forward extension property, the detection-substrate-only separation between this story and S2-02, the canonical sanitizer-import discipline, the __slots__ adapter fence, the per-workflow SQLite + WAL + flock substrate, the parity-contract test parametrized over both adapters, the parity-meta-test mutation guard, the additive-vs-breaking contract-snapshot extension, the Anti-refactor block forbidding seven specific over-abstractions, and the Open/Closed file-boundary discipline that lets Phase-9's Postgres adapter land additively are all encoded as enforceable structural defenses. A mutant implementation that violates any one of them fails at least one test.
The most important structural defense is AC-11 (detection-substrate-only tail_chain_head) — this is the load-bearing separation between this story (substrate fidelity) and S2-02 (integrity policy). Without it, an executor's "helpful" recomputation would silently collapse ADR-0003's "verify the previous chain head before hydration" decision into the substrate, breaking the Phase-9 byte-equality forward dep across SQLite ↔ Postgres adapters. The AC pins the contract; the test catches the mutation.