KB-5808
dot-iu-cutter v0.2 — Phase α fn_tac_birth_gate_lu Read-Only Inspection (2026-05-15)
14 min read Revision 1
dieu44-trien-khaidot-iu-cutterv0.2phase-alphafn-tac-birth-gate-luread-only-inspectionb-ddl-32026-05-15
dot-iu-cutter v0.2 — Phase α fn_tac_birth_gate_lu Read-Only Inspection
document_path: 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
revision: r1
date: 2026-05-15
author: Agent (Claude Code CLI, Opus 4.7 1M)
phase: v0.2 — Phase α DDL authoring prerequisite (B-DDL-3)
mutation_performed: false
function_modified: false
§1 — Purpose
Resolve B-DDL-3 from the Phase α design report: read-only inspection of the body of public.fn_tac_birth_gate_lu (the BEFORE INSERT OR UPDATE trigger function on public.tac_logical_unit) to confirm that adding the new Phase α columns (authority, canonical_address_format_version) does not conflict with any implicit constraint embedded in the trigger function.
Inspection method: pg_get_functiondef() + pg_get_triggerdef() via psql over SSH; read-only.
§2 — Function Signature
schema: public
name: fn_tac_birth_gate_lu
arguments: (none)
returns: trigger
language: plpgsql
security: SECURITY DEFINER
search_path: 'public', 'pg_catalog'
volatility: v (volatile)
§3 — Trigger Definition
CREATE TRIGGER trg_tac_birth_gate_lu
BEFORE INSERT OR UPDATE ON tac_logical_unit
FOR EACH ROW EXECUTE FUNCTION fn_tac_birth_gate_lu();
Fires on every row-level INSERT or UPDATE on public.tac_logical_unit. Phase α DDL is ADD COLUMN (metadata-only; does NOT fire INSERT/UPDATE triggers); the backfill is an UPDATE (does fire the trigger per row).
§4 — Function Body (verbatim)
CREATE OR REPLACE FUNCTION public.fn_tac_birth_gate_lu()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'public', 'pg_catalog'
AS $function$
DECLARE
v_section_active BOOLEAN;
v_parent_doc_code TEXT;
BEGIN
IF NEW.canonical_address !~ '^D38-[A-Z0-9]+-((ROOT)|(S[0-9]+(-P[0-9]+(-[0-9]+)*)?))$' THEN
RAISE EXCEPTION 'BG-LU-01: canonical_address % không khớp regex D38-local', NEW.canonical_address
USING ERRCODE = 'check_violation';
END IF;
IF NEW.doc_code IS NULL OR pg_catalog.length(pg_catalog.btrim(NEW.doc_code)) = 0 THEN
RAISE EXCEPTION 'BG-LU-02: doc_code rỗng' USING ERRCODE = 'not_null_violation';
END IF;
IF NEW.parent_id IS NOT NULL THEN
SELECT doc_code INTO v_parent_doc_code
FROM public.tac_logical_unit
WHERE id = NEW.parent_id;
IF v_parent_doc_code IS NULL THEN
RAISE EXCEPTION 'BG-LU-03: parent_id % không tồn tại', NEW.parent_id
USING ERRCODE = 'foreign_key_violation';
END IF;
IF v_parent_doc_code != NEW.doc_code THEN
RAISE EXCEPTION 'BG-LU-03: parent doc_code % != child doc_code %',
v_parent_doc_code, NEW.doc_code
USING ERRCODE = 'check_violation';
END IF;
END IF;
SELECT (lifecycle_status = 'active') INTO v_section_active
FROM public.tac_section_type_vocab
WHERE code = NEW.section_type;
IF NOT coalesce(v_section_active, FALSE) THEN
RAISE EXCEPTION 'BG-LU-04: section_type % không active', NEW.section_type
USING ERRCODE = 'check_violation';
END IF;
IF NEW.owner IS NULL OR pg_catalog.length(pg_catalog.btrim(NEW.owner)) = 0 THEN
RAISE EXCEPTION 'BG-LU-05: owner rỗng' USING ERRCODE = 'not_null_violation';
END IF;
IF NEW.sort_order IS NULL OR NEW.sort_order < 0 THEN
RAISE EXCEPTION 'BG-LU-06: sort_order invalid' USING ERRCODE = 'check_violation';
END IF;
NEW.updated_at := now();
RETURN NEW;
END;
$function$
§5 — Reference Inventory (what the body uses)
columns_referenced (NEW.*):
- NEW.canonical_address (read; regex-validated)
- NEW.doc_code (read; non-empty check)
- NEW.parent_id (read; existence + same-doc check)
- NEW.section_type (read; FK + vocab-active check)
- NEW.owner (read; non-empty check)
- NEW.sort_order (read; >= 0 check)
- NEW.updated_at (WRITTEN to now() — side effect)
tables_referenced:
- public.tac_logical_unit (SELECT to resolve parent_id → doc_code)
- public.tac_section_type_vocab (SELECT to confirm section_type is lifecycle 'active')
(note: `lifecycle_status='active'` here refers to tac_section_type_vocab.lifecycle_status, NOT tac_logical_unit.lifecycle_status)
vocabulary_refs:
- regex hardcoded: ^D38-[A-Z0-9]+-((ROOT)|(S[0-9]+(-P[0-9]+(-[0-9]+)*)?))$
§6 — Does the Function Reference canonical_address?
yes_references_NEW.canonical_address: TRUE
nature_of_reference: regex validation only (no mutation)
regex_pattern: ^D38-[A-Z0-9]+-((ROOT)|(S[0-9]+(-P[0-9]+(-[0-9]+)*)?))$
exception_code_on_mismatch: BG-LU-01 (check_violation)
implication_for_phase_α: NONE
- Phase α does NOT modify canonical_address
- Phase α backfill UPDATE sets `authority` only; canonical_address remains unchanged
- the regex check re-runs during backfill UPDATE for each row; it will pass because the existing value already conforms (all 86 production rows were inserted under this trigger)
§7 — Does the Function Reference lifecycle_status?
yes_references_lifecycle_status: TRUE
but_it_is_NOT_NEW.lifecycle_status:
- the lifecycle_status read inside the function is tac_section_type_vocab.lifecycle_status
- NOT public.tac_logical_unit.lifecycle_status
- these are DIFFERENT lifecycle vocabularies for different concerns
implication_for_phase_α_and_BR-4: NONE
- BR-4 mapping reads tac_logical_unit.lifecycle_status to derive authority
- the trigger reads tac_section_type_vocab.lifecycle_status to validate section_type
- the two never collide
§8 — Does the Function Reference authority or canonical_address_format_version?
authority_referenced: FALSE
canonical_address_format_version_referenced: FALSE
implication_for_phase_α: SAFE
- Phase α can add both columns without the trigger seeing them
- the trigger does NOT validate, write, or block on either new column
- the trigger fires on the backfill UPDATE but is INDIFFERENT to the new columns
§9 — Conflict Assessment for Phase α
9.1 ADD COLUMN operations
operation: ALTER TABLE public.tac_logical_unit ADD COLUMN authority text NULL DEFAULT 'draft'
trigger_fires? NO
- ADD COLUMN is metadata DDL; does not fire row-level INSERT/UPDATE triggers
conflict? NONE
operation: ALTER TABLE public.tac_logical_unit ADD COLUMN canonical_address_format_version text NOT NULL DEFAULT 'canonical-address-v1'
trigger_fires? NO
conflict? NONE
9.2 Backfill UPDATE
operation: 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;
trigger_fires? YES — BEFORE UPDATE FOR EACH ROW, 86 times
per_row_trigger_behavior:
- canonical_address regex check: PASSES (canonical_address unchanged; already conforms)
- doc_code non-empty: PASSES (existing value)
- parent_id existence + same-doc check: PASSES (existing parents are valid)
- section_type vocab check: PASSES (vocab is stable; all 86 rows have valid section_type)
- owner non-empty: PASSES (existing value)
- sort_order >= 0: PASSES (existing sort_order; CHECK constraint already in place)
- NEW.updated_at := now(): EXECUTED — every row's updated_at gets bumped to backfill time
side_effect_to_flag:
updated_at_bump_on_all_86_rows: TRUE
consequence: original `updated_at` timestamps will be lost on backfilled rows
acceptance: this is normal UPDATE semantics; the bump itself is correct behavior; no escalation required
alternative_(NOT_recommended): SET LOCAL session_replication_role='replica' to bypass triggers during backfill — would skip validation; NOT worth the risk
conflict? NONE (with the documented side effect)
9.3 New table creation (cutter_governance.canonical_address_alias)
operation: CREATE TABLE IF NOT EXISTS cutter_governance.canonical_address_alias (...)
trigger_fires? NO
- DDL on a different schema; trg_tac_birth_gate_lu only fires on public.tac_logical_unit
conflict? NONE
9.4 Sandbox mirror
operation: ALTER TABLE sandbox_tac.logical_unit ADD COLUMN ... (no backfill)
trigger_fires? NO
- sandbox_tac has its own helper function (sandbox_tac.fn_sbx_lu_parent_same_doc) but inspection earlier confirmed it does NOT reference the new columns either
conflict? NONE
§10 — Observations Beyond Conflict (informational)
O-1: trigger regex hardcodes "D38-" prefix
current_implication: only D38-* canonical_addresses validate; D40-*, D41-* etc. would be rejected at INSERT/UPDATE
Phase_α_implication: NONE (all 86 existing rows are D38-prefixed; Phase α adds no rows)
future_implication: when other doc ids land, the trigger regex must be revised — this is a Đ24/Đ32 followup item for v0.3+ (NOT a v0.2 blocker)
O-2: trigger regex grammar matches BR-5 canonical-address-v1 grammar exactly
effect: write-time enforcement of canonical-address-v1 is ALREADY active in production
this is positive corroboration of the BR-5 ratification choice — the syntax was already enforced; BR-5 simply documented the implicit rule
O-3: trigger reads tac_section_type_vocab to enforce section_type FK + activity
effect: rows with section_type values that have been DEACTIVATED in the vocab would fail re-validation during backfill
current_state: 86 rows × {heading, paragraph, checklist, principle, technical_spec, governance_process, process} section_types — all active in vocab
mitigation: pre-backfill, verify no section_type in current 86 rows is inactive in the vocab; if any inactive value found, address before backfill
O-4: trigger uses SECURITY DEFINER + search_path 'public', 'pg_catalog'
effect: function runs with definer's privileges (typically superuser at create time); search_path prevents schema poisoning
Phase_α_implication: NONE; the function does not write to anything outside its trigger scope
§11 — Risk Assessment
risk_to_phase_α_from_fn_tac_birth_gate_lu: NONE
risk_per_operation:
ADD COLUMN authority: 0 (trigger does not fire)
ADD COLUMN format_version: 0 (trigger does not fire)
UPDATE backfill of authority: 0 (trigger fires but is indifferent to authority; canonical_address regex passes)
CREATE TABLE alias: 0 (different schema; trigger does not fire)
ALTER TABLE sandbox_tac mirror: 0 (different schema; trigger does not fire)
documented_side_effect (not a risk):
updated_at bumped on all 86 rows during backfill (normal UPDATE semantics)
dependencies_to_validate_before_backfill_(simple_queries; not blocking):
Q-CHK-1: every existing tac_logical_unit row's section_type is currently active in tac_section_type_vocab
Q-CHK-2: every existing tac_logical_unit row's parent_id (when non-null) still resolves AND shares doc_code
Q-CHK-3: every existing canonical_address still matches the regex (defensive; should already be true since trigger has been live)
expected_outcome: all 3 queries return zero failing rows
authoring_location: include these checks in the Phase α dry-run preflight scenario set
§12 — Recommendation
recommendation_for_phase_α: PROCEED with Phase α DDL authoring as designed
specifics:
- no DDL modification required to accommodate fn_tac_birth_gate_lu
- no trigger modification required
- backfill UPDATE is safe; updated_at side effect is acceptable
- include Q-CHK-1/2/3 (§11) as pre-backfill verification queries in the dry-run plan
- leave the regex's hardcoded D38- prefix as a v0.3+ followup item; not a Phase α blocker
§13 — Hard Boundaries
no_function_modified: TRUE
no_DDL_executed: TRUE
no_mutation: TRUE
no_function_replaced: TRUE
no_trigger_recreated: TRUE
read_only_inspection_only: TRUE
output_form: br_phase_α_fn_inspection_read_only
§14 — 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 (B-DDL-3 prerequisite cited here)
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
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 (RS-5/RS-6 surfaces this inspection)
ddl_draft_companion: knowledge/dev/laws/dieu44-trien-khai/v0.2-ddl-authoring/dot-iu-cutter-v0.2-phase-alpha-ddl-draft-2026-05-15.sql.md
ddl_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
ddl_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
br_5_closure_(grammar_match): 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_3_reader_writer_inventory: knowledge/dev/laws/dieu44-trien-khai/v0.2-planning/dot-iu-cutter-v0.2-br-3-canonical-address-reader-writer-inventory-2026-05-15.md
End of fn_tac_birth_gate_lu read-only inspection.