05 — Phantom Policy & Law Patch Pack (Branch D, RG4)
title: 05 — Phantom Policy & Law Patch Pack (Branch D, RG4) date: 2026-05-31 status: decision package + law-patch DRAFT; NO enactment, NO self-approval
05 — Phantom Policy & Law Patch Pack (Branch D)
Resolves the LAW_DEFINITION_GAP: there is no authoritative phantom definition and no
phantom_count column anywhere. This is a council/RG4 decision; this doc packages it.
The core finding (why blind record > actual is wrong)
| code | model | record | actual | gap | truth |
|---|---|---|---|---|---|
| CAT-023 | A | 990,720 | 985,471 | +5,249 | write-race / stale scan — NOT phantom |
| CAT-006 | B | 309 | 163 | +146 | phantom candidate |
| CAT-007 | B | 37 | 52 | −15 | unregistered (source-over-registry) |
CAT-023 and CAT-006 have the identical sign (record > actual) yet opposite meaning.
Only source_model distinguishes them. The legacy /api/registry/health classifies
KHOP/ORPHAN/PHANTOM by blind gap sign in JS (health.get.ts:104-110) — source-model-blind,
therefore wrong.
Proposed source_model-aware definition (for RG4 ratification)
model-A (live PG table, trigger-counted): record > actual ⇒ model_a_surplus_recheck
= live-write race or stale full-scan. NEVER a phantom. Fix = refresh scan / live actual.
model-B (registry-vs-source: file/page): record > actual ⇒ model_b_phantom_candidate
= registered objects with no backing source artifact = PHANTOM CANDIDATE.
record < actual ⇒ model_b_unregistered_candidate (source exists, not registered).
phantom_confirmed ⇒ requires (a) source_model='B', (b) a verification scan of the source,
(c) council ratification. The pivot/view counts CANDIDATES only; never asserts "phantom".
This is exactly what the Macro-1 v_count_integrity.drift_classification already computes
(source_model-aware, labelled "candidate"). RG4 ratifies it as law + authorizes a
phantom_count column / PIV-302 promotion from "candidate count" to "phantom count".
Severity model
| classification | severity | rationale |
|---|---|---|
| model_a_surplus_recheck | info/warning | self-heals on next scan |
| model_b_phantom_candidate | warning → critical if confirmed | registered ghost objects |
| model_b_unregistered_candidate | warning | coverage gap, registrable |
| unverified (NULL counts) | warning | unmeasured (CAT-1006..1010) |
Scanner
Reuse the Macro-1 view layer: v_count_drift IS the scanner output (3 rows live). PIV-303
counts drift total; PIV-302 counts model_b_phantom_candidate. No new scanner code — the
classification is a view column. A periodic refresh of actual_count (model-A) and a source
scan (model-B) feed it (Macro 2 scan cadence; CAT-1006..1010 need a first count).
system_issues mapping (live distribution — reuse + 3 NEW types)
Existing reusable issue types: thiếu_quan_hệ(606, data_fault) / thiếu_mã_định_danh(9) =
orphan halves; sai_lệch_dữ_liệu(2, sync_fault) = drift; hardcode_violation(11) /
hc_finding_* = Đ28; lone apr_phantom_applied(1, info). NEW issue types required
(none exist today): count_integrity_failed, phantom_candidate, phantom_confirmed,
label_grouping_required. Use existing rich columns: coalesce_key (idempotent),
issue_class/sub_class, severity, occurrence_count, verification_contract_id.
event_outbox / notification contract (Đ45)
event_type_registry (40 rows) has zero count_integrity.* / phantom / pin /
label event types (verified). NEW event types needed, then event_outbox
(event_domain, event_type, event_severity, event_subject_table, event_subject_ref, safe_payload jsonb, delivery_lane, correlation_id) is the emit substrate:
registries_pivot.count_integrity_failed (warning) subject=meta_catalog ref=CAT-xxx
registries_pivot.phantom_candidate_found (warning) subject=meta_catalog ref=CAT-006
registries_pivot.label_grouping_required (info) subject=meta_catalog ref=CAT-xxx
registries_pivot.pin_changed (info) subject=registry_pin
No event emitted, no notification sent this macro (forbidden). Contract only.
Cleanup workflow trigger
On phantom_confirmed (post-RG4): a gated repair workflow (reuse the DOT/APR repair
pattern from S178) proposes deregistration of confirmed ghosts — propose-only, Đ32
approval per action, never auto-delete (Đ0 Atom soft-retire).
Output
- Phantom decision pack: the source_model-aware definition above.
- Law-patch DRAFT: amend the phantom/count-integrity law to (a) forbid blind
record>actualas a phantom signal, (b) mandate source_model discrimination, (c) definephantom_candidatevsphantom_confirmed, (d) authorizephantom_count+ PIV-302. - Approval wording (for RG4): "Council ratifies the source_model-aware phantom
definition (model-A surplus = recheck, model-B surplus = phantom_candidate), authorizes a
phantom_countcolumn and PIV-302, and approves NEW system_issues/event typescount_integrity_failed,phantom_candidate,phantom_confirmed,label_grouping_required." - Implementation gates: law ratified → add phantom_count + issue/event types (gated DDL) → PIV-302 promotion → cleanup workflow (propose-only).
Verdict
Phantom pack COMPLETE. Definition is source_model-aware and proven against live drift; no law enacted, no self-approval, no event/notification emitted. Awaits RG4.