KB-2A1F

02 — Accounting Invariant: Design + Read-Only Detection Rehearsal

5 min read Revision 1
architectureaccounting-invariantcount-integrityphantomorphanread-only-rehearsaldieu-28pivot-missing2026-05-31verified

title: 02 — Accounting Invariant — Design + Read-Only Detection Rehearsal date: 2026-05-31

02 — Accounting Invariant

The invariant

total_system_objects
  = counted_in_registries_pivot
  + orphan_objects        (real, not counted)
  + phantom_objects       (counted, not real)   [phantom = LAW_DEFINITION_GAP, see doc 01]

On failure: set count_integrity_status = failed; emit a system_issues row + (design) an event_outbox event; define the cleanup-workflow trigger target; never silently hide the mismatch (Đ28). No production notification is implemented in this session — the contract is designed and the detection is rehearsed read-only.

CRITICAL design rule discovered during rehearsal — scope to leaves, never blind-SUM

A naive SUM(record_count) over all 169 meta_catalog rows double-counts: the table mixes leaf categories with rollup/meta rows (composition_level='meta', entity_type like %_total / all — e.g. CAT-ALL actual=1,919,748, CAT-DOT, CAT-COL, CAT-SPE). Summing leaves and their rollups inflates the total. This is precisely the disguised-math trap Đ28 forbids. The invariant MUST be computed over a defined leaf set:

leaf set := meta_catalog rows WHERE composition_level <> 'meta'
            AND entity_type NOT LIKE '%_total' AND entity_type <> 'all'

and total_system_objects for cross-checking comes from the grand-total pivot (PIV-500, PIVOT_MISSING — propose), not from re-summing the catalog.

Read-only rehearsal — LIVE 2026-05-31 (zero mutation)

Aggregate over all 169 meta_catalog rows (intentionally unscoped, to expose the double-count):

metric value
categories 169
Σ record_count 3,638,356
Σ actual_count 3,838,798
Σ orphan_count 161
net_gap (Σactual − Σrecord) +200,442
drift_rows (actual ≠ record) 10
orphan-side rows (actual > record) 3
phantom-side rows (record > actual) 7

count_integrity_status = FAILED (net_gap ≠ 0 and drift_rows > 0).

The 10 drift rows (the actual integrity signal)

code name record actual orphan gap side
CAT-ALL Tổng nguyên tử (rollup) 1,682,113 1,919,748 0 +237,635 orphan-side (rollup stale)
CAT-023 Sổ khai sinh 980,221 943,726 0 −36,495 phantom-side
CAT-DOT Tổng DOT (rollup) 307 null 140 (−307) actual uncomputed
CAT-COL Tổng Collections (rollup) 168 null 20 (−168) actual uncomputed
CAT-006 DOT Tools 309 163 0 −146 phantom-side
CAT-CMP Tổng hợp chất 423 326 0 −97 phantom-side
CAT-MAT Tổng vật liệu 0 55 0 +55 orphan-side
CAT-SPE Tổng loài (rollup) 42 null 1 (−42) actual uncomputed
CAT-007 Pages/Routes 37 52 0 +15 orphan-side
CAT-MOL Tổng phân tử 774 766 0 −8 phantom-side

Note CAT-023 birth: record_count=980,221 equals the live birth_registry COUNT (980,221) and equals pivot_count('PIV-019') — but actual_count=943,726 is stale. So the stored number is current and the audited number is stale: the invariant correctly flags it as drift either way and routes it to recount, not to silent correction.

Proposed reconciliation surface (NEW, design-only)

  • v_count_integrity (view, propose): per leaf category emit counted (pivot value), actual, orphan_count, phantom_count := GREATEST(record_count−actual_count,0), orphan_excess := GREATEST(actual_count−record_count,0), drift := actual ≠ record, count_integrity_status, last_scan_date.
  • phantom_count column on meta_catalog (propose; additive). Today phantom is derivable but not stored.
  • PIV-500 grand-total + PIV-30x orphan / phantom totals (PIVOT_MISSING → propose as pivot_definitions INSERTs, never as Nuxt math).
  • fn_count_integrity_check() (propose): wraps the rehearsal query, writes system_issues(issue_type='count_integrity_failed', evidence_snapshot=<json of drift rows>, coalesce_key=<scope>) and (design) enqueues event_outbox. Reuses the existing fn_refresh_orphan_* + check_registry_coverage machinery.

Rehearsal verdict

  • Detection path is real and read-only-feasible today with existing columns — no new infrastructure required to detect.
  • The invariant already fails (10 drift rows), proving the surface must show plus/minus/orphan/phantom/drift/verification per row and must not paper over it with a single green total.
  • Everything above is propose-only; no meta_catalog, view, pivot, or system_issues row was written this session.
Back to Knowledge Hub knowledge/dev/reports/architecture/registries-pivot-os-agency-count-integrity-orphan-phantom-label-pin-rehearsal-2026-05-31/02-accounting-invariant-design-and-rehearsal.md