RS4A-PATCH2-03 — U3 Current-Head Uniqueness Policy — 2026-06-21
RS4A-PATCH2-03 — U3 Current-Head Uniqueness Policy — 2026-06-21
Macro: RS4A-PATCH2 · Mục tiêu C (closes Codex re-review residual R2: U3 defined only for status='active' while registration writes status='draft')
Deliverable: 03 of 6 (under rs4a-patch2/) · design-only · scoped correction addendum
Corrects: RS4A-PATCH1-02 §2 (U3 = UNIQUE(canonical_target_dot_code) WHERE status='active'). Does NOT overwrite PATCH1/RS4A; does NOT create any index/constraint.
Gate: REGISTRATION_HOLD · REGISTRATION_CAN_PROCEED = NO
Status: U3_HEAD_CURRENT_UNIQUENESS_DEFINED (Option 1) — one current/head row per canonical DOT code across the non-terminal lifecycle states {draft, active}; the enforcing surface is REQUIRED_NOT_PRESENT ⇒ fail-closed before any draft write.
0. The residual defect this file closes (Codex re-review §5 / §12.2)
PATCH1's U3 was UNIQUE(canonical_target_dot_code) WHERE status='active'. Codex:
"
WHERE status='active'does not protect the state created by registration, because Phase 3 writesstatus='draft'. Two different artifacts or authority-policy versions can therefore create multiple draft rows for the same code before either becomes active. This also contradicts Phase 4's requirement to read exactly one row fordot_code."
Codex's required correction (choose one):
- Option 1 — define one current/head row across all non-terminal lifecycle states, including
draftandactive; or - Option 2 — leave the exact U3 formula unresolved for the Owner while explicitly failing closed before any draft write.
PATCH2 adopts Option 1 (preferred), because it is directly consistent with the live, governed Directus status vocabulary.
1. Live evidence supporting Option 1 (read-only query_pg, db directus, 2026-06-21)
| Fact | Value (LIVE) | Bearing on U3 |
|---|---|---|
directus_fields.dot_tools.status.options.choices |
{draft, active, deprecated, retired} (validation=null, required=false) |
the governed lifecycle vocabulary exists and is exactly four states |
| field note | "Trạng thái: draft/active/deprecated/retired" |
declares the lifecycle order draft → active → deprecated → retired |
dot_tools constraints |
only PRIMARY KEY (id), chk_dot_tier, chk_dot_coverage, chk_dot_trigger, fk_dot_tools_domain |
no UNIQUE on code ⇒ no head protection today |
dot_tools.status data |
active 291 · published 16 (out-of-vocab) · null 2 · draft 0 |
head/code space is currently unconstrained; out-of-vocab rows already exist |
The four governed states partition cleanly into current/head (draft, active) and terminal/non-head (deprecated, retired), which is what Option 1 requires. The vocabulary is not invented; it is the field's declared SSOT.
2. U3 v2 — current-head uniqueness (closes R2, Option 1)
2.1 Canonical formula (lifecycle-role form)
U3: UNIQUE(canonical_target_dot_code) WHERE lifecycle_role = "current_head"
lifecycle_role is a derived classification of status, not a new business column:
lifecycle_role(status) =
"current_head" if status IN ('draft', 'active') -- non-terminal: at most ONE per code
"terminal" if status IN ('deprecated', 'retired') -- terminal/non-head: unconstrained count
2.2 Design-level equivalent (since live dot_tools has no lifecycle_role column)
U3 (equivalent partial uniqueness):
UNIQUE(canonical_target_dot_code) WHERE status IN ('draft','active')
=> one current head per canonical_target_dot_code across statuses {draft, active}
terminal/non-head statuses {deprecated, retired} are NOT counted toward the head
Either realization (a derived lifecycle_role generated column + partial unique, or a partial unique index WHERE status IN ('draft','active')) is a future, Owner/design-gated surface. Neither exists today (no UNIQUE on code), so U3 is REQUIRED_NOT_PRESENT ⇒ fail-closed (§4).
2.3 Policy
draftandactiveare current/head states; at most one such row may exist percanonical_target_dot_code.deprecatedandretiredare terminal/non-head states; any number may exist (history).- Registration may create a
draftrow only if no existingdraftoractivecurrent head exists for the same canonical code. - Activation changes a row
draft → activein place; it does not create another head (the head count stays at one). - Replacement / supersession must first retire/deprecate the old head (
active → deprecated/retired) or use an explicit governed lifecycle operation (cf. PATCH2-02 §2.1:register_dot_revision/supersede_dot_registration). Only then may a new head be created.
2.4 Reject set (Codex re-review §9 / §12.2)
| Reject code | Trigger |
|---|---|
DUPLICATE_CURRENT_HEAD |
a write would yield two current-head rows (draft/active) for one code |
DRAFT_HEAD_ALREADY_EXISTS |
registration attempts a second draft for a code that already has a draft head |
ACTIVE_HEAD_ALREADY_EXISTS |
registration attempts a draft for a code that already has an active head (must supersede/retire first) |
HEAD_POLICY_UNRESOLVED |
the head-uniqueness surface cannot be resolved/enforced ⇒ fail closed before any draft write (the live state today, §4) |
3. U3 is not U1 — axis discipline (carried + sharpened)
| Axis | Prevents | Keyed on | This file |
|---|---|---|---|
| U1 | duplicate exact effect | effect_identity (business-effect only, PATCH2-02) |
unchanged |
| U2 | duplicate grant consume | authorization_nonce |
unchanged |
| U3 | multiple current heads per code | canonical_target_dot_code WHERE current_head |
redefined here (across draft+active) |
| U4 | duplicate artifact registration | canonical_artifact_identity / hash |
remains Owner policy |
- U3 ≠ U1. U1 stops the same effect from committing twice; U3 stops two different effects (different artifact/authority) from both producing a current head for the same code. The "duplicate draft head" case (two distinct artifacts, same code, both
draft) is U3, not U1 — U1 would not catch it because the effects differ. - U4 may remain Owner policy, but per Codex it cannot be relied upon to repair an unstable U1 or a draft-head gap. U3 is the head backstop; U4 is a separate artifact-reuse policy.
4. Live posture — surface absent ⇒ fail-closed before draft write
dot_toolshas no UNIQUE oncode(LIVE) ⇒ the U3 partial-unique /lifecycle_rolehead index isREQUIRED_NOT_PRESENT.- Therefore U3 is defined at the contract level (Option 1) but not enforceable on the live surface ⇒
HEAD_POLICY_UNRESOLVEDat the DB layer ⇒ the replacement registrar must fail closed before any draft write (it must not write adraftrow it cannot prove is the sole current head). - This is the same fail-closed class as U1/U2 (
REQUIRED_NOT_PRESENT) and D13: the policy is decided; the enforcing constraint is a future Owner/design-gated surface. Until the partial-unique exists, no draft is written.
(Live data shows draft = 0 and 16 out-of-vocab published rows — i.e. the head space is presently unconstrained and even the vocabulary can drift, which is exactly why the surface must be added before any registration draft is written. The status-domain CHECK backstop carried in PATCH1-03 (STATUS_DOMAIN_NOT_DB_ENFORCED) is a prerequisite for the lifecycle_role partition to be trustworthy.)
5. What this corrects in PATCH1 (precise, no overwrite)
| PATCH1 location | PATCH1 text | PATCH2 correction |
|---|---|---|
| PATCH1-02 §2 (U3 row) | UNIQUE(canonical_target_dot_code) WHERE status='active' |
UNIQUE(canonical_target_dot_code) WHERE lifecycle_role='current_head' = across {draft, active} (§2) |
| PATCH1-02 §2.1.1 | "duplicate code is caught by U3 (head policy) and/or U1" | retained, but U3 head now spans draft+active so a duplicate draft head is caught (§2.3) |
| PATCH1-08 §4 (axes table) | U3 = UNIQUE(code) WHERE status='active' |
U3 = current-head across {draft, active}; terminal = {deprecated, retired} |
U3 remains a policy axis in the sense that the Owner ratifies the partial-unique surface; PATCH2 fixes the predicate so the decided policy actually protects the registration (draft) state. This is Option 1, so the verdict is not RS4A_PATCH2_HOLD_U3_HEAD_POLICY_UNRESOLVED.
6. Status
U3_HEAD_CURRENT_UNIQUENESS_DEFINED(Option 1): one current head percanonical_target_dot_codeacross{draft, active};{deprecated, retired}terminal/non-head.- Registration writes
draftonly if nodraft/activehead exists; activation is in-placedraft→active; replacement must retire/deprecate first or use an explicit lifecycle operation. - Surface
REQUIRED_NOT_PRESENT(no UNIQUE oncodelive) ⇒ fail closed before any draft write. - No index/constraint created; no DDL. Gate
REGISTRATION_HOLD·CAN_PROCEED = NO.