KB-641D

dot-iu-cutter v0.4 — State/History & Sweep Mapping Design (2026-05-17)

8 min read Revision 1
dot-iu-cutterv0.4schema-bindingstate-history-sweepdieu44design

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_historydecision_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_state is BIRTH=None for 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_logdecision_backlog_sweep_log (the SWEEP write)

2.1 Shapes

  • Code (LedgerWriter.append_sweep_log): {sweep_id, scanned, promoted, at} (called once per sweep(); 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_history stays 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 under cutter_verify; the SWEEP swept_by is cutter_exec consistent 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.

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.4-schema-binding/dot-iu-cutter-v0.4-state-history-and-sweep-mapping-design-2026-05-17.md