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:
co-authored by
Claude Opus 5
parent
dfb7734674
commit
7485cd86e4
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 396 KiB After Width: | Height: | Size: 392 KiB |
Reference in New Issue
Block a user