dot-iu-cutter v0.2 — Phase α Dry-Run Plan
document_path: knowledge/dev/laws/dieu44-trien-khai/v0.2-design/dot-iu-cutter-v0.2-phase-alpha-dry-run-plan-2026-05-15.md
revision: r1
date: 2026-05-15
author: Agent (Claude Code CLI, Opus 4.7 1M)
phase: v0.2 — Phase α dry-run PLAN (planning only; no dry-run executed)
no_dry_run_executed: TRUE
no_DDL_written: TRUE
no_mutation: TRUE
§1 — Purpose
Specify the dry-run scenario matrix that the Phase α DDL must satisfy before any production migration is authorized. This is the HB-05-equivalent for Phase α. No scenario is run in this document; this is the plan to be executed in a future explicitly-prompted session.
§2 — Dry-Run Environment Requirement
recommendation: SPIN A FRESH SIBLING dry-run env (do NOT reuse the v0.1 HB-05 env)
proposed_name: pg-dry-run-v0.2-phase-alpha-<date>
proposed_image: postgres:16 (matching production PG 16.13)
proposed_network: docker bridge (no host port published; isolated)
proposed_volume: pg-dry-run-v0.2-phase-alpha-<date>-data (persistent for the dry-run cycle)
reasons_to_NOT_reuse_HB_05_env (pg-dry-run-hb05-2026-05-15):
- HB-05 env carries v0.1 dry-run-only cutter_governance schema state + 26-scenario harness state
- mixing v0.1 dry-run with v0.2 scenarios risks cross-contamination of evidence
- HB-05 env must remain UNCHANGED per v0.1 final-readiness §4 condition (d) until v0.2 success declared
- rolling Phase α dry-run onto HB-05 env could inadvertently dirty it
baseline_restore_source:
- a FRESH production-bound backup (taken at dry-run setup time; HB-08 pattern, pg_dump -F c -Z 6)
- sha256 recorded; restore-test PASS before dry-run begins
- alternative: REUSE the v0.1 fresh-execution backup at /opt/incomex/backups/dieu44_exec_2026-05-15/directus_full_20260515T141429Z.dump (sha256 7d7e424c…)
→ acceptable IF the production state has not materially changed since 2026-05-15 14:14 UTC
→ recommended check: compare current row counts vs the pre-snapshot psv before restoring; if they differ, take a fresh backup instead
- dry-run baseline must include: 86 tac_logical_unit rows, 76 sandbox_tac.logical_unit rows, 5 cutter_governance tables (empty), all sister tables (events / drafts / units / etc.)
§3 — Dry-Run DDL Authoring (separate future session)
NOT_authored_here: this plan describes the scenarios; the actual DDL file is authored in a separate session under explicit prompt
planned_dry_run_DDL_outline_(for_clarity_only;_no_DDL_in_this_doc):
Step 1: ADD COLUMN public.tac_logical_unit.canonical_address_format_version text NOT NULL DEFAULT 'canonical-address-v1'
Step 2: ADD COLUMN public.tac_logical_unit.authority text NULL DEFAULT 'draft'
Step 3: UPDATE public.tac_logical_unit 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: ADD COLUMN sandbox_tac.logical_unit.canonical_address_format_version text NOT NULL DEFAULT 'canonical-address-v1'
Step 5: ADD COLUMN sandbox_tac.logical_unit.authority text NULL DEFAULT 'draft'
Step 6: (NO sandbox backfill of authority in Phase α; rows remain NULL)
Step 7: CREATE TABLE IF NOT EXISTS cutter_governance.canonical_address_alias (alias_id uuid PK, target_unit_id uuid NOT NULL, alias_text text NOT NULL, alias_kind text NOT NULL, valid_from timestamptz NOT NULL DEFAULT now(), valid_until timestamptz NULL, created_by text NOT NULL, rationale text NULL, scenario_ref text NULL)
Step 8: CREATE INDEX on alias_text, target_unit_id, alias_kind, (valid_from, valid_until DESC)
execution_form_in_dry_run:
single transaction, ON_ERROR_STOP=1, mirroring HB-05 / v0.1 production pattern
all 8 steps in one transaction; either all land or none
§4 — Scenario Matrix (Phase α HB-equivalent)
26 scenarios, modeled after the HB-05 pattern. NONE executed in this plan.
4.1 Column-add and DEFAULT scenarios (S01–S05)
| # |
Scenario |
Setup |
Expected Outcome |
Pass Condition |
| S01 |
Add canonical_address_format_version to tac_logical_unit |
baseline restored (86 rows) |
column exists; all 86 rows = 'canonical-address-v1' |
exact match |
| S02 |
Add authority to tac_logical_unit (no backfill yet) |
post-S01 |
column exists; all 86 rows = NULL initially |
exact match |
| S03 |
Backfill authority via Candidate B CASE |
post-S02 |
all 86 rows = 'draft' (since 100% lifecycle='draft_only') |
exact match; count(authority IS NULL) = 0 |
| S04 |
Add canonical_address_format_version to sandbox_tac.logical_unit |
baseline includes sandbox |
column exists; all 76 sandbox rows = 'canonical-address-v1' |
exact match |
| S05 |
Add authority to sandbox_tac.logical_unit (no backfill) |
post-S04 |
column exists; all 76 rows = NULL |
exact match; sandbox backfill intentionally skipped |
4.2 Alias-table creation scenarios (S06–S09)
| # |
Scenario |
Setup |
Expected Outcome |
Pass Condition |
| S06 |
Create cutter_governance.canonical_address_alias |
post-S05 |
table exists; 0 rows |
exact match |
| S07 |
Indexes on canonical_address_alias exist |
post-S06 |
PK + 4 btree indexes per §3 outline |
indexes verified by pg_indexes query |
| S08 |
Insert one synthetic alias row (then delete) |
post-S06 |
row inserted; row deleted; table back to 0 rows |
round-trip; no errors |
| S09 |
Alias table rollback (DROP TABLE) |
post-S08 (then re-create for S10) |
table dropped; no orphan; no FK violation |
clean drop |
4.3 Default-on-insert scenarios (S10–S12)
| # |
Scenario |
Setup |
Expected Outcome |
Pass Condition |
| S10 |
INSERT a new tac_logical_unit row without specifying authority |
post-S03 + table reset to a known synthetic baseline |
new row has authority = 'draft' (via DEFAULT); format_version = 'canonical-address-v1' |
both columns auto-populated |
| S11 |
INSERT a new tac_logical_unit row with explicit authority='enacted' |
post-S10 reset |
new row has authority = 'enacted'; format_version = 'canonical-address-v1' |
explicit value honored |
| S12 |
INSERT a new tac_logical_unit row with explicit format_version='canonical-address-v1.1' (forward-compat test) |
post-S11 reset |
row accepts the value (Phase α has no FK/CHECK on format_version) |
column accepts the value; no constraint violation |
4.4 Backfill correctness scenarios (S13–S16)
| # |
Scenario |
Setup |
Expected Outcome |
Pass Condition |
| S13 |
Synthetic row with lifecycle_status='active' before backfill |
inject synthetic active row before Step 3 backfill |
backfill assigns authority='enacted' |
mapping correct |
| S14 |
Synthetic row with lifecycle_status='retired' before backfill |
inject synthetic retired row before Step 3 backfill |
backfill assigns authority='enacted' |
mapping correct (matches BR-4 ratified interpretation) |
| S15 |
Backfill is idempotent (re-running the UPDATE produces no further change) |
post-S03 |
second UPDATE affects 0 rows (WHERE authority IS NULL matches nothing) |
rowcount=0 on re-run |
| S16 |
Synthetic row with NULL lifecycle_status (would fail FK but test backfill robustness) |
inject only if PG FK allows; otherwise skip |
backfill leaves authority=NULL (CASE returns NULL); WHERE authority IS NULL filter spares the row |
clear behavior under unexpected vocab |
4.5 Reader/writer smoke tests (S17–S20)
| # |
Scenario |
Setup |
Expected Outcome |
Pass Condition |
| S17 |
Existing trigger trg_tac_birth_gate_lu still fires on INSERT |
post-Phase-α |
trigger fires; new columns visible inside trigger but unused |
trigger does not error |
| S18 |
fn_event_unread query still returns canonical_address correctly |
post-Phase-α |
function output unchanged |
query result matches baseline |
| S19 |
fn_iu_verify_invariants still passes on synthetic IU |
post-Phase-α |
function returns its existing verdict |
result unchanged |
| S20 |
Existing UNIQUE constraint on canonical_address still enforced |
post-Phase-α |
attempt to insert duplicate canonical_address fails |
rejection observed |
4.6 sandbox_tac mirror verification (S21–S22)
| # |
Scenario |
Setup |
Expected Outcome |
Pass Condition |
| S21 |
sandbox_tac.fn_sbx_lu_parent_same_doc still functions |
post-Phase-α |
sandbox helper unaffected by new columns |
function passes synthetic test |
| S22 |
sandbox_tac.logical_unit row inserts get authority=NULL by default behavior choice (no backfill) and format_version='canonical-address-v1' |
post-S05 |
new sandbox row matches policy |
column values match policy |
4.7 Rollback scenarios (S23–S26)
| # |
Scenario |
Setup |
Expected Outcome |
Pass Condition |
| S23 |
Drop authority column from tac_logical_unit |
post-S03 |
column gone; rows preserved |
column gone; rowcount=86 unchanged |
| S24 |
Drop format_version column from tac_logical_unit |
post-S01 |
column gone; rows preserved |
column gone; rowcount=86 unchanged |
| S25 |
Drop canonical_address_alias table |
post-S06 |
table gone; no orphan; no error |
clean drop |
| S26 |
Full rollback inside the same dry-run transaction: BEGIN; (all 8 steps); ROLLBACK; |
fresh restored baseline |
NO trace of any new column or table after ROLLBACK |
schema diff against baseline = empty |
§5 — Expected Pass / Fail Matrix Summary
expected_PASS_count: 26
expected_FAIL_count: 0
expected_safety_critical_failures: 0
expected_stops: 0
per_category_summary:
column_add_default: 5 scenarios (S01–S05) — all PASS
alias_table: 4 scenarios (S06–S09) — all PASS
default_on_insert: 3 scenarios (S10–S12) — all PASS
backfill_correctness:4 scenarios (S13–S16) — all PASS
reader_writer_smoke: 4 scenarios (S17–S20) — all PASS (existing behavior unchanged)
sandbox_mirror: 2 scenarios (S21–S22) — all PASS
rollback: 4 scenarios (S23–S26) — all PASS
acceptance_threshold: 100% (26/26) — same discipline as v0.1 HB-05
failure_at_any_scenario_BLOCKS_production_authorization: TRUE
§6 — Backfill Verification Procedure
pre_backfill_state: authority = NULL on all 86 rows (post-S02)
post_backfill_state_expected: authority = 'draft' on all 86 rows
verification_queries (dry-run only):
Q-1: SELECT count(*) FROM public.tac_logical_unit WHERE authority IS NULL;
expected: 0
Q-2: SELECT authority, count(*) FROM public.tac_logical_unit GROUP BY authority;
expected: 'draft' | 86
Q-3: SELECT count(*) FROM public.tac_logical_unit WHERE authority='draft' AND lifecycle_status='draft_only';
expected: 86
Q-4: SELECT count(*) FROM public.tac_logical_unit WHERE authority NOT IN ('enacted','draft','runtime');
expected: 0 (defensive: no unexpected values)
verification_passes_iff: all 4 queries return their expected values
on_failure: rollback dry-run; record evidence; escalate to GPT review before retry
§7 — Reader/Writer Smoke Tests
purpose: confirm that NO existing reader/writer breaks after Phase α additive columns land
tests_to_run (in dry-run env):
T-1: fn_event_unread('synthetic_actor', NULL) — call the function; confirm it returns rows; confirm the canonical_address column in the result matches baseline values
T-2: fn_iu_verify_invariants('D38-DIEU28-S2-P1') OR equivalent synthetic call — confirm same verdict as baseline
T-3: existing Nuxt-style SELECT query: SELECT logical_unit_id, render_order, …, canonical_address, … FROM <appropriate join> LIMIT 5 — confirm rows return with expected fields; new columns NOT in this select
T-4: trigger trg_tac_birth_gate_lu — INSERT a synthetic tac_logical_unit row; confirm trigger fires; no error
T-5: UNIQUE constraint on canonical_address — attempt duplicate INSERT; confirm rejection
expected_outcome_for_all_smoke_tests: PASS, behavior identical to baseline before Phase α
on_any_smoke_test_failure: BLOCKS dry-run acceptance; rollback and escalate
§8 — sandbox_tac Mirror Verification
tests_in_dry_run:
M-1: sandbox_tac.logical_unit has the two new columns (S04 + S05 confirm structurally)
M-2: sandbox_tac.logical_unit rows have authority=NULL (76 rows; intentional non-backfill)
M-3: sandbox_tac.logical_unit rows have format_version='canonical-address-v1' (76 rows via DEFAULT)
M-4: sandbox_tac.fn_sbx_lu_parent_same_doc still passes existing synthetic test (S21)
M-5: sandbox_tac.canonical_address_alias does NOT exist (intentional omission per design master §5.3)
expected_outcome: all 5 mirror checks PASS
§9 — Rollback Test Cases (detailed)
test_case_R-1: drop authority column on tac_logical_unit
preconditions: S03 complete (backfilled state)
command_outline: ALTER TABLE public.tac_logical_unit DROP COLUMN authority;
expected: column removed; 86 rows preserved; canonical_address SSOT untouched
recovery: re-apply S02 + S03 to restore
test_case_R-2: drop canonical_address_format_version column on tac_logical_unit
preconditions: S01 complete
command_outline: ALTER TABLE public.tac_logical_unit DROP COLUMN canonical_address_format_version;
expected: column removed; 86 rows preserved
recovery: re-apply S01
test_case_R-3: drop canonical_address_alias table
preconditions: S06+ complete; table possibly empty or with synthetic rows (test both)
command_outline: DROP TABLE cutter_governance.canonical_address_alias;
expected: table removed; no orphan; no FK violation (no FK to it exists)
recovery: re-apply S06–S08
test_case_R-4: in-transaction ROLLBACK of full Phase α DDL
preconditions: fresh baseline; BEGIN; (Steps 1–8); ROLLBACK;
expected: post-ROLLBACK schema diff against baseline = empty
this is the strongest rollback evidence; PASSING this scenario alone covers most failure modes
test_case_R-5: drop sandbox_tac mirror columns
preconditions: S04 + S05 complete
command_outline: ALTER TABLE sandbox_tac.logical_unit DROP COLUMN authority, DROP COLUMN canonical_address_format_version;
expected: columns removed; 76 sandbox rows preserved
recovery: re-apply S04 + S05
cascade_concerns: NONE — no FKs target the new columns or the alias table
rollback_authorization_at_production_time: separate Đ32 + G-4 sign-off; not pre-authorized
§10 — Dry-Run Pass/Fail Acceptance Procedure
pre-dry-run_gates:
- Phase α DDL authored, GPT-reviewed
- dry-run env up and reachable
- baseline restored and verified (row counts, schema diff vs production)
- scenario matrix (§4) frozen
during_dry_run:
- execute scenarios in numbered order
- record per-scenario: status, evidence (queries + outputs), timestamp
- any FAIL: stop, record evidence, do NOT continue automatically
post_dry_run_gates_for_acceptance:
- 26/26 PASS
- smoke tests PASS
- rollback tests PASS
- results recorded in a Phase α HB-equivalent closure document
- GPT review of closure document
dry_run_artefacts_to_produce_(future session):
- schema/cutter_governance_alias_phase_alpha_v0.1.sql (DDL artefact; sha256 recorded)
- schema/tac_logical_unit_phase_alpha_additive.sql (DDL artefact; sha256 recorded)
- helpers/phase_alpha_harness.py (scenario harness; sha256 recorded)
- logs/phase_alpha_results.json (per-scenario PASS/FAIL)
- logs/phase_alpha_run.log
- SHA256SUMS
- location: /opt/incomex/backups/dieu44_phase_alpha_<date>/
§11 — Pre-Production Readiness After Dry-Run
sequence_after_26_of_26_PASS:
1. Phase α HB-equivalent closure document (rollback dry-run closure)
2. GPT review of the closure
3. Phase α final readiness review (analog of v0.1 final-readiness/*)
4. GPT review of final readiness
5. Phase α production command-review package
6. GPT review of command-review package
7. explicit User production execution prompt
8. production execution (separate session)
9. production execution report
10. GPT ratification
each_step_terminates_at_GPT_review_or_explicit_user_prompt: TRUE
agent_self_advance_at_any_step: PROHIBITED
§12 — Hard Boundaries
no_dry_run_executed_in_this_plan: TRUE
no_DDL_written: TRUE
no_SQL_executed: TRUE
no_baseline_restored_yet: TRUE
no_dry_run_env_provisioned_yet: TRUE
no_mutation: TRUE
no_change_to_production: TRUE
no_change_to_dry_run_HB_05_env: TRUE
no_phase_alpha_DDL_authoring_started: TRUE
no_production_migration_allowed: TRUE
output_form: phase_alpha_dry_run_planning_only
§13 — Cross-References
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
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
v0_1_rollback_test_plan_(pattern_baseline): knowledge/dev/laws/dieu44-trien-khai/implementation-planning/dot-iu-cutter-v0.1-p0-rollback-test-plan-2026-05-15.md
v0_1_HB_05_closure: knowledge/dev/laws/dieu44-trien-khai/blocker-closure/dot-iu-cutter-v0.1-hb-05-rollback-dry-run-closure-2026-05-15.md
v0_1_HB_05_dry_run_env: pg-dry-run-hb05-2026-05-15 (on VPS; RETAIN, do not reuse for Phase α dry-run)
fresh_backup_pattern_reference: /opt/incomex/backups/dieu44_exec_2026-05-15/ (v0.1 fresh backup; eligible as Phase α baseline if production state unchanged at dry-run setup time)
End of Phase α dry-run plan.