08 — Orphan / Phantom / Notification Contract (Branch H)
title: 08 — Orphan / Phantom / Notification Contract (Branch H) date: 2026-05-31 verdict: orphan = REUSE scanners; phantom = LAW_DEFINITION_GAP AND proposed def is unreliable (model-dependent); notification = REUSE system_issues+event_outbox, register 3 new issue types + count_integrity/pin/label events mutation: none (0 issues written, 0 events emitted)
08 — Orphan / Phantom / Notification Contract (Branch H)
A. ORPHAN — REUSE existing definition + scanners
- Def (Đ26 v3.9 / Đ19): missing birth record OR missing minimum relationship set. Live
system_issueshalves:thiếu_quan_hệ(606, open 0) +thiếu_mã_định_danh(9, open 0). - Detection (live fns):
fn_birth_onboarding_full_scan(_hc)(new-object/Side-B),fn_refresh_orphan_col/_dot/_species(+trg) →meta_catalog.orphan_count. Live Σ = 161, entirely on the 3 meta rollup rows (CAT-DOT 140, CAT-COL 20, CAT-SPE 1); leaf-level orphan = 0 (doc 03). - Note: the orphan issues are currently closed (open=0) while
orphan_count=161persists on the rollup rows → the column is a scan snapshot, the issues are resolved; reconcile cadence is the open question, not the definition. - Pivot dim: by composition_level/species. PIVOT_MISSING: PIV-30x orphan-total. Severity HIGH. Cleanup: birth/registration (Side A). Notify: the two
thiếu_*types.
B. PHANTOM — LAW_DEFINITION_GAP (do NOT pretend solved) + the proposed def is UNRELIABLE
- Status: no phantom law article. De-facto signals:
apr_phantom_applied(1, open 1) + the frontend-injected CAT-PHA from gap-math (an Đ28 violation, doc 09). - Proposed operational def (doc 06 design): phantom = counted but no backing real row →
record_count − actual_countwhere positive. - NEW FINDING — this def cannot be applied uniformly: the live drift rows show the surplus direction is source_model-dependent (doc 03):
- model-A (Directus):
record_counttracks the live pivot andactual_countis the stale field → arecord>actualsurplus is stale-scan noise, NOT phantom (CAT-023 birth: record 980,378 = PIV-019 live; actual 943,726 stale → the 36,652 are real births, not phantoms). - model-B (File):
record>actualcan be genuine phantom (registry rows with no backing file — CAT-006: 309 rows, 163 files, 146 likely phantom ≈ CAT-DOT orphan 140).
- model-A (Directus):
- Therefore the phantom operational definition must be: a counted unit whose substrate cannot be located by the source's own scanner — and it must be scoped per
source_model(file-existence for model-B; live-pivot reconciliation for model-A), never a blindrecord−actual. Law patch required: define phantom (and its per-model detection) as a council-ratified clause; until then phantom staysLAW_DEFINITION_GAPand the surface renders phantom asPIVOT_MISSING/warning_flag=phantom(unconfirmed). - Cleanup: model-A → recount via
refresh_meta_catalog_from_pivot; model-B → file-existence scan. Notify: proposecount_integrity_phantom.
C. UNMONITORED / UNREGISTERED / GHOST / mis-stabled (REUSE / DEFER)
| concept | live anchor | status |
|---|---|---|
| UNMONITORED | collection_onboarding_gap (345 open) |
REUSE |
| UNREGISTERED | information_schema.tables ∖ meta_catalog.source_location |
scan exists (Đ23 §4.2) |
| GHOST (cross-store) | kb_pg_sync_drift (82 open) — needs Qdrant read |
DETECTION_DEFERRED |
| mis-stabled (nhầm chuồng) | sai_lệch_dữ_liệu (2) |
LAW_DEFINITION_GAP (fold under Đ24/26/29) |
D. Notification spine (live) — what to REUSE vs REGISTER
system_issues issue_type distribution (live, top): template_gap 173,378 (open all) · NULL issue_type 4,843 (the SoT itself has untyped rows — a data-quality gap) · thiếu_quan_hệ 606 · silent_fail 546 · collection_onboarding_gap 345 · dot_bug 170 · kb_pg_sync_drift 82 · hc_finding_sql 72 · hardcode_violation 10 · thiếu_mã_định_danh 9 · hc_finding_builtin 5 · sai_lệch_dữ_liệu 2 · apr_phantom_applied 1 · hc_finding_function 1 …
Reuse (no new type): thiếu_quan_hệ, thiếu_mã_định_danh, collection_onboarding_gap, sai_lệch_dữ_liệu (drift), kb_pg_sync_drift (ghost), hardcode_violation/hc_finding_* (Đ28).
Propose NEW (confirmed absent): count_integrity_failed, count_integrity_phantom, label_missing.
event_type_registry (40): has issue_opened/issue_resolved/issue_archived (REUSE for the issue lifecycle), plus collection/proposal/structure/staging/governance families. Absent → must register before emit: count_integrity.*, pin.created/pin.removed, label.classify.
E. Cleanup-trigger contract (design-only; Đ45 signal-not-data, MOT-not-executor)
on detect(condition, scope):
upsert system_issues(issue_type=<reuse|new>, severity, entity_type=scope.kind, entity_code=scope.code,
source='registries-pivot-count-integrity',
coalesce_key=condition||':'||scope.code, -- idempotent, occurrence_count bumps
evidence_snapshot=json{counted,actual,orphan,phantom,gap,last_scan_date}, status='open')
emit event_outbox(event_type=<registered>, payload=signal-ref to the issue row) -- DESIGN
-- governed cleanup workflow consumes, keyed by issue_type:
-- thiếu_* -> birth/registration (Side A)
-- count_integrity_* -> refresh_meta_catalog_from_pivot + recount, then re-evaluate
-- collection_onboarding_gap-> fn_birth_onboarding_full_scan
-- label_missing -> label.classify (governed, Đ24)
-- hardcode_violation -> developer finding (no auto-fix)
Hard rules: never hide a mismatch (visible issue + surface flag); idempotent via coalesce_key; signal-not-data; register-before-emit; the scanner never self-executes cleanup; the integrity check registers as an Đ31 self-verification contract.
F. This session
Contract documented. Zero system_issues rows written; zero events emitted; no cleanup executed. All detection in docs 03/04 was read-only / BEGIN..ROLLBACK.