KB-792C
09 — Notification / Cleanup Trigger Contract (system_issues + event_outbox, Đ45)
4 min read Revision 1
designregistries-pivotnotificationcleanup-triggersystem-issuesevent-outboxdieu45dieu31dieu23contract2026-05-31
title: 09 — Notification / Cleanup Trigger Contract date: 2026-05-31
09 — Notification / Cleanup Trigger Contract
Design-only. No production emit this session. Reuses the live issue tracker + Đ45 event spine; proposes no new notification system.
Live targets (verified)
system_issues(179,074) — canonical issue store. Cols used:issue_type, severity, status, entity_type, entity_code, source, issue_class, sub_class, coalesce_key, violation_hash, evidence_snapshot(json), occurrence_count, first_seen_at/last_seen_at, reopen_count. (system_issuesingular does NOT exist.)event_outbox(170,498) +event_type_registry(40) — Đ45 PG-native queue: register-before-emit, signal-not-data, DLQ/retry, MOT-not-executor.
Issue types to REUSE (no new taxonomy for most)
| condition | reuse issue_type (live) | severity |
|---|---|---|
| orphan: missing relations | thiếu_quan_hệ |
warning→HIGH |
| orphan: missing ID/birth | thiếu_mã_định_danh |
warning→HIGH |
| unmonitored/un-onboarded collection | collection_onboarding_gap |
warning→critical |
| drift (record≠actual) | sai_lệch_dữ_liệu |
warning |
| cross-store drift / ghost | kb_pg_sync_drift |
warning |
| frontend/disguised hardcode | hardcode_violation / hc_finding_sql|builtin|function |
warning→critical |
| phantom in approval flow | apr_phantom_applied |
info |
Proposed NEW issue types (propose-only)
count_integrity_failed (invariant fails for a scope) · count_integrity_phantom (record_count>actual_count leaf) · label_missing (list exceeds threshold, no grouping dimension).
Contract — per detected condition
on detect(condition, scope):
upsert system_issues
SET issue_type=<reuse-or-propose>, severity=<table>,
entity_type=scope.kind, entity_code=scope.code,
source='registries-pivot-count-integrity',
coalesce_key=condition||':'||scope.code, -- idempotent: re-detect bumps occurrence_count
evidence_snapshot=<json: counted, actual, orphan, phantom, gap, last_scan_date>,
status='open'
emit event_outbox(event_type=<registered>, payload=signal-ref to the system_issues row) -- DESIGN
-- cleanup workflow consumes the event, keyed by issue_type → target:
-- thiếu_* → birth/registration workflow (Side A)
-- count_integrity_* → refresh_meta_catalog_from_pivot + recount, then re-evaluate
-- collection_onboarding_gap → onboarding (fn_birth_onboarding_full_scan)
-- label_missing → label.classify (governed label generation, Đ24)
-- hardcode_violation → developer finding (no auto-fix)
Hard rules (Đ23 / Đ28 / Đ31 / Đ45)
- Never hide a mismatch — every invariant failure → a visible
system_issuesrow AND reflected on the surface (verification_status/warning_flags/next_action). - Idempotent —
coalesce_key/violation_hash/occurrence_countcollapse repeats (the table already uses these). - Signal-not-data —
event_outboxcarries a reference, not the data (Đ45 §4 hard CHECK). - Register-before-emit — any new
event_typeinevent_type_registrybefore first emit. - Scanner never self-executes cleanup — detection writes an issue + emits a signal; a separate governed workflow acts (Đ45 MOT-not-executor). The scanner never deletes/repairs rows.
- Watchdog tie-in (Đ31): the count-integrity contract is a Đ31 self-verification contract;
fn_count_integrity_check()is its check function.
This session
Contract documented; zero system_issues rows written, zero events emitted. The doc-05 detection rehearsal was pure read SELECT only.