Repository navigation
fix: release-review follow-ups for account removal (workflow-state rejections, stats-row creator, sweep pages) - #9164
Conversation
…before they started Not-found, archived, undeployed, unattached and suspended rejections come from a successful read and never wait on an execution log (not-found and undeployed never write one), so they complete without the hasCompleted gate. Quota rejections (402/429) still complete only once their log is saved.
…rovider's last page Azure Blob, GCS and S3-compatible providers can return a short or empty page that is not the last, which the sweep read as done and left the rest of the prefix orphaned. The sweep now ends only on page.done, and follows a non-final page that removed nothing to the next one within the run.
…ent still names A relative /api/files/serve/<key>#frag kept the fragment in its candidate key, so a live reference could be missed and the object purged. The guard now also reads the path without its fragment, on both the decoded and fallback paths.
…side signup A storage write or an ownership transfer that creates a missing user_stats row gave it the default free allowance without the deleted-identity check ensureUserStatsExists makes. When either insert creates the row, the same email-only check now runs on the caller's transaction, in a savepoint that fails open; an existing row costs no extra query.
…status code Preprocessing tags not-found, archived, not-attached and not-deployed rejections with an internal workflowState (isWorkflowStateRejection); these write no execution log by design, so the job completes at once. Quota and account rejections (usage limit, spent reservation headroom, rate limit, suspended account) complete only once their log is saved; a suspended account goes back behind that gate. The tag is a separate field rather than a code because v2 execute, its streaming error and MCP serve copy error.code into their responses; a new code on a 404 would change those outputs. Headroom denials are matched through a new isReservationHeadroomDenial next to their descriptors instead of joining the deterministic-rejection set, which webhooks and polling read to stop retries and which deliberately leaves headroom out.
…reator createUserStatsIfMissing inserts the row on the caller's executor and, only when it created the row, runs the deleted-identity check and forfeits the allowance (a transaction on db, a savepoint inside a caller's transaction), failing open. ensureUserStatsExists, the storage write and the ownership transfer all use it; the two forfeit paths merge into one. One behavior delta: a stats row the ownership transfer creates for the new owner now starts at getFreeTierLimit() instead of the schema default, the same as every other creator (identical unless FREE_TIER_COST_LIMIT is set). The admin credits route keeps its own insert: it is not behavior-identical (short id, no usageLimitUpdatedAt, a plain insert that throws on a concurrent create rather than a no-op), and it rewrites the limit right after.
… email check isDeletedAccountIdentity reads on db as it did before; only userHasDeletedAccountIdentity takes a caller's transaction, so its savepoint wrapper moves inline. Hoist the headroom descriptors to a module constant.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
There was a problem hiding this comment.
No issues found across 17 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
This PR changes payment, billing, or quota logic. Ultrareviews find 2.4x more serious bugs than standard reviews. Comment @cubic-dev-ai ultrareview to run one.
Turn on auto-fix | Re-trigger cubic
[Medium impact] The PR appears safe to merge; no actionable issues were found. SummaryThis PR fixes account-removal follow-ups across execution jobs, billing stats, and storage cleanup.
Intentional behavior acknowledged by
|
Summary
Fixes the real findings from the v0.9.26 release review (#9155) on the account-removal work (#9139, #9141, #9146), plus one gap the review surfaced. Every other thread on that release was answered there.
Workflow-state rejections complete instead of alerting (
background/workflow-execution.ts,lib/execution/preprocessing.ts)PreprocessExecutionErrornow carries an internalworkflowState, andisWorkflowStateRejection()lets the task complete those rejections without waiting for a log.getDeterministicAdmissionRejectionCode;isReservationHeadroomDenial, kept out of the shared classifier so webhook and polling retries are unchanged;RATE_LIMIT_EXCEEDED_CODE.One creator for fallback
user_statsrows (lib/billing/core/usage.ts)createUserStatsIfMissing(executor, userId)is now the only fallback creator. A row it creates gets that check (email; linked sign-ins only for the signup request), which runs in a savepoint and fails open. An existing row still costs exactly one conflicting insert.forfeitFreeTierAllowance(executor, userId)merges the two forfeit paths: a transaction ondb, a savepoint inside a caller's transaction.ensureUserStatsExistsis now a wrapper; the storage tracker and the ownership transfer call the creator.getFreeTierLimit(), like every other creator, instead of the column default. The two differ only whenFREE_TIER_COST_LIMITis set.Retired Project storage sweep finishes only on the provider's last page (
lib/projects/files/prefix-cleanup.ts)page.done.Storage reference guard (
lib/users/storage-reference-guard.ts): also checks each URL with its#fragmentremoved. That only adds candidates, so it can only keep more objects.Docs:
cancelByWorkspaceis scoped to persisted queue jobs.Type of Change
Testing
Each behavioral change has a test that went red with the change reverted.
Integration (real Postgres, run locally):
deleted-identity22/22, covering:Also
foundation35/35 andstorage-sweep3/3.Unit and integration-adjacent suites: 1104 tests across background, preprocessing, admission, billing, Project cleanup and users.
Independent reviews: a correctness review (savepoints verified on real Postgres, no lock-order change, no transient 403/404), a design review (which drove the typed classifier and the single creator), and a final full-diff review with a per-rejection behavior table.
Checklist