fix: hold transfer inputs and raw transfer work to what was verified
The capability vault checked an issuer's registration and then read it again to store it, including its nested header rows. A stateful issuer could show an allowed header set to the forbidden-header check and hand `Authorization` to the copy, so the vault stored — and the executor sent — a credential no rule had ever seen. The registration and everything nested in it is now snapshotted once, and only that snapshot is validated, frozen and stored. The upload control plane had the same shape one level down: a `sessionId` that answered `session_01` to the regex and `../../unsafe` to the result snapshot reached a success receipt. Two lifetimes were also unowned. A download source lease that resolved after the caller's abort never reached the holder, so nothing closed it and its fetch reader and capability lease outlived the terminal result; a compensator sharing the holder's close-once latch now closes it exactly once. And `dispose()` proved quiescence from the wrapper registry alone, so a provider that ignored its attempt deadline let teardown report a drained runtime and close the checkpoint store while the provider was still running. Raw provider promises are now their own registry and the drain must prove both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
aa8ac35600
commit
39a4a973a8
@@ -125,6 +125,13 @@ type RuntimeDependencies<Capability> = Readonly<{
|
||||
random(): number;
|
||||
sleep(delayMs: number, signal: AbortSignal): Promise<void>;
|
||||
observer?: BrowserDataObserver;
|
||||
/**
|
||||
* TR-04. Every raw provider promise, from the moment the collaborator is
|
||||
* called until it actually settles. The wrapper that bounds the attempt can
|
||||
* settle long before the provider does, so the wrapper registry alone could
|
||||
* report an empty set while physical work was still running.
|
||||
*/
|
||||
physicalTasks: Set<Promise<unknown>>;
|
||||
}>;
|
||||
|
||||
type ActiveResolution =
|
||||
@@ -176,7 +183,9 @@ const RECOVERIES: ReadonlySet<string> = new Set([
|
||||
export function createResumableUploadRuntime<Capability>(
|
||||
inputDependencies: ResumableUploadRuntimeDependencies<Capability>,
|
||||
): ResumableUploadRuntime {
|
||||
const dependencies = snapshotDependencies(inputDependencies);
|
||||
/** TR-04. Raw provider work, tracked independently of its bounded wrapper. */
|
||||
const physicalTasks = new Set<Promise<unknown>>();
|
||||
const dependencies = snapshotDependencies(inputDependencies, physicalTasks);
|
||||
const lifetime = new AbortController();
|
||||
const localUploads = new Map<string, Set<AbortController>>();
|
||||
/** BT-UP-06. Terminal settlement of every admitted operation. */
|
||||
@@ -339,10 +348,17 @@ export function createResumableUploadRuntime<Capability>(
|
||||
dependencies.policy.cleanupDeadlineMs,
|
||||
);
|
||||
});
|
||||
const drained = await Promise.race([
|
||||
Promise.allSettled([...activeOperations]).then(() => "DRAINED" as const),
|
||||
expired,
|
||||
]);
|
||||
// TR-04. Quiescence means both registries: the bounded wrappers and the
|
||||
// raw provider work they may have outlived. A settling wrapper can still
|
||||
// register more physical work, so the drain repeats until both are empty
|
||||
// or the cleanup deadline expires.
|
||||
const quiescent = (async () => {
|
||||
while (activeOperations.size > 0 || physicalTasks.size > 0) {
|
||||
await Promise.allSettled([...activeOperations, ...physicalTasks]);
|
||||
}
|
||||
return "DRAINED" as const;
|
||||
})();
|
||||
const drained = await Promise.race([quiescent, expired]);
|
||||
if (timer !== undefined) clearTimeout(timer);
|
||||
if (drained === "EXPIRED") {
|
||||
// The store stays open: something can still write a checkpoint.
|
||||
@@ -1310,8 +1326,20 @@ async function invokeProviderAttempt<Capability, Value>(
|
||||
}, dependencies.policy.providerAttemptTimeoutMs);
|
||||
});
|
||||
try {
|
||||
// TR-04. The raw promise enters the physical registry the moment the
|
||||
// provider is called and stays there until it truly settles. Racing it
|
||||
// against a deadline let the bounded wrapper settle first and leave the
|
||||
// set empty, so `dispose()` reported a drained runtime while the provider
|
||||
// was still running.
|
||||
const raw = action(controller.signal);
|
||||
const tracked = Promise.resolve(raw).then(
|
||||
() => undefined,
|
||||
() => undefined,
|
||||
);
|
||||
dependencies.physicalTasks.add(tracked);
|
||||
void tracked.finally(() => dependencies.physicalTasks.delete(tracked));
|
||||
return await Promise.race([
|
||||
invokeProvider(operation, () => action(controller.signal)),
|
||||
invokeProvider(operation, () => raw),
|
||||
deadline,
|
||||
]);
|
||||
} finally {
|
||||
@@ -1743,6 +1771,7 @@ function snapshotRequest(
|
||||
|
||||
function snapshotDependencies<Capability>(
|
||||
input: ResumableUploadRuntimeDependencies<Capability>,
|
||||
physicalTasks: Set<Promise<unknown>>,
|
||||
): RuntimeDependencies<Capability> {
|
||||
const policy = resolveResumableUploadRuntimePolicy(input.policy);
|
||||
const controlPlane = snapshotControlPlane(input.controlPlane);
|
||||
@@ -1765,6 +1794,7 @@ function snapshotDependencies<Capability>(
|
||||
throw new TypeError("Upload runtime dependency is invalid.");
|
||||
}
|
||||
return Object.freeze({
|
||||
physicalTasks,
|
||||
controlPlane,
|
||||
partExecutor,
|
||||
checkpoints,
|
||||
|
||||
Reference in New Issue
Block a user