KB-242C rev 2
dot-iu-cutter v0.2 — Phase α DDL Authoring Report (rev 2, 2026-05-15)
22 min read Revision 2
dieu44-trien-khaidot-iu-cutterv0.2phase-alphaddl-authoring-reportready-for-gpt-re-reviewrevision-22026-05-15
dot-iu-cutter v0.2 — Phase α DDL Authoring Report
document_path: knowledge/dev/laws/dieu44-trien-khai/v0.2-ddl-authoring/dot-iu-cutter-v0.2-phase-alpha-ddl-authoring-report-2026-05-15.md
revision: r2
supersedes: r1 (2026-05-15)
date: 2026-05-15
revised: 2026-05-16
author: Agent (Claude Code CLI, Opus 4.7 1M)
sovereign: User / anh Huyên
verifier: GPT (Đ32 standard path)
secondary: Opus
phase: v0.2 — Phase α DDL authoring CONSOLIDATED REPORT
ddl_authored: TRUE
ddl_executed: FALSE
mutation_performed: FALSE
dry_run_started: FALSE
production_migration_allowed: FALSE
§0 — Revision 2 (response to GPT REVISION_REQUIRED)
gpt_verdict_on_r1: REVISION_REQUIRED — dry-run NOT opened
blocking_issue (as raised by GPT):
r1 added `authority text DEFAULT 'draft'` at column-add time. In PostgreSQL,
ADD COLUMN with a constant DEFAULT makes every pre-existing row immediately
read the default value. Therefore `UPDATE ... WHERE authority IS NULL`
(Step 3) would match ZERO rows → the BR-4 lifecycle_status → authority
backfill silently no-ops, and sandbox rows would read 'draft' instead of
remaining NULL. The end value coincidentally still landed on 'draft' for
the all-'draft_only' dataset, but the mapping never executed and any
active/retired row would be mis-stamped.
correction_applied_in_r2 (exactly as GPT specified):
public.tac_logical_unit:
Step 2 ALTER TABLE ... ADD COLUMN IF NOT EXISTS authority text; -- NO DEFAULT
Step 3 UPDATE ... SET authority = CASE lifecycle_status
WHEN 'draft_only' THEN 'draft'
WHEN 'active' THEN 'enacted'
WHEN 'retired' THEN 'enacted'
END
WHERE authority IS NULL;
Step 4 ALTER TABLE ... ALTER COLUMN authority SET DEFAULT 'draft';
sandbox_tac.logical_unit:
Step 6 ALTER TABLE ... ADD COLUMN IF NOT EXISTS authority text; -- NO DEFAULT
(no backfill — 76 rows stay NULL)
Step 7 ALTER TABLE ... ALTER COLUMN authority SET DEFAULT 'draft';
files_revised_to_r2:
1. dot-iu-cutter-v0.2-phase-alpha-ddl-draft-2026-05-15.sql.md → r2
2. dot-iu-cutter-v0.2-phase-alpha-ddl-authoring-report-2026-05-15.md → r2 (this file)
3. dot-iu-cutter-v0.2-phase-alpha-ddl-verification-plan-2026-05-15.md → r2
4. dot-iu-cutter-v0.2-phase-alpha-rollback-draft-2026-05-15.md → r2 (revision-tracking + step-renumber refs; DROP-COLUMN reversal logic unchanged)
accepted_parts_unchanged (per GPT):
fn_tac_birth_gate_lu inspection · single transaction · alias placement
cutter_governance.canonical_address_alias · no cross-schema FK · no CHECK in
Phase α · no sandbox alias table · no identity_profile cleanup ·
canonical_address_format_version column (NOT NULL DEFAULT — intentionally
unchanged) · canonical_address_alias general shape
still_no_execution: authoring only — no DDL run, no dry-run, no production mutation, no migration
§1 — Files Created / Revised (this lineage)
…/dot-iu-cutter-v0.2-phase-alpha-fn-tac-birth-gate-lu-inspection-2026-05-15.md— B-DDL-3 read-only inspection of the trigger function (r1; accepted by GPT; unchanged)…/dot-iu-cutter-v0.2-phase-alpha-ddl-draft-2026-05-15.sql.md— Phase α DDL draft (revised → r2: authority added without default; backfill before SET DEFAULT; 9 steps)…/dot-iu-cutter-v0.2-phase-alpha-ddl-verification-plan-2026-05-15.md— verification queries + pre-backfill sanity checks (revised → r2: mechanism notes; expectations unchanged because they were already correct)…/dot-iu-cutter-v0.2-phase-alpha-rollback-draft-2026-05-15.md— rollback SQL draft (revised → r2: revision tracking + step-renumber references; DROP COLUMN reversal logic unaffected)- (this file) — authoring report (revised → r2)
§2 — fn_tac_birth_gate_lu Inspection Result
(Unchanged from r1 — GPT accepted this part. Reproduced for self-containment.)
inspection_done: YES
mutation: NO
function_attributes:
language: plpgsql
security: SECURITY DEFINER
trigger: BEFORE INSERT OR UPDATE FOR EACH ROW
what_function_references_inside_body:
- NEW.canonical_address (regex validation against canonical-address-v1 grammar)
- NEW.doc_code (non-empty check)
- NEW.parent_id (existence + doc_code parity)
- NEW.section_type (FK + vocab activity check via tac_section_type_vocab)
- NEW.owner (non-empty check)
- NEW.sort_order (>= 0 check)
- NEW.updated_at (WRITTEN: NEW.updated_at := now())
what_function_does_NOT_reference:
- authority (NEW Phase α column — invisible to trigger)
- canonical_address_format_version (NEW Phase α column — invisible to trigger)
- public.tac_logical_unit.lifecycle_status (NOT used; the trigger reads tac_section_type_vocab.lifecycle_status which is a DIFFERENT field for a DIFFERENT vocabulary)
conflict_assessment: NONE
- ADD COLUMN / ALTER COLUMN SET DEFAULT operations do not fire the trigger (metadata DDL)
- backfill UPDATE (Step 3) fires the trigger; re-validates existing canonical_address (passes; value unchanged) and all other gates (pass)
- documented side effect: NEW.updated_at bumped to now() on all 86 backfilled rows (accepted as normal UPDATE semantics)
r2_note: the r2 add-without-default change does NOT alter the trigger surface — only Step 3 (the UPDATE) fires the row trigger, exactly once per row, exactly as analysed in r1.
informational_findings (NOT blocking Phase α):
- trigger regex hardcodes "D38-" prefix — future doc ids will not validate at write time; v0.3+ followup
- regex grammar matches BR-5 canonical-address-v1 — positive corroboration
risk_to_phase_α_from_this_trigger: NONE
§3 — DDL Summary (r2)
3.1 Operations (in execution order; all inside one single transaction)
| Step | Operation | Target | Risk |
|---|---|---|---|
| 1 | ADD COLUMN canonical_address_format_version text NOT NULL DEFAULT 'canonical-address-v1' | public.tac_logical_unit | LOW (additive; DEFAULT intentionally backfills 86 rows) |
| 2 | ADD COLUMN authority text (NO DEFAULT) — existing rows → NULL | public.tac_logical_unit | LOW (additive; nullable; r2 fix) |
| 3 | UPDATE … SET authority = CASE lifecycle_status … END WHERE authority IS NULL | public.tac_logical_unit (86 rows) | STANDARD (fires trigger; documented updated_at side effect) |
| 4 | ALTER COLUMN authority SET DEFAULT 'draft' — future rows only | public.tac_logical_unit | LOW (metadata; does not rewrite existing rows; r2 fix) |
| 5 | ADD COLUMN canonical_address_format_version text NOT NULL DEFAULT 'canonical-address-v1' | sandbox_tac.logical_unit | LOW (mirror; DEFAULT backfills 76 rows) |
| 6 | ADD COLUMN authority text (NO DEFAULT) — 76 rows stay NULL | sandbox_tac.logical_unit | LOW (mirror; no backfill; r2 fix) |
| 7 | ALTER COLUMN authority SET DEFAULT 'draft' — future rows only | sandbox_tac.logical_unit | LOW (metadata; 76 existing rows stay NULL; r2 fix) |
| 8 | CREATE TABLE IF NOT EXISTS cutter_governance.canonical_address_alias | cutter_governance | LOW (0 rows at create) |
| 9 | CREATE INDEX IF NOT EXISTS (4 btree indexes) | cutter_governance.canonical_address_alias | LOW (empty table) |
r1 → r2 step delta: r1 step 2 split into r2 steps 2+4; r1 step 5 split into r2 steps 6+7; the intentional "no sandbox backfill" NO-OP is now expressed as the absence of an UPDATE between steps 6 and 7. r1 step 7 (alias table) → r2 step 8; r1 step 8 (indexes) → r2 step 9. Step 3 = backfill is preserved (fn-inspection + verification pre-backfill checks still reference Step 3).
3.2 Posture statements
single_transaction: YES (BEGIN/COMMIT)
on_error_stop_required_at_dispatch: TRUE (caller dispatches with --single-transaction --set ON_ERROR_STOP=1)
idempotency_guards: ADD COLUMN IF NOT EXISTS, CREATE TABLE IF NOT EXISTS, CREATE INDEX IF NOT EXISTS, UPDATE WHERE authority IS NULL, ALTER COLUMN SET DEFAULT (re-set = same value)
r2_idempotency_correctness: because authority is added WITHOUT a default, a re-run finds all rows already non-NULL → Step 3 matches 0 rows (safe); SET DEFAULT re-applies the same value (safe). Under r1 a first run would have skipped the backfill entirely — the defect GPT blocked.
re-applicability_after_a_clean_first_run: 0 schema changes, 0 row updates (full no-op)
re-applicability_after_a_mid_transaction_failure: ROLLBACK restores pre-state; column never half-added; next run starts clean
COMMENT_ON_statements_present: YES (column/table docstrings; benignly overwrite on re-run)
GPT_decided_options_(applied):
Q1 alias placement → cutter_governance.canonical_address_alias
Q2 FK posture → soft uuid reference; no cross-schema FK
Q3 authority CHECK → none in Phase α
Q4 sandbox alias → omit
Q5 dry-run env → fresh sibling env (caller's responsibility at dry-run time)
Q6 jsonb cleanup → defer
NOT_in_the_DDL_(consistent_with_design + GPT_Q1-Q6 + r2 fix):
- no DEFAULT at authority column-add time (r2 — set in a later step after backfill)
- no CHECK constraint on authority
- no FK from alias.target_unit_id to tac_logical_unit.id
- no sandbox alias table
- no sandbox authority backfill (76 rows stay NULL)
- no identity_profile jsonb cleanup
- no NOT NULL promotion on authority
- no change to existing canonical_address column or v0.1 cutter_governance tables
§4 — Verification Plan Summary (r2)
total_post_DDL_verification_queries: 16 (V-α-1 .. V-α-16)
total_pre_backfill_sanity_checks: 3 (Q-CHK-1, Q-CHK-2, Q-CHK-3)
overall_pass_threshold: ALL queries PASS
r2_verification_expectation (as confirmed by GPT — these were already the r1 expectations; r2 makes them ACTUALLY ACHIEVED by the corrected mechanism):
public.tac_logical_unit.authority: draft=86, enacted=0, runtime=0, null=0
sandbox_tac.logical_unit.authority: null=76
(r1 would have produced authority='draft' on ALL 86 *without running the mapping*,
and sandbox authority='draft'×76 instead of null×76 — the mechanism is what r2 fixes.)
what_the_queries_assert:
V-α-1 .. V-α-5 structural: new columns + alias table + alias indexes
V-α-6, V-α-7 authority + format_version distribution on production tac_logical_unit
V-α-8, V-α-9 sandbox mirror distribution (V-α-8: authority NULL × 76)
V-α-10 alias table empty
V-α-11, V-α-12 existing canonical_address column + UNIQUE constraint unchanged
V-α-13 v0.1 cutter_governance tables unchanged
V-α-14, V-α-15 row counts of tac_logical_unit + sandbox_tac.logical_unit unchanged
V-α-16 schema diff against pre-snapshot: ADDITIONS ONLY
failure_response: STOP + capture evidence + rollback per the rollback draft + escalate to GPT
no_verification_executed_in_this_authoring_session: TRUE
§5 — Rollback Summary (r2)
rollback_scope: matches Phase α DDL exactly (LIFO order)
R-1: DROP TABLE cutter_governance.canonical_address_alias
R-2: DROP COLUMN sandbox_tac.logical_unit.authority + canonical_address_format_version
R-3: DROP COLUMN public.tac_logical_unit.authority + canonical_address_format_version
r2_note: DROP COLUMN removes the column AND its attached DEFAULT in one operation,
so the r2 "SET DEFAULT" steps need no separate reversal. Rollback logic is
unchanged from r1; only revision tracking + step-number references updated.
data_loss_considerations:
alias_table: 0 rows in Phase α → no loss
sandbox_tac: 76 NULL authority values (r2: genuinely NULL, not 'draft') + 76 DEFAULT format_version values → trivially reproducible
public.tac_logical_unit:
- authority='draft' for 86 rows → deterministically reproducible from BR-4 mapping (now actually executed by Step 3)
- format_version='canonical-address-v1' for 86 rows → reproducible via DEFAULT on re-apply
- updated_at bump from backfill → ORIGINAL TIMESTAMPS LOST after rollback; recoverable from pre-Phase-α backup (.dump)
rollback_dispatch_authorization:
- Đ32 standard path + G-4 Custodian co-sign
- sovereign User explicit acknowledgement
- separate explicit prompt naming: (a) operation = rollback, (b) environment, (c) triggering verification failure
- agent_self_authorize_rollback: PROHIBITED
cheaper_alternative_for_in_transaction_failure:
- dispatch Phase α DDL with --single-transaction --set ON_ERROR_STOP=1
- any failure aborts atomically; no separate rollback script needed
- the §3 rollback applies only to POST-COMMIT problems detected by V-α-1..16
§6 — Deviations from Design
deviations_from_phase_α_design_master / schema_design / alias_design: NONE
(the r2 add-without-default → backfill → set-default ordering is the
PostgreSQL-correct *implementation* of the design's stated intent —
"existing rows backfilled from lifecycle_status; default for future rows" —
not a deviation. r1 was an implementation defect, not a design change.)
confirmations_of_design_decisions_in_DDL:
- alias table placement: cutter_governance.canonical_address_alias ✓ (design: master §5.3, alias §7.2; GPT Q1)
- soft uuid reference (no PG FK): alias.target_unit_id uuid NOT NULL ✓ (design: alias §7.1; GPT Q2)
- no CHECK constraint on authority ✓ (design: schema §3.1; GPT Q3)
- no sandbox alias table ✓ (design: master §5.3; GPT Q4)
- no jsonb cleanup ✓ (design: master §6 Q4; GPT Q6)
- authority nullable; NOT NULL deferred to Phase β ✓ (BR-4 closure)
- authority backfilled from lifecycle_status BEFORE default is set ✓ (r2 — PG-correct realisation of BR-4 backfill rule)
- canonical_address_format_version NOT NULL DEFAULT 'canonical-address-v1' ✓ (BR-5 closure — intentionally column-add backfilled; unchanged)
- sandbox authority no backfill in Phase α (76 rows genuinely NULL) ✓ (BR-7 Option II; design alias §6.2; r2 makes this actually true)
- existing canonical_address SSOT preserved unchanged ✓ (Option D)
- single-transaction wrap ✓ (design master §7)
additions_beyond_design_(minor; not deviations):
- COMMENT ON column/table statements added for self-documentation (benign metadata)
- SET LOCAL statement_timeout / lock_timeout / idle_in_transaction_session_timeout added for safety
- trailing in-transaction SELECT added for live verification (informational, not pass/fail logic; r2 expanded to break out draft/enacted/runtime/null)
- 4 explicit btree indexes named per dry-run plan §10 recommendation (PK is implicit)
§7 — Blockers Before Dry-Run
B-DR-1: GPT RE-review of this r2 DDL authoring package (4 revised docs + 1 accepted inspection)
status: PENDING (this r2 report is the trigger)
B-DR-2: Phase α DDL draft frozen + sha256 captured at GPT-review-time
status: PENDING — sha256 MUST be captured on the r2 §2 body (r1 superseded)
B-DR-3: explicit GPT or User prompt authorizing dry-run authoring lane
status: NOT YET issued (GPT verdict on r1 was REVISION_REQUIRED — dry-run remains closed until r2 PASS)
B-DR-4: fresh dry-run env provisioned (pg-dry-run-v0.2-phase-alpha-<date>)
status: NOT YET (separate session)
B-DR-5: baseline backup taken / verified
status: NOT YET (separate session)
B-DR-6: dry-run scenario harness authored (Phase α HB-equivalent harness; mirroring HB-05 hb05_harness.py pattern)
status: NOT YET (separate session)
§8 — Blockers Before Production
(See risk review plan §6 for full enumeration. Unchanged by r2.)
B-PROD-1: dry-run 26/26 PASS
B-PROD-2: Phase α HB-equivalent closure document
B-PROD-3: Phase α final readiness review
B-PROD-4: Phase α command-review package
B-PROD-5: GPT review of (closure + readiness + command-review)
B-PROD-6: fresh production-bound backup < 60 min
B-PROD-7: explicit User production execution prompt
B-PROD-8: separate session for production execution
B-PROD-9: pg-dry-run-hb05-2026-05-15 v0.1 env still UP
B-PROD-10: production SSOT canonical_address column unchanged
B-PROD-11: dot_tools 991/992 still active and unchanged
§9 — Readiness for GPT Re-Review
phase_α_ddl_authoring_status: revised_r2_ready_for_gpt_re_review
all_authoring_docs_present: TRUE (4 revised to r2 + 1 accepted inspection at r1)
r1_blocking_issue_addressed: TRUE (PG fast-default pitfall fixed exactly per GPT's required pattern)
internally_consistent_with_design_package: TRUE
internally_consistent_with_BR_closures: TRUE
internally_consistent_with_GPT_Q1_Q6_decisions: TRUE
deviations_from_design: NONE (r2 is the PG-correct implementation of the unchanged design intent)
ddl_authored: TRUE
ddl_executed: FALSE
mutation_performed: FALSE
dry_run_started: FALSE
production_migration_allowed: FALSE
agent_self_advance_to_execution: PROHIBITED
can_dry_run_be_re_reviewed_now:
the DRY-RUN LANE remains CLOSED. r2 is submitted for GPT RE-review of the
authoring package only. Dry-run authoring may be opened ONLY after GPT
issues a PASS verdict on r2 AND an explicit prompt (B-DR-3) authorizes the
dry-run lane. The agent does NOT self-open it.
what_gpt_re_review_should_decide:
- confirm the r2 add-without-default → backfill → set-default ordering clears REVISION_REQUIRED
- PASS / further-revise the r2 authoring docs
- capture sha256 of the frozen r2 DDL draft (caller extracts §2 of the .sql.md into a real .sql file and sha256s it at review-time)
- if PASS: issue the explicit prompt to enter the Phase α dry-run authoring lane
§10 — Negative Confirmations
ddl_executed_anywhere: FALSE
psql_run: FALSE (the only DB access in the lineage was the read-only fn_tac_birth_gate_lu inspection via pg_get_functiondef + pg_get_triggerdef; no DDL or DML; r2 revision was pure document authoring)
mutation_performed: FALSE
dry_run_started: FALSE
dry_run_env_provisioned: FALSE
production_migration_allowed: FALSE
sister_tables_touched: FALSE
v0_1_cutter_governance_tables_touched: FALSE
sandbox_tac_data_modified: FALSE
trigger_modified: FALSE
function_modified: FALSE
identity_profile_jsonb_modified: FALSE
deploy_performed: FALSE
CUT_VERIFY_run: FALSE
output_form: phase_alpha_ddl_authoring_consolidated_report_r2
§11 — Sign-Off Block
agent (author): Claude Code CLI / Opus 4.7 1M action: r2 revision of 4 Phase α DDL authoring docs (DDL draft + verification plan + rollback draft + this report) per GPT REVISION_REQUIRED
verifier (Đ32 standard path): GPT action: Phase α DDL authoring RE-review (r2) — PENDING
secondary (record keeping): Opus action: r2 docs stored in Agent Data — DONE
sovereign (authorizing party): User / anh Huyên action: explicit prompt for Phase α dry-run authoring — PENDING (gated on r2 PASS)
council_status:
Đ0-G: authority semantics — ratified at BR-4 closure
Đ24: canonical-address-v1 grammar — ratified at BR-5 closure
Đ32: standard path — r1 REVISION_REQUIRED; r2 pending re-review
§12 — Cross-References
fn_inspection: knowledge/dev/laws/dieu44-trien-khai/v0.2-ddl-authoring/dot-iu-cutter-v0.2-phase-alpha-fn-tac-birth-gate-lu-inspection-2026-05-15.md (r1; accepted)
ddl_draft: knowledge/dev/laws/dieu44-trien-khai/v0.2-ddl-authoring/dot-iu-cutter-v0.2-phase-alpha-ddl-draft-2026-05-15.sql.md (r2)
verification_plan: knowledge/dev/laws/dieu44-trien-khai/v0.2-ddl-authoring/dot-iu-cutter-v0.2-phase-alpha-ddl-verification-plan-2026-05-15.md (r2)
rollback_draft: knowledge/dev/laws/dieu44-trien-khai/v0.2-ddl-authoring/dot-iu-cutter-v0.2-phase-alpha-rollback-draft-2026-05-15.md (r2)
phase_α_design_master: knowledge/dev/laws/dieu44-trien-khai/v0.2-design/dot-iu-cutter-v0.2-phase-alpha-design-master-2026-05-15.md
schema_design: knowledge/dev/laws/dieu44-trien-khai/v0.2-design/dot-iu-cutter-v0.2-phase-alpha-canonical-address-schema-design-2026-05-15.md
alias_design: knowledge/dev/laws/dieu44-trien-khai/v0.2-design/dot-iu-cutter-v0.2-phase-alpha-canonical-address-alias-design-2026-05-15.md
dry_run_plan: knowledge/dev/laws/dieu44-trien-khai/v0.2-design/dot-iu-cutter-v0.2-phase-alpha-dry-run-plan-2026-05-15.md
risk_review_plan: knowledge/dev/laws/dieu44-trien-khai/v0.2-design/dot-iu-cutter-v0.2-phase-alpha-risk-review-plan-2026-05-15.md
design_report: knowledge/dev/laws/dieu44-trien-khai/v0.2-design/dot-iu-cutter-v0.2-phase-alpha-design-report-2026-05-15.md
br_4_closure: knowledge/dev/laws/dieu44-trien-khai/v0.2-planning/dot-iu-cutter-v0.2-br-4-authority-backfill-rule-closure-2026-05-15.md
br_5_closure: knowledge/dev/laws/dieu44-trien-khai/v0.2-planning/dot-iu-cutter-v0.2-br-5-canonical-address-v1-ratification-closure-2026-05-15.md
br_4_5_closure_report: knowledge/dev/laws/dieu44-trien-khai/v0.2-planning/dot-iu-cutter-v0.2-br-4-5-closure-report-2026-05-15.md
br_2_3_7_report: knowledge/dev/laws/dieu44-trien-khai/v0.2-planning/dot-iu-cutter-v0.2-br-2-3-7-discovery-report-2026-05-15.md
v0_1_production_handoff: knowledge/dev/laws/dieu44-trien-khai/execution/dot-iu-cutter-v0.1-production-handoff-status-2026-05-15.md
v0_1_HB_05_dry_run_DDL_(pattern_reference): /opt/incomex/backups/dieu44_hb05_2026-05-15/schema/cutter_governance_dryrun_v0.1.sql (sha256 9eb032e2…)
End of Phase α DDL authoring report (revision 2).