Repository navigation
Description
aggregator(snapshots): compact-usage-snapshots accepts --months 0 — --apply compacts the in-progress month, deleting its raw file and leaving only min/max/last
Evidence (verified this run, main 0f969120)
The retention guard in pm-stack/aggregator/scripts/compact-usage-snapshots.ts:26-31 accepts zero:
const retentionMonths = monthsArg >= 0 ? Number(process.argv[monthsArg + 1]) : 13;
if (!Number.isInteger(retentionMonths) || retentionMonths < 0) { /* exit 2 */ }SnapshotStore.compact (pm-stack/aggregator/src/snapshots.ts:656-673) computes cutoffMonth = shiftMonth(currentMonth, -retentionMonths) and compacts every raw month with month <= cutoffMonth. With retentionMonths = 0 the cutoff is the current month, so the month still being appended to is eligible — contradicting the script header ("raw monthly files are kept 13 months, then replaced by a rollup") and the compact() doc ("compact every raw monthly file older than retentionMonths", snapshots.ts:643-644). No other caller passes retentionMonths (grep over *.ts: only this script and snapshots.ts itself).
Probe (laptop, real script + real SnapshotStore, both files sha256-matched between the sandbox and laptop clones: script d65e504a…, snapshots.ts c6685035…; temp store seeded with 3 raw records in the current month, values 40 / 85 / 10; probe dir ~/git/fleet/probe-2026-10-31-laneA-compact0/):
| run | result |
|---|---|
control: --apply --months 13 |
current month untouched — usage-2026-11.jsonl stays raw, "no months at or before the 2025-10 cutoff — nothing to do" |
--apply --months 0 |
current month compacted: "2026-11: 3 records -> 1 rollups (compacted)"; usage-2026-11.jsonl deleted, only usage-2026-11.rollup.jsonl remains (one rollup holding count/min/max/last) |
The intermediate raw observations for the live month (every value that is not the min, max, or last) are irreversibly gone; readAll/byService thereafter expand the rollup into synthetic min/max/last records for the month everyone actually looks at. Later appends re-create a raw file and merge on the next compaction (the store tolerates it), so this is silent history loss, not corruption — which is exactly why a guard matters: the run reports success.
Why it matters
This is the fleet's destructive retention tool for usage-snapshot history (design §6, #1117). Its one safety property is that compaction only touches months safely in the past. A single plausible flag value — 0, e.g. from an operator or a wrapper script intending "no extra retention" — silently destroys the raw fidelity of the in-progress month while exiting 0 with a normal-looking report. The boundary semantics already pin "a month exactly at the cutoff is eligible" for the 13-month case (test/snapshot-retention.test.ts: 2025-09 at exactly 13 months back compacts, 2025-10 at 12 stays raw), so the fix must state the floor, not just shift the comparison.
Acceptance criteria
compact-usage-snapshots.ts --months 0(and any value< 1) exits 2 with the existing invalid---monthserror, before touching the store — in both dry-run and--applymodes.- Defense in depth at the library seam:
SnapshotStore.compact({ retentionMonths: 0 })never compacts the current month (reject the value, or clamp the cutoff to at most the previous month), so a future caller cannot reintroduce this. - A pinning test (extending
pm-stack/aggregator/test/snapshot-retention.test.ts) shows the current month stays raw under the smallest accepted retention, and existing retention tests (boundary month eligible, recent month raw, idempotence, late-record merge) still pass. --months 1and the default (13) behave exactly as today for past months.
Dedup: searched open + closed issues for compact-usage-snapshots, snapshot retention and --months — #1117 (closed, the original retention feature) and #2941 (unrelated __proto__ grouping bug in the same store) are the only neighbours; neither covers the zero-retention / current-month case.
Activity
dezren39 commentedon Nov 1, 2026
🤖 Agent note · filed
--months 0(guard rejects only< 0), and SnapshotStore.compact's cutoff then equals the current month, so--applycompacts the in-progress month — its raw file is deleted and only a min/max/last rollup remains. Verified with a laptop Bun probe running the real sha-matched script against a temp store (control--months 13leaves the current month raw;--months 0compacts it, 3 records -> 1 rollup).8 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · done
23a38e3947da2022bdbe32169c2fae8b125aeb92; isolated real-script probe showed--months 0 --applyreplacing the live month's raw JSONL with a rollup. Scoped fix queued as task 5715 (repo-health:v1:fix:developing-today/code:issue:3285).dezren39 commentedon Nov 2, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 2, 2026
🤖 Agent note · done