KB-3D6A
dot-iu-cutter v0.2 — Phase α canonical_address_alias Design (2026-05-15)
16 min read Revision 1
dieu44-trien-khaidot-iu-cutterv0.2phase-alphaalias-designcanonical-address-aliasno-ddl2026-05-15
dot-iu-cutter v0.2 — Phase α canonical_address_alias Design
document_path: knowledge/dev/laws/dieu44-trien-khai/v0.2-design/dot-iu-cutter-v0.2-phase-alpha-canonical-address-alias-design-2026-05-15.md
revision: r1
date: 2026-05-15
author: Agent (Claude Code CLI, Opus 4.7 1M)
phase: v0.2 — Phase α (LOGICAL DESIGN ONLY; no DDL written here)
no_ddl_written: TRUE
no_mutation: TRUE
§1 — Purpose
Design a canonical_address_alias table that holds the history of address aliases for canonical_address values: renames, redirects, external-system references, and superseded historical names. The table is created empty in Phase α; no writers or readers are wired in Phase α. The actual rename/redirect pathways are Phase β work.
1.1 Why an alias table is needed at all
problem_statement:
- canonical_address is the SSOT for addressing IUs
- the SSOT is globally unique (DB-enforced) and currently immutable
- real-world events that an alias table needs to record:
a. RENAME — an address was discovered to be wrong (typo, doc renumber); the canonical changes but old references must still resolve
b. REDIRECT — an address points to a successor row that lives under a different canonical (supersession)
c. EXTERNAL_REFERENCE — third-party systems cite the IU using their own identifier; we record the mapping
d. PREVIOUS_CANONICAL — historical names retained for citation discipline even after the current canonical changed
without_an_alias_table:
- any rename loses prior citations
- supersession chains become opaque
- external references have no canonical home
- audit gap on identity history
1.2 Why NOW (Phase α) vs LATER
phase_α_creates_table_with_zero_rows:
- lock in the design before Phase β write hooks land
- guarantees the rollback story stays simple (DROP TABLE on an empty table)
- establishes the schema placement decision before alternatives multiply
- dry-run can exercise empty-table creation + drop without disturbing anything
phase_β_OR_later:
- actual writers (rename flow, redirect flow, supersession trigger)
- readers (presentation-layer renderer, citation resolver)
- retention policy (how long do redirects stay valid)
§2 — Alias Type Candidates (alias_kind enum, application-layer in Phase α)
alias_kind_values:
previous_canonical:
meaning: the alias_text WAS the canonical_address at some prior time; not currently canonical
write_event: a rename or supersession that moved the canonical to a new string
consumer: citation resolver — resolves old citation text to current row
typical_lifecycle: valid_from = date of rename; valid_until = NULL (kept indefinitely for historical resolution)
rename:
meaning: the alias_text is a known TYPO or correction-fixed variant of the canonical_address
write_event: editorial cleanup that fixed a typo (e.g., "D38-DIEU28-S2-P1" was originally typed "D38-DIEU28-S2-P1 " with trailing space; the trimmed form is canonical, the trailing-space form is the alias)
consumer: citation resolver — defensively maps typo variants
typical_lifecycle: valid_from = date of correction; valid_until usually NULL
redirect:
meaning: the alias_text is the OLD canonical of a SUPERSEDED row; the target_unit_id points to the SUCCESSOR row's id
write_event: supersession chain (e.g., DIEU revised, S2 became S3, new row created, old row retired)
consumer: citation resolver — returns the successor IU for a citation against the old canonical
typical_lifecycle: valid_from = date of supersession; valid_until = NULL OR set if the successor is itself superseded later (chain)
external_reference:
meaning: the alias_text is the identifier USED BY A THIRD-PARTY SYSTEM for the same row (e.g., a partner DB or external citation system uses "EXT-XYZ-123" for our D38-DIEU28-S2-P1)
write_event: external integration registers a foreign reference
consumer: external reverse lookup — find our row when a third party sends their identifier
typical_lifecycle: valid_from = date of registration; valid_until = NULL (or set if the partner integration is decommissioned)
phase_α_treatment:
- enum values defined in this document
- application-layer enforcement only (no PG CHECK constraint in Phase α)
- Phase γ can add a vocab table + FK if a registry pattern emerges
§3 — Current Canonical vs Alias (relationship discipline)
current_canonical_address:
- lives on public.tac_logical_unit.canonical_address (SSOT)
- exactly ONE current canonical per row (DB-enforced via UNIQUE)
- cannot be NULL (DB-enforced via NOT NULL)
alias_address_text:
- lives on cutter_governance.canonical_address_alias.alias_text
- MULTIPLE aliases per target_unit_id permitted (no UNIQUE on alias_text)
- MULTIPLE aliases with the SAME alias_text permitted if they reference different units (e.g., "D38-DIEU28-S2-P1" was canonical for row A in 2026-04, then row A retired and row B inherited the canonical in 2026-06; alias history records both)
- aliases are IMMUTABLE once written; on supersession, a NEW alias row is created
- the canonical column never references the alias table; the alias table references the canonical row
mandatory_invariants:
I-1: an alias row is meaningless if target_unit_id does not resolve (Phase β read should warn; Phase α has zero rows so n/a)
I-2: alias_text MUST be a v1-grammar-valid canonical_address (application-layer validation in Phase β; not enforced in Phase α)
I-3: alias_text MAY equal the current canonical of target_unit_id (e.g., for an external_reference alias that coincidentally matches)
I-4: alias_text MUST NOT be empty or whitespace (application-layer)
I-5: valid_from <= valid_until when both set (application-layer)
§4 — Relationship to the Future Display Renderer
storage_form: D38-DIEU28-S2-P1 (canonical, ASCII, v1)
human_display_form: e.g. "Đ28 §2.1" (rendered by Nuxt presentation layer; out of Phase α scope)
alias table's relationship to the renderer:
- the renderer reads the canonical from tac_logical_unit
- the renderer DOES NOT read aliases by default
- aliases are CONSULTED only when resolving a citation that doesn't match a canonical exactly (fallback path)
- resolution order at citation-time:
1. exact match against public.tac_logical_unit.canonical_address — return row
2. fall back to canonical_address_alias.alias_text — return the target_unit_id row
3. unresolved — return citation-resolution failure (with audit signal)
- the renderer NEVER stores or generates human display form into the alias table (display is computed; aliases are stored)
§5 — Uniqueness Expectations
PRIMARY KEY: alias_id (uuid)
UNIQUE constraints: NONE on alias_text (multiple aliases share text legitimately)
UNIQUE composite (alias_text, target_unit_id, alias_kind, valid_from)?
- candidate: prevents accidental duplicate alias rows for the same address mapping
- decision: NOT IN Phase α; can be added in Phase β when write pathways stabilize
INDEXES (recommended for Phase α DDL when authored):
- btree on alias_text (lookup by old citation string)
- btree on target_unit_id (lookup all aliases for a target)
- btree on alias_kind (filter by kind in reports)
- btree on valid_from, valid_until DESC (temporal range scans)
INDEXES_in_phase_α_DDL_(future_authoring):
- PK index automatic
- exact index set is a Phase α DDL authoring decision; the above list is a recommendation only
§6 — Supersession / Deprecation Relationship
supersession_pathway_(Phase β):
- when a row in tac_logical_unit is RETIRED (lifecycle_status='retired') and a SUCCESSOR row is created:
a. successor row inserted with its own canonical (or reused canonical if the predecessor's canonical is freed)
b. an alias row inserted: alias_kind='previous_canonical', alias_text=<predecessor canonical>, target_unit_id=<successor.id>
c. predecessor lifecycle_status set to 'retired'; predecessor's canonical_address remains unchanged (still unique; still resolvable directly)
- if the predecessor's canonical is REUSED for the successor:
- the predecessor's canonical_address MUST first be renamed (impossible directly because UNIQUE; would need a swap-via-temp or a redirect pattern)
- this is a complicated supersession model; recommendation: DO NOT reuse canonicals in Phase β; the v1 grammar has enough namespace headroom to coin a new address for the successor
- retired predecessors remain queryable by their canonical; aliases provide forward navigation to the successor
deprecation_lifecycle:
- an alias is NEVER deleted; it is BOUNDED via valid_until
- retention: indefinite (no expiry policy in Phase α; Phase γ may add retention if audit pressure demands)
§7 — Schema Placement Decision
7.1 Candidates
candidate_P:
location: public schema (alongside public.tac_logical_unit)
full_name: public.canonical_address_alias
pros:
- colocated with the SSOT table
- simplest cross-schema FK semantics (in-schema reference to public.tac_logical_unit.id)
- matches existing convention where TAC tables live in public
cons:
- public schema is already large; alias is a governance concern, not a data concern
- couples governance machinery into the public schema with the data
- if cutter governance later moves to a separate schema, public.alias becomes orphaned
candidate_C (RECOMMENDED):
location: cutter_governance schema (alongside cut_change_set, dot_pair_signature, verify_result)
full_name: cutter_governance.canonical_address_alias
pros:
- consistent with v0.1's "governance machinery lives in cutter_governance" pattern
- keeps public schema clean of vocabulary-governance specifics
- aliases are generated by review/change-set workflows (Phase β); colocating with the workflow tables tightens cohesion
- rollback symmetry with v0.1: DROP TABLE inside cutter_governance is operationally proven from v0.1 dry-run
cons:
- cross-schema soft reference from cutter_governance to public.tac_logical_unit.id
→ no PG FK in Phase α (target_unit_id is uuid stored without FK)
→ cross-schema FK is technically permissible if desired in Phase β; deferred
- readers must know to look in cutter_governance
candidate_T:
location: a new tac_vocab schema dedicated to address vocabulary
full_name: tac_vocab.canonical_address_alias
pros:
- clean separation of address-vocabulary concerns from both data (public) and governance (cutter_governance)
cons:
- introduces a new schema solely for one table in Phase α
- over-architecture; defer until vocabulary surface grows to ≥ 3 tables
candidate_S (sandbox mirror):
location: sandbox_tac schema
full_name: sandbox_tac.canonical_address_alias
pros:
- mirrors any Phase α sandbox alignment policy
cons:
- sandbox is a static test fixture; aliases would never be exercised
- duplicates governance machinery without operational benefit
RECOMMENDATION: OMIT in Phase α (already stated in design master §5.3)
7.2 Recommendation
recommended_placement: cutter_governance.canonical_address_alias (Candidate C)
rationale_summary:
- aliases are a governance concern (driven by review_decision / cut_change_set workflows in Phase β)
- cutter_governance is the existing governance-machinery home (v0.1-live)
- placement consistency with cut_change_set / dot_pair_signature / verify_result
- target_unit_id stored as uuid soft reference (no PG FK in Phase α; cross-schema FK is a Phase β decision)
fallback_recommendation_if_GPT_prefers_public_placement:
- Candidate P (public.canonical_address_alias) is acceptable
- the design changes are minimal: replace schema-qualifier; no field semantic change
§8 — Full Field Set (logical; for Phase α DDL authoring in a later session)
| # | Field | Type | NULL | Default | Notes |
|---|---|---|---|---|---|
| 1 | alias_id |
uuid | NO | gen_random_uuid() |
PRIMARY KEY |
| 2 | target_unit_id |
uuid | NO | — | soft FK to public.tac_logical_unit.id; NO PG FK in Phase α |
| 3 | alias_text |
text | NO | — | the historical/alternate address; application-layer validates v1 grammar |
| 4 | alias_kind |
text | NO | — | one of {previous_canonical, rename, redirect, external_reference}; application-layer enforced |
| 5 | valid_from |
timestamptz | NO | now() |
when this alias became recognized |
| 6 | valid_until |
timestamptz | YES | — | NULL = currently valid; set when alias is bounded |
| 7 | created_by |
text | NO | — | actor identifier recording the alias |
| 8 | rationale |
text | YES | — | free-text explanation (optional) |
| 9 | scenario_ref |
text | YES | — | links to dry-run / production scenarios for audit |
Estimated row footprint in Phase α: 0 (table created empty).
§9 — Phase α DDL Posture (for future authoring; NOT written here)
CREATE_TABLE_pattern: CREATE TABLE IF NOT EXISTS cutter_governance.canonical_address_alias (...)
indexes: see §5 (PK automatic; btree on alias_text, target_unit_id, alias_kind, (valid_from, valid_until DESC) recommended)
no_constraints_in_phase_α_beyond_PK_and_NOT_NULL_fields: TRUE
(no UNIQUE, no CHECK, no FK; Phase β can add)
no_TRIGGER_in_phase_α: TRUE
no_DEFAULT_DATA: TRUE (0 rows)
rollback: DROP TABLE cutter_governance.canonical_address_alias (safe with 0 rows; same pattern as v0.1 cutter_governance schema rollback)
§10 — Compatibility Statements
existing_v0_1_cutter_governance_tables: NOT touched
- decision_backlog_entry, dot_pair_signature, cut_change_set, cut_change_set_affected_row, verify_result remain empty and unchanged
- canonical_address_alias is the 6th table in cutter_governance schema after Phase α
public.tac_logical_unit: NOT touched at table level beyond the two new columns in §3/§4 of the schema design doc
Nuxt readers: INVISIBLE (no Nuxt code reads aliases yet)
PG functions/triggers: NOT modified
future_phase_β_couplings:
- lifecycle-transition trigger may write to canonical_address_alias on supersession
- review_decision (P0-6, blocked by BR-6) may reference alias resolution in its findings
- cut_change_set (P0-3, v0.1-live, currently empty) may include alias adjustments in its payload — Phase β when CUT is authorized
§11 — Hard Boundaries
no_DDL_written: TRUE
no_SQL_executed: TRUE
no_ALTER_TABLE: TRUE
no_CREATE_TABLE_executed: TRUE
no_INSERT_UPDATE_DELETE: TRUE
no_migration: TRUE
no_change_to_existing_schemas: TRUE
no_phase_α_DDL_authoring_started: TRUE
no_dry_run_started: TRUE
output_form: phase_alpha_alias_logical_design_only
§12 — 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
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
v0_1_p0_1_design (alias predecessor concept): knowledge/dev/laws/dieu44-trien-khai/migration-design/dot-iu-cutter-v0.1-p0-1-canonical-address-migration-design-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
End of Phase α alias design.