feat(dashboard-api): implement upsertProjectLimits - #3438
Conversation
The first of the /v1/management handlers to leave 501, now that project_limits gives it somewhere to write. Values arrive absolute and are stored as given. This side performs no arithmetic: the caller owns plans and add-ons and has already resolved them, which is the whole point of the push and what lets team_limits prefer this row over the tier it would otherwise compute from. Adds the two limit dimensions the contract was missing. tiers gained default_free_disk_size_mb and max_disk_size_mb, and the view exposes both, but AdminControlPlaneProjectLimits carried only seven fields -- so an override could never have set the other two. Required, like the rest, because a partial override has no meaning against a table whose columns are all NOT NULL. Three failures are distinguished rather than collapsed into 500. A foreign key violation is the only way the row can fail on identity, so it means the project is unknown here and answers 404. A check violation means the caller sent a combination the column set refuses -- currently free disk above the ceiling, which the request schema cannot express -- and answers 400. Everything else is ours. The team cache carries limits, so the write is invisible until the entry expires without invalidating it. The existing admin user-delete handler omits that call; this one does not. It logs rather than fails, because the row is already committed and an error would only invite a retry that cannot improve on a stale cache. Tests drive the handler and assert through team_limits rather than the table, so the two are proven wired together. Idempotency is covered because the caller retries. The 404, the 400 and the cache invalidation are each mutation verified.
PR SummaryMedium Risk Overview Reviewed by Cursor Bugbot for commit feaa416. Bugbot is set up for automated code reviews on this repo. Configure here. |
❌ 6 Tests Failed:
View the top 1 failed test(s) by shortest run time
View the full list of 5 ❄️ flaky test(s)
To view more test analytics, go to the Test Analytics Dashboard |
#3361 moved these routes from /admin/v1 to /v1/management but touched only the path keys, so the surface has carried three names for one concept ever since: /v1/management paths, AdminControlPlane* schemas, and a workspace-admin tag. Searching for the management contract finds none of the types that define it. Renames the seven schemas to Management* and the tag to management. AdminJWTAuth stays: it names a credential rather than the surface, and sits beside AdminApiKeyAuth, which is a different scheme entirely -- which is also why leaving Admin on the schemas had become actively misleading rather than merely stale. Folded in here rather than deferred because six more handlers are about to be written against these types, and the caller's call sites are about to multiply. The rename only gets more expensive. Breaks the caller on its next spec sync: four references in controlplanes/management.go. Loud and mechanical.
operationIds gain a management prefix, so the generated handler methods read ManagementUpsertProject rather than UpsertProject. Worth doing on a shared ServerInterface: without it these seven sit in the same namespace as the dashboard's own operations, and UpsertProject says nothing about which surface it answers on. Tag becomes control-plane-management, which is what these operations actually are -- the workspace-admin name predates the routes moving off /admin/v1 and described the caller rather than the surface. Handler files renamed to match. Leaving workspace_admin.go next to a management tag, management paths and Management* schemas would have kept a fourth name for the one concept the previous commit set out to unify. Every reference the caller holds now changes on its next sync: seven method names and four type names in controlplanes/management.go. All mechanical, all compile failures.
The seven were scaffolded into a single management.go, which is not how the rest of this directory is organised -- admin_users_delete.go, template_get.go, teams_list.go are all one operation each. Splitting them means a PR implementing one handler touches one file, and the surface is legible from a directory listing. Named resource-first to match the existing files, so the project operations sort together rather than by verb. sendNotImplemented moves to utils_management.go, following the utils_ convention already here. It loses a caller with every handler that lands and disappears with the last one. management_test.go becomes management_contract_test.go: it never tested management.go, it pins the wire shapes the caller depends on.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: da55e1e3c1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
InvalidateTeamCache cleared team-<id> and the API key hashes, but not the per-member entries ValidateAuthProviderTeam populates. Those hold a full Team including limits, so the browser session path -- bearer token plus X-Team-ID, which gates sandbox creation -- kept enforcing the old limits until the entry expired. That made the invalidation this PR added only partly effective, and it was already only partly effective for the other caller: killing a team's sandboxes after blocking it left the same entries stale. Adds GetTeamMemberIDs and sweeps the member keys alongside the rest, so "invalidate this team" means all three key kinds rather than two of them. The docstring now lists them, because the failure from missing one is stale auth on some paths and not others, which is harder to find than no invalidation at all. The query is covered against a team with members and a team without. Sweeping itself is not, for the same reason the API key hash loop is not: packages/auth has no Redis harness, and standing one up is out of proportion to a loop that mirrors the one above it. Also drops six int64 conversions in types/teams.go that became redundant when #3429 widened TeamLimit. Those were failing lint on main -- I had checked db and dashboard-api after that change but not the package that consumes the type.
🤖 I have created a release *beep* *boop* --- ## 0.0.1 (2026-07-30) ### Features * add workspace admin API foundations ([#3314](#3314)) ([0f72030](0f72030)) * **api:** LD-gated ClickHouse read switcher ([#3061](#3061)) ([29e74ca](29e74ca)) * **api:** soft-delete build layers in DB on user delete ([#3121](#3121)) ([ee88776](ee88776)) * **auth:** support admin token team auth ([#2934](#2934)) ([5496666](5496666)) * **auth:** verifiers on one axis, and a reusable authenticator constructor ([#3423](#3423)) ([923b99b](923b99b)) * **dashboard-api:** add internal admin route for deleting a user ([#2986](#2986)) ([ecc1291](ecc1291)) * **dashboard-api:** add internal team creation ([#2824](#2824)) ([375051b](375051b)) * **dashboard-api:** add OIDC admin user bootstrap endpoint ([#2841](#2841)) ([6a7a59e](6a7a59e)) * **dashboard-api:** add Ory user profile provider and auth middleware fix ([#2840](#2840)) ([30d40d2](30d40d2)) * **dashboard-api:** add template tags handlers ([#2885](#2885)) ([bf52a4b](bf52a4b)) * **dashboard-api:** batch member sync route, and unenumerate project_type ([#3427](#3427)) ([cc16acf](cc16acf)) * **dashboard-api:** expose auth profile admin routes ([#2743](#2743)) ([b673a10](b673a10)) * **dashboard-api:** flag sandboxes past data retention ([#3102](#3102)) ([9b162bf](9b162bf)) * **dashboard-api:** implement upsertProjectLimits ([#3438](#3438)) ([ec1ed29](ec1ed29)) * **dashboard-api:** include build resources in /builds response ([#3009](#3009)) ([bf49c32](bf49c32)) * **dashboard-api:** map Ory SSO organizations to E2B teams ([#3094](#3094)) ([dbd098f](dbd098f)) * **dashboard-api:** populate Ory identity external_id on admin bootstrap ([#3062](#3062)) ([6c51232](6c51232)) * **dashboard-api:** project upsert, member sync and user purge ([#3442](#3442)) ([f997c39](f997c39)) * **dashboard-api:** templates list pagination ([#2904](#2904)) ([6882463](6882463)) * **db:** add project_limits, an override the limits owner can write ([#3429](#3429)) ([021c2a4](021c2a4)) * improve templates list sorting ([#2983](#2983)) ([51ad7ff](51ad7ff)) * **otel:** instrument auth service HTTP client with otelhttp ([#2722](#2722)) ([69b085d](69b085d)) * per-team events TTL limit (tier + addons) ([#3181](#3181)) ([f76b2cb](f76b2cb)) ### Bug Fixes * added api and orch ([#3454](#3454)) ([fda5e45](fda5e45)) * **api:** copy auth/internal into api and dashboard-api image builds ([#3323](#3323)) ([bda1fee](bda1fee)) * **api:** invalidate auth cache on API key deletion ([#3324](#3324)) ([8b02910](8b02910)) * correct 3 CVES ([#3218](#3218)) ([076823b](076823b)) * **dashboard-api:** avoid repeated Ory bootstrap provisioning ([#2940](#2940)) ([da5ce59](da5ce59)) * **dashboard-api:** drop removed read-replica accessor in provisioning tests ([#3340](#3340)) ([6addc91](6addc91)) * **dashboard-api:** pass signup metadata to billing provisioning ([#2978](#2978)) ([d0ea5b4](d0ea5b4)) * **dashboard-api:** set Ory external_id only after the bootstrap commit ([#3133](#3133)) ([00ad04b](00ad04b)) * push client-proxy, dashboard-api, and docker-reverse-proxy image… ([#2953](#2953)) ([1d930ee](1d930ee)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: e2b-release-please[bot] <298072688+e2b-release-please[bot]@users.noreply.github.com> Co-authored-by: Charlie Wyse <charlie.wyse@e2b.dev>
🤖 I have created a release *beep* *boop* --- ## [0.1.0](dashboard-api-v0.0.1...dashboard-api-v0.1.0) (2026-07-31) ### Features * add workspace admin API foundations ([#3314](#3314)) ([0f72030](0f72030)) * **api:** LD-gated ClickHouse read switcher ([#3061](#3061)) ([29e74ca](29e74ca)) * **api:** soft-delete build layers in DB on user delete ([#3121](#3121)) ([ee88776](ee88776)) * **auth:** support admin token team auth ([#2934](#2934)) ([5496666](5496666)) * **auth:** verifiers on one axis, and a reusable authenticator constructor ([#3423](#3423)) ([923b99b](923b99b)) * **dashboard-api:** add internal admin route for deleting a user ([#2986](#2986)) ([ecc1291](ecc1291)) * **dashboard-api:** add internal team creation ([#2824](#2824)) ([375051b](375051b)) * **dashboard-api:** add OIDC admin user bootstrap endpoint ([#2841](#2841)) ([6a7a59e](6a7a59e)) * **dashboard-api:** add Ory user profile provider and auth middleware fix ([#2840](#2840)) ([30d40d2](30d40d2)) * **dashboard-api:** add template tags handlers ([#2885](#2885)) ([bf52a4b](bf52a4b)) * **dashboard-api:** batch member sync route, and unenumerate project_type ([#3427](#3427)) ([cc16acf](cc16acf)) * **dashboard-api:** expose auth profile admin routes ([#2743](#2743)) ([b673a10](b673a10)) * **dashboard-api:** flag sandboxes past data retention ([#3102](#3102)) ([9b162bf](9b162bf)) * **dashboard-api:** implement upsertProjectLimits ([#3438](#3438)) ([ec1ed29](ec1ed29)) * **dashboard-api:** include build resources in /builds response ([#3009](#3009)) ([bf49c32](bf49c32)) * **dashboard-api:** map Ory SSO organizations to E2B teams ([#3094](#3094)) ([dbd098f](dbd098f)) * **dashboard-api:** populate Ory identity external_id on admin bootstrap ([#3062](#3062)) ([6c51232](6c51232)) * **dashboard-api:** project upsert, member sync and user purge ([#3442](#3442)) ([f997c39](f997c39)) * **dashboard-api:** templates list pagination ([#2904](#2904)) ([6882463](6882463)) * **db:** add project_limits, an override the limits owner can write ([#3429](#3429)) ([021c2a4](021c2a4)) * improve templates list sorting ([#2983](#2983)) ([51ad7ff](51ad7ff)) * **otel:** instrument auth service HTTP client with otelhttp ([#2722](#2722)) ([69b085d](69b085d)) * per-team events TTL limit (tier + addons) ([#3181](#3181)) ([f76b2cb](f76b2cb)) ### Bug Fixes * added api and orch ([#3454](#3454)) ([fda5e45](fda5e45)) * **api:** copy auth/internal into api and dashboard-api image builds ([#3323](#3323)) ([bda1fee](bda1fee)) * **api:** invalidate auth cache on API key deletion ([#3324](#3324)) ([8b02910](8b02910)) * correct 3 CVES ([#3218](#3218)) ([076823b](076823b)) * creating whitespace to test publish ([#3476](#3476)) ([5158cc9](5158cc9)) * **dashboard-api:** avoid repeated Ory bootstrap provisioning ([#2940](#2940)) ([da5ce59](da5ce59)) * **dashboard-api:** drop removed read-replica accessor in provisioning tests ([#3340](#3340)) ([6addc91](6addc91)) * **dashboard-api:** pass signup metadata to billing provisioning ([#2978](#2978)) ([d0ea5b4](d0ea5b4)) * **dashboard-api:** set Ory external_id only after the bootstrap commit ([#3133](#3133)) ([00ad04b](00ad04b)) * push client-proxy, dashboard-api, and docker-reverse-proxy image… ([#2953](#2953)) ([1d930ee](1d930ee)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: e2b-release-please[bot] <298072688+e2b-release-please[bot]@users.noreply.github.com>
The first `/v1/management` handler to leave 501, now that `project_limits` (#3429) gives it somewhere to write. ## What it does Stores a project's effective limits exactly as sent. This side does **no arithmetic** — the caller owns plans and add-ons and has already resolved them. That's the point of the push, and it's what lets `team_limits` prefer this row over the tier it would otherwise compute from. ## Contract: two missing dimensions `tiers` gained `default_free_disk_size_mb` and `max_disk_size_mb`, and the view exposes both — but `AdminControlPlaneProjectLimits` carried only seven fields, so an override could never have set the other two. Both added, **required** like the rest: a partial override has no meaning against a table whose columns are all `NOT NULL`. This is a breaking contract change for the caller, and deliberately so — belt's `exhaustruct` rule is scoped to these generated types precisely to make a new field a build failure rather than a silently-zero payload. ## Errors are distinguished, not collapsed | Failure | Status | Why | |---|---|---| | Foreign key violation | **404** | The team is the row's only FK, so it means the project is unknown here — not that the payload was bad | | Check violation | **400** | The caller sent a combination the column set refuses. Currently free disk above the ceiling, which the request schema can't express | | Anything else | 500 | Ours | Adds `dberrors.IsCheckViolation` alongside the existing helpers. ## Cache invalidation, and a bug it uncovered The team cache carries limits, so **without invalidating it the write is invisible until the entry expires.** Review found `InvalidateTeamCache` was itself incomplete: it cleared `team-<id>` and the API-key hashes but not the per-member entries `ValidateAuthProviderTeam` populates — which hold a full `Team` including limits, and gate sandbox creation on the browser-session path. It now sweeps all three key kinds via a new `GetTeamMemberIDs`. That was **already a bug** for the other caller: `admin_kill_team_sandboxes` invalidates after blocking a team and left the same entries stale. The handler logs rather than fails: the row is already committed, and returning an error would invite a retry that can't improve on a stale cache. ## Tests Driven through the handler, asserting through `team_limits` rather than the table — so the two are proven wired together rather than assumed. Idempotency is covered because the caller retries. The 404, the 400 and the cache invalidation are each mutation-verified: removing the FK mapping, the check mapping, or the invalidation call fails exactly the intended test. ## Verification `packages/db` and `dashboard-api` build, vet, test and lint clean. Codegen idempotent. ## Also: finishing the management rename #3361 moved these routes from `/admin/v1` to `/v1/management` but touched only the path keys. The surface has carried three names for one concept ever since: | Layer | Was | Now | |---|---|---| | Paths | `/v1/management/*` | unchanged | | Schemas | `AdminControlPlane*` | **`Management*`** | | operationIds | `upsertProject` | **`managementUpsertProject`** | | Handler methods | `UpsertProject` | **`ManagementUpsertProject`** | | Tag | `workspace-admin` | **`control-plane-management`** | | Handler files | one `workspace_admin.go` | **one `management_*.go` per operation** | | Scheme | `AdminJWTAuth` | unchanged | `AdminJWTAuth` stays — it names a credential, not the surface, and sits beside `AdminApiKeyAuth`, which is a different scheme entirely. That adjacency is also why `Admin` on the schemas had become actively misleading rather than merely stale. Folded in here rather than deferred because six more handlers are about to be written against these types, and the caller's call sites are about to multiply. It only gets more expensive. The operationId prefix matters on a shared `ServerInterface`: without it these seven sit in the same namespace as the dashboard's own operations, and `UpsertProject` says nothing about which surface it answers on. **Breaks the caller on its next spec sync** — seven method names and four type names in `controlplanes/management.go`. All mechanical, all compile failures, same coordination as the two required fields above. Handler files are now one per operation, matching the rest of the directory (`admin_users_delete.go`, `template_get.go`, `teams_list.go`) rather than the single scaffolded file the seven arrived in. A PR implementing one handler touches one file, and the surface is legible from `ls`. `sendNotImplemented` moved to `utils_management.go` and loses a caller with each handler that lands. --- Also drops six `int64` conversions in `packages/auth/pkg/types/teams.go` that became redundant when #3429 widened `TeamLimit`. They were **failing lint on main** — after that change I checked `db` and `dashboard-api` but not the package that consumes the type.
🤖 I have created a release *beep* *boop* --- ## 0.0.1 (2026-07-30) ### Features * add workspace admin API foundations ([#3314](#3314)) ([0f72030](0f72030)) * **api:** LD-gated ClickHouse read switcher ([#3061](#3061)) ([29e74ca](29e74ca)) * **api:** soft-delete build layers in DB on user delete ([#3121](#3121)) ([ee88776](ee88776)) * **auth:** support admin token team auth ([#2934](#2934)) ([5496666](5496666)) * **auth:** verifiers on one axis, and a reusable authenticator constructor ([#3423](#3423)) ([f68e713](f68e713)) * **dashboard-api:** add internal admin route for deleting a user ([#2986](#2986)) ([ecc1291](ecc1291)) * **dashboard-api:** add internal team creation ([#2824](#2824)) ([375051b](375051b)) * **dashboard-api:** add OIDC admin user bootstrap endpoint ([#2841](#2841)) ([6a7a59e](6a7a59e)) * **dashboard-api:** add Ory user profile provider and auth middleware fix ([#2840](#2840)) ([30d40d2](30d40d2)) * **dashboard-api:** add template tags handlers ([#2885](#2885)) ([bf52a4b](bf52a4b)) * **dashboard-api:** batch member sync route, and unenumerate project_type ([#3427](#3427)) ([6d8dc38](6d8dc38)) * **dashboard-api:** expose auth profile admin routes ([#2743](#2743)) ([b673a10](b673a10)) * **dashboard-api:** flag sandboxes past data retention ([#3102](#3102)) ([9b162bf](9b162bf)) * **dashboard-api:** implement upsertProjectLimits ([#3438](#3438)) ([f4ee390](f4ee390)) * **dashboard-api:** include build resources in /builds response ([#3009](#3009)) ([bf49c32](bf49c32)) * **dashboard-api:** map Ory SSO organizations to E2B teams ([#3094](#3094)) ([dbd098f](dbd098f)) * **dashboard-api:** populate Ory identity external_id on admin bootstrap ([#3062](#3062)) ([6c51232](6c51232)) * **dashboard-api:** project upsert, member sync and user purge ([#3442](#3442)) ([8c90702](8c90702)) * **dashboard-api:** templates list pagination ([#2904](#2904)) ([6882463](6882463)) * **db:** add project_limits, an override the limits owner can write ([#3429](#3429)) ([5ab6259](5ab6259)) * improve templates list sorting ([#2983](#2983)) ([51ad7ff](51ad7ff)) * **otel:** instrument auth service HTTP client with otelhttp ([#2722](#2722)) ([69b085d](69b085d)) * per-team events TTL limit (tier + addons) ([#3181](#3181)) ([f76b2cb](f76b2cb)) ### Bug Fixes * added api and orch ([#3454](#3454)) ([d56e0a8](d56e0a8)) * **api:** copy auth/internal into api and dashboard-api image builds ([#3323](#3323)) ([bda1fee](bda1fee)) * **api:** invalidate auth cache on API key deletion ([#3324](#3324)) ([8b02910](8b02910)) * correct 3 CVES ([#3218](#3218)) ([076823b](076823b)) * **dashboard-api:** avoid repeated Ory bootstrap provisioning ([#2940](#2940)) ([da5ce59](da5ce59)) * **dashboard-api:** drop removed read-replica accessor in provisioning tests ([#3340](#3340)) ([6addc91](6addc91)) * **dashboard-api:** pass signup metadata to billing provisioning ([#2978](#2978)) ([d0ea5b4](d0ea5b4)) * **dashboard-api:** set Ory external_id only after the bootstrap commit ([#3133](#3133)) ([00ad04b](00ad04b)) * push client-proxy, dashboard-api, and docker-reverse-proxy image… ([#2953](#2953)) ([1d930ee](1d930ee)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: e2b-release-please[bot] <298072688+e2b-release-please[bot]@users.noreply.github.com> Co-authored-by: Charlie Wyse <charlie.wyse@e2b.dev>
🤖 I have created a release *beep* *boop* --- ## [0.1.0](dashboard-api-v0.0.1...dashboard-api-v0.1.0) (2026-07-31) ### Features * add workspace admin API foundations ([#3314](#3314)) ([0f72030](0f72030)) * **api:** LD-gated ClickHouse read switcher ([#3061](#3061)) ([29e74ca](29e74ca)) * **api:** soft-delete build layers in DB on user delete ([#3121](#3121)) ([ee88776](ee88776)) * **auth:** support admin token team auth ([#2934](#2934)) ([5496666](5496666)) * **auth:** verifiers on one axis, and a reusable authenticator constructor ([#3423](#3423)) ([f68e713](f68e713)) * **dashboard-api:** add internal admin route for deleting a user ([#2986](#2986)) ([ecc1291](ecc1291)) * **dashboard-api:** add internal team creation ([#2824](#2824)) ([375051b](375051b)) * **dashboard-api:** add OIDC admin user bootstrap endpoint ([#2841](#2841)) ([6a7a59e](6a7a59e)) * **dashboard-api:** add Ory user profile provider and auth middleware fix ([#2840](#2840)) ([30d40d2](30d40d2)) * **dashboard-api:** add template tags handlers ([#2885](#2885)) ([bf52a4b](bf52a4b)) * **dashboard-api:** batch member sync route, and unenumerate project_type ([#3427](#3427)) ([6d8dc38](6d8dc38)) * **dashboard-api:** expose auth profile admin routes ([#2743](#2743)) ([b673a10](b673a10)) * **dashboard-api:** flag sandboxes past data retention ([#3102](#3102)) ([9b162bf](9b162bf)) * **dashboard-api:** implement upsertProjectLimits ([#3438](#3438)) ([f4ee390](f4ee390)) * **dashboard-api:** include build resources in /builds response ([#3009](#3009)) ([bf49c32](bf49c32)) * **dashboard-api:** map Ory SSO organizations to E2B teams ([#3094](#3094)) ([dbd098f](dbd098f)) * **dashboard-api:** populate Ory identity external_id on admin bootstrap ([#3062](#3062)) ([6c51232](6c51232)) * **dashboard-api:** project upsert, member sync and user purge ([#3442](#3442)) ([8c90702](8c90702)) * **dashboard-api:** templates list pagination ([#2904](#2904)) ([6882463](6882463)) * **db:** add project_limits, an override the limits owner can write ([#3429](#3429)) ([5ab6259](5ab6259)) * improve templates list sorting ([#2983](#2983)) ([51ad7ff](51ad7ff)) * **otel:** instrument auth service HTTP client with otelhttp ([#2722](#2722)) ([69b085d](69b085d)) * per-team events TTL limit (tier + addons) ([#3181](#3181)) ([f76b2cb](f76b2cb)) ### Bug Fixes * added api and orch ([#3454](#3454)) ([d56e0a8](d56e0a8)) * **api:** copy auth/internal into api and dashboard-api image builds ([#3323](#3323)) ([bda1fee](bda1fee)) * **api:** invalidate auth cache on API key deletion ([#3324](#3324)) ([8b02910](8b02910)) * correct 3 CVES ([#3218](#3218)) ([076823b](076823b)) * creating whitespace to test publish ([#3476](#3476)) ([6b4177f](6b4177f)) * **dashboard-api:** avoid repeated Ory bootstrap provisioning ([#2940](#2940)) ([da5ce59](da5ce59)) * **dashboard-api:** drop removed read-replica accessor in provisioning tests ([#3340](#3340)) ([6addc91](6addc91)) * **dashboard-api:** pass signup metadata to billing provisioning ([#2978](#2978)) ([d0ea5b4](d0ea5b4)) * **dashboard-api:** set Ory external_id only after the bootstrap commit ([#3133](#3133)) ([00ad04b](00ad04b)) * push client-proxy, dashboard-api, and docker-reverse-proxy image… ([#2953](#2953)) ([1d930ee](1d930ee)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: e2b-release-please[bot] <298072688+e2b-release-please[bot]@users.noreply.github.com>
The first
/v1/managementhandler to leave 501, now thatproject_limits(#3429) gives it somewhere to write.What it does
Stores a project's effective limits exactly as sent. This side does no arithmetic — the caller owns plans and add-ons and has already resolved them. That's the point of the push, and it's what lets
team_limitsprefer this row over the tier it would otherwise compute from.Contract: two missing dimensions
tiersgaineddefault_free_disk_size_mbandmax_disk_size_mb, and the view exposes both — butAdminControlPlaneProjectLimitscarried only seven fields, so an override could never have set the other two.Both added, required like the rest: a partial override has no meaning against a table whose columns are all
NOT NULL.This is a breaking contract change for the caller, and deliberately so — belt's
exhaustructrule is scoped to these generated types precisely to make a new field a build failure rather than a silently-zero payload.Errors are distinguished, not collapsed
Adds
dberrors.IsCheckViolationalongside the existing helpers.Cache invalidation, and a bug it uncovered
The team cache carries limits, so without invalidating it the write is invisible until the entry expires.
Review found
InvalidateTeamCachewas itself incomplete: it clearedteam-<id>and the API-key hashes but not the per-member entriesValidateAuthProviderTeampopulates — which hold a fullTeamincluding limits, and gate sandbox creation on the browser-session path. It now sweeps all three key kinds via a newGetTeamMemberIDs.That was already a bug for the other caller:
admin_kill_team_sandboxesinvalidates after blocking a team and left the same entries stale.The handler logs rather than fails: the row is already committed, and returning an error would invite a retry that can't improve on a stale cache.
Tests
Driven through the handler, asserting through
team_limitsrather than the table — so the two are proven wired together rather than assumed. Idempotency is covered because the caller retries.The 404, the 400 and the cache invalidation are each mutation-verified: removing the FK mapping, the check mapping, or the invalidation call fails exactly the intended test.
Verification
packages/dbanddashboard-apibuild, vet, test and lint clean. Codegen idempotent.Also: finishing the management rename
#3361 moved these routes from
/admin/v1to/v1/managementbut touched only the path keys. The surface has carried three names for one concept ever since:/v1/management/*AdminControlPlane*Management*upsertProjectmanagementUpsertProjectUpsertProjectManagementUpsertProjectworkspace-admincontrol-plane-managementworkspace_admin.gomanagement_*.goper operationAdminJWTAuthAdminJWTAuthstays — it names a credential, not the surface, and sits besideAdminApiKeyAuth, which is a different scheme entirely. That adjacency is also whyAdminon the schemas had become actively misleading rather than merely stale.Folded in here rather than deferred because six more handlers are about to be written against these types, and the caller's call sites are about to multiply. It only gets more expensive.
The operationId prefix matters on a shared
ServerInterface: without it these seven sit in the same namespace as the dashboard's own operations, andUpsertProjectsays nothing about which surface it answers on.Breaks the caller on its next spec sync — seven method names and four type names in
controlplanes/management.go. All mechanical, all compile failures, same coordination as the two required fields above.Handler files are now one per operation, matching the rest of the directory (
admin_users_delete.go,template_get.go,teams_list.go) rather than the single scaffolded file the seven arrived in. A PR implementing one handler touches one file, and the surface is legible fromls.sendNotImplementedmoved toutils_management.goand loses a caller with each handler that lands.Also drops six
int64conversions inpackages/auth/pkg/types/teams.gothat became redundant when #3429 widenedTeamLimit. They were failing lint on main — after that change I checkeddbanddashboard-apibut not the package that consumes the type.