32 — Birth/Registry → Governance Handoff Ledger Design (Branch B, Điều 45, no Birth modification, design-only, read-only zero mutation, 2026-06-01)
32 — Birth/Registry → Governance Handoff Ledger Design (Branch B)
Path:
knowledge/dev/reports/architecture/one-roof-governance-technical-addendum-and-implementation-index-2026-06-01/Doc: 32. Track: Branch B of the Backfill / Handoff / Input-Control addendum. Builds on doc 31 (backfill), docs 24/25 (T7/T6), concept canon 01–02, mission §4K / Điều 45 Queue Law, GPT direction (backfill+handoff+input-control). Status: DESIGN ONLY. No PG/Directus/Qdrant/Nuxt mutation. No table/view/function/trigger created. No event domain registered. No event/job/notification emitted. Birth process is NOT modified (proof in §4). Register-before-emit means the governance event domain is registered later (T7 build); this doc specifies the durable handoff contract, not its activation. Owner (proposed): GOV-SIV runs the handoff intake (read/capture/enqueue); it never executes remediation. Producers are the existing Birth/Registry substrate; the executor of any governance work is a GOV-DOT under an approved APR (Điều 35). A Mother (MOT/MOUT) is never the executor. Evidence base: live read-only re-verified 2026-06-01 (counts inline); Điều 45 v1.0 enacted 2026-05-26.
0. §0-GOV declaration
§0-GOV Governance Coverage Declaration — Branch B (Handoff Ledger)
governed_objects: [ handoff_signal, handoff_intake_worker, handoff_cursor ] (Class-2 process records)
owner_per_scope: { policy: GOV-COUNCIL, health: GOV-SIV, execution: GOV-DOT,
render: GOV-MOUT(TTL→C-5), approval: Điều32, audit: GOV-SIV }
coverage_profile: [ queue/worker profile — owner, audit, rollback, heartbeat, issue-event ]
axes_introduced: [ none ]
detection_path: birth_registry tail + registry_changelog tail + event_outbox/event_pending
issue_event_types: [ handoff_lag, handoff_dlq, handoff_silent_gap, handoff_unregistered_type ]
(register-before-emit, Điều 45 — NOT registered here)
exceptions: [ none minted ]
1. Goal and the Điều 45 frame
Backfill (doc 31) onboards the existing 1.04M-object backlog once. Branch B answers the future: every new birth/registry/change must be handed off to the governance candidate/coverage pipeline durably, replayably, and auditably — with no lost handoff and no dependence on memory or prompt discipline (GPT direction). The handoff layer is bound by the Điều 45 Queue Law (mission §4K), whose five boundaries this design preserves verbatim:
- Queue carries signal, not data. A handoff carries
{object_type, object_ref, canonical_address, group_key, change_kind, source, occurred_at}— the address to find the truth, never the object's contents. - Event vs job. A handoff is a signal ("object X was born / changed"). Classifying it (candidate scan) and remediating it (assign owner) are jobs — the classify job runs read-only under GOV-SIV; any remediation job routes through the Điều 32 APR spine. The signal never carries an executable instruction.
- Executor boundary. The intake worker only captures and enqueues a dirty-group mark (doc 34); it does not classify or remediate. The executor of governance work is a governed DOT under an approved APR.
- MOT is not the executor. No Mother consumes the handoff to mutate governed state. MOUT/MOT may render coverage, never execute remediation.
- Silent-gap / heartbeat. The intake worker emits a periodic heartbeat; a missed heartbeat is itself a finding (
handoff_silent_gap). Absence of handoffs (quiet system) is distinguishable from intake-not-running.
2. The durable substrate already exists — reuse map (no new bus, no new ledger)
Discover-first: the live system already has every primitive a durable handoff needs. Branch B wires them, it does not mint a parallel ledger:
| Handoff need | Reused live object | Live evidence (2026-06-01) | How |
|---|---|---|---|
| Durable born/registered ledger | birth_registry |
1,037,716 rows, append-only, (born_at, id) monotone |
the source-of-truth tail for births (object born / registered) |
| Durable change ledger | registry_changelog |
68,323 rows; (entity_type, entity_code, action, timestamp, alert_level, resolved) |
the source-of-truth tail for registry/collection/count/source/policy/owner changes |
| Registered-signal emit (Đ45) | event_outbox |
Đ45 signal-not-data shape; payload_classification='safe_metadata', safe_payload jsonb |
the emission path once a governance domain is registered |
| Pre-registration capture / retry staging | event_pending |
0 rows (structure present, unused) — event_type_hint, entity_table, entity_ref, capture_payload, processed_at, error_count, last_error |
durable capture of a handoff before its type is registered+active; retry ledger |
| Type registry (register-before-emit) | event_type_registry |
40 rows; domains iu(16)/mother(9,0 active)/piece(6)/staging(5)/system(4); no governance domain (SB-4) |
the governance domain is defined (T7 doc 24) and registered later |
| Consumer cursor + DLQ counters | iu_route_worker_cursor |
1 active (iu_outbound_default, events_seen 68, dead_lettered 0) |
the exact durable-cursor + replay + DLQ precedent the intake worker reuses |
| Audit | registry_changelog (+ event_outbox heartbeat) |
— | one row per intake batch / DLQ event |
Decision (reuse vs new vs hybrid): HYBRID, reuse-first.
- Default = tail existing ledgers via a cursor (CDC-style), reusing
iu_route_worker_cursor— no new ledger table, no Birth modification, no trigger (§4). - Capture via
event_pendingwhen a handoff must be staged before its governance event type is registered+active (register-before-emit), reusingevent_pending'serror_count/last_error/processed_atfor retry/DLQ. - Emit via
event_outboxunder thegovernancedomain once registered (T7 build) for downstream subscribers. - A dedicated governance handoff ledger is NOT created now. It is recorded as a future option only if the handoff signal taxonomy outgrows
event_pending+ the tailed ledgers (see §10). Minting one now would be a second roof and is forbidden.
3. The ten handoff signal kinds → source tail → registered event type
Each mission-mandated handoff kind maps to a tail source (where the truth already lands durably) and a registered governance event type (the Đ45 signal). On consume, each marks the affected group_key dirty in the candidate-state store (doc 34) — that dirty-mark is the handoff into the candidate layer.
| # | Handoff kind | Tail source (durable) | Registered event type (domain=governance) |
Dirties group_key dimension |
|---|---|---|---|---|
| 1 | object born | birth_registry (born_at tail) |
governance.handoff.object_born |
object_class + source_registry |
| 2 | object registered | birth_registry / per-class registry |
governance.handoff.object_registered |
object_class + source_registry |
| 3 | collection changed | registry_changelog (entity_type='collection') + collection_registry |
governance.handoff.collection_changed |
source_registry + scope |
| 4 | registry count changed | registry_changelog / v_registry_counts |
governance.handoff.count_changed |
source_registry (count>1 candidacy, M-DEF-10) |
| 5 | object retired | registry_changelog (action=retire/supersede) + status cols |
governance.handoff.object_retired |
object_class + lifecycle/status |
| 6 | source changed | registry_changelog (source_kind/migration_state) |
governance.handoff.source_changed |
source_registry |
| 7 | axis introduced | Axis Registry (when live) / pivot_definitions / law_jurisdiction classification |
pivot | governance.handoff.axis_introduced |
| 8 | policy changed | normative_registry / law_jurisdiction / measurement_registry |
governance.handoff.policy_changed |
scope (rule scope) → bumps ruleset_version (doc 31 §5) |
| 9 | owner / approval / exception changed | governance_object_ownership (SB-2, when live) / approval_requests / exception register |
governance.handoff.authority_changed |
owner scope + scope |
| 10 | ruleset changed | governance_ruleset registry row (proposed) / enabled measurement_registry set |
governance.handoff.ruleset_changed |
ALL groups in the changed rule's scope |
No-hardcode: the kinds are config conventions tied to the registered event taxonomy, not code literals; the tail source for each is resolved from the registry, and the group_key dimensions are computed (doc 34 §group_key), not embedded.
4. Birth is NOT modified — proof (mission §5 hard constraint)
The mission forbids modifying Birth "unless proven unavoidable." It is avoidable. Three options were evaluated; the default touches Birth zero ways:
| Option | Touches Birth? | Latency | Verdict |
|---|---|---|---|
| (A) Cursor-tail / CDC (DEFAULT) | No — the intake worker polls birth_registry and registry_changelog by (born_at,id) / (timestamp,id) high-water mark, exactly as iu_outbound_default already tails IU events |
seconds–minutes (poll interval) | RECOMMENDED. Zero Birth change, zero trigger, fully reuse iu_route_worker_cursor. Birth keeps its single responsibility (existence/ID/registry visibility). |
(B) Additive observer trigger on birth_registry/registries that writes a capture row to event_pending |
Adds a trigger to a Birth table | near-real-time | Optional latency optimization only. It is additive and fail-open — it can never alter or block a birth row (an AFTER INSERT that only writes a capture row); if it errors it must not fail the birth. Even so it is a new trigger ⇒ build NO-GO and a council/decision point (does an observer-only, fail-open trigger count as "modifying Birth"? Recommended ruling: NO — it observes, never gates — but it is deferred to C-7). |
| (C) Modify the Birth gate to call governance inline | Yes | inline | REJECTED. Couples Birth to governance, violates "keep Birth stable," and makes a governance fault a birth fault. Forbidden. |
Default = Option A. Because births are append-only and ordered by (born_at, id), a cursor tail is lossless: every new birth has born_at ≥ cursor.last_created_at and is captured on the next poll. The same holds for registry_changelog (timestamp monotone). Birth remains responsible for existence/ID/registry visibility; governance processing begins after birth/registry visibility (GPT principle), reading the same durable ledgers the birth process already writes.
5. The intake worker (reuse iu_route_worker_cursor)
gov_worker_cursor (proposed; reuses iu_route_worker_cursor columns 1:1)
worker_name = 'gov_handoff_intake'
event_domain = 'governance'
last_created_at = max processed (born_at | changelog.timestamp) low-water mark per tailed source
last_event_id = tie-breaker id per source (one cursor row per tailed source, or composite in metadata)
events_seen, attempts_written, dead_lettered (bigint)
last_run_at, last_run_summary(jsonb), metadata(jsonb = { sources, poll_interval, coalesce_window })
Per-poll algorithm (read-only design):
- For each tail source, read rows
WHERE (order_key) > (:low_water) ORDER BY order_key LIMIT :batch(keyset; no OFFSET; under the 5 s read timeout). - Map each row →
(change_kind, group_key, object_ref, canonical_address)(§3). - Coalesce within the poll window: many changes to the same
group_keycollapse to one dirty-mark (anti-storm; reuse the T7coalesce_keydiscipline). - Enqueue the dirty-mark by upserting the candidate-state row's
dirty=true, dirty_reason, dirtied_at(doc 34) — this is the handoff; the signal carries address-only. - Emit a
governance.handoff.<kind>signal toevent_outbox(Đ45) iff the type is registered+active; otherwise capture toevent_pending(register-before-emit) and raisehandoff_unregistered_typeonce. - Advance the cursor only after the dirty-marks are durable (happens-after) → resumable, no loss.
- Emit
governance.handoff.heartbeatwith{seen, coalesced, enqueued, dlq}.
6. Replay, idempotency, ordering, retry, DLQ, coalesce (mission §5 musts)
- Durable storage — the truth already lives in
birth_registry/registry_changelog(append-only) andevent_pending/event_outbox; the cursor is the only added state. Nothing is held in memory or prompt. - Replay — reset the cursor's
last_created_at/last_event_idto any past watermark and re-tail; because dirty-marks are idempotent upserts (§idempotency) and signals are keyed, replay is safe and produces no duplicate work. - Idempotency — a handoff is keyed by
(source, order_key)for consume-once, and the resulting dirty-mark is keyed bygroup_key(upsert) — re-delivering the same change re-sets the same dirty flag (no-op) rather than enqueuing twice. Downstreamsystem_issuesdedup viacoalesce_key/occurrence_count(T7). - Ordering where needed — within a
group_key, the latestoccurred_atwins (last-writer dirty state); cross-group ordering is irrelevant (groups are independent). Causal ordering (born → registered → owner-assigned) is preserved byoccurred_aton the tailed rows. - Retry — transient consume errors increment
event_pending.error_count/last_errorand retry with backoff. - DLQ — after N attempts, dead-letter (
dead_letteredcounter;event_pendingflagged) and raisehandoff_dlq; intake continues. A growing DLQ raises severity. - Coalesce — §5 step 3; plus a digest for high-churn groups (Đ45 + T7 §6).
- No lost handoff — the lossless cursor tail (§4) + register-before-emit capture (
event_pending) + DLQ-instead-of-drop together guarantee a change is never silently lost; the worst case is delayed (in DLQ) and visible (a finding), never missing.
7. Provenance & source trust (handoff side of Branch C)
Each handoff carries provenance the input-quality gate (doc 33) consumes:
source= the tail source (birth_registry/registry_changelog/approval_requests/ Axis Registry /governance_ruleset).source_system+actor_ref(reuseevent_outbox/birth_registrycolumns) = who/what produced the change.payload_classification='safe_metadata'enforced (Đ45) — a handoff that tries to carry object contents is rejected and raises an input-quality finding (doc 33), never enqueued.- A handoff from an untrusted/unknown source is captured but flagged
untrusted_source(doc 33) and not promoted to a candidate-relevant verdict until trust is established — it cannot silently inject governance work.
8. Handoff issue types (proposed, register-before-emit — NOT registered)
| issue_type | bucket | severity | detection event | route |
|---|---|---|---|---|
handoff_lag |
sai_lệch_dữ_liệu |
medium→high | governance.handoff.lag |
GOV-SIV (cursor behind by > threshold) |
handoff_dlq |
silent_fail |
high | governance.handoff.dlq |
GOV-SIV |
handoff_silent_gap |
silent_fail |
high→critical | governance.handoff.silent_gap |
GOV-SIV (missed heartbeat) |
handoff_unregistered_type |
thiếu_quan_hệ |
low→medium | governance.handoff.unregistered_type |
GOV-SIV (register-before-emit: type not yet in registry) |
These ride system_issues + the governance event domain; anti-spam reused from T7.
9. Relation to backfill (doc 31) and candidate scan (doc 34)
- Backfill seeds; handoff sustains. The backfill (doc 31) is the one-time sweep that creates the initial candidate-state rows for the 1.04M backlog; the handoff intake keeps that store current by dirtying groups as births/changes arrive. Together they guarantee complete then continuously-correct coverage with no perpetual full scan.
- Boundary handshake. New births that arrive during the backfill sweep are caught by whichever runs first (the sweep's keyset will reach them, or the handoff tail will dirty them); the idempotent candidate key (doc 31 §6) makes the overlap harmless.
- The handoff is the dirty-source for Branch D. Every consumed handoff = a dirty-mark; Branch D (doc 34) decides re-evaluation from the dirty + stale set.
10. Future option (recorded, not built): a dedicated governance handoff ledger
If the handoff signal taxonomy outgrows event_pending + the tailed ledgers (e.g. needs per-signal SLA, multi-consumer fan-out with independent cursors, or a signal shape the existing columns cannot carry), a dedicated additive governance_handoff_ledger (modeled on event_pending + event_outbox, Đ45-shaped) may be designed in a later macro. Not now — reuse-first; minting it now is an unjustified second roof. This is flagged as part of new blocker SB-11 (doc 35 §2).
11. Dependencies, gates, NO-GO
| Capability | Needs | Status |
|---|---|---|
| Cursor-tail design (read-only) | nothing live | designable now |
Register governance event domain + handoff types |
T7 build + register-before-emit (Đ45) + GOV-SIV ownership ruled (SB-11/SB-4) | NO-GO |
| Run the intake worker / emit signals | governance domain active + worker cursor family (SB-13) | NO-GO |
Capture to event_pending under governance domain |
event types defined (no registration to capture-stage, but worker build) | NO-GO (design only) |
| Observer trigger on Birth (Option B) | C-7 ruling + build authorization | NO-GO (default is Option A, no trigger) |
| Dirty-mark target (candidate-state store) | SB-10 (doc 34) | NO-GO |
No gate may be satisfied by self-approval.
12. Verdict
Branch B handoff-ledger design: COMPLETE. A durable, replayable, idempotent, auditable, no-lost-handoff path from Birth/Registry into the governance candidate layer is specified, fully Điều 45-compliant (signal-not-data, event≠job, executor boundary, MOT-not-executor, silent-gap/heartbeat). Birth is not modified — the default (Option A) is a lossless cursor-tail over the already-durable birth_registry/registry_changelog, reusing the live iu_route_worker_cursor precedent; capture/retry/DLQ reuse event_pending; emission reuses event_outbox. The reuse-vs-new decision is hybrid, reuse-first — no new ledger now, a dedicated ledger flagged as a future option only. All ten handoff kinds, provenance/trust, and the four handoff issue types are specified. Nothing registered, emitted, or mutated; no trigger created. Next: doc 33 (input-quality gate that validates each handoff/candidate before classification).