Durable per-user usage rollup and GDPR/PIPL content erasure#5
Merged
Conversation
Rollup: the advance window now reaches back to the rollup watermark when it
trails the 20-minute backfill (first run rolls a pre-existing ledger whole; a
stalled task catches up), and bucket upserts take the per-column max so a
recompute over a partially pruned ledger can never shrink a bucket. Postgres
elects one replica per pass via an advisory xact lock; periodic tasks skip
missed ticks. usage_by_user bounds are minute-aligned on entry so repeated
queries can't drift as the watermark advances.
Erasure: batch inputs are deleted at terminal status; batch results carry the
effective user and their messages are erased with retained content; the
content_erase audit entry commits in the same transaction as the deletion.
DELETE /v1/files/{id} (OpenAI-compatible) lets tenants remove uploaded files,
which stay tenant-owned assets outside per-user erasure.
…tx pruning A minute is rolled only after a one-minute settle window, so no in-flight billing write can land in an already-rolled minute — the precondition that makes the max-upsert sound. Erasure now blanks a user's queued batch items in place (they fail at execution instead of running erased content), reaching pending/running batches. File deletion is one guarded DELETE (id + tenant) and SQLite file ids derive from the non-rewinding AUTOINCREMENT sequence, closing an id-reuse/check-then-delete cross-tenant window. Fenced/unfenced terminal status writes prune batch inputs in the same transaction.
A batch executor re-reads each item's stored copy immediately before dispatch (backends that persist items), so an erasure landing while the batch sat queued stops the item instead of running the pre-load snapshot. Config validation rejects negative prices — cost accounting (and the rollup's max-upsert) relies on per-column monotone sums.
…l mid-batch items A distributed executor now fails an item closed when the pre-dispatch re-read errors or finds no row — a stale pre-load copy never dispatches. Local backends (which don't persist items) record an erasure marker in the erase transaction; the executor checks it per item against the batch's start time, so an erasure landing mid-batch stops the user's remaining items while batches submitted after the erasure run normally.
The marker comparison point is now the submit instant (when items are captured), not the spawned executor's first poll, closing the pre-poll erasure window. Markers keep only the latest instant per (tenant, user) — an upserted unique row on SQLite, a map in memory — so repeated erasures can't grow unbounded and the per-item check is a keyed lookup.
…dentity, CI isolation The ledger cap now spares rows the rollup hasn't folded (created at or past the watermark) on all backends, so a burst can briefly exceed ledger_max_rows instead of losing usage forever. Distributed enqueue persists the EFFECTIVE user (owner overrides the hint), so execution, billing, and erasure key on one identity; the Postgres migration backfills pre-upgrade batch_results ownership from their surviving item rows. Erasure markers and the submit baseline moved to millisecond precision, so erase-then-resubmit within one second is no longer misjudged. The PG store test runs in a private database (the rollup watermark is global per database and the shared one serves parallel tests) instead of truncating shared tables — the CI parallel-run failure.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the two remaining audit/billing gaps identified against the reference architecture: long-horizon per-user usage that survives ledger pruning, and the right-to-erasure hook for retained content.
Durable minute rollup
usage_rolluptable — per-(minute, tenant, user, model) buckets on all three Store backends — fed by a 60s background task (spawn_usage_rollup).usage_by_userserves rolled minutes from buckets plus the raw ledger tail (watermark split, minute-aligned bounds), soGET /admin/usage/usersstays correct afterledger_max_rowspruning and repeated queries can't drift.Content erasure (GDPR/PIPL)
DELETE /admin/audit/content?user=erases every retained trace of one end user: retained prompt/response rows, generated batch-result messages (batch_resultsgained auser_idcolumn), and queued batch inputs — blanked in place across all batch states, so a pending/running item fails at execution instead of running erased content. Thecontent_eraseaudit entry commits in the same transaction as the deletion.DELETE /v1/files/{id}(OpenAI-compatible): files are tenant-owned assets, deleted with one guarded id+tenant statement; SQLite file ids now derive from the non-rewinding AUTOINCREMENT sequence, closing an id-reuse window.Review
Converged through six adversarial review rounds on the full diff: 18 findings raised, 13 fixed across five
fix(review)commits, 4 pushbacks accepted, 1 self-labeled contrived residual accepted (unreachable outside unmerged intermediate states). Key invariants (settle window, monotone buckets, fail-closed dispatch, submit-instant erasure baseline) are documented in code comments anddocs/governance.md.Testing
cargo fmt --check,cargo clippy --all-targets -- -D warningsclean;cargo test: 320 passed, 0 failed.--test-threads=1): gw-state 45, gw-handler 31, gw-server 83 — exercising the advisory-lock rollup, watermark union, transactional erase + audit, in-place item blanking through a real pending batch, pre-dispatch snapshots, and the guarded file delete.