Skip to content

feat(usage): add the Plane-A token-usage store (RIG-2872) - #1365

Merged
trunk-io[bot] merged 9 commits into
mainfrom
compass-server/rig-2872-token-usage-store
Oct 3, 2026
Merged

trunk-io[bot] merged 9 commits into
mainfrom
compass-server/rig-2872-token-usage-store

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

This PR is part of a stack containing 3 PRs:

  1. main
  2. "feat(usage): add the Plane-A token-usage store (RIG-2872)" (this PR)
  3. feat(usage): record compute-usage intervals with the session binding (RIG-2872) #1509
  4. feat(usage): derive compute-usage rollups and prune the raw compute log (RIG-2872) #1513

Summary

Adds the Plane-A token-usage store that the LLM gateway's usage events will be written into. This is the token-usage half of the observability record's T1. The compute-usage (billing) event is left out until its unit is decided (RIG-4046).

  • go/internal/usage:
    • TokenUsageEvent (the gateway record's field set, with micro-USD money).
    • The Store interface: append, series, rebuild and prune.
    • NewMemory, the in-memory reference.
    • usagetest.Run, one contract suite that runs against both backends.
  • Postgres (new migration 0002_token_usage.sql; 0001_init.sql is frozen because migrations are append-only per RIG-4077):
    • token_usage_events is append-only, keyed (tenant_id, id). A retried batch is ignored.
    • token_usage_rollups_hourly and _daily are incremented only from newly inserted rows, and can be rebuilt from the log.
    • All three tables are under the tenant_isolation RLS policy. 0002 repeats 0001's RLS, grant and trigger loop bodies for these tables.
  • Migration number: fix(auth): scope bearer callers to their token's tenant (RIG-4066) #1375 also adds a 0002. The runner rejects a gap or a duplicate, so whichever PR merges second renumbers to the next free version.
  • Retention:
    • A daily sweeper deletes raw events older than --usage-event-retention (or $COMPASS_USAGE_EVENT_RETENTION). The default is 90 days, and 0 disables the sweeper. The rollups are kept.
    • A global prune horizon, which only moves forward, stops a rebuild from wiping rollups for days whose events were pruned.
    • The prune advances the horizon, then deletes each tenant's events in that tenant's own RLS-scoped transaction. It does not use the BYPASSRLS system role, so DL-315's list of loops is unchanged.

Tests

  • usagetest (11 cases) passes on memory and on Postgres. The cases cover: re-append counts once, mixed batches, UTC hour and day boundaries, filters, empty range, ordering, rebuild after cleared rollups, prune keeps rollups, tenant isolation, and invalid events and queries.
  • The suite was red against a stub backend before the implementation.
  • TestRetentionSweeper*: prune at start and then daily, a failed prune is retried, and a 0 window disables the sweeper.
  • TestOpenUpgradesV1DatabaseToTokenUsage opens a database that stopped at v1 and checks that Open migrates it to v2 with RLS forced, the horizon row seeded, and tenant-scoped reads. It fails without 0002.
  • Store RLS and updated_at pgtests, and the serve boot and shutdown pgtests, all pass. squawk and sqruff pass on 0002.
  • Lint, vet and sqlc-drift are clean.

Refs RIG-2872

@trunk-io

trunk-io Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

😎 Merged successfully - details.

@linear-code

linear-code Bot commented Sep 27, 2026

Copy link
Copy Markdown

RIG-2872

@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-server-rig-2872-toke.compass-eng-docs.pages.dev

Deployed from compass-server/rig-2872-token-usage-store at 95edbd7.

Changed pages:

rigel-mintaka and others added 9 commits October 3, 2026 12:41
The usage package holds the Store contract, an in-memory reference and one
contract suite run against both backends. Postgres keeps an append-only
token_usage_events log (idempotent on tenant and id) with hourly and daily
rollups that can be rebuilt from it. A daily sweeper prunes raw events past
--usage-event-retention (default 90 days) and keeps the rollups; each tenant's
delete runs under row-level security. The compute-usage event waits on RIG-4046.

Refs RIG-2872

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…on (RIG-2872)

Review follow-up. New pgtests hold the horizon row and the per-tenant lock to
prove that prune and append wait for them. The contract suite now covers
unbounded ranges, extreme prune cutoffs and one id shared by two tenants. The
horizon row cannot be inserted or deleted by the runtime roles, a missing row
fails the prune, and events past the timestamptz range are rejected.

Refs RIG-2872

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…rants (RIG-2872)

The lock test now covers the rebuild as well as the append. Removing the
rebuild's lock turns it red. The privilege test pins the revoked INSERT and DELETE on
the prune horizon, and the migration comment names the real hazard.

Refs RIG-2872

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…orizon comments (RIG-2872)

Refs RIG-2872

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…(RIG-2872)

Migrations are append-only now (RIG-4077), so 0001_init.sql returns to
main's bytes. 0002 repeats 0001's RLS, grant, and trigger loop bodies
for its own tables. An upgrade pgtest opens a database at v1 and checks
that Open migrates it forward. The test fails without 0002.

Refs RIG-2872

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…e the migration comments (RIG-2872)

applyV1Only now holds the lock that migrate() holds, because 0001 edits
cluster-global roles. The updated_at test and squawk comments now point
at the migration that adds a table, not only at 0001.

Refs RIG-2872

Co-authored-by: Matt Wilkinson <matt@rigel.build>
… tenant backfill (RIG-2872)

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…2872)

Main now requires a NATS URL for serve. Without one, buildServeConfig fails on that check before it reads the retention flag.

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…dget (RIG-2872)

After the rebase, Serve was one line over the funlen limit (121 > 120). This shortens the retention comment to one line.

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@rigel-mintaka
rigel-mintaka force-pushed the compass-server/rig-2872-token-usage-store branch from 902d9bc to 95edbd7 Compare October 3, 2026 17:11
@trunk-io
trunk-io Bot merged commit 2c2c3e5 into main Oct 3, 2026
15 checks passed
@trunk-io
trunk-io Bot deleted the compass-server/rig-2872-token-usage-store branch October 3, 2026 18:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants