12714c7527
improvise-1wft and the five beads it blocked were one failure: nothing
carried a boot failure's REASON from the worker to the page. The channel
is one rule -- a protocol frame crosses the port as a STRING, a shell
notice as an OBJECT -- so it needs no protocol change and the
discriminator is total.
A boot failure is a STATE, not a rejected promise. A document another
context holds the exclusive handle to now OPENS read-only: measured first
(harness probe, Chromium + Firefox), getFile() reads the bytes while the
handle is held, with the second-handle refusal asserted so the probe
cannot pass vacuously.
- improvise-1wft the channel: Notice/NoticeKind, boot_notice, take_notice
- improvise-i0v9 read-only boot over vfs::ReadOnly + OpfsFs::read_unattached
- improvise-86nw start() rejecting for any reason now posts BootFailed
- improvise-9cma boot::document_name -- and a/b/c.improv was ACCEPTED as
c.improv, not rejected, which is worse than the report
- improvise-iobc vfs::Origin: a seed error is a notice only when the
document really had to be created without it
- improvise-vep8 the watchdog arms BEFORE transport.ready; its message
stopped claiming the connection was accepted
- improvise-7gip DedicatedWorkerGlobalScope (needs a web-sys feature)
- improvise-zre1 a persist that fails raises a notice; the trigger is the
dirty flag surviving, observed rather than predicted
- improvise-7gf8 the render breaker trips once instead of retrying forever
- improvise-ociy non-OPEN sends are loud; a viewport announcement is not
- improvise-y01k the three ws-server drops are logged
- improvise-r4nf serde failures throw instead of reading as "nothing to send"
Every notice routes through shell.fail, so gotcha 48's disarm half is not
re-broken. Two comments in shell.spec.js stated the opposite of the new
arming order and were corrected rather than left.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
100 lines
3.8 KiB
JavaScript
100 lines
3.8 KiB
JavaScript
// `storage_contract` against `OpfsFs`, in a browser (improvise-vvl3/xf1). The
|
|
// assertions here are about *the report*: context/test-evidence/clients.md.
|
|
|
|
import { test, expect } from "@playwright/test";
|
|
|
|
/** Run one entry point in the harness worker. */
|
|
async function run(page, cmd) {
|
|
return JSON.parse(await page.evaluate((c) => window.opfs(c), cmd));
|
|
}
|
|
|
|
/** The checks the report must contain, in order. Named here as well as in Rust
|
|
* on purpose — see context/test-evidence/clients.md. */
|
|
const EXPECTED = [
|
|
"open_and_refresh_see_an_origin_root_holding_no_documents",
|
|
"an_attached_but_unwritten_entry_is_not_a_document",
|
|
"a_directory_entry_is_not_a_document",
|
|
"refresh_walks_every_key_and_sorts_them",
|
|
"read_and_write_are_absolute_at_offset_zero",
|
|
'storage_contract_at_""',
|
|
'storage_contract_at_"docs"',
|
|
"a_delete_is_visible_at_once_and_lands_on_commit",
|
|
];
|
|
|
|
test.describe("OpfsFs in a real browser", () => {
|
|
test.beforeEach(async ({ page, browserName }, testInfo) => {
|
|
await page.goto("/opfs/index.html");
|
|
|
|
// The skip is conditional on the PROBE, not on the browser name, so it
|
|
// cannot quietly swallow a real regression. See clients.md.
|
|
const opfs = await page.evaluate(async () => {
|
|
try {
|
|
await navigator.storage.getDirectory();
|
|
return "yes";
|
|
} catch (err) {
|
|
return String(err?.name ?? err);
|
|
}
|
|
});
|
|
if (opfs !== "yes") {
|
|
expect(
|
|
browserName,
|
|
`${browserName} lost OPFS (${opfs}) — that is a regression, not a known gap`,
|
|
).toBe("webkit");
|
|
testInfo.skip(true, `this WebKit build provides no OPFS (${opfs})`);
|
|
}
|
|
});
|
|
|
|
// Check the checker, and do it first: a green run against a worker that
|
|
// never started is the failure this repo has a doctrine about.
|
|
test("the harness can see a failure", async ({ page }) => {
|
|
const report = await run(page, "run_canary");
|
|
expect(report).toHaveLength(1);
|
|
expect(report[0].name).toBe("the_canary_must_fail");
|
|
expect(report[0].ok).toBe(false);
|
|
expect(report[0].detail).toContain("the harness can see a failure");
|
|
});
|
|
|
|
test("every check in the battery runs and passes", async ({ page }) => {
|
|
const report = await run(page, "run_checks");
|
|
|
|
// The battery is complete before any of it is believed.
|
|
expect(report.map((c) => c.name)).toEqual(EXPECTED);
|
|
|
|
const failed = report.filter((c) => !c.ok);
|
|
expect(
|
|
failed,
|
|
`failing checks:\n${failed.map((c) => ` ${c.name}: ${c.detail}`).join("\n")}`,
|
|
).toEqual([]);
|
|
});
|
|
|
|
// Measurements, not contracts: the read-only boot path (improvise-1wft,
|
|
// i0v9) is decided on these, so they run per engine and say which.
|
|
test("the probes the read-only boot design rests on", async ({ page }, testInfo) => {
|
|
const report = await run(page, "run_probes");
|
|
expect(report.map((c) => c.name)).toEqual([
|
|
"getfile_reads_a_document_under_a_foreign_sync_handle",
|
|
"a_dedicated_worker_satisfies_the_narrow_scope_guard",
|
|
]);
|
|
const failed = report.filter((c) => !c.ok);
|
|
expect(
|
|
failed,
|
|
`failing probes:\n${failed.map((c) => ` ${c.name}: ${c.detail}`).join("\n")}`,
|
|
).toEqual([]);
|
|
// The measurement is the point, so it is attached rather than only asserted.
|
|
await testInfo.attach("opfs-probes.json", {
|
|
body: JSON.stringify(report, null, 2),
|
|
contentType: "application/json",
|
|
});
|
|
});
|
|
|
|
// The one thing no in-page check can establish: "commit_deletes actually
|
|
// removed it" is only answered by a fresh page.
|
|
test("what OPFS held survives a reload, and a committed delete does not", async ({
|
|
page,
|
|
}) => {
|
|
expect(await page.evaluate(() => window.opfs("before_reload"))).toBe("staged");
|
|
await page.reload();
|
|
expect(await page.evaluate(() => window.opfs("after_reload"))).toBe("survived");
|
|
});
|
|
});
|