KB-2A77

dot-iu-cutter v0.4 PG-backed Dry-run — EXECUTION BLOCKED #2: accepted-code ↔ deployed-schema contract mismatch (2026-05-17)

11 min read Revision 1
dot-iu-cutterv0.4db-adapterdry-runexecution-blockedschema-mismatchdieu44blocking-finding

dot-iu-cutter v0.4 — PG-backed Dry-run: EXECUTION BLOCKED #2 (pre-provision)

Date: 2026-05-17 · execution_status: BLOCKED_PRE_EXECUTION — accepted-code ↔ deployed-schema CONTRACT MISMATCH. Not an adapter defect; not an r3 defect; not fixable by code change or by the harness without violating boundaries.

After GPT PASSed verification-plan r3 and authorized execution, the authorized artefact-authoring step began with assembly-first read-only grounding (project rule: verify PG/Directus reality BEFORE building). That grounding found a hard structural blocker. No isolated env was provisioned, no pg_dump taken, no production connection, no secret/.env read, no code change, no commit. Per the hard rules (do not improvise · no self-advance · report honest · do not mark PASS) execution was halted before C-01.

1. Finding

The accepted code's append-only ledger writer (cutter_agent/ledger.py @ commit 56d3732) emits INSERT row shapes that do not match the deployed cutter_governance schema (the v0.2/v0.3 production DDL that a faithful pg_dump/restore reproduces, read live during grounding). LedgerWriter writes a simplified abstract ledger (e.g. from_state/to_state/actor/reason/at) while the deployed tables use a different, richer governance shape (e.g. entry_version_before/after, change_kind, changed_by, changed_at, rationale). 11 of 12 writer methods are incompatible; only append_entry → decision_backlog_entry matches.

A faithful PG-backed dry-run of the accepted code therefore cannot reach the r3 15-row baseline: the canonical happy path is MARK → SWEEP → REVIEW → CUT → VERIFY; mark() does append_entry (OK) then immediately append_history(...) — whose INSERT references non-existent columns (from_state,to_state,actor,reason,at) and omits the deployed NOT-NULL-no-default columns (entry_version_after,change_kind,changed_by,changed_at). psycopg3 raises 42703 (undefined_column) / 23502 (not_null_violation); classify_sqlstate → STOP → PhaseStop. S4 MARK fails on its second write statement; no further phase runs.

2. Evidence — per-writer column reconciliation (live DDL vs accepted ledger.py)

LedgerWriter method → table Code-inserted columns Verdict vs deployed DDL
append_entrydecision_backlog_entry entry_id,kind,status,payload,emitted_at,scenario_ref MATCH — all columns exist; required kind supplied (only compatible writer)
append_historydecision_backlog_history history_id,entry_id,from_state,to_state,actor,reason,at FAILfrom_state/to_state/actor/reason/at do not exist (42703); deployed requires entry_version_after,change_kind,changed_by,changed_at NOT NULL (23502)
append_dependencydecision_backlog_dependency dependency_id,from_entry_id,to_entry_id FAIL — omits NOT NULL dependency_kind,created_at,created_by
append_sweep_logdecision_backlog_sweep_log sweep_id,scanned,promoted,at FAILscanned/promoted/at absent (42703); requires swept_at,swept_by,trigger_kind,entries_evaluated_count,entries_re_surfaced_count,escalations_routed_count NOT NULL
write_manifest_envelopemanifest_envelope envelope_id,canonical_address,content_hash,source_entry_id,canon_lib_version FAIL — those 4 absent (42703); requires operation_kind,status,source_doc_ref,created_by,created_at NOT NULL
write_manifest_unit_blockmanifest_unit_block envelope_id,unit_local_id,descriptor FAIL — descriptor absent (42703); requires block_role,source_span,render_order,created_at NOT NULL
write_review_decisionreview_decision review_decision_id,manifest_id,manifest_unit_local_id,decision,reviewer,prior_review_decision_id,superseded_by_review_decision_id FAIL — decision/reviewer absent (42703); requires 15 NOT-NULL-no-default cols incl. governance_event_kind,manifest_version,review_scope,status,verdict,findings,reviewer_class,reviewer_identity,risk_class_assessment,decision_at,decided_by,cross_signed_by_dot_verifier,version,created_at,updated_at
write_signaturedot_pair_signature signature_id,lane,signer_identity,payload_digest,placeholder_signature,prior_signature_id,is_production FAIL — those absent (42703); requires signature_kind,signer_dot_id,signer_tool_revision,payload_hash,payload_envelope,signature_payload NOT NULL
write_cut_change_setcut_change_set change_set_id,decision_backlog_entry_id,executor_signature_id,verifier_signature_id,manifest_id,content_hash FAIL — content_hash absent (42703); requires rollback_key,manifest_version,review_decision_id,executor_tool_revision,verifier_tool_revision,emitted_by NOT NULL
write_affected_rowcut_change_set_affected_row affected_row_id,change_set_id,target_ref,effect FAIL — target_ref/effect absent (42703); requires target_table,target_row_id,operation_kind NOT NULL
write_verify_resultverify_result verify_result_id,change_set_id,executor_signature_id,verifier_signature_id,outcome,rollback_change_set_id_triggered,escalation_ref,prior_verify_result_id FAIL — outcome absent (42703); requires manifest_id,manifest_version,review_decision_id,executor_tool_revision,verifier_tool_revision NOT NULL
(canonical_address_alias) never written (0 rows) n/a — irrelevant to the failure (OD-2 deferral)

