Skip to content

feat(usage): record compute-usage intervals with the session binding (RIG-2872) - #1509

Merged
trunk-io[bot] merged 10 commits into
mainfrom
compass-server/rig-2872-compute-intervals
Oct 4, 2026
Merged

trunk-io[bot] merged 10 commits into
mainfrom
compass-server/rig-2872-compute-intervals

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

This PR is part of a stack containing 2 PRs:

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

Each session binding is one billable compute interval. Binding writes now emit start and end rows into an append-only compute_usage_events table, in the same transaction as the binding change.

Changes

  • Migration 0004_compute_usage.sql adds compute_usage_events with tenant RLS and grants, and backfills an estimated start for every binding already open.
  • RecordSessionBinding, DeleteSessionBinding and DeleteSessionBindingsForRunner write interval events atomically. A same-session runner change keeps its interval.
  • A system-role hourly sweep closes an interval whose binding vanished without an end event, marked estimated.
  • docs/concepts/tokens-and-billing.md describes the event log.

Stop, a lost-session drop, and the Runner re-enroll reap all delete the binding, so each ends the interval. There is no pause path in the protocol today.

Verification

  • pgtest race: ./internal/store, ./internal/usage, ./internal/runnerhub, ./server all ok.
  • sql-migration-gate (squawk + sqruff) and sqlc drift pass; golangci-lint 0 issues.

Refs RIG-2872

Co-authored-by: Matt Wilkinson matt@rigel.build

Review follow-ups

Four review rounds. Fixed in additive commits: backfill start kept (no trigger-firing UPDATE), backfill runs as compass_system, events stamped with clock_timestamp(), re-enroll reap spans tenants, estimated start for bindings an older server wrote, and tests for each.

Deferred to Matt:

  • RIG-4228: joint Server+Runner restart leaves bindings, so intervals stay open.
  • RIG-4229: refuse a binding whose account lives in another tenant?
  • RIG-4231: compute usage while old servers still write bindings.

@linear-code

linear-code Bot commented Oct 3, 2026

Copy link
Copy Markdown

RIG-2872

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

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

Deployed from compass-server/rig-2872-compute-intervals at b3a2758.

Changed pages:

@rigel-mintaka
rigel-mintaka marked this pull request as ready for review October 3, 2026 11:05
@rigel-mintaka
rigel-mintaka force-pushed the compass-server/rig-2872-compute-intervals branch from 870fe85 to 232742c Compare October 3, 2026 17:11
Base automatically changed from compass-server/rig-2872-token-usage-store to main October 3, 2026 18:15
@trunk-io

trunk-io Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

😎 Stack merged successfully - details.

@rigel-mintaka
rigel-mintaka force-pushed the compass-server/rig-2872-compute-intervals branch from 232742c to 01e0847 Compare October 3, 2026 20:26
@mattwilkinsonn
mattwilkinsonn added this pull request to stack #1560 October 3, 2026 21:57
@rigel-mintaka
rigel-mintaka force-pushed the compass-server/rig-2872-compute-intervals branch from 01e0847 to d5a4411 Compare October 4, 2026 02:37
rigel-mintaka and others added 10 commits October 4, 2026 08:04
…(RIG-2872)

Each session binding is one billable compute interval. Binding writes now emit start and end rows into an append-only compute_usage_events table, in the same transaction as the binding change.

### Changes

- Migration 0004_compute_usage.sql adds compute_usage_events with tenant RLS and grants, and backfills an estimated start for every binding already open.
- RecordSessionBinding, DeleteSessionBinding and DeleteSessionBindingsForRunner write interval events atomically. A same-session runner change keeps its interval.
- A system-role hourly sweep closes an interval whose binding vanished without an end event, marked estimated.
- docs/concepts/tokens-and-billing.md describes the event log.

Stop, a lost-session drop, and the Runner re-enroll reap all delete the binding, so each ends the interval. There is no pause path in the protocol today.

### Verification

- pgtest race: ./internal/store, ./internal/usage, ./internal/runnerhub, ./server all ok.
- sql-migration-gate (squawk + sqruff) and sqlc drift pass; golangci-lint 0 issues.

Refs RIG-2872

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…mp events at write time (RIG-2872)

- 0004 adds usage_interval_id with a volatile default instead of UPDATE, so the set_updated_at trigger no longer overwrites the estimated start, and older servers can still insert during a rolling deploy.
- The backfill runs as compass_system, as 0002 does, so FORCE RLS cannot hide rows from a non-superuser owner.
- Interval events use clock_timestamp(), so an event written after a lock wait is stamped when it is written.
- Hub.enroll reaps a reconnecting Runner's bindings under the system role, so every tenant's interval ends; each archive runs under the row's tenant.
- Tests: conflict rollback, re-point ordering, upgrade start timestamp, cross-tenant re-enroll reap.

Open decisions: RIG-4228 (joint restart leaves bindings) and RIG-4229 (cross-tenant account binding).

Refs RIG-2872

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

During a rolling deploy an older server inserts bindings that have no start event. A same-session rebind, a single release, or a runner sweep now first writes an estimated start (at the binding's created_at) for such a row, so the interval stays complete. ON CONFLICT keeps any real start.

Tests seed a binding without events and exercise rebind then release, rebind then runner sweep, and release alone. All three fail without this change.

Refs RIG-2872

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

- TestComputeUsageEventsStampedAfterLockWait gates a bind on the account advisory lock and asserts its events are stamped after release. It fails if the queries use now().
- TestComputeUsageLegacyBindingRepointKeepsBothIntervals re-points an event-less binding to a new session. It fails without the ensure-start call.
- tenant_tx.go lists Hub.enroll's reap among system-role entrypoints; hub.go says why it is cross-tenant.
- docs: inferred timestamps carry the estimated flag; drop the unqualified exact claim.

Rolling-deploy coverage for old-server writes is deferred to RIG-4231.

Refs RIG-2872

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

- TestComputeUsageEventsStampedAfterLockWait fails if it finds fewer than the two events it pins.
- tenant_tx.go lists the Runner session tenant lookup among system-role entrypoints.
- docs: name which events are estimated, and say a reconnect reap records the reap time.

Refs RIG-2872

Co-authored-by: Matt Wilkinson <matt@rigel.build>
After the rebase, Serve was one line over the funlen limit. startUsageSweepers starts the retention prune and the orphan-interval close together, so Serve makes one call.

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

Main added 0004_token_tenant.sql, so 0004_compute_usage.sql now collides and Open refuses the duplicate version.

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

Main now reaps a Runner's bindings on every enroll, so the drop test re-records its row after enroll and the reap test needs one enroll.

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

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@rigel-mintaka
rigel-mintaka force-pushed the compass-server/rig-2872-compute-intervals branch from be6f306 to b3a2758 Compare October 4, 2026 13:13
@trunk-io
trunk-io Bot merged commit d0cffd2 into main Oct 4, 2026
14 checks passed
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