16 — Sync to PG Truth & Birth Promotion (added doc; the promotion runtime)
16 — Sync to PG Truth & Birth Promotion (added doc; the promotion runtime)
Package:
one-roof-axis-auth-proposal-operational-hardening-build-ready-design-2026-06-02Mode: DESIGN ONLY · READ-ONLY · NO COMMIT · NO MUTATION Added beyond the mission's 00–15 (allowed: "you may add more docs if needed") to keep the promotion/sync runtime coherent and build-ready. Hardens prior…-2026-06-01/11-sync-to-pg-truth-and-birth-promotion.md. This is the doc that docs 02/05/06/07/08/10 reference as "the promotion txn (doc 16)".
16.0 Principle
Promotion = one idempotent, L3-authorized transaction that writes PG truth (and a Birth row for born objects), then projections refresh by Điều 45 signal (after SB-11; synchronous + manual until then). PG → Directus → Nuxt; draft/provisional content is not indexed into production Qdrant.
16.1 The promotion transaction (build-ready shape)
BEGIN; -- one small txn, per-step, under a valid governance_build_authorization (doc 03)
-- 0. PRE-CHECK (read-only):
-- verifier ALLOW (v_build_auth_valid for this step, granted_by<>agent),
-- freshness (draft not stale: unit_edit_draft.stale_at IS NULL / axis_assignment.revision current),
-- idempotency unused (change_set_id not yet applied),
-- pg_advisory_xact_lock(entity_id) -- concurrency
-- 1. WRITE TRUTH:
-- taxonomy.status 'candidate'/'provisional' → 'active' (topic node)
-- OR axis_registry row lifecycle_status → 'active' (axis)
-- + birth_registry row (born object: entity_code, collection_name, owner, certified=f)
-- + owner-per-scope (SB-2) + collection_registry coverage_status
-- 2. FLIP ASSIGNMENT + RECONCILE:
-- axis_assignment.zone 'candidate' → 'approved'; close valid_time; revision+1
-- INSERT entity_labels(entity_code,label_code,...) ON CONFLICT DO NOTHING (doc 05 §5.5)
-- 3. RECORD:
-- registry_changelog + governance_audit_log; change_set_id;
-- governance_build_authorization.consumed_at/consumed_by = now()/agent (single-use, INV-9)
COMMIT; -- (rehearsal: BEGIN..ROLLBACK, entry==exit, zero residue)
-- 4. POST-COMMIT (async): emit governance event (SB-11) → projection refresh → Qdrant (approved only)
consumed_at is written here, inside the COMMIT txn — never by the read-only verifier (doc 02 §2.6).
16.2 Idempotency & concurrency
- Idempotency:
iu_merge_set/iu_split_setidempotency_key+change_set_id; re-running a consumed change-set is a no-op (the grant is alreadyconsumed, and the change-set is recorded). - Concurrency: base-version + content-hash (
unit_edit_draft) andrevision/valid_time(axis_assignment) detect conflicting concurrent edits;pg_advisory_xact_lock(entity_id)serializes per-entity promotions. - Single-use grant: one
governance_build_authorization⇒ one promotion COMMIT (doc 03 partial-unique +consumed_at).
16.3 Five-layer sync (Điều 45-aware)
| Layer | Action | Trigger |
|---|---|---|
| PG | the promotion txn (truth) | direct (the COMMIT) |
| Directus | re-read projection views | on refresh |
| Nuxt | re-render from Directus | on refresh (no direct PG) |
| AgentData / KB | record the design/decision | report write |
| Qdrant | index approved content only | post-commit, approved-only (draft/provisional excluded) |
Until the governance event domain is registered (SB-11), steps 4/Directus/Nuxt/Qdrant refresh is synchronous + manual; after SB-11, signal-driven (signal-not-data; event≠job; MOT-not-executor; heartbeat — Điều 45).
16.4 Rollback
| When | Mechanism |
|---|---|
| pre-apply | drop the unit_edit_draft / retract the candidate axis_assignment (zone→retracted) |
| post-apply | reverse the change_set_id; revoke the governance_build_authorization (if not yet consumed elsewhere); run the rollback runbook referenced by rollback_plan_ref |
| always | deprecate-not-delete (taxonomy.replaced_by, status='deprecated'); pg_dump preflight; verify idle_in_transaction=0 |
16.5 Worked example (topic promotion)
A FAC-08 candidate topic-X with provenance, confidence ≥ threshold, no blocking issue, L2 register_topic_node approved, owner queued, valid L3 grant → promotion txn: taxonomy.status='active' + Birth + owner + coverage; flip its approved assignments + materialize entity_labels; changelog/audit; consume grant. Post-commit: governance event → topic appears in official UI (doc 10); coverage scanner now expects an owner + no island (doc 09).
Forbidden-compliance: design-only; no promotion run; no truth/Birth/label/grant/event written; rehearsals are BEGIN..ROLLBACK only; read-only.