fix: let the browser, visual and performance evidence describe the product again

Four browser-capability specs never reached the code they were named for. The
`PRESIGNED_TRANSFER_V1` envelope gained a top-level `protocol` field, and the
fixtures kept answering without it, so every capability was refused before any
object request was made: the download and part-upload success paths were
asserting against an empty transcript rather than exercising a real GET or PUT.
The fixtures now speak the protocol they claim to, and the part-deletion
expectation carries the physical effect the adapter reports.

A refused capability document also answered `recovery: NONE`, telling the
caller there was nothing to be done. The design record fixes this class of
refusal as re-issuable and the vault already answers `REISSUE_CAPABILITY` for
it, so the HTTP decoder disagreed with both. It now agrees.

Lab performance produced no evidence at all. Playwright matches accessible
names by substring, so the navigation entry "플랫폼 구성" also matched the home
page's "플랫폼 구성 보기" call to action; the locator resolved to two links and
the run died on a strict-mode violation before the first measurement. With an
exact match the metrics are collected, and they show the named-interaction
budget is missed on this machine — a real signal that was previously invisible.

The platform overview baseline was captured before the reference routes moved
from `integration-defined` to `session-required` and was never regenerated, so
the only visual gate that could catch a regression on that page was failing for
its own staleness. Regenerated after confirming the diff is exactly that label.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-15 16:49:48 +09:00
co-authored by Claude Opus 5
parent dfb7734674
commit 7485cd86e4
6 changed files with 68 additions and 3 deletions
+7 -1
View File
@@ -90,7 +90,13 @@ try {
throw new Error("Performance route must be present in navigation.");
}
const interactionStarted = performance.now();
await page.getByRole("link", { name: targetLabel }).click();
// Playwright matches accessible names by substring, so the navigation entry
// "플랫폼 구성" also matched the home page's "플랫폼 구성 보기" call to
// action and the locator resolved to two links. That is a strict-mode
// violation before the first measurement is taken, so no lab performance
// evidence could be produced at all — the run failed for an ambiguous
// selector rather than for anything about performance.
await page.getByRole("link", { name: targetLabel, exact: true }).click();
await page.getByRole("heading", { name: target.title }).waitFor();
const namedInteractionMs = performance.now() - interactionStarted;
const paint = await page.evaluate(
@@ -671,7 +671,14 @@ function validateCapabilityPayload(
}),
);
} catch {
return browserDataFailure("POLICY_REJECTED", "PRESIGNED_TRANSFER");
// BT-PRE-04. A capability document this adapter refuses is not a dead end
// for the caller: the only way forward is to ask the issuer for a new one.
// `NONE` said the opposite — that nothing could be done — and disagreed
// with both the design record for an unsupported protocol and the vault,
// which already answers `REISSUE_CAPABILITY` for the same class of refusal.
return browserDataFailure("POLICY_REJECTED", "PRESIGNED_TRANSFER", {
recovery: "REISSUE_CAPABILITY",
});
}
}
@@ -32,6 +32,10 @@ test("issues an opaque capability and streams a verified object into the writabl
status: 200,
contentType: "application/json",
body: JSON.stringify({
// The wire envelope names the protocol it speaks. Without it the
// capability is refused before any object request is made, so the whole
// download path below was asserting on an empty transcript.
protocol: "PRESIGNED_TRANSFER_V1",
capabilityReceipt: "browser-download-capability-1",
method: "GET",
binding: {
@@ -307,6 +311,11 @@ test("issues an opaque capability and streams a verified object into the writabl
expect(bffRequests).toEqual([
{
// The capability request names the protocol it is asking for. Leaving it
// out of this expectation meant the fixture stopped describing the
// request the adapter actually sends when PRESIGNED_TRANSFER_V1 was
// hardened, and the object GET path stopped being exercised at all.
protocol: "PRESIGNED_TRANSFER_V1",
method: "GET",
binding: {
kind: "DOWNLOAD",
@@ -129,6 +129,10 @@ test("uploads three presigned parts through native IndexedDB, Web Locks and fetc
const objectPath =
`/uploads/${SESSION_ID}/parts/${String(partNumber)}`;
await fulfillJson(route, 200, {
// The part capability travels in a presigned envelope, so it names the
// transfer protocol even though its binding names the upload one.
// Without it every part was refused before any object PUT was made.
protocol: "PRESIGNED_TRANSFER_V1",
capabilityReceipt: `browser-part-capability-${String(partNumber)}`,
method: "PUT",
binding,
@@ -483,9 +487,12 @@ test("uploads three presigned parts through native IndexedDB, Web Locks and fetc
},
});
expect(result.checkpoint).toEqual({ ok: true, value: null });
// The deletion reports the physical effect it observed, not only the state it
// reached, so a caller can tell a delete that happened from one that found
// nothing to do. The fixture asserts it rather than ignoring it.
expect(result.deletion).toEqual({
ok: true,
value: { state: "DELETED" },
value: { state: "DELETED", effect: "APPLIED" },
});
expect(
uploadedBodies
+36
View File
@@ -242,6 +242,42 @@ describe("presigned transfer", () => {
);
});
it("answers a refused capability envelope with a re-issuable recovery", async () => {
// BT-PRE-04. A capability document the adapter will not accept is closed as
// `POLICY_REJECTED`, and the caller's only way forward is a new capability.
// `NONE` said there was nothing to be done, which contradicted both the
// design record for an unsupported protocol and the vault, which already
// answers `REISSUE_CAPABILITY` for the same class of refusal.
for (const [label, overrides] of [
["unknown protocol", { protocol: "PRESIGNED_TRANSFER_V2" }],
["missing protocol", { protocol: undefined }],
] as const) {
const bytes = new Uint8Array([1, 2, 3]);
const payload: Record<string, unknown> = {
...downloadCapabilityPayload(bytes),
...overrides,
};
if (overrides.protocol === undefined) delete payload["protocol"];
const fetcher = vi.fn(async () => jsonResponse(payload)) as unknown as typeof fetch;
const { provider } = createHarness({ fetcher });
expect(
await provider.issueDownload({
resourceId: "resource-1",
signal: new AbortController().signal,
}),
label,
).toMatchObject({
ok: false,
error: {
code: "POLICY_REJECTED",
retryable: false,
recovery: "REISSUE_CAPABILITY",
},
});
}
});
it("keeps URL and headers adapter-private and streams bounded verified chunks", async () => {
const bytes = new Uint8Array([1, 2, 3, 4, 5]);
const payload = downloadCapabilityPayload(bytes);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 KiB

After

Width:  |  Height:  |  Size: 392 KiB