KB-3E3A

dot-iu-cutter v0.4 — LedgerWriter Schema Gap Analysis (2026-05-17)

6 min read Revision 1
dot-iu-cutterv0.4schema-bindinggap-analysisdieu44design

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_entryLedgerWriter.append_entry MATCH No
update_entry_statustransition_statusadapter.cas_status MATCH No
update_review_superseded_by_if_anysupersede_review_decisionadapter.stamp_superseded MATCH No
append_historyLedgerWriter.append_history MISMATCH Yes
append_sweep_logLedgerWriter.append_sweep_log MISMATCH Yes
insert_manifest_envelopewrite_manifest_envelope MISMATCH Yes
insert_manifest_unit_blockwrite_manifest_unit_block MISMATCH Yes
insert_review_decisionwrite_review_decision MISMATCH Yes
insert_dot_pair_signaturewrite_signature MISMATCH Yes
insert_cut_change_setwrite_cut_change_set MISMATCH Yes
insert_cut_change_set_affected_rowwrite_affected_row MISMATCH Yes
insert_verify_resultwrite_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: deployed decision_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 is kind, which the code supplies ("cut_request" / "escalation"). Code-supplied status ('marked') and emitted_at (ISO clock) override defaults harmlessly. Free-text status (BATCH-1, no PG enum) accepts the state-machine vocabulary.
  • update_entry_status (adapter._do_cas_status): emits UPDATE cutter_governance.decision_backlog_entry SET status=%s WHERE entry_id=%s AND status=%s — targets only the existing status column with the CAS predicate. Schema-compatible as-is.
  • update_review_superseded_by_if_any (adapter._do_stamp_superseded): restricted to review_decision.superseded_by_review_decision_id (deployed: uuid, nullable, self-FK), write-once NULL→value, pk review_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 LedgerWriter write shape ≠ deployed cutter_governance schema.
  • 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-cutter empty; HEAD 56d3732 unchanged).
Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.4-schema-binding/dot-iu-cutter-v0.4-ledgerwriter-schema-gap-analysis-2026-05-17.md