Spec↔code contradiction (independent Opus conformance audit, doc 50)
CKPT-R5 / CKPT-R9 assert a stable, single-use interrupt_id across pause/resume:
- CKPT-R5 (spec/50:97): the interrupt yields "a unique interrupt_id"; resume matches
thread_id + interrupt_id.
- CKPT-R9 (spec/50:101): "A stale, already-consumed, or mismatched interrupt_id MUST be rejected, never silently re-applied."
Code violates it (self-admitted KNOWN-ISSUE): make_interrupt_gate (src/wattwise_core/agent/graph_gate.py:48-55) mints a fresh uuid4 and records a new live row on every resume pass — because langgraph re-runs the node body on each Command(resume) before interrupt() returns. Comment at graph_gate.py:50-53: "mints a FRESH uuid and re-records a NEW live row on each resume — the interrupt-identity scheme needs a redesign … Do NOT fix here; tracked separately." So the interrupt_id is not stable across the pause/resume boundary, prior live rows are orphaned, and the CKPT-R9 "already-consumed must be rejected" guarantee is weakened.
Fix (pick one so spec and code agree)
- (a) Derive a stable per-pause
interrupt_id deterministically from thread_id + checkpoint/pause point, so re-running the node body on resume reproduces the same id and records no second live row; then CKPT-R9 rejection holds.
- (b) Add an explicit known-limitation carve-out to CKPT-R5/CKPT-R9 documenting the langgraph re-run behaviour and the bounded risk until the redesign lands.
Owner's both-ways mandate (spec ⇄ code) requires reconciliation. Surfaced by the doc-50 conformance audit; this is the previously-"tracked separately" item, now ticketed.
Spec↔code contradiction (independent Opus conformance audit, doc 50)
CKPT-R5 / CKPT-R9 assert a stable, single-use
interrupt_idacross pause/resume:thread_id+interrupt_id.Code violates it (self-admitted KNOWN-ISSUE):
make_interrupt_gate(src/wattwise_core/agent/graph_gate.py:48-55) mints a freshuuid4and records a new live row on every resume pass — because langgraph re-runs the node body on eachCommand(resume)beforeinterrupt()returns. Comment at graph_gate.py:50-53: "mints a FRESH uuid and re-records a NEW live row on each resume — the interrupt-identity scheme needs a redesign … Do NOT fix here; tracked separately." So theinterrupt_idis not stable across the pause/resume boundary, prior live rows are orphaned, and the CKPT-R9 "already-consumed must be rejected" guarantee is weakened.Fix (pick one so spec and code agree)
interrupt_iddeterministically fromthread_id+ checkpoint/pause point, so re-running the node body on resume reproduces the same id and records no second live row; then CKPT-R9 rejection holds.Owner's both-ways mandate (spec ⇄ code) requires reconciliation. Surfaced by the doc-50 conformance audit; this is the previously-"tracked separately" item, now ticketed.