fix: make workspace policy waits selection-safe

This commit is contained in:
2026-08-04 05:55:49 +02:00
parent cddd906719
commit b3ff39fc26
4 changed files with 181 additions and 32 deletions
+58 -25
View File
@@ -9,41 +9,74 @@ export interface WorkspaceDraft {
updatedAt: string;
}
let pendingPolicy: { workspaceId: string; promise: Promise<void>; resolve: () => void } | undefined;
let failedPolicy: { workspaceId: string; error: Error } | undefined;
type PolicySelection = {
workspaceId: string;
generation: number;
state: "pending" | "ready" | "error";
error?: Error;
settled: Promise<void>;
settle: () => void;
superseded: Promise<void>;
supersede: () => void;
};
/** In-memory handoff between the footer's policy query and all session entry points. */
let selection: PolicySelection | undefined;
let nextGeneration = 0;
function pendingSelection(workspaceId: string): PolicySelection {
let settle!: () => void;
let supersede!: () => void;
return {
workspaceId,
generation: ++nextGeneration,
state: "pending",
settled: new Promise<void>((resolve) => { settle = resolve; }),
settle,
superseded: new Promise<void>((resolve) => { supersede = resolve; }),
supersede,
};
}
/** In-memory coordination between the footer's selected policy and session creation. */
export const workspacePolicyGate = {
begin(workspaceId: string): void {
if (pendingPolicy?.workspaceId === workspaceId) return;
if (failedPolicy?.workspaceId === workspaceId) failedPolicy = undefined;
let resolve!: () => void;
const promise = new Promise<void>((onResolve) => {
resolve = onResolve;
});
pendingPolicy = { workspaceId, promise, resolve };
select(workspaceId: string): void {
if (selection?.workspaceId === workspaceId) return;
selection?.supersede();
selection = pendingSelection(workspaceId);
},
clear(): void {
selection?.supersede();
selection = undefined;
},
resolve(workspaceId: string): void {
if (pendingPolicy?.workspaceId === workspaceId) {
pendingPolicy.resolve();
pendingPolicy = undefined;
}
if (failedPolicy?.workspaceId === workspaceId) failedPolicy = undefined;
if (!selection || selection.workspaceId !== workspaceId || selection.state !== "pending") return;
selection.state = "ready";
selection.settle();
},
reject(workspaceId: string): void {
if (pendingPolicy?.workspaceId !== workspaceId) return;
pendingPolicy.resolve();
pendingPolicy = undefined;
failedPolicy = { workspaceId, error: new Error("Selected workspace policy could not be loaded.") };
if (!selection || selection.workspaceId !== workspaceId || selection.state !== "pending") return;
selection.state = "error";
selection.error = new Error("Selected workspace policy could not be loaded.");
selection.settle();
},
waitFor(workspaceId: string | undefined): Promise<void> {
const failed = failedPolicy;
if (failed && failed.workspaceId === workspaceId) return Promise.reject(failed.error);
const pending = pendingPolicy;
return pending && pending.workspaceId === workspaceId ? pending.promise : Promise.resolve();
async waitForCurrent<T extends { workspaceId?: string }>(read: () => T): Promise<T> {
while (true) {
const current = read();
const pending = selection;
if (!current.workspaceId || pending?.workspaceId !== current.workspaceId) return current;
const generation = pending.generation;
if (pending.state === "ready") return read();
if (pending.state === "error") throw pending.error;
await Promise.race([pending.settled, pending.superseded]);
const completed = selection;
if (!completed || completed.generation !== generation || read().workspaceId !== current.workspaceId) continue;
if (completed.state === "error") throw completed.error;
if (completed.state === "ready") return read();
}
},
};