feat(campaign): machine-wide always-loaded token roll-up — pure layer (v5.9 B2a)

Extend the campaign ledger with a machine-wide always-loaded token bill, the
pure data-model + aggregation half of B2. Deliberately does NOT do live I/O:
the cross-repo readActiveConfig sweep that populates real numbers is B2b.

Design — shared layer counted ONCE, structurally:
- Ledger root gets an optional `sharedGlobal` summary (the always-loaded layer
  paid in every repo: global CLAUDE.md + agent listing + global MCP + unscoped
  global rules + active plugins' always components), stored ONCE via the new
  setSharedGlobal().
- Each repo entry gets an optional `tokens` summary (its PER-REPO delta only),
  set via the new setRepoTokens(); addRepo now seeds `tokens: null` (mirrors
  findingsBySeverity).
- rollUp() stays PURE and gains a `tokens` aggregate: machineWide = sharedGlobal
  + Σ(per-repo deltas), so the shared layer is counted exactly once by
  construction — the structural guard against the historic double-count bug.
  Plus a `byRepo` table ranked DESC by always-loaded cost ("most expensive
  repos") with a deterministic name tie-break.

Summary shape mirrors manifest's summarizeByLoadPattern exactly
({always|onDemand|external|unknown: {tokens,count}}) so B2b wires in trivially.

Keeps rollUp pure (no filesystem I/O) → byte-stable, respects the THIN campaign
motor invariant. Chosen over the plan's literal "run readActiveConfig inside
rollUp" which would break both (operator-approved deviation).

- 8 new ledger tests incl. the counted-once regression guard + byRepo ranking.
- Updated addRepo shape test (+tokens:null) and campaign-cli EMPTY_ROLLUP.
- No frozen snapshot affected (campaign is v5.7, absent from v5.0.0 baselines).
  Suite green: 1189 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-06-23 16:57:31 +02:00
commit cefa751990
3 changed files with 185 additions and 4 deletions

View file

@ -57,6 +57,13 @@ const EMPTY_ROLLUP = {
byStatus: { pending: 0, audited: 0, planned: 0, implemented: 0 },
bySeverity: { critical: 0, high: 0, medium: 0, low: 0 },
reposWithFindings: 0,
tokens: {
sharedGlobal: { always: 0, onDemand: 0, external: 0, unknown: 0 },
perRepoDelta: { always: 0, onDemand: 0, external: 0, unknown: 0 },
machineWide: { always: 0, onDemand: 0, external: 0, unknown: 0 },
reposWithTokens: 0,
byRepo: [],
},
};
describe('campaign-cli — exit codes', () => {