KB-42D8

08 — Issue / Event / Notification Hardening (Branch H) (2026-06-01)

12 min read Revision 1
one-roof-governanceclause-hardeningbranch-hissue-event-modelevent-namespace-defectissue-type-vocabularyregister-before-emitcooldownescalationanti-spam2026-06-01

08 — Issue / Event / Notification Hardening (Branch H)

Reviews decision-pack doc 07 (…/07-issue-event-notification-model.md). Adversarial. Live-verified the dormant event names this session.

H0. What the pack says (summary)

Doc 07 reuses system_issues (188,250 rows; issue_type/issue_class/sub_class free-text, coalesce_key idempotency) + event_type_registry (40) + event_outbox/event_subscription (Đ45). It proposes 16 governance issue types (folding sub_classes to ≈10 event registrations), reuses orphan-half issue types (thiếu_quan_hệ/thiếu_mã_định_danh/sai_lệch_dữ_liệu), proposes activating/generalizing dormant mother.governance.* events, and applies Đ45 register-before-emit + signal-not-data + throttle-to-summary. Auto-resolve on clean scan; coalesce dedup; retire→archive.

Overall: reuse-first is correct and the Đ45 discipline (register-before-emit, signal-not-data, throttle) is the right backbone. Defects: a live naming defect, an asymmetry (events gated but issue_type free-text), and three missing anti-spam controls the mission §11 named explicitly.


H1 — Live naming defect: the dormant events are governance.*, not mother.governance.*

  • Original: doc 00 §0.3 and doc 07 §7.1 cite the dormant types as mother.governance.blocked / mother.governance.unblocked / mother.proposal.created etc., and propose to "activate + generalize them."
  • Live fact (re-verified this session): the rows are event_type ∈ {governance.blocked, governance.unblocked, proposal.created, proposal.approved, proposal.rejected}, all active=false. The event_type strings are bare (governance.blocked); mother is the domain/owner, not part of the event_type. So the pack's reuse target names are wrong by a prefix.
  • Consequence (not cosmetic): the proposed new types governance.orphan_detected, governance.island_detected, governance.approval_path_gap, etc. (doc 07 §7.2) land in the same governance.* event_type namespace as the existing governance.blocked. So they don't "fork" — they extend a shared family — which is good for SSOT, but means the registration must be deliberate about that shared namespace and about whether the new coverage events belong to the mother domain (where governance.blocked lives) or a different domain (e.g. a new governance/integrity domain owned by GOV-SIV).
  • Hardened wording: correct all references to the bare names; state explicitly: "new governance-coverage events extend the existing governance.* event_type family. Decide the domain (the dormant governance.*/proposal.* are domain=mother; coverage events are GOV-SIV/COUNCIL-owned, so they likely warrant domain=governance or system, not mother). Register-before-emit applies; the dormant mother-domain rows are activated only if their semantics match (governance.blocked/unblocked ≈ a coverage-degraded gate — confirm before reuse, don't assume)."
  • Acceptance test: the registration doc (P6) lists exact event_type + domain values that match live naming; no reference to a non-existent mother.governance.* string.
  • Open question: OQ-H1 — reuse the mother-domain dormant rows, or register a new governance/integrity domain owned by GOV-SIV? (The dormant ones are mother-factory lifecycle events; coverage events are integrity events — likely a new domain is cleaner, with the dormant ones left for the mother factories.)