Secondary corroboration: phases.py review()/cut() use the InMemory-only virtual filter find("review_decision", _source_entry=…); _source_entry is not a real column either (a separate symptom of the same code↔schema gap).

3. Why this is a hard blocker (and why I did not improvise around it)

  • Cannot change code (forbidden, and the cycle's purpose is to dry-run the accepted 56d3732 faithfully).
  • Cannot "fix" it in the harness. A harness that silently remaps/renames columns or injects the deployed NOT-NULL fields would no longer be exercising the accepted LedgerWriter; it would be testing a bespoke shim. That is improvising past a discovered structural defect and would produce a misleading "PASS". Forbidden by do not improvise / no self-advance / report honest.
  • r3 is not at fault. r3's 15-row baseline correctly describes the accepted code's intended row counts; the defect is that the accepted code cannot physically write those rows into the deployed schema.
  • The adapter is not at fault. RealPostgresAdapter faithfully parameterises and issues exactly the columns LedgerWriter hands it; it would correctly surface 42703/23502 as PhaseStop.

This is an accepted-code ↔ deployed-cutter_governance-schema contract mismatch: the v0.4 LedgerWriter row shapes (an abstract ledger) were never reconciled with the v0.2/v0.3 deployed governance DDL (the schema dot-iu-cutter v0.2/v0.3 actually built and that a production restore reproduces). It must be adjudicated by GPT/User; it is out of scope for the agent to resolve unilaterally.

4. Candidate resolutions (for GPT/User — NOT self-applied)

  1. Schema-binding cycle for LedgerWriter: author a design→review→code cycle that maps each ledger write to the real cutter_governance columns (supplying the deployed NOT-NULL fields: change_kind, governance_event_kind, signature_kind, rollback_key, tool-revisions, etc.). Largest, most correct; changes accepted code (new cycle, new commit, GPT-gated).
  2. Restore-target reframing: if the PG-backed dry-run was intended to run against a v0.4 ledger schema (the abstract shape) rather than the deployed v0.2/v0.3 governance schema, define/seed that schema explicitly and re-spec the dry-run + r-series matrix against it.
  3. Adapter-mapping layer: a GPT-reviewed, accepted (committed) translation layer between LedgerWriter's abstract rows and the deployed DDL — then dry-run that.
  4. Re-scope the v0.4 dry-run to the InMemory adapter only and defer the PG-backed dry-run until (1)/(2)/(3) lands.

Each requires its own GPT-gated design→review chain. No self-advance.

5. Boundaries honoured (nothing executed)

No isolated env created · no pg_dump taken · no production DB connection (only read-only catalog/information_schema queries via the existing local docker exec trust path) · no production secret/.env read · no production row write · no production CUT/VERIFY · no code change · no git commit · no deploy/restart · no docker prune/wildcard · 3 protected prior dry-run envs untouched (snapshot only).

Git SSOT proof

  • Branch main; /opt/incomex/dot HEAD = 56d3732cb74d07546c938242180a434ed1067a9a (accepted, unchanged); git status --short -- iu-cutter empty.
  • No code change ⇒ no commit needed. Last authoritative: python3 -m unittest discover -s tests → 92/92 at 56d3732 (the unit suite passes precisely because it uses the InMemory adapter / injected fakes and never asserts against the real cutter_governance DDL — which is exactly how this gap stayed latent until a PG-backed dry-run was attempted).

Read-only grounding evidence

  • PROD system_identifier = 7611578671664259111 (unchanged); prod postgres container StartedAt 2026-04-17T05:35:18.48439927Z (no restart).
  • 3 protected prior dry-run containers running, Id+StartedAt snapshotted, untouched.
  • new env/network names collision-free (absent).
  • cutter_governance = 12 base tables + 12 v_*_observe views, all 0 rows; frozen privilege matrix validated (cutter_exec 18 table-priv + 2 col-UPDATE; cutter_verify 15 + 1; cutter_ro NOLOGIN + 13).
  • Full live DDL of all 12 base tables captured; reconciled against ledger.py @ 56d3732 (table in §2).

6. Next gate

GPT/User adjudication of this contract-mismatch blocker and selection of a resolution path (§4). Until then: no execution, no provisioning, no code/spec change, no self-advance. This finding supersedes, in priority, the (already-resolved) r2 matrix defect; r3 itself remains GPT-PASSed and correct as a statement of intended counts.

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.4-db-adapter-dry-run/dot-iu-cutter-v0.4-pg-backed-dry-run-EXECUTION-BLOCKED-2-schema-contract-mismatch-2026-05-17.md