RS4A-PATCH1-03 — Canonical Inert State Resolution — 2026-06-21
RS4A-PATCH1-03 — Canonical Inert State Resolution — 2026-06-21
Macro: RS4A-PATCH1 · Mục tiêu C (closes Codex C2 canonical inert persisted status)
Deliverable: 03 of 10 · design-only · correction addendum (does NOT overwrite RS4A-02/04/09; does NOT write any row, does NOT add any constraint)
Builds on / corrects: RS4A-02 §3 (status: "<inert/non-active>" placeholder) and RS4A-04 Phase 3/Phase 6.
Gate: REGISTRATION_HOLD · REGISTRATION_CAN_PROCEED = NO
Status: CANONICAL_INERT_STATE_RESOLVED = "draft" (Option 1) + one carried hardening backstop (status-domain CHECK) — the placeholder is replaced by an exact, governed, live-proven value.
0. The Codex defect this file closes
C2 (Codex §4.1 / §17): "The output uses status: \"<inert/non-active>\". A testable contract must select or govern an exact persisted value and prove it is accepted by current metadata/constraints and ignored by activation consumers. A placeholder cannot support deterministic validation or readback." Codex §17: "REJECT <inert/non-active> as a final contract value."
The brief allowed two outcomes: Option 1 (find an existing allowed inert value, prove it) or Option 2 (CANONICAL_INERT_STATE_NOT_AVAILABLE + INERT_STATUS_VALUE_NOT_PROVEN, fail-closed). Live evidence supports Option 1. No value is invented.
1. Live evidence (Claude read-only query_pg, db directus, 2026-06-21)
1.1 The status field is governed at the Directus-metadata layer
directus_fields for (collection='dot_tools', field='status'):
options.choices = [ {Draft: "draft"}, {Active: "active"}, {Deprecated: "deprecated"}, {Retired: "retired"} ]
validation = null
required = false
note = "Trạng thái: draft/active/deprecated/retired"
The field's governed vocabulary (its SSOT) is {draft, active, deprecated, retired}, with a declared lifecycle draft → active → deprecated → retired. The registrar writes through the Directus REST item layer (POST /items/dot_tools, source L156), so this field metadata is part of the effective constraint surface, not merely a UI hint.
1.2 The status column is NOT hard-enforced at the DB layer
dot_toolsconstraints (LIVE):dot_tools_pkey PRIMARY KEY (id),chk_dot_tier,chk_dot_coverage,chk_dot_trigger,fk_dot_tools_domain. No CHECK touchesstatus.dot_tools.statusischaracter varying, nullable.- Live data distribution:
active= 291,published= 16,null= 2. Notepublishedis out-of-vocabulary (not one of the four governed choices) — proof that the choice set is declared but not DB-enforced (legacy/drift rows exist).
1.3 The activation producer fires only on status='active'
fn_context_pack_on_dot_register (RS4A-09, live body): pg_notify('context_pack_event', …) fires iff NEW.tier ∈ watch_tiers (["A","B","C"]) AND NEW.status='active'.
2. Resolution: canonical inert persisted status = draft
draft is selected as the canonical inert registration state. Proof against each C2 criterion:
| C2 criterion | Proof for draft |
Evidence tier |
|---|---|---|
| Exact value (not a placeholder) | status = "draft" — a single literal, supports deterministic validation + readback |
— |
| Governed, not invented | draft is an explicit declared choice in directus_fields.dot_tools.status.options.choices (value "draft", label "Draft"); the field note names it first in the lifecycle |
LIVE |
| Accepted by current metadata/constraints | DB: no CHECK rejects it (status unconstrained at PG). Directus: validation=null, required=false, and draft IS in the declared choices ⇒ accepted by the REST item write path |
LIVE (PG pg_constraint + directus_fields) |
| Ignored by the activation producer | 'draft' ≠ 'active' ⇒ fn_context_pack_on_dot_register notify condition NEW.status='active' is false ⇒ no context_pack_event emitted at a draft insert |
LIVE (function body, RS4A-09) |
| Lifecycle-coherent (pre-activation) | the field's own lifecycle is draft → active → …; draft = registered-but-not-activated, exactly the inert registration state RS4A requires; activation is a separate Owner-gated UPDATE draft → active (RS4A-04 Phase 6) |
LIVE (field note) |
Therefore the RS4A-02 §3 output is corrected:
registered_row_intent.columns.status = "draft" # was "<inert/non-active>"
and the no-activation invariant (RS4A-02 §5.2, RS4A-09 §3.1) is now concrete: register with status='draft'; never status='active' at registration.
3. Why this is Option 1, not Option 2 (and not a HOLD)
Codex C2 asked for "select or govern an exact persisted value." draft is selected from the field's governed vocabulary and proven accepted + inert. The placeholder is gone. There is no invention: draft is the field's declared first-lifecycle state. Hence the inert-state axis is CLOSED, and the PATCH1 verdict is not RS4A_PATCH1_HOLD_INERT_STATE_UNPROVEN.
(If the field metadata had instead constrained status to a set with no inert member, or had been empty, Option 2 / INERT_STATUS_VALUE_NOT_PROVEN would have been the honest outcome. It is not the case here.)
4. Carried residual hardening (NOT a blocker on the value)
Two honesty caveats are carried so the resolution is not overclaimed. Neither blocks selecting draft; both are CONTRACT_BACKSTOP / future-surface items, like D13's missing UNIQUE.
STATUS_DOMAIN_NOT_DB_ENFORCED(CONTRACT_BACKSTOP). Because there is no PG CHECK onstatusand out-of-vocab rows already exist (published×16), the governed vocabulary{draft, active, deprecated, retired}is advisory at the DB layer — a future/buggy writer could persistactive(or any string) and bypass the inert intent. The replacement contract must therefore also require a governed CHECK/enum that locksstatusto the declared vocabulary, so the inert value cannot be bypassed andactivecannot be set at registration. RS4A does not author this constraint (no DDL); it is carried as a backstop the governed registration depends on, Owner/design-gated. Until it exists, the inert-status invariant is enforced by the registrar's own write discipline + the Phase-4 post-commit readback (verifystatus='draft'), not by the DB.G7-consumer(carried from RS4A-09). "Ignored by activation consumers" is proven at the producer (no notify on a draft insert). For the registration path this is structurally sufficient: a draft insert emits nocontext_pack_event, so the consumer is never invoked at registration. The consumer body remains unread, so "consumer is inert" is not asserted for the separate activation UPDATE path (Phase 6) — that path stays fail-closed (context_pack_mode='warn'live, but unproven). This does not affect the inert-registration claim.
5. Added acceptance tests (feed PATCH1-07)
| New case | Input/state | Expected | Layer |
|---|---|---|---|
T-PX-1 |
registrar writes status='draft' for a watch-tier (A/B) row |
ACCEPT as inert; no context_pack_event; post-commit readback confirms status='draft' |
R/SN |
T-PX-2 |
registrar attempts status='active' (the source D05 behavior) |
ACTIVATION_AT_REGISTRATION (reject) |
R |
T-PX-3 |
a non-vocabulary status (e.g. published/arbitrary) is written at registration |
HOLD STATUS_DOMAIN_NOT_DB_ENFORCED until a governed status CHECK exists |
R/SCHEMA |
T-PX-4 |
Phase-4 verifier reads back the committed row | PASS only if status='draft' and notify-not-emitted proof holds |
R |
6. Status
- Canonical inert state:
CANONICAL_INERT_STATE_RESOLVED = "draft"— governed Directus choice, live-proven accepted + non-activating; placeholder retired. - Carried:
STATUS_DOMAIN_NOT_DB_ENFORCED(add status CHECK backstop) +G7-consumer(activation-UPDATE path only). Neither blocks the value. - No row written, no constraint added, no DDL. Gate
REGISTRATION_HOLD·CAN_PROCEED = NO.