KB-4954 rev 2

24 — T7 Issue / Event / Notification Technical Design (register-before-emit, no emit, design-only, 2026-06-01)

25 min read Revision 2
one-roof-governanceimplementation-indext7issue-event-notificationregister-before-emitdieu45system-issuesevent-outboxevent-type-registrygov-sivanti-spamcoalesce-keycooldownemit-ceilingowner-routing20-issue-typesno-emitno-islanddesign-only2026-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_address is NULL in ALL rows (key on collection_name:entity_code); worker watermark is text type-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 the governance event 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):

  1. Register before emit. Every (event_domain, event_type) must exist in event_type_registry (with active=true) before any event_outbox row is emitted. T7 designs the rows; it does not register them. Until registered+active, the scanner (T6) raises system_issues findings only and emits nothing.
  2. Queue carries signal, not data. event_outbox.safe_payload is safe_metadata only — 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.
  3. 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 (a grant/assign action-type, C-2) — never auto-executed off the event. The event never carries an executable instruction.
  4. 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.
  5. 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_gap on 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.unblockedboth 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_issuesissue_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/.revoked companions.)
  • (B) Detection / finding events — emitted by the T6 scanner when it raises (or closes) a system_issues finding. One detection event_type per 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 a gap_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 orphanlow/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_hintGOV-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_hintno 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

  1. Coalesce at the governance grain (M-DEF-7). A finding is keyed by coalesce_key; a re-detection of the same key increments occurrence_count and updates last_seen, it does not create a new row. Inherited leaf records never get their own issue.
  2. Cooldown. No new emit (no new event_outbox signal) for a given coalesce_key within its cooldown window (severity-tiered: critical 1h, high 6h, medium 24h, low/info 7d). The system_issues row stays open and counts; only the notification is rate-limited.
  3. 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.
  4. 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.
  5. 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.
  6. Stale fails closed. If a coalesced finding's underlying state fingerprint is stale/unknown, it stays open (fails closed) rather than auto-closing.
  7. Heartbeat. governance.coverage.scan_completed emits 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 (the owner_gap case) → governance_responsibility_scope.default_owner_hint for 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_outbox signal on its delivery_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; a governance.<type>.resolved event is emitted. Closing is idempotent on coalesce_key.
  • Suppression precedence (highest first):
    1. Birth precedence (M-DEF-4): object unborn/unregistered → governance findings suppressed (yields to the birth-orphan scan; one root cause, shared coalesce_key).
    2. Granted governed exception (M-DEF-6): an active, non-expired grant_governance_exception for (object × scope × gap_type) suppresses that finding for its TTL. On expiry, issue_on_expiry re-opens it; a stale fingerprint lifts suppression immediately (fails closed).
    3. 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).
    4. Anti-hiding floor (M-DEF-7): suppression of owner_gap via inheritance is allowed (owner-link inherits); suppression of approval_path_gap/rollback_gap/dot_authority_gap/exception via inheritance is forbidden — these never inherit, so a covered parent never silences a child's authority-critical gap.
  • 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_issues taxonomy; severity is computed from (gap_family × object_risk_class × shared_truth); routing resolves from v_object_effective_owner + governance_responsibility_scope; event types are rows in event_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).

Back to Knowledge Hub knowledge/dev/reports/architecture/one-roof-governance-technical-addendum-and-implementation-index-2026-06-01/24-t7-issue-event-notification-technical-design.md