24 — T7 Issue / Event / Notification Technical Design (register-before-emit, no emit, design-only, 2026-06-01)
⚠️ BUILD ADDENDUM — NON-SEMANTIC CROSS-REFERENCE (added 2026-06-01; design below UNCHANGED) This is the T7 issue/event/notification base design (doc 24, complete). Do NOT build from this document alone. T6/T7 BUILD requires the GCOS substrate — SB-10/11/12/13 (docs 38–41) built first, plus the 10 build addenda in doc 35 §3.2, per the consolidated build index doc 45. Live corrections that override values printed below:
birth_registry≈1.04M & still growing;canonical_addressis NULL in ALL rows (key oncollection_name:entity_code); worker watermark istexttype-generalized (int birth-id vs uuid outbox-id). Re-verify live before any build. START HERE: doc 45 → doc 42 → docs 38–41 → docs 31–35 → doc 46 (C-7) → then this design.
24 — T7 Issue / Event / Notification Technical Design
Path:
knowledge/dev/reports/architecture/one-roof-governance-technical-addendum-and-implementation-index-2026-06-01/Doc: 24. Track: T7 (Branch B). Builds on doc 09 (issue/event scaffold), docs 16/17/18/20 (SB-1/SB-2), concept canon 01–02. Status: DESIGN ONLY. No event-type is registered. No event/issue/notification is emitted. No PG/Directus/Qdrant/Nuxt mutation. Register-before-emit means the registration itself is a later, gated build step (T7 build / T11) — this doc specifies what would be registered, not the registration. Owner of this substrate (proposed): GOV-SIV (Điều 31,monitoring.integrity) owns thegovernanceevent domain and the governance issue taxonomy; it detects and proposes, never self-applies. Coverage events are informed by T6 (doc 25) which is the producer. Evidence base: doc 18 + re-verified read-only 2026-06-01 (event_type_registry=40, system_issues≈190,288, dot_coverage_required=11; full schemas below).
1. Scope, non-goals, and the no-island stance
In scope (design): (a) the governance event domain to register in event_type_registry; (b) the 20 governance issue/finding types and how they sit on the existing system_issues taxonomy; (c) the anti-spam (coalesce/cooldown/emit-ceiling/summary) model; (d) owner routing; (e) auto-close and suppression rules; (f) the Điều 45 register-before-emit contract.
Non-goals / forbidden here (doc 00 §0.6, mission §4H/§6): registering any event_type_registry row; emitting any event_outbox signal; inserting any system_issues row; creating any notification/job; any PG/Directus/Qdrant/Nuxt mutation.
No second roof: this design reuses the live event_type_registry / event_outbox / system_issues / registry_changelog substrate. It mints no new event bus, no new issue table, no new notification service, and no governance-local detector. It adds rows-to-be-registered and conventions, never a parallel mechanism. system_issues.issue_type is free-text (no CHECK) — so governance findings ride the existing anti-spam machinery (coalesce_key, occurrence_count) rather than a new one.
2. Điều 45 register-before-emit contract (binds all of T7)
Per mission §4K and the concept canon (Điều 37 REFERENCES Điều 45 for events; doc 02 §4.2):
- Register before emit. Every
(event_domain, event_type)must exist inevent_type_registry(withactive=true) before anyevent_outboxrow is emitted. T7 designs the rows; it does not register them. Until registered+active, the scanner (T6) raisessystem_issuesfindings only and emits nothing. - Queue carries signal, not data.
event_outbox.safe_payloadissafe_metadataonly — the address/refs needed to find the truth, never the truth itself. A governance finding event carries{object_type, object_ref, scope, gap_type, coalesce_key, issue_id}— not the object's contents. - Event vs job distinction. A governance finding event (
governance.owner.gap) is a signal (something is true). A remediation is a job routed through the Điều 32 APR spine (agrant/assignaction-type, C-2) — never auto-executed off the event. The event never carries an executable instruction. - Executor boundary; MOT is not the executor. The detector (GOV-SIV) emits the signal; it does not apply remediation. The executor of any remediation is a governed DOT under an approved APR (Điều 35). A Mother (MOT/MOUT/…) is never the executor of governance remediation.
- Silent-gap / heartbeat. The scanner emits a periodic heartbeat (
governance.coverage.scan_completed, with counts) so that absence of findings is distinguishable from scanner-not-running (a silent gap). A missed heartbeat is itself a finding (audit_gapon the scanner).
3. Live substrate the design reuses (re-verified 2026-06-01)
event_type_registry (40 rows) columns: event_domain, event_type, event_stream, delivery_lane, default_severity, description, active, created_at. Domains live: iu(16 active), mother(9, 0 active), piece(6), staging(5), system(4/3 active). Only governance-named rows: mother.governance.blocked / mother.governance.unblocked — both active=false. No governance/integrity/coverage/axis/owner/exception domain exists (SB-4).
event_outbox (Điều 45 emit) columns: id(uuid), event_domain, event_type, event_stream, delivery_lane(default immediate), event_severity, event_subject_table(NOT NULL), event_subject_ref, canonical_address(NOT NULL), actor_ref(NOT NULL), source_system(NOT NULL), correlation_id, payload_classification(default 'safe_metadata'), safe_payload(jsonb default '{}'), occurred_at, created_at.
system_issues — issue_type is FREE-TEXT (no CHECK); live anti-spam machinery present (coalesce_key, occurrence_count). Top live types: template_gap≈183,378 (anti-spam scale proof), null≈5,033, thiếu_quan_hệ=606 (orphan / missing-relation), silent_fail=546, collection_onboarding_gap=345, dot_bug=170, kb_pg_sync_drift=86, hardcode_violation=11, thiếu_mã_định_danh=9, sai_lệch_dữ_liệu=2 (drift), apr_phantom_applied=1. No governance/coverage/owner/island/exception type yet.
registry_changelog (68,323 rows) — generic entity-keyed audit (entity_type, entity_code, action, alert_level NOT NULL, resolved NOT NULL, …). The reuse target for finding audit. governance_audit_log (1 stale row) is relation-scoped (FK → governance_relations.id, no object FK) → it cannot audit object findings; not used here.
4. Two event families under the governance domain
T7 registers (later) two families under one GOV-SIV-owned event_domain='governance':
- (A) Lifecycle / action events — emitted by the SB-1 handlers on a successful approved apply (already enumerated in docs 16/17):
governance.owner.assigned,governance.owner.conflict,governance.exception.granted,governance.exception.expired,governance.authority.delegated,governance.delegation.expiring,governance.axis.owner_assigned. (Plus their.resolved/.revokedcompanions.) - (B) Detection / finding events — emitted by the T6 scanner when it raises (or closes) a
system_issuesfinding. One detectionevent_typeper governance gap type (§5 table). These are signals; remediation routes through APR.
Both families are register-before-emit; none is registered or emitted in this macro.
4.1 Issue-type design choice (reuse-first)
system_issues.issue_type being free-text, the design rides the existing coarse taxonomy as the bucket and carries the precise governance gap in detail jsonb + the registered detection event_type:
thiếu_quan_hệ(missing-relation/link) is the bucket for all missing-link gaps (orphan, owner, capability, approval-path, audit, rollback, dot-authority, issue-event, law-ref, design-ref, axis-unregistered, the three "unowned" policy gaps).sai_lệch_dữ_liệu(drift/inconsistency) is the bucket for conflict/drift gaps (owner-conflict, governance-schema-drift, phantom-definition).- Three genuinely-new buckets are proposed as registered conventions (no existing equivalent):
governance_island,unratified_exception(covers both the general and the direct-PG variant via agap_subtype). These are config conventions tied to the registered event taxonomy, not code literals — consistent with no-hardcode.
detail jsonb on every governance finding carries: {gap_type, gap_subtype, object_type, object_ref, scope, owner_resolved, coalesce_anchor, source_model?, severity_reason, proposed_action_code}.
5. The 20 governance issue/finding types
Severity is computed, not hardcoded per row: severity = escalate(base_by_gap_family, object_risk_class, shared_truth). The escalation rule (M-DEF-5): a missing authority-critical link on a write/high-risk object is anarchic → escalate to high/critical; the same gap on a read-only/descriptive object stays orphan → low/medium. The "Severity (base→max)" column shows base and the escalated ceiling.
Table 5A — identity, gap family, severity, detection event, routing
| # | issue (gap_type) | system_issues bucket | M-DEF-5 gap family | Severity (base→max) | Detection event_type (domain=governance) |
Remediation route / notification target |
|---|---|---|---|---|---|---|
| 1 | governance_orphan |
thiếu_quan_hệ |
ORPHAN (root) | medium→high | governance.coverage.orphan |
resolved accountable owner per scope; else scope default_owner_hint→GOV-COUNCIL |
| 2 | local_governance_island |
governance_island (new) |
LOCAL_GOVERNANCE_ISLAND | high→critical | governance.island.detected |
GOV-COUNCIL (authority dispute) + GOV-DOT (code-channel islands) |
| 3 | owner_gap |
thiếu_quan_hệ |
OWNER | medium→high | governance.owner.gap |
scope default_owner_hint; else GOV-COUNCIL |
| 4 | owner_conflict |
sai_lệch_dữ_liệu |
OWNER (conflict) | high | governance.owner.conflict |
GOV-COUNCIL (two claimants must be adjudicated) |
| 5 | capability_gap |
thiếu_quan_hệ |
CAPABILITY | medium→high | governance.capability.gap |
accountable owner of the object's execution scope; else GOV-COUNCIL |
| 6 | approval_path_gap |
thiếu_quan_hệ |
APPROVAL_PATH | high→critical | governance.approval_path.gap |
accountable approval-scope owner; GOV-COUNCIL if mutating |
| 7 | audit_gap |
thiếu_quan_hệ |
AUDIT | medium→high | governance.audit.gap |
accountable audit-scope owner → GOV-SIV |
| 8 | rollback_gap |
thiếu_quan_hệ |
ROLLBACK | high | governance.rollback.gap |
accountable execution-scope owner; GOV-DOT for DOTs |
| 9 | dot_authority_gap |
thiếu_quan_hệ |
DOT_AUTHORITY | high→critical | governance.dot_authority.gap |
GOV-DOT (Điều 35) |
| 10 | issue_event_gap |
thiếu_quan_hệ |
ISSUE_EVENT | low→medium | governance.issue_event.gap |
GOV-SIV (register-before-emit owner) |
| 11 | law_ref_gap |
thiếu_quan_hệ |
LAW_REF | medium | governance.law_ref.gap |
GOV-NRM-SYS (Điều 38) |
| 12 | design_ref_gap |
thiếu_quan_hệ |
DESIGN_REF | low→medium | governance.design_ref.gap |
the object's policy-scope owner + design author |
| 13 | axis_unregistered |
thiếu_quan_hệ |
AXIS (M-DEF-8/9) | medium→high | governance.axis.unregistered |
Axis Registry owner; pivot→NRM-LAW-26 owner, classification→NRM-LAW-24 owner; else GOV-COUNCIL |
| 14 | direct_pg_unratified_exception |
unratified_exception (new; gap_subtype=direct_pg) |
UNRATIFIED_EXCEPTION | critical | governance.exception.direct_pg_unratified |
GOV-COUNCIL + GOV-SIV (live bypass) |
| 15 | unratified_exception |
unratified_exception (new) |
UNRATIFIED_EXCEPTION | high | governance.exception.unratified |
accountable owner + GOV-COUNCIL |
| 16 | governance_schema_drift |
sai_lệch_dữ_liệu |
GOVERNANCE_SCHEMA_DRIFT | high | governance.schema.drift |
GOV-SIV + the drifting object's policy owner |
| 17 | pivot_coverage_unowned |
thiếu_quan_hệ |
OWNER (axis/pivot) | medium→high | governance.pivot.unowned |
NRM-LAW-26 owner (pivot); else GOV-COUNCIL |
| 18 | classification_policy_unowned |
thiếu_quan_hệ |
OWNER (axis/class) | medium→high | governance.classification.unowned |
NRM-LAW-24 owner (label); else GOV-COUNCIL |
| 19 | pin_policy_unowned |
thiếu_quan_hệ |
OWNER (render/pin) | medium→high | governance.pin.unowned |
GOV-MOUT (render; interim GOV-COUNCIL delegate per C-5) |
| 20 | phantom_definition_gap |
sai_lệch_dữ_liệu |
SCHEMA_DRIFT (per-source-model, L-3) | low→medium | governance.phantom.gap |
the source-object's owner; model-A write-race → suppress (see §9) |
Detector / finding owner for ALL 20 = GOV-SIV (Điều 31). The "route" column is the remediation owner the notification is addressed to. Routing is resolved at runtime via v_object_effective_owner(object_type, object_ref, scope) (SB-2) and governance_responsibility_scope.default_owner_hint — no agency is hardcoded; the table above shows the resolution policy, not literals.
Table 5B — coalesce, cooldown, emit ceiling, auto-close, suppression, audit
| # | issue | coalesce_key pattern | cooldown | emit ceiling | auto-close predicate | suppression rule | audit relation |
|---|---|---|---|---|---|---|---|
| 1 | governance_orphan |
gov:orphan:{object_type}:{coalesce_anchor} |
24h | 1 open / key; weekly digest | re-scan finds all profile-mandatory links resolved | M-DEF-4 unborn; Class-0 non-shared | changelog raise+close; outbox signal |
| 2 | local_governance_island |
gov:island:{cluster_ref} |
6h | 1 open / key; daily | island dismantled (owner/approval folded into roof) | none (islands never suppressed) | changelog; outbox; CI-scan log |
| 3 | owner_gap |
gov:owner_gap:{object_type}:{coalesce_anchor}:{scope} |
24h (high:6h) | 1 open / key | accountable owner row exists in governance_object_ownership |
M-DEF-4 unborn; granted exception (TTL); Class-0 | changelog; outbox |
| 4 | owner_conflict |
gov:owner_conflict:{object_type}:{object_ref}:{scope} |
6h | 1 open / key; daily | exactly one active accountable remains | none | changelog; outbox |
| 5 | capability_gap |
gov:capability:{object_type}:{coalesce_anchor}:{scope} |
24h | 1 open / key | required capability link resolves | granted exception; Class-0 | changelog; outbox |
| 6 | approval_path_gap |
gov:approval_path:{object_type}:{object_ref} |
6h (critical:1h) | 1 open / key | approval path resolves (Điều 32 reachable) | granted exception only (never inherited — anti-hiding) | changelog; outbox |
| 7 | audit_gap |
gov:audit:{object_type}:{coalesce_anchor} |
24h | 1 open / key | audit link resolves / heartbeat resumes | granted exception; Class-0 | changelog; outbox |
| 8 | rollback_gap |
gov:rollback:{object_type}:{object_ref} |
6h | 1 open / key | rollback_ref present | granted exception (never inherited) | changelog; outbox |
| 9 | dot_authority_gap |
gov:dot_auth:{dot_code} |
1h | 1 open / key; immediate | DOT owner authority resolves | none (authority-critical) | changelog; outbox |
| 10 | issue_event_gap |
gov:issue_event:{object_type}:{event_type} |
7d | 1 open / key; monthly | event_type registered+active | Class-0 | changelog; outbox |
| 11 | law_ref_gap |
gov:law_ref:{object_type}:{object_ref} |
7d | 1 open / key | source_law_ref resolves to a registered NRM |
descriptive on read-only → digest-only | changelog; outbox |
| 12 | design_ref_gap |
gov:design_ref:{object_type}:{object_ref} |
7d | 1 open / key; monthly | source_design_ref present |
descriptive → digest-only; Class-0 | changelog; outbox |
| 13 | axis_unregistered |
gov:axis_unreg:{axis_code|surface_ref} |
24h | 1 open / key | axis present in Axis Registry | none (drives shared truth) | changelog; outbox |
| 14 | direct_pg_unratified_exception |
gov:direct_pg:{object_type}:{object_ref} |
1h | 1 open / key; immediate | exception ratified via APR OR reverted | none (live bypass) | changelog; outbox; escalate |
| 15 | unratified_exception |
gov:unratified_exc:{object_type}:{object_ref}:{scope} |
6h | 1 open / key | exception granted via grant_governance_exception |
none | changelog; outbox |
| 16 | governance_schema_drift |
gov:schema_drift:{scope_name} |
6h | 1 open / key; daily | invariant v3 closes for that scope | none | changelog; outbox |
| 17 | pivot_coverage_unowned |
gov:pivot_unowned:{pivot_code}:{scope} |
24h | 1 open / key | accountable owner row (object_type='pivot') exists | granted exception | changelog; outbox |
| 18 | classification_policy_unowned |
gov:class_unowned:{label_axis}:{scope} |
24h | 1 open / key | accountable owner row (object_type='axis'/'classification') exists | granted exception | changelog; outbox |
| 19 | pin_policy_unowned |
gov:pin_unowned:{pin_scope}:{scope} |
24h | 1 open / key | accountable owner row exists OR pin is user-scoped (Class-0) | Class-0: user/personal pin suppressed; global/shared pin not (C-6) | changelog; outbox |
| 20 | phantom_definition_gap |
gov:phantom:{source_model}:{object_ref} |
24h | 1 open / key | record≡actual reconciled | model-A stale-actual write-race → suppress (not phantom, L-3); model-B → open | changelog; outbox |
coalesce_anchor = the nearest inherited owner-anchor ref (M-DEF-7) so that adding 10⁶/10⁸ children under one anchored container collapses to one coalesced issue (Δ open-issues = 0). This is the same coalesce_key+occurrence_count mechanism live template_gap (≈183,378 occurrences, far fewer open rows) already proves at scale.
6. Anti-spam / coalesce / cooldown / emit-ceiling model
- Coalesce at the governance grain (M-DEF-7). A finding is keyed by
coalesce_key; a re-detection of the same key incrementsoccurrence_countand updateslast_seen, it does not create a new row. Inherited leaf records never get their own issue. - Cooldown. No new emit (no new
event_outboxsignal) for a givencoalesce_keywithin its cooldown window (severity-tiered: critical 1h, high 6h, medium 24h, low/info 7d). Thesystem_issuesrow stays open and counts; only the notification is rate-limited. - Emit ceiling. At most one open issue per
coalesce_key; beyond the per-scan ceiling, additional detections roll into a summary finding (governance.coverage.summary, counts only) and a periodic digest (daily/weekly/monthly by tier). Hard per-scan emit ceiling prevents a storm if a whole class regresses. - Summary-vs-detail. Detail is in
system_issues.detail; the emitted event carries only address+counts (Đ45 signal-not-data). A digest is one event referencing N coalesced findings. - Sampling. For very large classes, the scan may sample representative members for detail and report the population count in the summary — never N per-row emits.
- Stale fails closed. If a coalesced finding's underlying state fingerprint is stale/unknown, it stays open (fails closed) rather than auto-closing.
- Heartbeat.
governance.coverage.scan_completedemits once per scan with{scanned, covered, orphans, exceptions, retired, stale}so silence ≠ health.
7. Owner routing model
- Detect → propose → route, never self-apply. GOV-SIV detects and raises the finding; remediation is proposed via an APR carrying the matching SB-1
proposed_action_code(assign_governance_owner/grant_governance_exception/ …); COUNCIL/owner approves; a governed DOT executes (Điều 35). SIV never writes the owner row. - Route resolution (data-driven):
route_owner = v_object_effective_owner(object_type, object_ref, scope).owner_gov_code. If null (theowner_gapcase) →governance_responsibility_scope.default_owner_hintfor that scope → escalate to GOV-COUNCIL if still null. Authority disputes (island, conflict, unratified exception, direct-PG) route to GOV-COUNCIL regardless. No agency literal appears in code. - Notification = event, not job (Đ45). The route target receives an
event_outboxsignal on itsdelivery_lane; acting on it (raising/approving an APR) is a separate governed step. The MOT/Mother is never the executor.
8. Relation to system_issues / event_outbox / registry_changelog (reuse map)
| Concern | Reused live object | How |
|---|---|---|
| Finding store | system_issues |
governance findings as rows; bucket = existing thiếu_quan_hệ/sai_lệch_dữ_liệu + new governance_island/unratified_exception; precise gap in detail+event_type |
| De-dup / scale | system_issues.coalesce_key + occurrence_count |
same mechanism as template_gap; no new de-dup engine |
| Event registry | event_type_registry |
one governance domain (GOV-SIV), rows in §5; active=false until T7 build |
| Emit (signal) | event_outbox |
Đ45 signal-not-data; payload_classification='safe_metadata' |
| Audit | registry_changelog |
one row per raise/close (entity_type='governance_finding'); not governance_audit_log (relation-scoped, no object FK) |
| DOT coverage | dot_coverage_required |
T6/SB-8 adds governance.coverage/classification/pivot/axis/iu rows (designed in doc 25) |
No third channel is introduced. governance_audit_log stays exactly as it is (relation-scoped, 1 stale row).
9. Auto-close & suppression (consolidated rules)
- Auto-close (per-type predicate, Table 5B). A finding closes (
resolved=true, resolved_by='dot_governance_coverage_scan', resolved_at=now()) when the next scan confirms the gap is gone; agovernance.<type>.resolvedevent is emitted. Closing is idempotent oncoalesce_key. - Suppression precedence (highest first):
- Birth precedence (M-DEF-4): object unborn/unregistered → governance findings suppressed (yields to the birth-orphan scan; one root cause, shared
coalesce_key). - Granted governed exception (M-DEF-6): an active, non-expired
grant_governance_exceptionfor(object × scope × gap_type)suppresses that finding for its TTL. Onexpiry,issue_on_expiryre-opens it; a stale fingerprint lifts suppression immediately (fails closed). - Class-0 (M-DEF-1): private/user/session-scoped objects suppressed unless they reach shared truth (e.g. user pin suppressed; global pin not — ties to C-6 and
pin_policy_unowned). - Anti-hiding floor (M-DEF-7): suppression of
owner_gapvia inheritance is allowed (owner-link inherits); suppression ofapproval_path_gap/rollback_gap/dot_authority_gap/exceptionvia inheritance is forbidden — these never inherit, so a covered parent never silences a child's authority-critical gap.
- Birth precedence (M-DEF-4): object unborn/unregistered → governance findings suppressed (yields to the birth-orphan scan; one root cause, shared
- No suppression by self-approval. A finding is suppressed only by a recorded exception/birth-state/Class-0 fact, never by an agent's judgement.
10. No-hardcode & no-island attestation
- No-hardcode: the 20 issue types resolve their bucket from the live
system_issuestaxonomy; severity is computed from(gap_family × object_risk_class × shared_truth); routing resolves fromv_object_effective_owner+governance_responsibility_scope; event types are rows inevent_type_registry. No agency, severity, or event literal is embedded in code; the tables above are policy, materialized as data at build time. - No-island: one event domain (GOV-SIV
governance), one issue store (system_issues), one emit path (event_outbox, Đ45), one audit (registry_changelog). No parallel bus/store/notifier.
11. Gates / NO-GO
| Step | Gate | Status |
|---|---|---|
| T7 taxonomy design (this doc) | concept GO + (coverage events) informed by T6 | DONE |
Register governance event domain + rows |
T7 build authorization + GOV-SIV ownership ruled + register-before-emit (Đ45) | NO-GO (design only) |
Add governance system_issues buckets/conventions |
T7 build authorization | NO-GO |
| Emit any finding/notification | scanner built (T6/T11) + event rows registered+active + SB-1/SB-2 live | NO-GO |
| Auto-close / suppression live | depends on SB-1 (exceptions) + SB-2 (owner resolution) live | NO-GO |
No gate may be satisfied by self-approval.
12. Verdict
T7 issue/event/notification design: COMPLETE (Branch B). All 20 mandated issue types are specified with issue_type/bucket, severity (computed), gap family, detection event_type, owner/route, coalesce_key, cooldown, emit ceiling, auto-close, suppression, and audit relation. The Điều 45 register-before-emit contract, the anti-spam (coalesce/cooldown/ceiling/summary/heartbeat) model, and the reuse map to system_issues/event_outbox/registry_changelog are defined. Nothing registered, nothing emitted, no mutation. Producer of detection events is the T6 scanner (doc 25); GOV-SIV is the proposed domain owner. Next: doc 25 (T6 scanner consumes this taxonomy).