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
+1 -1
View File
@@ -6,7 +6,7 @@ The ordinary Rust test exercises loopback HTTP signed Worker ingress: explicit r
The explicit process test starts a loopback Axum Server and launches Deno against it. `roundtrip.ts` imports the production `src/lib/workspace/drive/api.ts` adapter, not a copy or mock. Its typed `fetchFn` resolves relative paths only to the bound loopback origin and adds fixture bearer authorization. Tokens travel over stdin, not command-line arguments or logs; Rust failure diagnostics defensively redact both fixture bearer tokens. Deno has network permission only for the exact loopback port, uses no npm dependencies, and runs with `--cached-only`. Each listener is abort-on-drop, child processes are kill-on-drop, and the child has a 60-second upper timeout, without sleeps or readiness polling.
Coverage: Worker file list/read, Japanese folder/Markdown names, create/read/edit/download, empty files, actual PNG upload/authenticated image/download and binary update, publication receipt lookup, paged list, two authenticated accounts racing a common revision (one commit/one typed conflict, no hidden retry), stable latest URL through rename and move, deleted URL and same-name recreation, and unauthorized latest URLs. The exact Workers-page `drive-grants/api.ts` adapter also lists persisted grants, creates both access levels, revokes them and re-reads durable state, and rejects member administration. Its global browser fetch is temporarily origin/auth-bound in this isolated child; network responses are never mocked. This tests real authenticated HTTP with test bearer credentials, not a Passkey/cookie browser login or a full Runtime process. UI dirty-draft retention, delayed response handling, visual review and browser cancellation belong to the **separate browser fixture**, not this API test.
Coverage: Worker file list/read, Japanese folder/Markdown names, create/read/edit/download, empty files, actual PNG upload/authenticated image/download and binary update, publication receipt lookup, paged list, two authenticated accounts racing a common observed committed request (one commit/one typed conflict, no hidden retry), stable latest URL through rename and move, deleted URL and same-name recreation, and unauthorized latest URLs. The exact Workers-page `drive-grants/api.ts` adapter also lists persisted grants, creates both access levels, revokes them and re-reads durable state, and rejects member administration. Its global browser fetch is temporarily origin/auth-bound in this isolated child; network responses are never mocked. This tests real authenticated HTTP with test bearer credentials, not a Passkey/cookie browser login or a full Runtime process. UI dirty-draft retention, delayed response handling, visual review and browser cancellation belong to the **separate browser fixture**, not this API test.
## Registration
+13 -13
View File
@@ -93,9 +93,9 @@ async function download(
entry: DriveEntry,
): Promise<Uint8Array> {
const response = await fetchFn(
driveDownloadUrl(ws, entry.entry, entry.revision),
driveDownloadUrl(ws, entry.entry, entry.last_mutation_id),
);
assert(response.status === 200, "download must succeed at exact revision");
assert(response.status === 200, "download must succeed at exact last_mutation_id");
equal(
response.headers.get("x-content-type-options"),
"nosniff",
@@ -203,7 +203,7 @@ equal(
const updated = await saved(owner, ws, "web-edit", {
operation: "update_text",
id: markdown.entry,
expected_revision: markdown.revision,
expected_mutation_id: markdown.last_mutation_id,
text: "# 更新\n",
content_type: "text/markdown",
});
@@ -274,7 +274,7 @@ const binaryUpdated = await owner.upload(
{
operation: "update",
id: empty.entry,
expected_revision: empty.revision,
expected_mutation_id: empty.last_mutation_id,
content_type: "image/png",
},
);
@@ -313,12 +313,12 @@ equal(
"paged traversal must contain each file once",
);
// Two authenticated accounts read the same revision, then race real HTTP writes.
// Two authenticated accounts read the same last_mutation_id, then race real HTTP writes.
const secondRead = await second.readText(ws, updated.entry);
equal(
secondRead.entry.revision,
updated.revision,
"second actor must observe same revision",
secondRead.entry.last_mutation_id,
updated.last_mutation_id,
"second actor must observe same last_mutation_id",
);
const drafts = ["# actor A draft\n", "# actor B draft\n"];
const race = await Promise.allSettled(
@@ -326,7 +326,7 @@ const race = await Promise.allSettled(
saved(client, ws, `web-race-${index}`, {
operation: "update_text",
id: updated.entry,
expected_revision: updated.revision,
expected_mutation_id: updated.last_mutation_id,
text: drafts[index],
content_type: "text/markdown",
})
@@ -335,7 +335,7 @@ const race = await Promise.allSettled(
equal(
race.filter((result) => result.status === "fulfilled").length,
1,
"same-revision two-actor CAS must have exactly one winner",
"same-last_mutation_id two-actor CAS must have exactly one winner",
);
const loser = race.find((result) => result.status === "rejected");
assert(
@@ -358,14 +358,14 @@ const stableUrl = markdown.latest_url;
const renamed = await saved(owner, ws, "web-rename", {
operation: "relocate",
id: latest.entry.entry,
expected_revision: latest.entry.revision,
expected_mutation_id: latest.entry.last_mutation_id,
parent: folder.entry,
name: "改名.md",
});
const moved = await saved(owner, ws, "web-move", {
operation: "relocate",
id: renamed.entry,
expected_revision: renamed.revision,
expected_mutation_id: renamed.last_mutation_id,
parent: root.entry,
name: renamed.name,
});
@@ -397,7 +397,7 @@ await rejected(
const deleted = await owner.mutate(ws, "web-delete", {
operation: "delete",
id: moved.entry,
expected_revision: moved.revision,
expected_mutation_id: moved.last_mutation_id,
});
equal(deleted.entry, null, "delete must remove live metadata");
await rejected(() => owner.readText(ws, moved.entry), "not_found");