authorgravatar for saiansh2525@gmail.comYash Singh <saiansh2525@gmail.com> 2026-10-06 03:58:31-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-06 03:58:31-05:00
log0290c4a04e52ca4e93be7a6fb94c14bcc7b53728
tree2f2bd5b53bb507fc8018eecabca51e662cfb678d
parent76d3c96fd5b89cca2c06aeaceaea16100c75084a
signature Signed by PGP key B5690EEEBB952194

fix(server): avoid scanning completed history for pending secrets (#16409)


2 files changed, 73 insertions(+), 4 deletions(-)

apps/server/src/orchestration-v2/ProjectionStore.test.ts+67
...@@ -2850,6 +2850,73 @@ it.layer(layerTest)("ProjectionStoreV2", (it) => {...@@ -2850,6 +2850,73 @@ it.layer(layerTest)("ProjectionStoreV2", (it) => {
2850 }),2850 }),
2851 );2851 );
28522852
2853 it.effect("selects the latest waiting secret only from active runs", () =>
2854 Effect.gen(function* () {
2855 const projectionStore = yield* ProjectionStore.ProjectionStoreV2;
2856 const sql = yield* SqlClient.SqlClient;
2857 const suffix = "shell-pending-secret";
2858 const threadId = yield* addRolledBackRecoveryCandidate(suffix);
2859 const runId = RunId.make(`run:${suffix}:rolled-back`);
2860 const nodeId = NodeId.make(`node:${suffix}:rolled-back`);
2861 const now = yield* DateTime.now;
2862 const addSecret = (id: string, ordinal: number, status: "waiting" | "completed") =>
2863 projectionStore.apply({
2864 id: EventId.make(`event:${id}`),
2865 type: "turn-item.updated",
2866 threadId,
2867 runId,
2868 nodeId,
2869 driver,
2870 occurredAt: now,
2871 payload: {
2872 id: TurnItemId.make(id),
2873 threadId,
2874 runId,
2875 nodeId,
2876 providerThreadId: null,
2877 providerTurnId: null,
2878 nativeItemRef: null,
2879 parentItemId: null,
2880 ordinal,
2881 status,
2882 title: null,
2883 startedAt: now,
2884 completedAt: null,
2885 updatedAt: now,
2886 type: "secret_request",
2887 label: "Test credential",
2888 reason: "Test pending input",
2889 secretStatus: status === "waiting" ? "pending" : "saved",
2890 },
2891 });
2892 yield* addSecret("secret:a", 2, "waiting");
2893 yield* addSecret("secret:b", 3, "waiting");
2894 yield* addSecret("secret:c", 4, "completed");
2895
2896 for (const status of [
2897 "preparing",
2898 "starting",
2899 "running",
2900 "waiting",
2901 "completed",
2902 "rolled_back",
2903 ]) {
2904 yield* sql`
2905 UPDATE orchestration_v2_projection_runs
2906 SET status = ${status}, payload_json = json_set(payload_json, '$.status', ${status})
2907 WHERE run_id = ${runId}
2908 `;
2909 const shell = yield* projectionStore.getShellSnapshot();
2910 const thread = shell.threads.find((candidate) => candidate.id === threadId);
2911 assert.isDefined(thread);
2912 assert.equal(
2913 thread?.pendingRuntimeRequest?.id ?? null,
2914 status === "completed" || status === "rolled_back" ? null : "secret:b",
2915 );
2916 }
2917 }),
2918 );
2919
2853 it.effect("builds shell snapshots without decoding full turn item payloads", () =>2920 it.effect("builds shell snapshots without decoding full turn item payloads", () =>
2854 Effect.gen(function* () {2921 Effect.gen(function* () {
2855 const projectionStore = yield* ProjectionStore.ProjectionStoreV2;2922 const projectionStore = yield* ProjectionStore.ProjectionStoreV2;
apps/server/src/orchestration-v2/ProjectionStore.ts+6-4
...@@ -4999,12 +4999,14 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =...@@ -4999,12 +4999,14 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> =
4999 ) AS pending_request_payload_json,4999 ) AS pending_request_payload_json,
5000 (5000 (
5001 SELECT secret.payload_json5001 SELECT secret.payload_json
5002 FROM orchestration_v2_projection_turn_items secret5002 -- Keep runs outermost so completed history is never scanned for a pending secret.
5003 FROM orchestration_v2_projection_runs r
5004 CROSS JOIN orchestration_v2_projection_turn_items secret
5003 INDEXED BY orchestration_v2_projection_turn_items_thread_run_idx5005 INDEXED BY orchestration_v2_projection_turn_items_thread_run_idx
5004 INNER JOIN orchestration_v2_projection_runs r ON r.run_id = secret.run_id5006 WHERE r.thread_id = t.thread_id
5005 WHERE secret.thread_id = t.thread_id
5006 AND secret.type = 'secret_request' AND secret.status = 'waiting'
5007 AND r.status IN ('preparing', 'starting', 'running', 'waiting')5007 AND r.status IN ('preparing', 'starting', 'running', 'waiting')
5008 AND secret.thread_id = t.thread_id AND secret.run_id = r.run_id
5009 AND secret.type = 'secret_request' AND secret.status = 'waiting'
5008 ORDER BY secret.updated_at DESC, secret.turn_item_id DESC5010 ORDER BY secret.updated_at DESC, secret.turn_item_id DESC
5009 LIMIT 15011 LIMIT 1
5010 ) AS pending_secret_request_payload_json,5012 ) AS pending_secret_request_payload_json,