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.

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.2-design/dot-iu-cutter-v0.2-phase-alpha-canonical-address-alias-design-2026-05-15.md