Skip to content

aggregator(snapshots): compact-usage-snapshots accepts --months 0 — --apply compacts the in-progress month, deleting its raw file and leaving only min/max/last #3285

Closed

Description

@dezren39

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---months error, before touching the store — in both dry-run and --apply modes.
  • 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 1 and 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

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/aggregatorPackage/app: aggregator (pm-stack in this repo)
    effort/sSmall: expected under 2 hours of work
    on Nov 1, 2026
  2. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  3. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  4. 8 remaining items

  5. added
    queue/queuedHas a queued task in the Supabase agent queue
    status/readyTriaged and ready for queued implementation work
    on Nov 1, 2026
  6. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

  7. added
    app/pm-stackpm-stack (Pad/Grist) in developing-today/code
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    status/readyTriaged and ready for queued implementation work
    on Nov 1, 2026
  8. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  9. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/aggregatorPackage/app: aggregator (pm-stack in this repo)app/pm-stackpm-stack (Pad/Grist) in developing-today/codeeffort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobtask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions