41 — SB-11 Governance Event Domain + Handoff Path — Detailed Technical Design (register-before-emit, Điều 45, no registration/emit, design-only, read-only zero mutation, 2026-06-01)
41 — SB-11 Governance Event Domain + Handoff Path — Detailed Technical Design
Package:
knowledge/dev/reports/architecture/one-roof-governance-technical-addendum-and-implementation-index-2026-06-01/Track: GCOS substrate. Blocker SB-11 (subsumes SB-4 = nogovernanceevent domain). Status: Detailed technical design ONLY. NO event registration. NO emit. No DDL/DML, noevent_type_registryinsert, noevent_outboxemit, no notification. KB document only. Reads / controls: doc 00 → concept canon → Round-4 law →prompt-muc-tieu-mo-for-claude-code.md(§4K Điều 45 reference). Builds on doc 24 (T7 register-before-emit + 20 types), doc 32 (handoff §1–§8, Birth-not-modified §4), doc 35 (one domain, GOV-SIV). Điều 45 Queue Law (enacted 2026-05-26) binds every clause. Date: 2026-06-01 · Mutation footprint: KB document only. Zero PG/Directus/Qdrant/Nuxt mutation.
41.0 §0-GOV — governed objects
| governed_object | class | grain | purpose |
|---|---|---|---|
governance event domain |
Class-2 governed config (rows in event_type_registry) |
one domain, owned by GOV-SIV | the single bus lane for all GCOS + coverage signals |
governance_event_type |
Class-2 governed config | one row per (governance, event_type) |
register-before-emit vocabulary |
handoff_signal |
Class-2 process record (rides event_outbox/event_pending) |
one per change signal | Birth/Registry → candidate-scan handoff (doc 32) |
Owner: GOV-SIV (Điều 31) detects/emits the signal; it never executes remediation. Executor of any remediation = a governed DOT under an approved APR (Điều 35). A Mother (MOT/MOUT) is never the executor (Điều 45).
41.1 Problem statement
The T6 scanner (doc 25), T7 taxonomy (doc 24), backfill (doc 31), handoff (doc 32), input gate (doc 33) and candidate scan (doc 34) all need to emit signals — but no governance event domain exists (this is SB-4). Until it is registered, every component must raise system_issues findings only and emit nothing (Điều 45 register-before-emit). SB-11 designs (a) the governance event domain and its register-before-emit vocabulary, and (b) the handoff path that carries Birth/Registry changes to the candidate scan without modifying Birth — reusing the live event substrate, never minting a second bus.
41.2 Live PG validation (read-only, re-verified 2026-06-01)
| Object | Live finding | Implication for SB-11 |
|---|---|---|
event_type_registry |
40 rows. Domains: iu(16/16 active), mother(9/0 active), piece(6/6), staging(5/5), system(4/3). NO governance domain. Cols: event_domain NN, event_type NN, event_stream NN, delivery_lane NN default 'immediate', default_severity, description NN, active NN default true, created_at. |
SB-4 confirmed. SB-11 registers the governance domain rows here. Intended unique key (event_domain, event_type). mother domain shows the register-but-inactive pattern (registered, active=false) — exactly the staging state GCOS types sit in until activation. |
event_outbox |
181,712 rows (heavily used Điều 45 emit bus). Cols: id uuid, event_domain NN, event_type NN, event_stream NN, delivery_lane NN, event_severity, event_subject_table NN, event_subject_ref, canonical_address NN, actor_ref NN, source_system NN, correlation_id, payload_classification NN default 'safe_metadata', safe_payload jsonb NN default '{}', occurred_at, created_at. |
The one emit path. event_subject_table/event_subject_ref = the subject-addressing pattern; safe_payload = signal-not-data. SB-11 emits here (gated). No schema change. |
event_pending |
0 rows, structure present. Cols: event_domain NN, event_type_hint NN, entity_table NN, entity_ref, canonical_address NN, actor_ref NN, capture_payload jsonb, processed_at, error_count int NN, last_error. |
Pre-registration capture/staging + retry lane — unused, available. A signal whose type isn't yet registered+active is captured here (no-lost-handoff), promoted to event_outbox once registered. |
event_read |
181,351 rows. Cols: event_id uuid NN, actor_ref NN, read_at NN, read_status_source NN. |
Live consumer read-tracking — the notification-boundary "has the owner seen this signal?" substrate. Reuse for governance notifications. |
event_subscription |
3 rows. Cols: id uuid, recipient_ref NN, event_domain, event_type, event_stream, scope_subject_table, scope_filter jsonb NN, mute bool NN, created_at. |
Live consumer routing/subscription — owners subscribe to governance.* with a scope_filter; the notification-boundary substrate. Reuse, no new router. |
queue_heartbeat |
3 rows (dieu45_phase3_pilot proves Đ45 pilot live). executor_name/kind, last_tick_at, last_tick_status, lease_owner. |
The silent-gap heartbeat substrate (Điều 45 boundary #5). GCOS workers tick here; missed tick = finding. |
system_issues |
190,288 rows; free-text issue_type, coalesce_key, occurrence_count, reopen_count, parent_issue_id, status. |
The finding store the scanner writes before any emit; emit is downstream of a registered type. |
registry_changelog |
68,323 rows. | The single audit channel (raise/close/emit/audit rows). |
job_queue |
13 rows (cut.*, lease/retry/idempotency). |
The JOB lane (Điều 45 event≠job). Governance signals do NOT go here; remediation jobs route via APR (Điều 32), not via this queue directly. |
41.3 Reuse / Extend / New decision
| Need | Decision | Rationale |
|---|---|---|
| Event domain + vocabulary | REUSE event_type_registry — register governance domain rows (no new registry) |
One vocabulary store; iu/mother/… already coexist; governance is one more domain, GOV-SIV-owned. |
| Emit path | REUSE event_outbox |
One Điều 45 bus, 181k rows live; signal-not-data + subject-addressing already enforced. |
| Pre-registration capture / retry / DLQ | REUSE event_pending (unused, exact shape) |
No-lost-handoff staging without a new table. |
| Notification boundary | REUSE event_subscription (routing) + event_read (delivery/seen) |
Live consumer-side substrate; no new notifier. |
| Silent-gap heartbeat | REUSE queue_heartbeat |
Điều 45 boundary #5; live. |
| Findings / audit | REUSE system_issues / registry_changelog |
One issue store, one audit channel. |
| Handoff transport | REUSE cursor-tail (SB-13) of birth_registry+registry_changelog (Option A, default); observer-trigger (Option B) deferred to C-7; inline (Option C) REJECTED |
Birth not modified (doc 32 §4). |
| Dedicated handoff ledger | NOT NOW (future option only, doc 32 §10) | Minting one now = second roof. The cursor + event_pending + event_outbox triad suffices. |
Net: SB-11 = 0 new tables. Pure reuse: register governance rows in the existing registry; emit via the existing outbox; capture via the existing pending lane; notify via the existing subscription/read substrate; heartbeat via the existing queue_heartbeat. This is the strongest no-island result of the four blockers.
41.4 The governance event domain — register-before-emit vocabulary (NONE registered now)
All rows below are proposed for event_type_registry with event_domain='governance', event_stream per family, active=false at first (the live mother-domain pattern), flipped to active=true only at the gated T7 build after the taxonomy + SB-10/12/13 land. Until then: findings to system_issues only, zero emit.
(A) Handoff signals (doc 32 §3) — event_stream='governance.handoff'
governance.handoff.object_born, .object_registered, .collection_changed, .count_changed, .object_retired, .source_changed, .axis_introduced, .policy_changed, .authority_changed, .ruleset_changed (the 10 kinds) + governance.handoff.heartbeat + faults governance.handoff.lag / .dlq / .silent_gap / .unregistered_type (doc 32 §8).
(B) Backfill (doc 31 §10) — event_stream='governance.backfill'
governance.backfill.inventory_gap, .batch_failed, .incomplete, .dlq_overflow, .sweep_completed (heartbeat).
(C) Input-quality (doc 33 §8) — event_stream='governance.input'
governance.input.incomplete, .invalid_schema, .duplicate, .conflict, .stale, .untrusted_source, .quarantined (the 7 states that route to a finding; birth_or_registry_missing reuses Đ19, needs_backfill reuses backfill).
(D) Candidate scan (doc 34 §10) — event_stream='governance.candidate'
governance.candidate.stale, .unknown, .scan_lag, .group_invalidation_storm, .scan_completed (heartbeat).
(E) Coverage detection + lifecycle (doc 24 §4/§5) — event_stream='governance.coverage' / 'governance.lifecycle'
The 20 detection events (governance.coverage.orphan, .owner.gap, .island.detected, … governance.phantom.gap) + governance.coverage.scan_completed / .audit_completed / .summary heartbeats + lifecycle events on approved apply (governance.owner.assigned, .exception.granted, .authority.delegated, .axis.owner_assigned, + .resolved/.revoked/.expired companions).
Counts are illustrative of structure, not a registration list. Nothing here is registered or emitted by this design.
41.5 Event subject-addressing + safe metadata (Điều 45 signal-not-data)
Every emit sets event_subject_table / event_subject_ref and a safe_payload of addresses/refs only:
| event family | event_subject_table | event_subject_ref | safe_payload (addresses + counts ONLY) |
|---|---|---|---|
| handoff.* | birth_registry / registry_changelog |
entity_code (or candidate_key) |
{object_type, collection_name, group_key, change_kind, occurred_at, source} |
| backfill.* | governance_candidate_state |
group_key |
{group_key, scope, batch_range, counts} |
| input.* | governance_candidate_state |
candidate_key/group_key |
{input_quality_state, scope, source} |
| candidate.* | governance_candidate_state |
group_key |
{candidate_verdict, ruleset_version, source_snapshot_ref, dirty_reason} |
| coverage.* / lifecycle.* | the governed object's table | object ref | {gap_type, scope, coalesce_anchor, issue_id, proposed_action_code} (doc 24 §2) |
Never the object's contents (Điều 45 boundary #1). payload_classification='safe_metadata' (the live default). canonical_address on the outbox row is the live NOT-NULL emit-address column (distinct from birth_registry.canonical_address which is NULL — the emit address is constructed from collection_name:entity_code at emit time).
41.6 The five Điều 45 boundaries — how SB-11 satisfies each
| # | Boundary (Điều 45) | SB-11 compliance |
|---|---|---|
| 1 | Queue carries signal, not data | safe_payload = address/refs/counts only; object contents never travel (§41.5). |
| 2 | Event ≠ job | A governance.* signal asserts something is true; remediation is a JOB routed through the Điều 32 APR spine (a grant/assign action-type, SB-1/C-2) or, where a worker job is needed, job_queue — never executed off the event. The event carries no executable instruction. |
| 3 | Executor boundary | GOV-SIV emits the signal; it does not apply. The executor = a governed DOT under an approved APR (Điều 35). The handoff intake worker only captures + dirty-marks (doc 32 §1.3); it does not classify or remediate. |
| 4 | MOT is not the executor | No Mother consumes a governance signal to mutate governed state. MOUT may render coverage; it never executes remediation (doc 32 §1.4). |
| 5 | Silent-gap / heartbeat | Each worker ticks queue_heartbeat + emits governance.<stream>.heartbeat/scan_completed/sweep_completed; a missed heartbeat is itself a finding (*_silent_gap). "Quiet" is distinguishable from "dead." |
The doc cites Điều 45 (law §4K mandatory) and every clause here is checked against the five boundaries.
41.7 Handoff path — Birth NOT modified (doc 32 §4)
| Option | Description | Verdict |
|---|---|---|
| A — Cursor-tail / CDC (DEFAULT) | The gov_handoff_intake worker (SB-13) keyset-tails birth_registry (born_at,id) + registry_changelog (timestamp,id) and (for lifecycle) event_outbox. Maps each row to (change_kind, group_key, object_ref, canonical_address), coalesces within a window, enqueues a dirty-mark (the handoff) on candidate-state (SB-10), and emits governance.handoff.<kind> iff the type is registered+active, else captures to event_pending + raises handoff_unregistered_type once. |
RECOMMENDED. Touches Birth zero ways; lossless (append-only ordered); latency seconds–minutes. |
| B — Additive observer trigger | AFTER INSERT trigger on birth_registry/registries writing a capture row to event_pending; fail-open (never blocks/alters a birth). |
DEFERRED to C-7. Adds a trigger to a Birth table → council ruling "does an observer-only, fail-open trigger count as modifying Birth?" Recommended answer: NO (observes, never gates) — but the ruling is council's, not the agent's. Build NO-GO until C-7. |
| C — Inline Birth-gate call | Modify the Birth gate to call governance synchronously. | REJECTED. Couples Birth to governance; a governance fault becomes a birth fault. Forbidden. |
No-lost-handoff (doc 32 §6): lossless cursor-tail + register-before-emit capture (event_pending) + DLQ-instead-of-drop (dead_lettered counter + handoff_dlq). Worst case = delayed (in DLQ) and visible (a finding), never missing. The periodic full audit (doc 34 §5) is the catch-all if any dirty signal is lost.
Replay: reset the SB-13 cursor watermark and re-tail; dirty-marks are idempotent upserts (SB-10 §40.5); event_read records which consumers have seen each signal so replay does not double-notify a consumer that already acted.
41.8 Notification boundary (signal, not job)
- Routing: an owner/role subscribes via
event_subscription(recipient_ref, event_domain='governance', event_type/stream, scope_filter). Thegov_issue_routeDOT (doc 25 DOT #7) resolves the route owner (v_object_effective_owner, SB-2; elsedefault_owner_hint; else GOV-COUNCIL) and emits a signal on the owner'sdelivery_lane— honoring cooldown / emit-ceiling / digest (doc 24 §6). Authority disputes (island, conflict, unratified exception, direct-PG) route to GOV-COUNCIL regardless. - Delivery / seen:
event_readtracks consumption (the notification boundary "owner has seen the gap").event_subscription.mutelets a recipient suppress a lane without losing the underlyingsystem_issuesfinding (the issue stays open; only the notification is muted). - A notification is a signal, never a job. It does not carry or trigger an executable remediation; remediation is a separate APR (Điều 45 boundary #2). The notification's job is to make the gap visible to the accountable owner, nothing more.
41.9 Retry / DLQ / coalesce / failure modes
- Retry/DLQ:
event_pending.error_count/last_error+ bounded backoff; dead-letter after N →dead_lettered(SB-13) +*_dlqfinding. Never drop. - Coalesce: one open
system_issuespercoalesce_key(occurrence_count++); beyond the per-scan ceiling →governance.coverage.summary+ digest (doc 24 §6). Handoff dirty-marks coalesce bygroup_keywithin the window. - Failure modes: event type not registered → HOLD (capture to
event_pending, raiseissue_event_gap/handoff_unregistered_type); domain registered butactive=false→ same hold (register ≠ activate); outbox emit fails → stays inevent_pending, retried; subscription/route owner unresolved → escalate GOV-COUNCIL; consumer never reads (noevent_read) past SLA →handoff_lag/notification escalation.
41.10 Relation to docs 24 / 32 / 35 and the no-island attestation
- doc 24 (T7): SB-11 supplies the domain + register-before-emit substrate that doc 24's 20 detection events + lifecycle events + anti-spam assume but cannot create themselves. The GCOS event families (A–D) extend doc 24's taxonomy under the same one domain (doc 35 §3.2 patch #9).
- doc 32 (handoff): SB-11 is the transport for doc 32's 10 handoff kinds; it ratifies Option A (cursor-tail) as default and carries Option B to C-7.
- doc 35 (one domain, GOV-SIV): exactly one
governancedomain, GOV-SIV-owned; no per-component bus. - No-island: one domain (
governance), one emit path (event_outbox), one capture lane (event_pending), one finding store (system_issues), one audit (registry_changelog), one notification substrate (event_subscription+event_read), one heartbeat (queue_heartbeat). Zero new tables. No parallel bus/store/notifier/heartbeat. - No-hardcode: event types are rows; routing is data (
event_subscription+ SB-2 owner resolution +default_owner_hint); severity computed (doc 24). No agency literal, no event-type array in code.
41.11 Acceptance tests (build-time; cannot run now)
- Register-before-emit: no
event_outboxrow withevent_domain='governance'exists until the matchingevent_type_registry(governance, type)row isactive=true; an unregistered emit attempt holds inevent_pending+ raisesissue_event_gap. - Signal-not-data: every governance
event_outboxrow haspayload_classification='safe_metadata'and asafe_payloadcontaining no object contents (addresses/refs/counts only). - Birth untouched: Option A path runs with zero schema/trigger changes to
birth_registry; (Option B only after C-7, and only as fail-open observer). - No-lost-handoff: kill the intake worker mid-tail → on restart every change between the watermark and head is processed; a poison signal is dead-lettered + visible, never dropped; the periodic audit reconciles.
- Event ≠ job: no governance signal triggers a mutation directly; remediation occurs only via an approved APR / governed DOT.
- Silent-gap: stop a worker →
*_silent_gapfires within threshold (heartbeat absence detected). - Notification boundary: a finding routes to the subscribed owner via a signal;
event_readrecords the owner's view;mutesuppresses the notification but not the opensystem_issuesfinding. - One domain: all GCOS + coverage event types live under
event_domain='governance'; no second domain/bus is created.
41.12 Dependencies, gates, verdict
- Designable now: YES (done). Build now: NO (register-before-emit; Điều 45). Registering the domain + types is the gated T7 build step.
- Build gates: registering/activating
governanceevent rows = gated (T7 build / T11); running the intake worker requires SB-13 (cursors) + SB-10 (dirty-mark target); C-7 owns the observer-trigger ruling (Option B) and input-trust policy (which input.* signals fire); SB-2 sharpens owner-routing (degrades todefault_owner_hint/COUNCIL until live). - No COMMIT (
os_proposal_approvals=0). No event registered, none emitted by this design.
SB-11 design verdict: COMPLETE — GO for build-prep, BUILD NO-GO (register-before-emit). Decision = 0 new tables; reuse event_type_registry (register one governance domain), event_outbox (emit), event_pending (capture/retry/DLQ), event_subscription+event_read (notification boundary), queue_heartbeat (silent-gap), system_issues/registry_changelog (findings/audit). Handoff path = Option A cursor-tail (default, Birth untouched); Option B observer-trigger deferred to C-7; Option C rejected. All five Điều 45 boundaries satisfied; strongest no-island result of the four blockers.
(Cross-refs: doc 24 §2/§4/§5/§6/§7, doc 32 §1/§3/§4/§6/§8, doc 35 §3.2 patch #9 + §5, doc 38 SB-12, doc 39 SB-13, doc 40 SB-10, doc 42 integration.)