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_issue singular 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_issues row AND reflected on the surface (verification_status/warning_flags/next_action).
  • Idempotentcoalesce_key/violation_hash/occurrence_count collapse repeats (the table already uses these).
  • Signal-not-dataevent_outbox carries a reference, not the data (Đ45 §4 hard CHECK).
  • Register-before-emit — any new event_type in event_type_registry before 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.