Repository navigation
feat(api): budget sponsorship per project and run the operator CLI as a job - #2579
multipletwigs wants to merge 5 commits into
Conversation
… 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.
[High impact] The latest changes appear safe to merge, with no new blocking defect found. Findings
SummaryThis PR changes sponsorship budgets to global and project limits and packages the operator CLI as
Reviews (2) · Last reviewed commit: "test(api): cover a sponsorship budget pr..." · Reviewed by Greptile |
| expectedVersion, | ||
| result: null, | ||
| }; | ||
| if (command.mode === "preview") return plan; |
There was a problem hiding this comment.
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.
| 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); |
There was a problem hiding this comment.
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!
| interface ProjectSummary { | ||
| id: string; | ||
| environment: string; | ||
| status: string; |
There was a problem hiding this comment.
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> } { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
|
backoffice solves this |
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.project:<id>), usage sums and reconciler. Org rows still in the table are ignored until the migration.--environment sandbox|productionand--organization-id, resolves the org's active project, and prints the org name, id and project.--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.killneeds no version.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.