Skip to content

Commit b490afe

Browse files
committed
fix(webapp): cancel API findResource must return non-null for buffered runs
The route builder treats a null `findResource` result as a 404 BEFORE the action handler runs (`apiBuilder.server.ts:321`). My C1 commit had `findResource: async () => null`, which meant every cancel call — including for valid PG-row runs — was 404'd by the builder before the mutateWithFallback flow could resolve anything. Fixes by mirroring the Phase A discriminated-union pattern: findResource checks PG first, falls back to the buffer with env+org auth, returns `null` only when neither store has the run. The action handler still uses mutateWithFallback (slightly redundant lookup) so the wait-and- bounce path stays intact. Found while running the Phase F challenge suite — cancel was 404'ing on a confirmed-buffered runId.
1 parent 469dd3a commit b490afe

1 file changed

Lines changed: 29 additions & 6 deletions

File tree

apps/webapp/app/routes/api.v2.runs.$runParam.cancel.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import { json } from "@remix-run/server-runtime";
22
import { z } from "zod";
3+
import { $replica } from "~/db.server";
34
import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
45
import { getRequestAbortSignal } from "~/services/httpAsyncStorage.server";
56
import { CancelTaskRunService } from "~/v3/services/cancelTaskRun.server";
67
import { mutateWithFallback } from "~/v3/mollifier/mutateWithFallback.server";
8+
import { getMollifierBuffer } from "~/v3/mollifier/mollifierBuffer.server";
79

810
const ParamsSchema = z.object({
911
runParam: z.string(),
1012
});
1113

14+
type ResolvedCancelTarget =
15+
| { source: "pg"; friendlyId: string }
16+
| { source: "buffer"; friendlyId: string };
17+
1218
const { action } = createActionApiRoute(
1319
{
1420
params: ParamsSchema,
@@ -18,12 +24,29 @@ const { action } = createActionApiRoute(
1824
action: "write",
1925
resource: (params) => ({ type: "runs", id: params.runParam }),
2026
},
21-
// PG-side authorisation is performed inside mutateWithFallback. Routing
22-
// the resource through findResource (which would require a PG-or-buffer
23-
// resolved discriminated union here) would duplicate the resolution
24-
// mutateWithFallback already does, so we pass `null` to signal "open"
25-
// and let the helper do the lookup atomically with the mutation.
26-
findResource: async () => null,
27+
// Mirror the Phase A read-fallback discriminated-union pattern. The
28+
// route builder 404s if findResource returns null
29+
// (`apiBuilder.server.ts:321`), so we must check both stores here.
30+
// The action then re-resolves via mutateWithFallback (PG-first →
31+
// buffer patch → wait-and-bounce) — slightly redundant lookup but
32+
// keeps the helper's atomicity intact.
33+
findResource: async (params, auth): Promise<ResolvedCancelTarget | null> => {
34+
const pgRun = await $replica.taskRun.findFirst({
35+
where: { friendlyId: params.runParam, runtimeEnvironmentId: auth.environment.id },
36+
select: { friendlyId: true },
37+
});
38+
if (pgRun) return { source: "pg", friendlyId: pgRun.friendlyId };
39+
const buffer = getMollifierBuffer();
40+
const entry = buffer ? await buffer.getEntry(params.runParam) : null;
41+
if (
42+
entry &&
43+
entry.envId === auth.environment.id &&
44+
entry.orgId === auth.environment.organizationId
45+
) {
46+
return { source: "buffer", friendlyId: params.runParam };
47+
}
48+
return null;
49+
},
2750
},
2851
async ({ params, authentication }) => {
2952
const runId = params.runParam;

0 commit comments

Comments
 (0)