Skip to content

Commit 04cb3fe

Browse files
d-csclaude
andcommitted
fix(run-ops split): invalidate control-plane cache on writes; route auth-env through the cache-first resolver
The ControlPlaneCache served env/org data with no invalidation, so admin/control-plane writes were only reflected after the TTL. Add two invalidation scopes to the cache (invalidateEnvironment for one env's slots; invalidateOrganization via a per-org epoch that env/authEnv values are stamped with, so all of an org's cached rows drop with no reverse index), expose them on the resolver, and call them at every write site that mutates cache-served data: pause/resume, archive, env/org concurrency + burst-factor, API-key regeneration, feature flags, API/batch rate limits, runs enable/disable, org + project delete, and stream-basin provisioning. Also extend the resolver's authenticated-env slot to carry `git` and make the run-engine adapter's resolveAuthenticatedEnv delegate to the cache-first, split-aware resolver instead of issuing its own $replica.findFirst, so it honors splitEnabled() and the cache like its siblings while still returning `git` and the deleted-project guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4bece33 commit 04cb3fe

23 files changed

Lines changed: 477 additions & 38 deletions

.server-changes/run-ops-split-webapp-foundation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ area: webapp
33
type: feature
44
---
55

6-
Add the webapp foundation for the run-ops database split: topology/flag wiring, split-mode gating, a distinct-DB boot sentinel, and control-plane resolver read-through (all inert until `RUN_OPS_SPLIT_ENABLED`).
6+
Add the webapp foundation for the run-ops database split: topology/flag wiring, split-mode gating, a distinct-DB boot sentinel, and control-plane resolver read-through (all inert until `RUN_OPS_SPLIT_ENABLED`). The control-plane cache is now invalidated at env/org write sites (pause/resume, archive, concurrency/burst-factor, API-key regen, feature flags, rate limits, runs enable/disable, org/project delete, stream-basin provisioning) so admin/control-plane changes are reflected immediately rather than after the cache TTL, and the run-engine authenticated-env resolution goes through the cache-first, split-aware resolver.

