dot-iu-cutter v0.4 — LedgerWriter Schema Gap Analysis (2026-05-17)
dot-iu-cutter v0.4 — LedgerWriter Schema Gap Analysis
Date: 2026-05-17 · Phase: v0.4 LedgerWriter Schema-Binding DESIGN ONLY (no code, no commit, no dry-run, no provisioning). GPT selected this resolution after the PG-backed dry-run Halt #2 (accepted-code ↔ deployed-schema contract mismatch; agent halt ruled CORRECT). Grounding inputs: accepted code /opt/incomex/dot @ 56d3732; deployed cutter_governance DDL (columns/NOT NULL/defaults/PK/FK/UNIQUE) read live read-only.
1. Root cause (restated, precise)
cutter_agent/ledger.py @ 56d3732 builds abstract ledger rows (e.g. from_state/to_state/actor/reason/at); the deployed cutter_governance schema (the v0.2/v0.3 production DDL a faithful restore reproduces) uses a richer governance shape (e.g. entry_version_before/after, change_kind, changed_by, changed_at). The two were never reconciled. The 92/92 unit suite stayed green because it exercises the InMemory adapter / injected fakes and never asserts against the real DDL. A PG-backed dry-run is the first context where the gap is fatal: mark() → append_entry (OK) then append_history → SQLSTATE 42703 (undefined column) / 23502 (NOT NULL) → PhaseStop at S4 MARK.
2. Compatibility classification (12 writers)
| Writer (GPT name → code symbol) | Class | Code change? |
|---|---|---|
append_entry → LedgerWriter.append_entry |
MATCH | No |
update_entry_status → transition_status→adapter.cas_status |
MATCH | No |
update_review_superseded_by_if_any → supersede_review_decision→adapter.stamp_superseded |
MATCH | No |
append_history → LedgerWriter.append_history |
MISMATCH | Yes |
append_sweep_log → LedgerWriter.append_sweep_log |
MISMATCH | Yes |
insert_manifest_envelope → write_manifest_envelope |
MISMATCH | Yes |
insert_manifest_unit_block → write_manifest_unit_block |
MISMATCH | Yes |
insert_review_decision → write_review_decision |
MISMATCH | Yes |
insert_dot_pair_signature → write_signature |
MISMATCH | Yes |
insert_cut_change_set → write_cut_change_set |
MISMATCH | Yes |
insert_cut_change_set_affected_row → write_affected_row |
MISMATCH | Yes |
insert_verify_result → write_verify_result |
MISMATCH | Yes |
3 MATCH (no code change) · 9 MISMATCH (row-builder rebinding). (append_dependency is not in GPT's 12 and not in the canonical happy path — dependency final = 0 in r3; reconciled briefly in the per-writer doc for completeness, not on the binding critical path.)
3. Why MATCH for the 3
append_entry: deployeddecision_backlog_entry(entry_id pk/default, kind NN, status NN default 'open', payload jsonb null, emitted_at NN default now(), scenario_ref null)— every code key exists; the only NN-without-default column iskind, which the code supplies ("cut_request"/"escalation"). Code-suppliedstatus('marked') andemitted_at(ISO clock) override defaults harmlessly. Free-textstatus(BATCH-1, no PG enum) accepts the state-machine vocabulary.update_entry_status(adapter._do_cas_status): emitsUPDATE cutter_governance.decision_backlog_entry SET status=%s WHERE entry_id=%s AND status=%s— targets only the existingstatuscolumn with the CAS predicate. Schema-compatible as-is.update_review_superseded_by_if_any(adapter._do_stamp_superseded): restricted toreview_decision.superseded_by_review_decision_id(deployed: uuid, nullable, self-FK), write-onceNULL→value, pkreview_decision_id(matches_pk_field). Schema-compatible as-is (invoked 0× on the canonical happy path; compatible if used).
4. Representability conclusion (drives "no migration")
Every NOT-NULL-without-default deployed column missing from a code row is fillable from a deterministic, semantically-correct source already available at the call site (state-machine vocabulary, principal constants, lane→kind, a single tool/version constant, the clock, the stub canon/sign records) or an existing jsonb column can absorb the code's extra abstract fields (descriptor, effect, content_hash). No required runtime semantic is unrepresentable in the deployed schema (the deployed schema is a strict superset of the abstract ledger's information need). Therefore no schema migration is required (constraint C honoured: migration only if a blocker proves the schema cannot represent required semantics — none found). Full per-column derivations: see per-writer-mapping-design and state-history-and-sweep-mapping-design.
5. Lineage gap (call-out, resolved in mapping docs)
phases.py review()/cut() use the InMemory-only virtual filter find("review_decision", _source_entry=entry_id). Deployed schema has no _source_entry; entry→manifest→review lineage must travel a real column. SB-DEC-1 (proposed): carry entry_id in manifest_envelope.source_doc_ref; the real-schema prior-review lookup becomes review_decision ⋈ manifest_envelope ON manifest_id=envelope_id WHERE source_doc_ref=entry_id. On the canonical happy path there is no prior review (prior_id=None), so behaviour is identical; the join only matters for the (out-of-canonical-scope) re-review path.
6. Verdict (this doc)
- Adapter defect: false. r3 defect: false (r3 states correct intended counts; binding does not change counts — see test-revision-plan).
- Root cause: accepted
LedgerWriterwrite shape ≠ deployedcutter_governanceschema. - Resolution: rebind 9 row-builders + thread a few extra args in a later GPT-gated code-authoring cycle; no schema migration; PG-backed dry-run resumes after that cycle PASSes (command-review r1 / verification-plan r3 unchanged).
- This phase: design only — no code change, no commit (
git status --short -- iu-cutterempty; HEAD56d3732unchanged).