dot-iu-cutter v0.4 — State/History & Sweep Mapping Design (2026-05-17)
dot-iu-cutter v0.4 — State/History & Sweep Mapping Design
Date: 2026-05-17 · DESIGN ONLY. Deep-dive on the three writers most central to the canonical MARK→SWEEP→REVIEW→CUT→VERIFY flow and the r3 count gate: append_history (drives decision_backlog_history=5), append_sweep_log (decision_backlog_sweep_log=1), and the status CAS coupling (update_entry_status).
1. append_history → decision_backlog_history (the S4 fatal write)
1.1 Shapes
- Code (
LedgerWriter.append_history):{history_id, entry_id, from_state, to_state, actor, reason, at}.from_stateisBIRTH=Nonefor the MARK creation row. - Deployed:
history_id uuid NN(pk),entry_id uuid NN (FK→decision_backlog_entry.entry_id),entry_version_before text NULL,entry_version_after text NN,change_kind text NN,change_diff jsonb NULL,changed_by text NN,changed_at timestamptz NN,rationale text NULL.
1.2 Mismatch
Code columns from_state/to_state/actor/reason/at do not exist → 42703 on the first MARK history INSERT. Deployed NN entry_version_after/change_kind/changed_by/changed_at unfilled → 23502. Both fatal at S4.
1.3 Exact mapping (semantic-preserving)
| Deployed col | Source | Rule |
|---|---|---|
history_id |
code history_id |
uuid (LedgerWriter _id()) |
entry_id |
code entry_id |
FK→decision_backlog_entry (parent inserted earlier in MARK; exists for SWEEP/REVIEW/CUT/VERIFY) |
entry_version_before |
code from_state |
None on MARK birth (column nullable — correct) |
entry_version_after |
code to_state |
NN; always a concrete persisted state (marked/review_pending/reviewed_approved/cut_applied/verified_complete) |
change_kind |
code reason |
NN; the transition class (mark,sweep_promote,review:approve,cut,verify:pass) — already the semantic "kind of change" |
change_diff |
derived | {"from":from_state,"to":to_state} jsonb (nullable; optional but recommended for audit completeness) |
changed_by |
code actor |
NN (cutter_exec/cutter_verify/reviewer label) |
changed_at |
code at |
NN (phase clock → timestamptz) |
rationale |
code reason |
nullable; same human reason string |
No code column is dropped (all are mapped); no deployed NN is left empty. Code change: YES — rebind append_history's row dict (signature/callers unchanged: phases.py already passes from_state,to_state,actor,reason,at). One transitional subtlety: the MARK creation calls append_history(from_state=BIRTH=None,…) → entry_version_before=NULL (nullable) ✓; entry_version_after='marked' ✓.
1.4 Count invariance
Rebinding changes only the column payload, not the number of INSERTs. The 5 history rows of the canonical flow (MARK, SWEEP, REVIEW, CUT, VERIFY) are unchanged → r3 decision_backlog_history=5 preserved.
2. append_sweep_log → decision_backlog_sweep_log (the SWEEP write)
2.1 Shapes
- Code (
LedgerWriter.append_sweep_log):{sweep_id, scanned, promoted, at}(called once persweep();scanned=len(all entries),promoted=len(promotable)). - Deployed:
sweep_id uuid NN(pk),swept_at timestamptz NN,swept_by text NN,trigger_kind text NN,entries_evaluated_count int NN,entries_re_surfaced_count int NN,escalations_routed_count int NN,mirror_regenerated_at timestamptz NULL,mirror_path text NULL,findings jsonb NULL.
2.2 Mapping
| Deployed col | Source | Rule |
|---|---|---|
sweep_id |
code sweep_id |
uuid |
swept_at |
code at |
NN |
swept_by |
ACTOR_EXEC |
NN — sweep() runs under the exec adapter (OD-SM-3 same-agent logged pass) |
trigger_kind |
constant "agent_logged_pass" |
NN — OD-SM-3 |
entries_evaluated_count |
code scanned |
NN |
entries_re_surfaced_count |
code promoted |
NN |
escalations_routed_count |
constant 0 |
NN — sweep() only promotes; it routes no escalations |
mirror_*, findings |
NULL/{} |
nullable — not produced by the stub sweep |
Code change: YES (row-builder). Count invariance: exactly one append_sweep_log per sweep(); canonical flow sweeps once → r3 decision_backlog_sweep_log=1 preserved.
3. Status CAS coupling — update_entry_status (MATCH, no code change)
LedgerWriter.transition_status does, in order, sm.validate_transition(expected,new) → adapter.cas_status(entry_id,expected,new) → append_history(...), all inside the caller's one atomic phase txn. The CAS SQL targets only the existing decision_backlog_entry.status column (deployed: text NN default 'open'); free-text status accepts the state-machine vocabulary (BATCH-1, no PG enum). No code change for the CAS itself. Audit invariant A-5 (status & history never disagree) is preserved precisely because the rebinding does not move append_history out of the same txn — only its column payload changes.
Transition coverage (canonical): BIRTH→marked (MARK, append_history only, no CAS — creation), marked→review_pending (SWEEP, CAS+history), review_pending→reviewed_approved (REVIEW), reviewed_approved→cut_applied (CUT), cut_applied→verified_complete (VERIFY). All to_state values are non-null persisted states → entry_version_after NN always satisfied.
4. Lineage (SB-DEC-1) interaction with history/review
append_history does not carry lineage; entry→manifest→review lineage uses SB-DEC-1 (manifest_envelope.source_doc_ref ← entry_id). The InMemory _source_entry virtual filter in phases.review()/cut() must be re-expressed as the real-schema join review_decision ⋈ manifest_envelope ON manifest_id=envelope_id WHERE source_doc_ref=entry_id. Canonical happy path: first review ⇒ prior_id=None ⇒ join returns empty ⇒ identical behaviour and identical counts; the join is only material on the out-of-canonical re-review path. This is a phases.py read-path adjustment (small) bundled into the same code cycle, not a schema concern.
5. Invariants preserved (constraint C)
- Append-only audit:
append_historystays an INSERT in the same txn as the CAS; only payload columns change. No history row is ever updated/deleted. - No DELETE/TRUNCATE/DDL/GRANT: none introduced; history/sweep are pure INSERT; status/supersede remain the only (pre-existing, sanctioned) column UPDATEs.
- Principal routing unchanged: MARK/SWEEP/REVIEW/CUT under
cutter_exec; VERIFY undercutter_verify; the SWEEPswept_byiscutter_execconsistent with routing. - 5-phase flow unchanged:
MARK→SWEEP→REVIEW→CUT→VERIFY— binding is column-level only. - No production write / no schema migration: mapping uses only existing deployed columns.
6. Verdict (this doc)
append_history + append_sweep_log are the count-critical rebindings; both are semantically representable with zero count change and zero schema migration. update_entry_status/stamp_superseded need no change. r3 decision_backlog_history=5 and decision_backlog_sweep_log=1 are preserved by the binding.