Skip to content

feat(api): budget sponsorship per project and run the operator CLI as a job - #2579

Closed
multipletwigs wants to merge 5 commits into
mainfrom
chore/sponsorship-budget-job-entrypoint
Closed

multipletwigs wants to merge 5 commits into
mainfrom
chore/sponsorship-budget-job-entrypoint

Conversation

@multipletwigs

Copy link
Copy Markdown
Collaborator

Drops the organization level from sponsorship budgets: global + project, where a project is one org's one environment. Also ships the operator CLI in the image as sponsorship-budget.js. First of three: the migration PR (deletes org rows) merges only after this is live in prod; the sdp-infra job runs the new entrypoint.

  • Resolution is global + project in the repository, Redis counter fields (project:<id>), usage sums and reconciler. Org rows still in the table are ignored until the migration.
  • The CLI takes --environment sandbox|production and --organization-id, resolves the org's active project, and prints the org name, id and project.
  • Every write takes --mode preview|apply; every enabling write takes --expected-version. It's a compare-and-set, so a write can't silently resume a breaker trip. kill needs no version.
  • Reservations admitted under the old three-field layout settle correctly: ownership markers are per field, and the orphaned org counter expires with its bucket.
  • Fixes the CLI never exiting (the Redis client stayed open).

Verification: typecheck 28/28 packages, biome clean. Local CLI against dev Postgres/Redis covered preview, apply, stale-version refusal, kill, resume compare-and-set and status sync. Vitest not run locally; the DB/Redis suites run in CI.

… a job

Drop the organization budget scope: a project is one organization's one
environment, so global + project is the whole hierarchy. The operator CLI
takes an SDP environment and organization id, previews before it applies,
refuses writes against a version the operator did not read, and ships in the
image as sponsorship-budget.js.
status syncs each policy forward-only before comparing, so a row no
sponsorship has touched no longer reads as drift. Output is one JSON line so
a Cloud Run execution logs it as one entry.
@vercel

vercel Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
sdp-docs Ready Ready Preview Oct 27, 2026 10:52am UTC
sdp-web Ready Ready Preview Oct 27, 2026 10:52am UTC

Request Review

@greptile-apps

greptile-apps Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[High impact] The latest changes appear safe to merge, with no new blocking defect found.

Findings

  1. P2 Resume preview approves refused commands ▶
  2. P2 Command variants are defined twice ▶
  3. P2 Summaries discard shared type checks ▶
  4. P2 Flag types allow unsupported names ▶
  5. P2 Resume rollback is untested ▶

Summary

This PR changes sponsorship budgets to global and project limits and packages the operator CLI as sponsorship-budget.js.

  • Sponsorship requests now use global limits and project limits.
  • Operators can manage sponsorship limits with an environment-aware CLI job.

Reviews (2) · Last reviewed commit: "test(api): cover a sponsorship budget pr..." · Reviewed by Greptile

expectedVersion,
result: null,
};
if (command.mode === "preview") return plan;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Resume preview approves refused commands

resume --mode preview succeeds when the global policy is already enabled, provided --expected-version matches. Apply then refuses because resumeGlobalPolicyAtVersion requires a disabled policy. Check that requirement before returning the preview so operators do not approve a change that cannot run.

Comment on lines +143 to +154
export type SponsorshipBudgetCommand =
| { command: "status"; network: SponsorshipNetwork }
| ({
command: "set-project";
environment: (typeof SDP_ENVIRONMENTS)[number];
organizationId: string;
} & SponsorshipOperatorAttribution &
SponsorshipPolicyWrite)
| ({ command: "set-project-default" | "set-global" } & SponsorshipOperatorAttribution &
SponsorshipPolicyWrite)
| ({ command: "kill" } & SponsorshipOperatorAttribution)
| ({ command: "resume"; expectedVersion: number } & SponsorshipOperatorAttribution);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Command variants are defined twice

SponsorshipBudgetCommand repeats the variants by hand beside sponsorshipBudgetCommandSchema. The TypeScript section of docs/contributing/code-standards.md requires variant types to come from z.infer. Derive the converted command type from a schema so command changes have one source of truth. This repository requirement must be satisfied before merging.

File Used: docs/contributing/code-standards.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +44 to +47
interface ProjectSummary {
id: string;
environment: string;
status: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Summaries discard shared type checks

ProjectSummary widens environment and status to string; organizationRowSchema.status does the same. The TypeScript section of docs/contributing/code-standards.md requires scripts to use the canonical types from @sdp/types. Use the corresponding fields from Project and parse organization status with ORGANIZATION_STATUSES. This repository requirement must be satisfied before merging.

File Used: docs/contributing/code-standards.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

*
* @returns The command and the flags that were given.
*/
function readCommandLine(): { command: string | undefined; flags: Record<string, string> } {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Flag types allow unsupported names

readCommandLine represents the fixed flag set as Record<string, string>, and the parser accepts that same type. The TypeScript section of docs/contributing/code-standards.md forbids this representation for finite sets. Derive a flag-name type from FLAG_NAMES and use a partial record of those keys. This repository requirement must be satisfied before merging.

File Used: docs/contributing/code-standards.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

);
if (!row) throw new Error("Failed to persist sponsorship budget policy");
if (!row) return null;
await this.insertRevision(tx, row, input.operator, input.reason);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Resume rollback is untested

resumeGlobalPolicyAtVersion changes the policy and then writes its audit revision, but its tests never make the revision write fail and check that the policy change rolls back. The Correctness section of docs/contributing/code-standards.md requires this failure-path test. Add it for the new resume transition before merging.

File Used: docs/contributing/code-standards.md (source)

…ly binds to

--expected-version is required only with --mode apply. A preview reports
currentVersion, so the operator workflow binds its approved apply to exactly
the row its preview read.
@multipletwigs

Copy link
Copy Markdown
Collaborator Author

backoffice solves this

This branch was successfully deployed

2 active deployments
Preview – sdp-web — bd755af1 Deployed Oct 27, 2026 by vercel[bot]
Preview – sdp-docs — bd755af1 Deployed Oct 27, 2026 by vercel[bot]
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.

1 participant