fix: bound browser transfer leases and effect reporting

BT-X-01: add the shared abortable-operation utility with golden tests for first
terminal owner, idempotent close, listener and timer cleanup under a throwing
scheduler, observed late rejection and late-handle compensation. It carries no
subsystem result taxonomy.

BT-PRE-01: make the presigned download lease lazy and single-start. open() now
validates, claims and consumes the capability without any network I/O; the
fetch, the transfer deadline and the expiry recheck happen at first stream
consumption. The source gained close(), which discards an unused lease with no
I/O and otherwise cancels the body and releases the scope exactly once.

BT-UP-01: require removeEventListener in the AbortSignal structural guard and
isolate release cleanup so a hostile facade cannot replace a typed terminal
result with a rejection.

BT-UP-03: deleteDatabase cannot be cancelled after dispatch, so a blocked
deadline now returns PENDING with effect UNKNOWN instead of a failure that reads
as NOT_APPLIED. A realm-scoped (factory, databaseName) registry prevents
recreating the partition until the native request settles.

BT-UP-04: reject non-finite and negative upload clocks as a dependency failure
instead of letting them bypass every capability expiry comparison.

BT-IMG-02: replace the naive Cache-Control quote stripping with a quote- and
escape-aware tokenizer, so max-age="60 or 60" is no longer read as 60 and a
comma inside a quoted extension is not a directive boundary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-14 00:13:54 +09:00
co-authored by Claude Opus 5
parent 8f67974f68
commit cc4e875c2d
13 changed files with 831 additions and 134 deletions
@@ -154,41 +154,34 @@ export function createPresignedTransferExecutor(
const consumed = consume(capability);
if (!consumed.ok) return consumed;
const scope = createAbortScope(signal, timeoutMs, scheduler);
try {
const response = await fetcher(binding.href, {
method: "GET",
headers: headersFor(binding),
credentials: "omit",
redirect: "error",
referrerPolicy: "no-referrer",
cache: "no-store",
signal: scope.signal,
});
const validated = validateDownloadResponse(
response,
binding,
);
if (!validated.ok) {
cancelBody(response);
scope.release();
return validated;
}
const source = createDownloadSource({
response,
binding,
capability,
externalSignal: signal,
scope,
hardMaxChunkBytes,
createVerifier,
observer,
});
return browserDataSuccess(source);
} catch {
scope.release();
return transferFailure(signal, scope.timedOut());
}
// BT-PRE-01. The lease is lazy and single-start: `open()` performs no
// network I/O, so the transfer deadline begins at first consumption and an
// unused source can be discarded through `close()` without leaking a body,
// a timer or a listener.
const source = createDownloadSource({
start: async (scope) =>
await fetcher(binding.href, {
method: "GET",
headers: headersFor(binding),
credentials: "omit",
redirect: "error",
referrerPolicy: "no-referrer",
cache: "no-store",
signal: scope.signal,
}),
validateResponse: (response) =>
validateDownloadResponse(response, binding),
createScope: () => createAbortScope(signal, timeoutMs, scheduler),
recheckExpiry: () =>
validateExpiry(capability, minimumRemainingLifetimeMs, now()),
binding,
capability,
externalSignal: signal,
hardMaxChunkBytes,
createVerifier,
observer,
});
return browserDataSuccess(source);
}
async function putUploadPart(
@@ -406,29 +399,53 @@ export function createPresignedTransferExecutor(
}
function createDownloadSource(input: Readonly<{
response: Response;
start: (
scope: ReturnType<typeof createAbortScope>,
) => Promise<Response>;
validateResponse: (response: Response) => BrowserDataResult<unknown>;
createScope: () => ReturnType<typeof createAbortScope>;
recheckExpiry: () => BrowserDataResult<unknown>;
binding: PresignedCapabilityBinding;
capability: PresignedDownloadCapability;
externalSignal: AbortSignal;
scope: ReturnType<typeof createAbortScope>;
hardMaxChunkBytes: number;
createVerifier: (
expectedSha256: string,
) => StreamingSha256Verifier;
observer: BrowserDataObserver | undefined;
}>): PresignedDownloadByteSource {
let started = false;
/** BT-PRE-01. One state machine shared by `stream()` and `close()`. */
let state: "READY" | "STREAMING" | "CLOSED" = "READY";
let activeScope: ReturnType<typeof createAbortScope> | undefined;
let activeResponse: Response | undefined;
const releaseActive = () => {
if (activeResponse) {
cancelBody(activeResponse);
activeResponse = undefined;
}
if (activeScope) {
activeScope.release();
activeScope = undefined;
}
};
return Object.freeze({
byteLength: input.capability.byteLength,
capability: input.capability,
integrity: "VERIFIED_ON_SUCCESSFUL_EXHAUSTION" as const,
close() {
// READY -> CLOSED performs no I/O; STREAMING -> CLOSED cancels once.
if (state === "CLOSED") return;
state = "CLOSED";
releaseActive();
},
async *stream(
consumerSignal: AbortSignal,
): AsyncIterable<BrowserDataResult<Uint8Array>> {
if (!isAbortSignal(consumerSignal)) {
started = true;
cancelBody(input.response);
input.scope.release();
state = "CLOSED";
releaseActive();
const failure = browserDataFailure(
"INVALID_INPUT",
"PRESIGNED_TRANSFER",
@@ -437,7 +454,7 @@ function createDownloadSource(input: Readonly<{
yield failure;
return;
}
if (started) {
if (state !== "READY") {
const failure = browserDataFailure(
"CONFLICT",
"PRESIGNED_TRANSFER",
@@ -449,7 +466,45 @@ function createDownloadSource(input: Readonly<{
yield failure;
return;
}
started = true;
state = "STREAMING";
// The capability may have expired while the lease sat unused.
const stillActive = input.recheckExpiry();
if (!stillActive.ok) {
state = "CLOSED";
observeTransferResult(
input.observer,
"DOWNLOAD",
stillActive,
0,
);
yield stillActive as BrowserDataResult<never>;
return;
}
const scope = input.createScope();
activeScope = scope;
let response: Response;
try {
response = await input.start(scope);
} catch {
state = "CLOSED";
releaseActive();
const failure = transferFailure(
input.externalSignal,
scope.timedOut(),
);
observeTransferResult(input.observer, "DOWNLOAD", failure, 0);
yield failure;
return;
}
activeResponse = response;
const validated = input.validateResponse(response);
if (!validated.ok) {
state = "CLOSED";
releaseActive();
observeTransferResult(input.observer, "DOWNLOAD", validated, 0);
yield validated as BrowserDataResult<never>;
return;
}
let combined:
| ReturnType<typeof combineConsumerAbort>
| undefined;
@@ -471,13 +526,13 @@ function createDownloadSource(input: Readonly<{
};
try {
combined = combineConsumerAbort(
input.scope,
scope,
consumerSignal,
);
const verifier = input.createVerifier(
input.capability.expectedSha256,
);
if (!input.response.body) {
if (!response.body) {
let verified = false;
try {
verified =
@@ -492,7 +547,7 @@ function createDownloadSource(input: Readonly<{
yield fail("INTEGRITY_FAILED");
return;
}
reader = input.response.body.getReader();
reader = response.body.getReader();
while (true) {
if (
input.externalSignal.aborted ||
@@ -501,7 +556,7 @@ function createDownloadSource(input: Readonly<{
yield fail("ABORTED");
return;
}
if (input.scope.timedOut()) {
if (scope.timedOut()) {
yield fail("UNAVAILABLE", {
retryable: true,
recovery: "REISSUE_CAPABILITY",
@@ -510,7 +565,7 @@ function createDownloadSource(input: Readonly<{
}
const result = await readWithSignal(
reader,
input.scope.signal,
scope.signal,
);
if (result.done) break;
const chunk = result.value;
@@ -544,7 +599,7 @@ function createDownloadSource(input: Readonly<{
yield fail("ABORTED");
return;
}
if (input.scope.timedOut()) {
if (scope.timedOut()) {
yield fail("UNAVAILABLE", {
retryable: true,
recovery: "REISSUE_CAPABILITY",
@@ -590,7 +645,7 @@ function createDownloadSource(input: Readonly<{
consumerSignal.aborted
) {
yield fail("ABORTED");
} else if (input.scope.timedOut()) {
} else if (scope.timedOut()) {
yield fail("UNAVAILABLE", {
retryable: true,
recovery: "REISSUE_CAPABILITY",
@@ -605,14 +660,18 @@ function createDownloadSource(input: Readonly<{
combined?.release();
if (!completed) {
if (reader) cancelReader(reader);
else cancelBody(input.response);
else cancelBody(response);
}
try {
reader?.releaseLock();
} catch {
// Reader cleanup cannot change stream success or failure.
}
input.scope.release();
// The lease is terminal once its single stream ends; cleanup runs once.
state = "CLOSED";
activeResponse = undefined;
activeScope = undefined;
scope.release();
observeBrowserData(input.observer, {
operation: "DOWNLOAD",
outcome: completed