Pre-Birth Admission Control — 02 Permit Model Options
02 — Pre-Birth Permit / Admission Model — Options & Recommendation
Goal: define the simplest robust rule — "no valid birth/admission permit → no production object insert" — for critical families, reusing existing machinery (doc 01), reversible, owner-gated. Design only; nothing applied.
0. Design constraints (from law + live evidence)
- Reuse-first. The codebase already has a permit-ledger pattern (
iu_gate_transition+fn_iu_gate_open), a deferred-finalize pattern (IU layer2), and a BEFORE gate (fn_birth_gate). A new model must compose these, not duplicate them. - Decouple from the BORN ledger.
birth_registryis the post-fact truth (1.12M rows, all'born'). Pre-birth reservation is a different lifecycle phase and must not be mixed into it. - Reversible by default. Any new object must be droppable/detachable with a one-line rollback and zero data migration on existing rows.
- Fail-closed only where clean. Blocking is permitted only for a family that is backlog-clean and explicitly approved; everywhere else, report-only.
- Idempotent + auditable. Mirror Điều 32: approval_id, TTL/expiry, actor, reason, single-use, command log.
Option 1 — Extend birth_registry with reservation states
Add states RESERVED → BORN → FAILED → EXPIRED → RETIRED to the existing status column; a row is inserted at RESERVED before object creation and promoted to BORN on finalize.
Impact assessment (live):
- 1,121,537 existing rows are all
status='born'with no CHECK. Introducing a state machine means either (a) adding a CHECK that must accept the legacy 'born' value forever, or (b) backfilling — touching 1.12M rows. Both raise migration + rollback risk. - The UNIQUE(entity_code)-alone defect poisons reservations too: a RESERVED row for
(PIV-101, pivot_definitions)collides with an existing(PIV-101, pivot_results)row → reservation silently impossible via the sameON CONFLICTpath. The defect must be fixed first and the AFTER trigger (166 tables) reworked to understand RESERVED rows (it currently doesON CONFLICT (entity_code) DO NOTHING— it would now hit RESERVED rows and skip the BORN promotion). fn_birth_registry_autowould need a rewrite on all 166 tables' worth of behavior: instead of "insert BORN if absent," it must "find matching RESERVED permit, promote to BORN, else create/flag." High blast radius.- Two semantics in one table. Queries, views (
v_birth_orphan,v_birth_phantom), and the 1402 certified rows all assumebirth_registryrow == a born object. Reservation rows would have to be excluded everywhere — a pervasive, error-prone change.
Verdict: REJECTED for normal operation. Conflates pre-birth and post-birth in the highest-value, highest-volume table; forces a 1.12M-row migration and a 166-table trigger rewrite; the entity_code-alone defect makes reservations themselves collide. Rollback is not a one-liner.
Option 2 — Separate birth_admission_permit table ✅ RECOMMENDED
A new, initially-empty table that holds reservations issued before object creation. The existing fn_birth_gate (BEFORE INSERT) is extended to consume a permit; a new DEFERRABLE CONSTRAINT TRIGGER (modeled on IU layer2) finalizes at commit. birth_registry semantics are untouched.
2.1 Proposed shape (design — NOT created)
birth_admission_permit
permit_id uuid PK default gen_random_uuid()
collection_name text NOT NULL
entity_code text NOT NULL -- the code the object WILL have
request_hash text NOT NULL -- idempotency key (see B-design)
requested_by_dot text -- DOT that requested it
requested_by_actor text NOT NULL -- human/service principal
species_code text -- decided BEFORE insert
composition_level text
governance_role text -- decided BEFORE insert (governed/observed/excluded)
approval_id uuid -- required for governed families (Điều 32 parity)
status text NOT NULL default 'RESERVED'
CHECK (status IN ('RESERVED','CONSUMED','FINALIZED','FAILED','EXPIRED','REVOKED'))
expires_at timestamptz NOT NULL -- TTL (watchdog expires)
consumed_at timestamptz -- set when the matching insert fires
finalized_birth_id bigint -- FK→birth_registry.id, set at commit
failure_reason text
created_at timestamptz default now()
-- KEYS:
UNIQUE (collection_name, entity_code) WHERE status IN ('RESERVED','CONSUMED') -- one live permit per identity
UNIQUE (request_hash) -- replay protection
Note the composite
(collection_name, entity_code)uniqueness — the permit layer is born free of theentity_code-alone defect. This also surfaces the birth_registry composite-unique fix as an explicit prerequisite (the permit and the BORN row must agree on the same key shape).
2.2 How it plugs into existing machinery (reuse map)
| Need | Reuse |
|---|---|
| Issue permit w/ approval + TTL + ledger | mirror fn_iu_gate_open → iu_gate_transition; new fn_birth_permit_request(...) writes the permit + a command-log row via fn_dot_iu_command_log |
| BEFORE-insert "permit required" check | extend fn_birth_gate: after the 5 shape checks, for an enforced family, require a RESERVED permit matching (TG_TABLE_NAME, code), mark it CONSUMED. No permit → behave per mode (warning/blocking). |
| Finalize at commit, no partial state | new DEFERRABLE INITIALLY DEFERRED CONSTRAINT TRIGGER fn_birth_permit_finalize (pattern = fn_iu_birth_gate_layer2): at commit, assert the consumed permit now has a matching BORN row; set finalized_birth_id, status='FINALIZED'; else RAISE (whole tx rolls back). |
| Expire stale reservations | new fn_birth_permit_watchdog() (pattern = fn_iu_gate_watchdog): RESERVED past expires_at → EXPIRED. |
| Visibility of failed/expired | new read-only view v_birth_admission_permit_status (RESERVED/CONSUMED/FINALIZED/EXPIRED/FAILED counts + tail). |
2.3 Why Option 2 wins
- Reversible:
DROP TABLE birth_admission_permit+ detach the gate extension + drop the constraint trigger = full rollback, zero touch to the 1.12M-rowbirth_registry. - Non-coupling: birth_registry keeps meaning "born objects." Permits are a separate, droppable lifecycle store.
- Hook already exists:
fn_birth_gateis already BEFORE INSERT on the three cleanest families (dot_tools, collection_registry, meta_catalog). We extend, not install. - Finalize already proven: the IU layer2 deferred constraint trigger is a live, working precedent for "nothing partial survives commit."
- Permit ledger already proven:
iu_gate_transitionshows the exact issue/expire/audit pattern works in this DB under Điều 32. - Sidesteps the defect at the permit layer and forces the birth_registry composite-unique fix to be done deliberately (prerequisite), not as a hidden coupling.
2.4 Residual risks
- Two writes (permit + object) — but the deferred constraint guarantees atomicity at commit (no orphan permit, no permit-less born row for enforced families).
- Requires the composite-unique fix on
birth_registrybefore the FINALIZED↔BORN assertion is reliable for collision-prone families. - Requires registrar creds to issue permits operationally (today ABSENT).
Option 3 — Current AFTER-trigger + stronger scanner only
Keep fn_birth_registry_auto as the creator; add more/scheduled scanners to detect permit-less or unborn objects faster.
Verdict: REJECTED for normal operation; RETAINED for legacy/backfill + emergency. A scanner is, by definition, after-the-fact: it can find a permit-less object only after it already exists. It can never make "no permit → no insert" true (the open-goal law's "automation-first: safety must not depend on remembering to run something later"). It remains the correct tool for (a) the pre-cutoff backlog sweep (doc 31 design), (b) emergency reconciliation, and (c) detecting bypass attempts (doc 07). It is necessary but not sufficient; promoting it to the normal mechanism is the exact anti-pattern this macro exists to end.
Recommendation
Adopt Option 2 (separate birth_admission_permit table), composed from the existing IU layered-trigger + Điều 32 gate-token-ledger patterns, with the BORN-side birth_registry composite-unique fix as a hard prerequisite. Option 1 rejected (couples lifecycles, 1.12M-row migration, defect bleed). Option 3 demoted to legacy/backfill/emergency only.
Pilot family: dot_tools (rationale in doc 04): already has the BEFORE gate, orphan-clean, creation already DOT-driven/governed, controlled volume, and making DOT creation permit-first is maximally on-strategy. Enforcement (blocking) is applied for that family only after the permit table + composite-unique fix land under owner approval — never as a global flip.
Exact risks to accept before any apply: (1) composite-unique migration on a 1.12M-row table (online, but must be rehearsed); (2) the gate extension must preserve the kill-switch + null-code semantics for non-enforced families so nothing else breaks; (3) registrar creds must exist to issue permits or the pilot blocks legitimate creation; (4) the deferred constraint must fail-closed without deadlocking concurrent inserts.