H2 — Issue_type is free-text (no CHECK) → 16 new values will drift (the pack's own anti-pattern)

  • Original: §7.1: governance gaps "get new issue_type values under the existing free-text column (no schema change)." 16 types + sub_class semantics, hand-written across 7 DOTs.
  • Trap: system_issues.issue_type has no CHECK (confirmed in pack doc 00 §0.3: "free-text"). 16 hand-typed values across multiple DOTs will drift — casing, typos, owner_gap vs OWNER_GAP vs ownergap. That drift is precisely a GOVERNANCE_SCHEMA_DRIFT (doc 03 §3.2) the pack warns about — the issue layer manufactures the very drift the detector hunts.
  • Hardened wording: introduce a governed issue_type vocabulary — a small reference table governance_issue_type_registry(issue_type, sub_class, default_severity, owner_gov_code, event_type, active), COUNCIL/SIV-owned, that the DOTs read (constant-from-registry, no string literals). This mirrors event_type_registry (register-before-use). It needs no CHECK on the big system_issues table (avoids a migration on 188k rows) — the discipline is "DOTs may only write an issue_type that exists in the vocabulary registry," enforced by the DOT code + CI (P8), not by a column CHECK. Register-before-write for issues, paralleling register-before-emit for events.
  • Acceptance test: a DOT writing an unregistered issue_type fails CI; all 16 governance issue_types resolve from the vocabulary registry; grep finds no issue_type string literals in DOT code.
  • Open question: OQ-H2 — vocabulary registry as a new table, or reuse an existing reference table? (New small table is cleanest; it is itself a governed object owned by SIV.)

H3 — Missing anti-spam controls the mission named: cooldown, escalation, manual-suppression-with-approval

  • Original: the pack has coalesce (dedup), occurrence_count/last_seen_at, auto-resolve, and throttle-to-summary. Mission §11 additionally requires cooldown, severity escalation, and manual suppression with approval.
  • Gaps:
    1. Cooldown — coalesce stops duplicate rows, but re-notification can still fire every scan for a persistent open issue. No minimum re-notify interval.
    2. Severity escalation — a warning orphan open for months should escalate to high (the F3 deadline mechanism), but the pack has no age-based escalation.
    3. Manual suppression — mission §11 wants "manual suppression with approval"; the pack only auto-resolves and excludes approved-exception objects — there is no governed way for a human to mute a known-but-deferred issue.
  • Hardened wording:
    • Cooldown: reuse Đ45 §15.4 thresholds — re-notify on a coalesced issue at most once per cooldown window (e.g. daily for high, immediate for critical); occurrence_count still bumps every scan, but notifications are rate-limited.
    • Escalation: an open warning past its remediation deadline auto-escalates to high (this is the F3 deadline made operational); a high open past its SLA escalates to critical + president. Escalation is computed from (severity, age, deadline), never hand-set.
    • Manual suppression = a governed exception: muting an issue is not a free action — it is an approved exception (Branch E) with TTL + reason + replacement_plan. A silent mute is forbidden (it would hide a real gap). The suppressed issue still shows in the summary as "suppressed-by-exception," never disappears.
  • Acceptance test: a persistent high issue notifies at most once/cooldown; a warning past deadline escalates without human action; suppressing an issue requires an exception record and the issue remains visible-but-muted in the summary.
  • Open question: OQ-H3 — escalation SLAs per severity (council-set thresholds, governed per G4).

H4 — Register-before-emit is enforced for events but not for issues (asymmetry)

  • Original: §7.3: "register-before-emit … every NEW event_type … inserted into event_type_registry … before any emit." But issues are free-text (H2) with no such gate.
  • Asymmetry: the event layer has a hard registry CHECK (can't emit an unregistered type — doc 06 says "emit unregistered type → producer fail by Đ45 anti-pattern"); the issue layer has no equivalent. So the weaker, higher-volume layer (issues: 188k rows) is the less governed one — backwards.
  • Hardened wording: extend register-before-emit to register-before-write for governance issue_types via the H2 vocabulary registry. Symmetry: both issues and events must be registered before use. (This closes the asymmetry without a 188k-row migration — the gate is at the DOT/CI layer.)
  • Acceptance test: as H2.
  • Open question: none.

H5 — Define the issue grain the mission asked for: summary / detail / recurring / expired-exception

  • Original: mission §11 asks to "define: summary issue; detail issue; recurring issue; expired exception issue." The pack has the concepts implicitly (aggregate summary event, per-object issue, coalesce for recurring, EXCEPTION-REVIEW for expiry) but never names the four issue grains.
  • Hardened wording — name the four issue grains:
    Grain When Cardinality Routing
    summary issue a scope's coverage degraded; high-cardinality class one per scope/cycle owner-agency digest
    detail issue a single governance-grain object orphaned one per (ref, gap) coalesced owner queue
    recurring issue a detail issue seen again same row, occurrence_count++ cooldown-limited
    expired-exception issue an exception past TTL/review one per exception president + COUNCIL, critical
  • Acceptance test: the scanner emits the right grain per situation; a mass-orphan source yields a summary issue (not 10⁶ detail issues); an expired Direct-PG exception yields exactly one expired-exception issue.
  • Open question: none.

H6 — Signal-not-data is enforced by safe_payload CHECK — but the issue body has no such CHECK

  • Original: §7.3: events carry governed_object_ref/gap_type/severity/coalesce_key only; event_outbox.safe_payload CHECK enforces. Issues, however, are free-text rows with no payload CHECK.
  • Risk: a DOT could write object bodies / secrets into a system_issues.detail field (no CHECK), leaking past the event discipline through the issue channel.
  • Hardened wording: apply the same signal-not-data discipline to governance issue rows — detail carries refs + gap metadata, never object bodies or secrets; enforced by the DOT contract + CI (no payload-shaped issue details). (No new CHECK on 188k rows; gate at the writer.)
  • Acceptance test: a DOT attempting to write a row body into an issue detail fails CI/contract.
  • Open question: none.

H-summary — Branch H verdict

ID Severity Type Disposition
H1 high live defect correct names to bare governance.*; decide domain; don't assume mother.governance.*
H2 high trap (drift) governed issue_type vocabulary registry; register-before-write
H3 high gap (mission-explicit) add cooldown + age escalation + approval-gated suppression
H4 medium asymmetry symmetric register-before-write for issues
H5 medium gap name the four issue grains
H6 medium gap signal-not-data for issue bodies too

H1 is a concrete live-verified correction; H2+H4 close the issue-layer governance hole (the higher-volume layer was the less-governed one); H3 supplies the named anti-spam controls.

Back to Knowledge Hub knowledge/dev/reports/architecture/one-roof-governance-clause-review-hardening-2026-06-01/08-issue-event-notification-hardening.md