feat: replace revision counters with domain state contracts

Use content digests, key fingerprints, binding and trust identities, immutable mutation receipts, and observation tokens instead of state-update ordinals across API, storage, Runtime, and Web contracts.

Preserve historical replay and secret material through frozen migrations; preflight uncertain removals before cutover, resume provisioning receipts, validate encrypted migration candidates, and align fresh/upgraded schemas. Include reviewed regression fixes, generated artifacts, and validation evidence.
This commit is contained in:
2026-10-10 15:55:20 +09:00
parent 72ce817de4
commit c78f7f7d80
358 changed files with 25965 additions and 12069 deletions
+4 -4
View File
@@ -34,19 +34,19 @@ Start `tools/web-ux/drive-fixture/server.ts <port> <build-root>` with `--allow-n
- `*denied*`: typed HTTP 403 root/read; `*offline*`/`home-error`: typed HTTP 503; `*empty*`: empty successful root list; `*paged*`: 240 additional nodes.
- `POST /__fixture/fault` JSON `{workspace, mode}`: one-shot `conflict` or committed `unknown` mutation response; persistent `image_error` binary fetch failure.
- `home-owner`/`home-member`/`home-long`: representative authoritative Worker catalogs; REST and subscription projections come from the same generated-shape records. `home-long` includes long display names and opaque Runtime/Worker IDs. `workers-error` intentionally fails catalog reads with HTTP 503.
- `GET /__fixture/log`: observable API request IDs/methods/paths/node IDs/CAS revisions for identity and no-resend assertions.
- `GET /__fixture/log`: observable API request IDs/methods/paths/node IDs/CAS observed committed requests for identity and no-resend assertions.
- `POST /__fixture/reset`: fresh in-memory state.
Request delay/loss tests intercept the API boundary with Playwright and synchronize on gates, not guessed sleeps. They let the fixture commit before dropping/holding the response, then verify receipt query without blind upload retry.
## Visual evidence
Preserved production baseline: `.svelte-kit/t724/before-build/{site,output,build.log}`. Official bundles live below `.svelte-kit/t724/visual/`. Each contains screenshots, console/request/page diagnostics, accessibility snapshots, screenshot hashes, source revision/dirty context, and `review-context.json`.
Preserved production baseline: `.svelte-kit/t724/before-build/{site,output,build.log}`. Official bundles live below `.svelte-kit/t724/visual/`. Each contains screenshots, console/request/page diagnostics, accessibility snapshots, screenshot hashes, source commit/dirty context, and `review-context.json`.
Home/sidebar and Workers are captured under identical owner/member/error/empty/long conditions, light/dark at 1440/768/390/320. After captures add Drive owner/member/empty/error/denied/paged/Markdown/image/image-error/truncated surfaces, pinned rename/replace revision forms, and owner grant selection/read-only/read-write/revoked states. Expected injected 503/403 diagnostics and superseded breadcrumb metadata aborts remain in official context; `diagnostic-disposition.json` explicitly separates unexpected diagnostics. CLI `completed-with-errors` is not relabeled a visual pass.
Home/sidebar and Workers are captured under identical owner/member/error/empty/long conditions, light/dark at 1440/768/390/320. After captures add Drive owner/member/empty/error/denied/paged/Markdown/image/image-error/truncated surfaces, pinned rename/replace observed committed request forms, and owner grant selection/read-only/read-write/revoked states. Expected injected 503/403 diagnostics and superseded breadcrumb metadata aborts remain in official context; `diagnostic-disposition.json` explicitly separates unexpected diagnostics. CLI `completed-with-errors` is not relabeled a visual pass.
The production shell scrolls `main` internally, so `fullPage` does not expand that scroll panel. Capture points use ordinary keyboard focus to show the actual form or the top denial state; no CSS/DOM screenshot override is applied. The responsive audit separately records document/main/table-region geometry, visible keyboard focus, and page errors across the full theme/viewport matrix. Raster checks wait for actual decoding, assert natural dimensions 1200×800, preserve the 3:2 aspect ratio, and constrain the rendered box by both the available width and 60vh height at all four widths/both themes.
The Drive browser suite also exercises the file-browser redesign at 1440/768/390/320px in light/dark themes: menu placement, native-dialog focus and Escape restoration, folder creation, search, and destination-folder navigation. Screenshots are written to `.svelte-kit/drive-redesign/` for the list, New menu, creation dialog, Markdown preview, and move dialog. Component tests cover failed/paged destination reads, exclusion of files and the moving folder itself, and pinned mutation revisions. These checks use the same fixture boundary, not a live Backend.
The Drive browser suite also exercises the file-browser redesign at 1440/768/390/320px in light/dark themes: menu placement, native-dialog focus and Escape restoration, folder creation, search, and destination-folder navigation. Screenshots are written to `.svelte-kit/drive-redesign/` for the list, New menu, creation dialog, Markdown preview, and move dialog. Component tests cover failed/paged destination reads, exclusion of files and the moving folder itself, and pinned mutation observed committed requests. These checks use the same fixture boundary, not a live Backend.
Parent and reviewer must open images and inspect context before asserting visual acceptance. In particular, a successful capture command is not sufficient and HTTP 200 does not excuse visible UI errors.
@@ -55,31 +55,31 @@ for (const scheme of ["light", "dark"]) {
}
if (stage === "after") {
routes.push({
id: "drive-form-revision",
label: "Pinned rename and replace form revisions",
id: "drive-form-observation",
label: "Pinned rename and replace form observations",
path: "/w/home-owner/drive/3",
goal: "Show the form's locked read revision and explicit destination/file controls.",
goal: "Show the form's locked read observation and explicit destination/file controls.",
dataState:
"Revision 1 read from fixture metadata; no mutation is sent by these capture interactions.",
"Initial committed request read from fixture metadata; no mutation is sent by these capture interactions.",
ready: { kind: "selector", selector: '.drive-page[data-drive-ready="true"]' },
capturePoints: [
{
id: "rename",
label: "Rename / move pinned revision",
label: "Rename / move pinned observation",
fullPage: true,
interaction: [
{ action: "click", selector: '.drive-menu > summary[aria-label="More actions"]' },
{ action: "click", selector: '.drive-toolbar button:has-text("Rename / move")' },
{
action: "wait",
ready: { kind: "selector", selector: '.drive-form:has-text("Expected revision 1.")' },
ready: { kind: "selector", selector: '.drive-form:has-text("Changes by another writer will not be overwritten.")' },
},
{ action: "press", selector: ".drive-form input:not([inputmode])", key: "Shift" },
],
},
{
id: "replace",
label: "Replace file pinned revision",
label: "Replace file pinned observation",
fullPage: true,
interaction: [
{ action: "click", selector: '.drive-form button:has-text("Cancel form")' },
@@ -87,7 +87,7 @@ for (const scheme of ["light", "dark"]) {
{ action: "click", selector: '.drive-toolbar button:has-text("Replace file")' },
{
action: "wait",
ready: { kind: "selector", selector: '.drive-form:has-text("Expected revision 1.")' },
ready: { kind: "selector", selector: '.drive-form:has-text("Changes by another writer will not be overwritten.")' },
},
{ action: "press", selector: ".drive-form input[type=file]", key: "Shift" },
],
@@ -367,7 +367,7 @@ for (const scheme of ["light", "dark"]) {
if (
[
"drive-markdown",
"drive-form-revision",
"drive-form-observation",
"drive-image",
"drive-image-error",
"drive-truncated",
@@ -10,7 +10,7 @@ const ready = (page: Page) =>
const button = (page: Page, name: string) =>
page.getByRole("button", { name, exact: true });
const draft = (page: Page) =>
page.getByRole("textbox", { name: /^Draft \(expected revision/ });
page.getByRole("textbox", { name: /^Draft$/ });
const preview = (page: Page) =>
page.getByRole("article", { name: "Current saved content" });
function gate() {
@@ -135,7 +135,7 @@ async function safeFulfill(
} catch { /* selection intentionally canceled this old request */ }
}
Deno.test("production Drive two tabs retain losing CAS draft and never retry with a newer revision", () =>
Deno.test("production Drive two tabs retain losing CAS draft and never retry with a newer observation", () =>
harness(async (origin, browser) => {
const context = await browser.newContext();
const first = await context.newPage(), second = await context.newPage();
@@ -151,7 +151,7 @@ Deno.test("production Drive two tabs retain losing CAS draft and never retry wit
await first.getByText("Published — DB commit confirmed", { exact: false })
.last().waitFor();
await button(second, "Save").click();
await second.getByText("Revision conflict — your draft is retained.", {
await second.getByText("Content changed — your draft is retained.", {
exact: true,
})
.waitFor();
@@ -167,7 +167,7 @@ Deno.test("production Drive two tabs retain losing CAS draft and never retry wit
"Second actor unsaved draft",
);
assert(
(await second.getByText("Draft (expected revision 1)", { exact: false })
(await second.getByText("Draft", { exact: false })
.count()) > 0,
);
assert(
@@ -187,7 +187,7 @@ Deno.test("production Drive two tabs retain losing CAS draft and never retry wit
const casWrites = (await logs(origin)).filter((x) =>
x.path === "/mutate"
);
assertEquals(casWrites.map((x) => [x.nodeId, x.expectedRevision]), [[
assertEquals(casWrites.map((x) => [x.nodeId, x.expectedMutationId]), [[
"3",
"1",
], ["3", "1"]]);
@@ -229,7 +229,7 @@ Deno.test("production Drive delayed old read cannot replace another file or work
.last().waitFor();
const mutation = (await logs(origin)).filter((x) => x.path === "/mutate");
assertEquals(
mutation.map((x) => [x.workspace, x.nodeId, x.expectedRevision]),
mutation.map((x) => [x.workspace, x.nodeId, x.expectedMutationId]),
[[
"home-owner",
"4",
@@ -330,12 +330,14 @@ Deno.test("production Drive delayed old save and status preserve destination dra
const pinnedWrites = (await logs(origin)).filter((x) =>
x.path === "/mutate"
);
assertEquals(pinnedWrites.map((x) => [x.nodeId, x.expectedRevision]), [
assertEquals(pinnedWrites.map((x) => [x.nodeId, x.expectedMutationId]), [
["3", "1"],
["4", "1"],
["3", "2"],
["4", "2"],
["3", pinnedWrites[0].requestId],
["4", pinnedWrites[1].requestId],
]);
assert(pinnedWrites.every((x) => typeof x.requestId === "string" && x.requestId.length > 0));
assertEquals(new Set(pinnedWrites.map((x) => x.requestId)).size, 4);
} finally {
release.release();
await handled;
@@ -47,13 +47,13 @@ Deno.test("browser fixture preserves decimal identity, bounded reads, binary and
assertEquals(escaped.text, "\u0000".repeat(65536));
assertEquals(escaped.truncated, false);
});
Deno.test("browser fixture two revisions conflict without committing the losing request", async () => {
Deno.test("browser fixture two last_mutation_ids conflict without committing the losing request", async () => {
const { client } = setup();
const entry = await client.metadata("home-owner", ref("home-owner", "3"));
await client.mutate("home-owner", "tab-one", {
operation: "update_text",
id: entry.entry,
expected_revision: entry.revision,
expected_mutation_id: entry.last_mutation_id,
text: "first actor",
content_type: "text/markdown",
});
@@ -62,7 +62,7 @@ Deno.test("browser fixture two revisions conflict without committing the losing
client.mutate("home-owner", "tab-two", {
operation: "update_text",
id: entry.entry,
expected_revision: entry.revision,
expected_mutation_id: entry.last_mutation_id,
text: "second actor draft",
content_type: "text/markdown",
}),
@@ -113,7 +113,7 @@ Deno.test("browser fixture rename and move retain identity while delete and recr
const moved = await client.mutate("home-owner", "move", {
operation: "relocate",
id: entry.entry,
expected_revision: entry.revision,
expected_mutation_id: entry.last_mutation_id,
parent: ref("home-owner", "2"),
name: "renamed.md",
});
@@ -122,7 +122,7 @@ Deno.test("browser fixture rename and move retain identity while delete and recr
await client.mutate("home-owner", "delete", {
operation: "delete",
id: entry.entry,
expected_revision: entry.revision,
expected_mutation_id: entry.last_mutation_id,
});
const replacement = await client.mutate("home-owner", "recreate", {
operation: "create_text",