KB-2E7B
06 — Notification / Cleanup Trigger Contract (system_issues + event_outbox)
5 min read Revision 1
architecturenotificationcleanup-triggersystem-issuesevent-outboxdieu-23dieu-45contract2026-05-31
title: 06 — Notification / Cleanup Trigger Contract date: 2026-05-31
06 — Notification / Cleanup Trigger Contract
Design-only. No production notification is wired this session. The contract reuses the live issue tracker and event spine; it does not propose a new notification system.
Live targets (verified)
system_issues(179,074 rows) — canonical issue/violation store. Columns 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. (Note: singularsystem_issuedoes NOT exist —system_issuesis canonical.)event_outbox+event_type_registry— the Đ45 PG-native queue (register-before-emit; signal-not-data; DLQ/retry). Cleanup workflows are triggered via events, not by the scanner doing the work.
Existing issue types to REUSE (no new taxonomy needed for most)
| condition | reuse issue_type (live) | severity |
|---|---|---|
| orphan: missing relationship set | 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 |
| data 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 — additive to the existing enum-by-convention)
count_integrity_failed— the accounting invariant fails for a scope (doc 02).count_integrity_phantom—record_count > actual_counton a leaf (phantom, doc 01).label_missing— a list exceeds itsmax_ungrouped_thresholdwith no resolvable grouping dimension (doc 04).
Contract — per detected condition
on detect(condition, scope):
upsert system_issues
SET issue_type = <reuse-or-propose>,
severity = <table above>,
entity_type = scope.kind, entity_code = scope.code,
source = 'registries-pivot-count-integrity',
coalesce_key = condition || ':' || scope.code, # idempotent: re-detect bumps occurrence_count, not a new row
evidence_snapshot = <json: counted, actual, orphan, phantom, gap, last_scan_date>,
status = 'open'
emit event_outbox(event_type = <registered type>, payload = signal-only ref to the system_issues row) # DESIGN
# cleanup workflow is the consumer of the event, keyed by issue_type → target workflow:
# thiếu_* → birth/registration workflow (Side A)
# count_integrity_* → refresh_meta_catalog_from_pivot + recount, then re-evaluate
# collection_onboarding_gap → onboarding workflow (fn_birth_onboarding_full_scan)
# label_missing → label.classify workflow (governed label generation, Đ24)
# hardcode_violation → developer-facing finding (no auto-fix)
Hard rules (Đ23 / Đ28 / Đ45)
- Never silently hide a mismatch — every invariant failure produces a visible
system_issuesrow AND is reflected on the surface (verification_status / warning_flags / next_action columns). - Idempotent —
coalesce_keycollapses repeat detections (the live table already usescoalesce_key,violation_hash,occurrence_countfor exactly this). - Signal-not-data —
event_outboxcarries a reference to the issue, not the data payload (Đ45 §4 hard CHECK). - Register-before-emit — any new
event_typemust be inevent_type_registrybefore first emit. - No self-execution by the scanner — detection writes an issue + emits a signal; a separate governed workflow performs cleanup. The scanner never deletes/repairs rows itself.
This session
Contract documented; zero system_issues rows written, zero events emitted. Detection rehearsal (doc 02) was pure read SELECT only.