apps/webapp/app/components/admin/backOffice/ApiRateLimitSection.server.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { prisma } from "~/db.server";
22
import { env } from "~/env.server";
33
import { logger } from "~/services/logger.server";
44
import { type Duration } from "~/services/rateLimiter.server";
5+
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";
56
import { API_RATE_LIMIT_INTENT } from "./ApiRateLimitSection";
67
import {
78
handleRateLimitAction,
@@ -31,6 +32,8 @@ export const apiRateLimitDomain: RateLimitDomain = {
3132
where: { id: orgId },
3233
data: { apiRateLimiterConfig: next as any },
3334
});
35+
// apiRateLimiterConfig is embedded in every env of the org; drop all its cached env rows.
36+
controlPlaneResolver.invalidateOrganization(orgId);
3437
logger.info("admin.backOffice.apiRateLimit", {
3538
adminUserId,
3639
orgId,

apps/webapp/app/components/admin/backOffice/BatchRateLimitSection.server.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { prisma } from "~/db.server";
22
import { env } from "~/env.server";
33
import { logger } from "~/services/logger.server";
44
import { type Duration } from "~/services/rateLimiter.server";
5+
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";
56
import { BATCH_RATE_LIMIT_INTENT } from "./BatchRateLimitSection";
67
import {
78
handleRateLimitAction,
@@ -31,6 +32,8 @@ export const batchRateLimitDomain: RateLimitDomain = {
3132
where: { id: orgId },
3233
data: { batchRateLimitConfig: next as any },
3334
});
35+
// batchRateLimitConfig is embedded in every env of the org; drop all its cached env rows.
36+
controlPlaneResolver.invalidateOrganization(orgId);
3437
logger.info("admin.backOffice.batchRateLimit", {
3538
adminUserId,
3639
orgId,

apps/webapp/app/models/api-key.server.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { RuntimeEnvironment } from "@trigger.dev/database";
22
import { prisma } from "~/db.server";
33
import { customAlphabet } from "nanoid";
44
import { RuntimeEnvironmentType } from "~/database-types";
5+
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";
56

67
const apiKeyId = customAlphabet(
78
"1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
@@ -87,6 +88,9 @@ export async function regenerateApiKey({ userId, environmentId }: RegenerateAPIK
8788
});
8889
});
8990

91+
// The env's apiKey changed in the control-plane; drop any cached copy.
92+
controlPlaneResolver.invalidateEnvironment(environmentId);
93+
9094
return updatedEnviroment;
9195
}
9296

apps/webapp/app/routes/admin.api.v1.environments.$environmentId.burst-factor.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { type ActionFunctionArgs, json } from "@remix-run/server-runtime";
22
import { z } from "zod";
33
import { prisma } from "~/db.server";
44
import { requireAdminApiRequest } from "~/services/personalAccessToken.server";
5+
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";
56
import { updateEnvConcurrencyLimits } from "~/v3/runQueue.server";
67

78
const ParamsSchema = z.object({
@@ -26,5 +27,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
2627

2728
await updateEnvConcurrencyLimits(environment);
2829

30+
controlPlaneResolver.invalidateEnvironment(environmentId);
31+
2932
return json({ success: true });
3033
}

apps/webapp/app/routes/admin.api.v1.environments.$environmentId.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { json } from "@remix-run/server-runtime";
33
import { z } from "zod";
44
import { prisma } from "~/db.server";
55
import { requireAdminApiRequest } from "~/services/personalAccessToken.server";
6+
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";
67
import { engine } from "~/v3/runEngine.server";
78
import { updateEnvConcurrencyLimits } from "~/v3/runQueue.server";
89

@@ -45,6 +46,10 @@ export async function action({ request, params }: ActionFunctionArgs) {
4546

4647
await updateEnvConcurrencyLimits(environment);
4748

49+
// Org max-concurrency changed too, which is embedded in every env of the org; invalidating
50+
// the org drops the env/authEnv rows for all of them (including this env).
51+
controlPlaneResolver.invalidateOrganization(environment.organizationId);
52+
4853
return json({ success: true });
4954
}
5055

apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.concurrency.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { json } from "@remix-run/server-runtime";
33
import { z } from "zod";
44
import { prisma } from "~/db.server";
55
import { requireAdminApiRequest } from "~/services/personalAccessToken.server";
6+
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";
67
import { updateEnvConcurrencyLimits } from "~/v3/runQueue.server";
78

89
const ParamsSchema = z.object({
@@ -83,5 +84,8 @@ export async function action({ request, params }: ActionFunctionArgs) {
8384
await updateEnvConcurrencyLimits({ ...modifiedEnvironment, organization });
8485
}
8586

87+
// Org + every affected env's concurrency changed; one org invalidation covers them all.
88+
controlPlaneResolver.invalidateOrganization(organizationId);
89+
8690
return json({ success: true });
8791
}

apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.feature-flags.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { json } from "@remix-run/server-runtime";
33
import { z } from "zod";
44
import { prisma } from "~/db.server";
55
import { requireAdminApiRequest } from "~/services/personalAccessToken.server";
6+
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";
67
import { validatePartialFeatureFlags } from "~/v3/featureFlags";
78

89
const ParamsSchema = z.object({
@@ -101,6 +102,9 @@ export async function action({ request, params }: ActionFunctionArgs) {
101102
},
102103
});
103104

105+
// Org feature flags are embedded in every env of the org; drop all its cached env rows.
106+
controlPlaneResolver.invalidateOrganization(organizationId);
107+
104108
const updatedFlagsResult = updatedOrganization.featureFlags
105109
? validatePartialFeatureFlags(updatedOrganization.featureFlags as Record<string, unknown>)
106110
: { success: false as const };

apps/webapp/app/routes/admin.api.v1.orgs.$organizationId.runs.enable.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { EnvironmentPauseSource } from "@trigger.dev/database";
33
import { z } from "zod";
44
import { prisma } from "~/db.server";
55
import { requireAdminApiRequest } from "~/services/personalAccessToken.server";
6+
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";
67
import { PauseEnvironmentService } from "~/v3/services/pauseEnvironment.server";
78

89
const ParamsSchema = z.object({
@@ -43,6 +44,10 @@ export async function action({ request, params }: ActionFunctionArgs) {
4344
return json({ error: "Organization not found" }, { status: 404 });
4445
}
4546

47+
// `runsEnabled` is embedded in every env of the org; drop all its cached env rows. The
48+
// per-env pause writes below invalidate their own envs via PauseEnvironmentService.
49+
controlPlaneResolver.invalidateOrganization(organizationId);
50+
4651
const environments = await prisma.runtimeEnvironment.findMany({
4752
where: {
4853
organizationId,

apps/webapp/app/routes/admin.api.v2.orgs.$organizationId.feature-flags.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Prisma } from "@trigger.dev/database";
44
import { z } from "zod";
55
import { prisma } from "~/db.server";
66
import { requireUser } from "~/services/session.server";
7+
import { controlPlaneResolver } from "~/v3/runOpsMigration/controlPlaneResolver.server";
78
import { flags as getGlobalFlags } from "~/v3/featureFlags.server";
89
import {
910
FEATURE_FLAG,
@@ -132,5 +133,8 @@ export async function action({ request, params }: ActionFunctionArgs) {
132133
throw e;
133134
}
134135

136+
// Org feature flags are embedded in every env of the org; drop all its cached env rows.
137+
controlPlaneResolver.invalidateOrganization(organizationId);
138+
135139
return json({ success: true });
136140
}

0 commit comments

Comments
 (0)