dot-iu-cutter v0.2 — Phase α DDL Verification Plan (rev 2, 2026-05-15)
dot-iu-cutter v0.2 — Phase α DDL Verification Plan
document_path: knowledge/dev/laws/dieu44-trien-khai/v0.2-ddl-authoring/dot-iu-cutter-v0.2-phase-alpha-ddl-verification-plan-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)
phase: v0.2 — Phase α DDL post-application verification PLAN (planning only)
no_execution: TRUE
no_verification_queries_run_in_this_doc: TRUE
§0 — Revision 2 Changelog
trigger: GPT REVISION_REQUIRED on DDL draft r1 (PG fast-default pitfall).
verification_expectation_status:
The r1 PASS targets in this plan were ALREADY correct:
V-α-6 : public.tac_logical_unit.authority → draft=86, no enacted/runtime/NULL
V-α-8 : sandbox_tac.logical_unit.authority → NULL × 76
r1's DDL would NOT have produced these via the intended path: ADD COLUMN
DEFAULT 'draft' would have made all 86 public rows read 'draft' WITHOUT the
BR-4 mapping ever running, and would have made the 76 sandbox rows read
'draft' instead of NULL — i.e. V-α-8 would have FAILED under r1.
r2_changes_to_this_plan:
- revision r1 → r2
- §2.1 / §2.2: added a mechanism note (authority added WITHOUT default, then
SET DEFAULT in a later step; catalog column_default still resolves to
'draft'::text, so the EXPECTED OUTPUT of these queries is UNCHANGED)
- §2.6 V-α-6: made the {draft=86, enacted=0, runtime=0, null=0} expectation
fully explicit per GPT, with an r2 note that this is now achieved by the
Step 3 backfill (not by a column default)
- §2.8 V-α-8: added an r2 note that NULL × 76 is now genuinely guaranteed
because the sandbox column is added WITHOUT default and no UPDATE runs
- step-reference fix: alias indexes are DDL draft r2 "Step 9" (was r1 "Step 8");
the backfill is DDL draft r2 "Step 3" (UNCHANGED — pre-backfill checks still valid)
no_verification_expectation_VALUES_changed: TRUE (only the mechanism narrative)
§1 — Purpose
Define the verification queries and pass/fail criteria that MUST be evaluated after the Phase α DDL is applied (either in dry-run or production). This plan is written so the verifier can re-run the queries verbatim without ambiguity. Nothing is executed in this document.
The plan applies in two contexts:
- dry-run env (after the Phase α DDL applies inside the fresh dry-run sibling env)
- production env (after the eventual production execution of Phase α DDL under explicit prompt)
In production, the queries are the closing gates of the production command-review package's verification phase.
§2 — Verification Queries (read-only; NOT executed here)
2.1 V-α-1 — New columns exist on public.tac_logical_unit
-- expected: 2 rows (authority, canonical_address_format_version)
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema='public'
AND table_name='tac_logical_unit'
AND column_name IN ('authority', 'canonical_address_format_version')
ORDER BY column_name;
Expected result:
authority | text | YES | 'draft'::text
canonical_address_format_version | text | NO | 'canonical-address-v1'::text
PASS condition: exactly 2 rows; authority is nullable with DEFAULT 'draft'; canonical_address_format_version is NOT NULL with DEFAULT 'canonical-address-v1'.
r2 mechanism note:
authority'scolumn_defaultreads'draft'::textbecause DDL draft r2 Step 4 appliesALTER COLUMN authority SET DEFAULT 'draft'after the Step 3 backfill. The catalog default is recorded the same way it would be under r1, so this query's expected output is unchanged. The difference is purely when the default is attached (after backfill, affecting future rows only) — invisible toinformation_schema.columns. The distribution queries V-α-6 / V-α-8 are what actually distinguish a correct r2 run from the defective r1 behaviour.
2.2 V-α-2 — Mirror columns exist on sandbox_tac.logical_unit
-- expected: 2 rows
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema='sandbox_tac'
AND table_name='logical_unit'
AND column_name IN ('authority', 'canonical_address_format_version')
ORDER BY column_name;
Expected result: identical shape to §2.1.
PASS condition: same as §2.1.
r2 mechanism note: identical reasoning to §2.1 — sandbox
authoritygets its'draft'::textdefault from DDL draft r2 Step 7 (ALTER COLUMN … SET DEFAULT), applied with no preceding backfill. The catalog default shape is unchanged from r1; the row-level distribution (V-α-8) is where the correctness shows.
2.3 V-α-3 — Alias table exists
-- expected: 1 row
SELECT count(*)
FROM information_schema.tables
WHERE table_schema='cutter_governance'
AND table_name='canonical_address_alias'
AND table_type='BASE TABLE';
Expected result: 1.
PASS condition: exactly 1.
2.4 V-α-4 — Alias table columns
-- expected: 9 rows in correct order
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema='cutter_governance'
AND table_name='canonical_address_alias'
ORDER BY ordinal_position;
Expected columns (9):
alias_id | uuid | NO | gen_random_uuid()
target_unit_id | uuid | NO | (none)
alias_text | text | NO | (none)
alias_kind | text | NO | (none)
valid_from | timestamptz | NO | now()
valid_until | timestamptz | YES | (none)
created_by | text | NO | (none)
rationale | text | YES | (none)
scenario_ref | text | YES | (none)
PASS condition: exactly 9 columns in the expected order with the expected nullability/default shape.
2.5 V-α-5 — Alias table indexes
-- expected: 5 indexes (1 PK + 4 btree)
SELECT indexname, indexdef
FROM pg_indexes
WHERE schemaname='cutter_governance'
AND tablename='canonical_address_alias'
ORDER BY indexname;
Expected indexes:
canonical_address_alias_pkey
idx_alias_alias_kind
idx_alias_alias_text
idx_alias_target_unit_id
idx_alias_validity_window
PASS condition: exactly 5 indexes present; PK is on alias_id; the 4 btree indexes match the columns specified in DDL draft r2 Step 9 (was r1 Step 8).
2.6 V-α-6 — public.tac_logical_unit.authority distribution
SELECT authority, count(*) AS n
FROM public.tac_logical_unit
GROUP BY authority
ORDER BY authority NULLS FIRST;
Expected result (production state after the Step 3 backfill):
authority | n
---------------|----
draft | 86
Equivalent explicit breakdown (the GPT r2 verification expectation):
draft = 86
enacted = 0
runtime = 0
null = 0
PASS conditions:
- exactly 1 row in the grouped result
authority='draft'withn=86- no NULL row (
null = 0) — proves the Step 3 backfill actually fired - no
'enacted'row (enacted = 0) and no'runtime'row (runtime = 0) — consistent with the current dataset being 100%lifecycle_status='draft_only' - if the production dataset ever contains
active/retiredrows (currently zero), those map to'enacted'per BR-4; re-run and re-baseline before treating as failure
r2 note: under DDL draft r1 this query would still have shown
draft=86, but for the wrong reason — the ADD COLUMN default, not the BR-4 mapping. Thenull = 0andenacted = 0assertions only become meaningful under r2, where Step 2 adds the column with all rows NULL and Step 3 performs the actual lifecycle mapping. This query is the primary evidence that the r2 correction works.
2.7 V-α-7 — public.tac_logical_unit.canonical_address_format_version distribution
SELECT canonical_address_format_version, count(*) AS n
FROM public.tac_logical_unit
GROUP BY canonical_address_format_version
ORDER BY canonical_address_format_version;
Expected result:
canonical-address-v1 | 86
PASS condition: exactly 1 row; value 'canonical-address-v1'; n=86.
r2 note: unchanged from r1.
canonical_address_format_versionis intentionally addedNOT NULL DEFAULT 'canonical-address-v1'(DDL draft r2 Step 1), so the column-add-time fast-default backfilling all 86 rows is the desired behaviour for this column — the opposite of theauthorityrequirement. GPT explicitly retained this as an accepted part.
2.8 V-α-8 — sandbox_tac.logical_unit.authority distribution (no backfill expected)
SELECT authority, count(*) AS n
FROM sandbox_tac.logical_unit
GROUP BY authority
ORDER BY authority NULLS FIRST;
Expected result:
(NULL) | 76
(SQL aggregation typically renders the NULL group as an empty cell or (null) indicator.)
PASS conditions:
- exactly 1 row
authority IS NULLwithn=76- NO
'draft'rows — sandbox is NOT backfilled in Phase α; DDL draft r2 Step 6 adds the column WITHOUT a default, and Step 7'sSET DEFAULTaffects only FUTURE inserts, so all 76 existing sandbox rows remain genuinely NULL - intentional per BR-7 Option II + GPT Q4 decision
r2 note — this is the query r1 would have FAILED. Under DDL draft r1 (
ADD COLUMN authority text DEFAULT 'draft'), all 76 sandbox rows would have read'draft', producingdraft=76, null=0— directly contradicting the sandbox expectation. The r2 add-without-default correction is what makesnull=76actually true. Treat any non-NULL sandbox authority value here as a hard failure (rollback trigger T-3-class).
2.9 V-α-9 — sandbox_tac.logical_unit.canonical_address_format_version distribution
SELECT canonical_address_format_version, count(*) AS n
FROM sandbox_tac.logical_unit
GROUP BY canonical_address_format_version
ORDER BY canonical_address_format_version;
Expected result:
canonical-address-v1 | 76
PASS condition: exactly 1 row; value 'canonical-address-v1'; n=76 (DEFAULT applied at column-add time across all 76 sandbox rows — intentional mirror of Step 1; unchanged from r1).
2.10 V-α-10 — Alias table row count = 0
SELECT count(*) AS n
FROM cutter_governance.canonical_address_alias;
Expected: 0.
PASS condition: alias table is empty (Phase α creates it with no rows; writers are Phase β).
2.11 V-α-11 — Existing canonical_address column unchanged
-- column metadata unchanged
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema='public'
AND table_name='tac_logical_unit'
AND column_name='canonical_address';
Expected result:
canonical_address | text | NO | (none)
PASS condition: column still text NOT NULL; no default added; UNIQUE constraint still present (verifiable via V-α-12 below).
2.12 V-α-12 — UNIQUE constraint on canonical_address still in place
SELECT conname, pg_get_constraintdef(c.oid)
FROM pg_constraint c
JOIN pg_class t ON t.oid=c.conrelid
WHERE t.relname='tac_logical_unit' AND conname='tac_logical_unit_canonical_address_key';
Expected: 1 row showing UNIQUE (canonical_address).
PASS condition: UNIQUE constraint intact.
2.13 V-α-13 — v0.1 cutter_governance tables unchanged
-- exactly 6 tables in cutter_governance after Phase α
-- (5 from v0.1 + 1 new alias table)
SELECT relname, n_live_tup
FROM pg_stat_user_tables
WHERE schemaname='cutter_governance'
ORDER BY relname;
Expected result (post-Phase-α):
canonical_address_alias | 0
cut_change_set | 0
cut_change_set_affected_row | 0
decision_backlog_entry | 0
dot_pair_signature | 0
verify_result | 0
PASS conditions:
- exactly 6 tables
- all 6 have
n_live_tup = 0 - the original 5 v0.1 tables are present with names exactly as shipped in v0.1 (no rename / no drop)
2.14 V-α-14 — Row count of public.tac_logical_unit unchanged
SELECT count(*) AS rows_total,
count(canonical_address) AS canonical_non_null,
count(DISTINCT canonical_address) AS distinct_canonical
FROM public.tac_logical_unit;
Expected result:
86 | 86 | 86
PASS condition: row count unchanged from pre-Phase-α state (86); canonical_address still 100% non-null; still 100% distinct. (The Step 3 backfill UPDATEs in place; it does not change the row count.)
2.15 V-α-15 — Row count of sandbox_tac.logical_unit unchanged
SELECT count(*) FROM sandbox_tac.logical_unit;
Expected: 76.
PASS condition: row count unchanged from pre-Phase-α state.
2.16 V-α-16 — Schema diff between pre-Phase-α snapshot and post-Phase-α schema
# Compare CREATE TABLE / ALTER TABLE / CREATE INDEX / CREATE SCHEMA additions.
diff \
<(grep -E '^(CREATE TABLE|CREATE SCHEMA|CREATE INDEX|ALTER TABLE)' <pre_snapshot.sql> | sort) \
<(grep -E '^(CREATE TABLE|CREATE SCHEMA|CREATE INDEX|ALTER TABLE)' <post_snapshot.sql> | sort)
Expected diff: ADDITIONS ONLY, exclusively within:
public.tac_logical_unit(the 2 new columns + theALTER COLUMN authority SET DEFAULT— all additive on this table)sandbox_tac.logical_unit(the 2 new mirror columns + theALTER COLUMN authority SET DEFAULT)cutter_governance.canonical_address_alias(1 CREATE TABLE + 4 explicit CREATE INDEX + PG-generated PK/comment metadata)- comments via COMMENT ON
PASS conditions:
- NO removed lines
- NO ALTER lines outside
public.tac_logical_unit/sandbox_tac.logical_unit(additive only on those tables; existing columns unchanged) - NO touched lines on any other production table
r2 note: a pg_dump of
public.tac_logical_unitafter r2 rendersauthoritywithDEFAULT 'draft'::textin the recreatedCREATE TABLE(pg_dump always materialises the current default regardless of when it was attached). This is expected and additive — theSET DEFAULTis not a separate destructive line.
§3 — Pre-Backfill Sanity Checks (recommended in dry-run + production preflight)
These are the Q-CHK-1/2/3 checks recommended in the fn_tac_birth_gate_lu inspection doc. They run before DDL draft r2 Step 3 (the backfill UPDATE) to confirm trigger re-validation will not fail. (Step 3 is unchanged between r1 and r2 — these checks remain valid as authored.)
3.1 Q-CHK-1 — section_type vocab activity
SELECT count(*) AS rows_with_inactive_section_type
FROM public.tac_logical_unit u
LEFT JOIN public.tac_section_type_vocab v
ON v.code = u.section_type
WHERE coalesce(v.lifecycle_status, 'missing') != 'active';
Expected: 0.
PASS condition: every row's section_type is active in the vocab.
3.2 Q-CHK-2 — parent_id resolution + doc_code parity
SELECT count(*) AS rows_with_parent_problem
FROM public.tac_logical_unit child
LEFT JOIN public.tac_logical_unit parent
ON parent.id = child.parent_id
WHERE child.parent_id IS NOT NULL
AND (parent.id IS NULL OR parent.doc_code != child.doc_code);
Expected: 0.
PASS condition: every non-null parent resolves and shares doc_code.
3.3 Q-CHK-3 — canonical_address regex defensive recheck
SELECT count(*) AS rows_failing_regex
FROM public.tac_logical_unit
WHERE canonical_address !~ '^D38-[A-Z0-9]+-((ROOT)|(S[0-9]+(-P[0-9]+(-[0-9]+)*)?))$';
Expected: 0.
PASS condition: every row passes the canonical-address-v1 regex (matches the trigger's hardcoded regex).
If any of the three pre-backfill checks fail, the Phase α DDL must NOT proceed in that environment until the underlying data is fixed.
§4 — Failure-Stop Criteria (per verification query)
fail_action_on_ANY_failure_during_dry_run:
- record evidence (query output + timestamp)
- rollback the dry-run transaction
- do NOT proceed to subsequent scenarios
- escalate to GPT review
fail_action_on_ANY_failure_during_production_post_DDL_verification:
- STOP immediately
- capture all verification query outputs to the production artefact directory
- execute the rollback per the rollback draft (separate file)
- record an admin_fallback_log entry (manual; the runtime APR path is not Phase α scope)
- notify Đ32 (standard path) + G-4 Custodian
- do NOT self-retry
specific_pass_threshold:
- V-α-1 .. V-α-16: ALL must PASS
- Q-CHK-1, Q-CHK-2, Q-CHK-3: ALL must PASS (pre-backfill)
- any single failure: STOP
production_specific_rollback_trigger:
any_of:
- V-α-1 / V-α-2 missing columns
- V-α-3 / V-α-4 alias table malformed or missing
- V-α-6 authority distribution not {'draft' × 86, no enacted/runtime/NULL}
- V-α-7 format_version distribution not {'canonical-address-v1' × 86}
- V-α-8 sandbox authority distribution not {NULL × 76} (r2: a 'draft' value here means the column was wrongly given a column-add default — the exact r1 defect)
- V-α-13 cutter_governance tables changed beyond the additive alias table
- V-α-14 / V-α-15 row counts differ from pre-state
- V-α-16 schema diff shows ANY non-additive change
§5 — Hard Boundaries
no_verification_query_executed_here: TRUE
no_DDL_executed: TRUE
no_mutation: TRUE
no_dry_run_started: TRUE
no_production_migration_allowed: TRUE
output_form: phase_alpha_verification_planning_only
revision: r2 (mechanism narrative updated; no expectation values changed)
§6 — 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
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)
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)
authoring_report: knowledge/dev/laws/dieu44-trien-khai/v0.2-ddl-authoring/dot-iu-cutter-v0.2-phase-alpha-ddl-authoring-report-2026-05-15.md (r2)
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
v0_1_verification_pattern_(C-09a..e): knowledge/dev/laws/dieu44-trien-khai/execution/dot-iu-cutter-v0.1-p0-production-command-review-package-2026-05-15.md (§2.9)
v0_1_production_execution_report: knowledge/dev/laws/dieu44-trien-khai/execution/dot-iu-cutter-v0.1-production-execution-report-2026-05-15.md (§8 verification results pattern)
End of Phase α DDL verification plan (revision 2).