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
@@ -11,7 +11,7 @@ README documents the tool; running the tool is not itself visual review completi
The workbench produces a **review context bundle** rather than treating a screenshot as evidence by
itself. Every capture records the persona, route, viewport, theme, intended user goal, expected data
state, sanitized document URL/status, console/page/request failures, screenshot hashes, an
accessibility snapshot, source revision, and browser version.
accessibility snapshot, source Git commit SHA, and browser version.
## Environment
@@ -164,6 +164,9 @@ Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
if (url.pathname === `/api/w/${workspaceId}/working-directories`) {
return json({ workspace_id: workspaceId, items: [], diagnostics: [] });
}
if (url.pathname === `/api/w/${workspaceId}/workers`) {
return json({ workspace_id: workspaceId, limit: 200, items: [worker], source: "fixture", diagnostics: [] });
}
if (
url.pathname === `/api/w/${workspaceId}/workers/${workerResourceKey}` ||
url.pathname === `/api/w/${workspaceId}/runtimes/${runtimeId}/workers/${workerId}`
@@ -191,7 +194,7 @@ Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
identity: { session_id: "session-a", segment_id: "segment-a", entry_count: 36 },
snapshot: {
pending_submissions: {
revision: 0,
notification_count: hasNotifications ? 2 : 0,
...(hasNotifications && pendingMode !== "legacy" ? { notification_previews: ["First notification", "Long notification — " + "notification text ".repeat(30)] } : {}),
head_id: submissions[0]?.submission_id ?? null,
@@ -501,12 +501,16 @@ async function checkConsoleHistory(viewportHeight: number): Promise<void> {
});
assert(centering <= 1, `turn marker is not vertically centered (${centering}px)`);
assertEquals(await transcript.getByText("searched 1 time・ran 1 command", { exact: true }).count(), 12);
await page.getByRole("button", { name: "Normal", exact: true }).click();
const overview = page.getByRole("switch", { name: "Overview", exact: true });
assertEquals(await overview.getAttribute("aria-checked"), "true");
await overview.click();
assertEquals(await overview.getAttribute("aria-checked"), "false");
const rowIds = await transcript.locator("[data-console-line-id]").evaluateAll((rows) =>
rows.map((row) => row.getAttribute("data-console-line-id")));
assertEquals(rowIds.length, 48);
assertEquals(new Set(rowIds).size, rowIds.length);
await page.getByRole("button", { name: "Overview", exact: true }).click();
await overview.click();
assertEquals(await overview.getAttribute("aria-checked"), "true");
assertEquals(await transcript.getByText("searched 1 time・ran 1 command", { exact: true }).count(), 12);
const sidebar = page.locator(".sidebar-frame");
const settleSidebar = () => sidebar.evaluate(async (element) => {
@@ -99,7 +99,7 @@ const entries = [
},
];
const session = {
pending_submissions: { revision: 0, notification_count: 0, head_id: null, submissions: [] },
pending_submissions: { notification_count: 0, head_id: null, submissions: [] },
entries,
};
const state = { last_command_id: 0, state: { kind: "idle" } };
@@ -250,6 +250,9 @@ Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
if (url.pathname === `/api/w/${workspaceId}/working-directories`) {
return json({ workspace_id: workspaceId, items: [], diagnostics: [] });
}
if (url.pathname === `/api/w/${workspaceId}/workers`) {
return json({ workspace_id: workspaceId, limit: 200, items: [worker], source: "fixture", diagnostics: [] });
}
if (url.pathname === `/api/w/${workspaceId}/workers/W-901` || url.pathname === workerPath) {
return json(worker);
}
@@ -82,18 +82,23 @@ Deno.test("production live Console completes, edits/removes typed drafts and sta
await observePicker(page);
page.setDefaultTimeout(5000);
const errors: string[] = [];
const supersededCatalogReads: string[] = [];
const acknowledgedDeletes = new Set<string>();
page.on("console", (message) => {
if (message.type() === "error") errors.push(message.text());
});
page.on("pageerror", (error) => errors.push(String(error)));
page.on(
"requestfailed",
(request) =>
errors.push(
`failed ${request.method()} ${request.url()} ${request.failure()?.errorText}`,
),
);
page.on("requestfailed", (request) => {
// Subscription snapshots deliberately supersede the bootstrap GET.
if (
request.url() === `${base}/api/w/console-live-review/workers` &&
request.method() === "GET" &&
request.failure()?.errorText === "net::ERR_ABORTED"
) supersededCatalogReads.push(request.url());
else errors.push(
`failed ${request.method()} ${request.url()} ${request.failure()?.errorText}`,
);
});
page.on("response", (response) => {
if (response.request().method() === "DELETE" && response.status() === 204) {
acknowledgedDeletes.add(response.url());
@@ -248,6 +253,7 @@ Deno.test("production live Console completes, edits/removes typed drafts and sta
theme,
width,
errors: recordedErrors,
supersededCatalogReads,
acknowledgedDeleteDiagnostics,
acknowledgedDeleteResponseStatus: releasedResponse.status(),
picker: pickerEvidence,
@@ -54,15 +54,33 @@ Deno.test("Console pending scopes are independent and match Tasks row typography
try {
const page = await context.newPage();
const errors: string[] = [];
const supersededCatalogReads: string[] = [];
page.on("pageerror", (error) => errors.push(String(error)));
page.on("console", (message) => {
if (message.type() === "error") errors.push(message.text());
});
page.on("requestfailed", (request) => errors.push(request.url()));
page.on("requestfailed", (request) => {
// The subscription snapshot supersedes the bootstrap catalog GET.
// Keep this intentional AbortController cancellation separate from
// transport failures; each mode still awaits a completed catalog GET.
if (
request.url() === `${url}/api/w/console-history-review/workers` &&
request.method() === "GET" &&
request.failure()?.errorText === "net::ERR_ABORTED"
) supersededCatalogReads.push(request.url());
else errors.push(`${request.url()}: ${request.failure()?.errorText}`);
});
for (const mode of ["both", "queue", "notifications", "legacy", "empty"]) {
const catalogFinished = page.waitForEvent("requestfinished", {
predicate: (request) =>
request.url() === `${url}/api/w/console-history-review/workers` &&
request.method() === "GET",
timeout: 10_000,
});
await page.goto(
`${url}/w/console-history-review/workers/W-900-console-fixture/console?pending=${mode}`,
);
await catalogFinished;
await page.locator(".task-mini-row").waitFor();
const metadata = page.getByLabel("Worker model and context", { exact: true });
const statusGeometry = await metadata.evaluate((status) => {
@@ -257,7 +275,12 @@ Deno.test("Console pending scopes are independent and match Tasks row typography
await page.getByText("2 pending · Preview unavailable", { exact: true }).waitFor();
}
}
assertEquals(await page.locator(".workspace-alert").count(), 0);
assertEquals(errors, []);
console.log(JSON.stringify({
colorScheme, width, supersededCatalogReads,
unexpectedErrors: errors,
}));
} finally {
await context.close();
}
@@ -26,7 +26,7 @@ const subjectPageCursor = "fixture-subject-page-2";
const emptySubjectPageCursor = "fixture-empty-subject-page";
const errorSubjectPageCursor = "fixture-error-subject-page";
const memoryPageCursor = "fixture-memory-page-2";
const revisionPageCursor = "fixture-revision-page-2";
const changePageCursor = "fixture-change-page-2";
const createdSubjectIds = new Set<string>();
const subjectCreateRequests: unknown[] = [];
let createdSubjectCount = 0;
@@ -51,7 +51,7 @@ const surfaceBodyMd =
const detailBodyMd = `# Committed Memory detail
This immutable revision preserves its candidate evidence, source refs, and derivation refs.
This immutable change preserves its candidate evidence, source refs, and derivation refs.
| Local table overflow | Value |
| --- | --- |
@@ -70,18 +70,17 @@ const subjects = [
representativeSubjectId,
"Release coordination",
"active",
42,
"fingerprint-current",
"Prefer explicit evidence over assumptions.\nAsk before irreversible actions, and state uncertainty plainly.",
3,
connectedWorker("Release coordination Worker with a deliberately long display name"),
),
subject(emptySubjectId, "Empty ready subject", "active", 0),
subject(staleSubjectId, "Stale surface subject", "active", 19),
subject(failedSubjectId, "Failed surface subject", "active", 8),
subject(ungeneratedSubjectId, "Ungenerated subject", "active", 0),
subject(errorSubjectId, "Unavailable subject", "retired", 3),
subject(emptySubjectId, "Empty ready subject", "active", "fingerprint-0"),
subject(staleSubjectId, "Stale surface subject", "active", "fingerprint-19"),
subject(failedSubjectId, "Failed surface subject", "active", "fingerprint-8"),
subject(ungeneratedSubjectId, "Ungenerated subject", "active", "fingerprint-0"),
subject(errorSubjectId, "Unavailable subject", "retired", "fingerprint-3"),
];
const pagedSubject = subject(pagedSubjectId, "Subject on the next page", "active", 1);
const pagedSubject = subject(pagedSubjectId, "Subject on the next page", "active", "fingerprint-1");
function connectedWorker(displayName: string) {
return {
@@ -111,18 +110,16 @@ function subject(
id: string,
role: string,
state: "active" | "retired",
storeRevision: number,
memoryFingerprint: string,
behaviorMd = "",
behaviorRevision = 0,
currentWorker?: ReturnType<typeof connectedWorker>,
) {
return {
id,
role,
behavior_md: behaviorMd,
behavior_revision: behaviorRevision,
state,
store_revision: storeRevision,
memory_fingerprint: memoryFingerprint,
created_at: "2026-01-01T00:00:00Z",
updated_at: "2026-01-02T03:04:05Z",
...(currentWorker ? { current_worker: currentWorker } : {}),
@@ -134,14 +131,14 @@ function memoriesFor(subjectId: string) {
if (subjectId === emptySubjectId || subjectId === ungeneratedSubjectId) return [];
if (subjectId === staleSubjectId) {
return [
memorySummary("stale-memory-1", 2, "lesson", "resolved", "A resolved stale-surface lesson"),
memorySummary("stale-memory-1", "change-earlier", "lesson", "resolved", "A resolved stale-surface lesson"),
];
}
if (subjectId === failedSubjectId) {
return [
memorySummary(
"failed-memory-1",
1,
"change-original",
"constraint",
"retracted",
"A retracted failed-surface constraint",
@@ -151,21 +148,21 @@ function memoriesFor(subjectId: string) {
return [
memorySummary(
representativeMemoryId,
3,
"change-current",
"decision",
"active",
"Keep subject Memory provenance typed and visible",
),
memorySummary(
"memory-resolved-0002",
2,
"change-earlier",
"lesson",
"resolved",
"The bounded reader must preserve continuations",
),
memorySummary(
"memory-retracted-0003",
1,
"change-original",
"working_assumption",
"retracted",
"Memory list end marker for scroll validation",
@@ -175,18 +172,18 @@ function memoriesFor(subjectId: string) {
function memorySummary(
id: string,
revision: number,
changeId: string,
kind: string,
state: string,
claim: string,
) {
return {
id,
revision,
change_id: changeId,
kind,
state,
claim,
excerpt: `Read-only ${state} Memory with exact revision and provenance metadata.`,
excerpt: `Read-only ${state} Memory with exact change ID and provenance metadata.`,
updated_at: "2026-01-02T03:04:05Z",
};
}
@@ -200,10 +197,10 @@ function surfaceFor(subjectId: string) {
snapshot_id: "surface-snapshot-0042",
body_md: surfaceBodyMd,
memory_refs: [
{ memory_id: representativeMemoryId, revision: 3 },
{ memory_id: derivedMemoryId, revision: 4 },
{ memory_id: representativeMemoryId, change_id: "change-current" },
{ memory_id: derivedMemoryId, change_id: "change-parent" },
],
built_from_store_revision: 42,
built_from_memory_fingerprint: "fingerprint-current",
created_at: "2026-01-02T03:04:05Z",
},
};
@@ -216,7 +213,7 @@ function surfaceFor(subjectId: string) {
snapshot_id: "surface-snapshot-empty",
body_md: "",
memory_refs: [],
built_from_store_revision: 0,
built_from_memory_fingerprint: "fingerprint-0",
created_at: "2026-01-02T03:04:05Z",
},
};
@@ -226,27 +223,28 @@ function surfaceFor(subjectId: string) {
return { subject_id: subjectId, availability: "ungenerated" };
}
function memoryDetail(memoryId: string, requestedRevision: number | null) {
const revision = requestedRevision && requestedRevision <= 3 ? requestedRevision : 3;
const states = { 1: "retracted", 2: "resolved", 3: "active" } as const;
function memoryDetail(memoryId: string, requestedChangeId: string | null) {
const currentChangeId = memoryId === derivedMemoryId ? "change-parent" : "change-current";
const changeId = requestedChangeId ?? currentChangeId;
const states: Record<string, string> = { "change-original": "retracted", "change-earlier": "resolved", "change-current": "active", "change-parent": "active" };
return {
memory_id: memoryId,
revision,
current_revision: 3,
change_id: changeId,
current_change_id: currentChangeId,
kind: "decision",
state: states[revision as keyof typeof states],
claim: revision === 3
state: states[changeId],
claim: changeId === "change-current"
? "Keep subject Memory provenance typed and visible"
: `Historical provenance decision, revision ${revision}`,
: `Historical provenance decision, change ${changeId}`,
body_md: detailBodyMd,
why_useful:
"Future readers can audit why a committed Memory exists without exposing unbounded session content.",
staleness: null,
change_reason: revision === 3
change_reason: changeId === "change-current"
? "Clarified the durable read contract."
: "Immutable historical revision.",
: "Immutable historical change.",
created_at: "2026-01-01T00:00:00Z",
updated_at: `2026-01-0${revision}T03:04:05Z`,
updated_at: "2026-01-02T03:04:05Z",
body_offset: 0,
body_byte_offset: 0,
body_truncated: false,
@@ -264,7 +262,6 @@ function memoryDetail(memoryId: string, requestedRevision: number | null) {
worker_id: "worker-fixture",
flow_selector: "builtin:coder-review",
flow_definition_id: "flow-definition-fixture",
flow_definition_revision: 7,
},
excerpt: "Use normal explicit subject routes and preserve provenance.",
summary: "The product contract was explicitly requested.",
@@ -279,20 +276,20 @@ function memoryDetail(memoryId: string, requestedRevision: number | null) {
origin: { kind: "human_input", account_id: "account-fixture" },
evidence_kind: "message",
label: "Memory information design source",
summary: "Bounded source reference retained with the committed revision.",
summary: "Bounded source reference retained with the committed change.",
}],
source_refs_total: 1,
source_refs_truncated: false,
}],
derived_from: [{ memory_id: derivedMemoryId, revision: 4 }],
derived_from: [{ memory_id: derivedMemoryId, change_id: "change-parent" }],
evidence_has_more: false,
};
}
function revisions(memoryId: string, cursor: string | null) {
function changes(memoryId: string, cursor: string | null) {
const all = [
{
revision: 3,
change_id: "change-current",
kind: "decision",
state: "active",
claim: "Keep subject Memory provenance typed and visible",
@@ -300,28 +297,28 @@ function revisions(memoryId: string, cursor: string | null) {
updated_at: "2026-01-03T03:04:05Z",
},
{
revision: 2,
change_id: "change-earlier",
kind: "decision",
state: "resolved",
claim: "Historical provenance decision, revision 2",
claim: "Historical provenance decision, change change-earlier",
change_reason: "Resolved after endpoint integration.",
updated_at: "2026-01-02T03:04:05Z",
},
{
revision: 1,
change_id: "change-original",
kind: "decision",
state: "retracted",
claim: "Historical provenance decision, revision 1",
claim: "Historical provenance decision, change change-original",
change_reason: "Retracted the legacy shape.",
updated_at: "2026-01-01T03:04:05Z",
},
];
const continued = cursor === revisionPageCursor;
const continued = cursor === changePageCursor;
return {
memory_id: memoryId,
current_revision: 3,
current_change_id: "change-current",
items: continued ? all.slice(2) : all.slice(0, 2),
...(continued ? {} : { next_cursor: revisionPageCursor }),
...(continued ? {} : { next_cursor: changePageCursor }),
has_more: !continued,
};
}
@@ -410,6 +407,9 @@ Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
if (url.pathname === `/api/w/${workspaceId}/working-directories`) {
return json({ workspace_id: workspaceId, items: [], diagnostics: [] });
}
if (url.pathname === `/api/w/${workspaceId}/workers`) {
return json({ workspace_id: workspaceId, limit: 200, items: [], source: "fixture", diagnostics: [] });
}
if (url.pathname === `/api/w/${workspaceId}/workers/launch-options`) {
return json({
workspace_id: workspaceId,
@@ -462,7 +462,7 @@ Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
}
createdSubjectCount += 1;
const id = `created-subject-${String(createdSubjectCount).padStart(4, "0")}`;
const created = subject(id, payload.role, "active", 0, payload.behavior_md);
const created = subject(id, payload.role, "active", "fingerprint-empty", payload.behavior_md);
createdSubjectIds.add(id);
subjects.unshift(created);
return json(created, 201);
@@ -493,21 +493,20 @@ Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
if (parts.length === 2 && parts[1] === "behavior" && request.method === "PATCH") {
const payload = await request.json().catch(() => null) as {
expected_behavior_revision?: unknown;
expected_behavior_md?: unknown;
behavior_md?: unknown;
} | null;
if (
!payload ||
typeof payload.expected_behavior_revision !== "number" ||
typeof payload.expected_behavior_md !== "string" ||
typeof payload.behavior_md !== "string"
) {
return json({ error: "Bad Request", message: "invalid behavior update" }, 400);
}
if (payload.expected_behavior_revision !== foundSubject.behavior_revision) {
return json({ error: "Conflict", message: "subject behavior revision conflict" }, 409);
if (payload.expected_behavior_md !== foundSubject.behavior_md) {
return json({ error: "Conflict", message: "subject behavior content conflict" }, 409);
}
foundSubject.behavior_md = payload.behavior_md;
foundSubject.behavior_revision += 1;
foundSubject.updated_at = "2026-01-03T04:05:06Z";
return json(foundSubject);
}
@@ -518,7 +517,7 @@ Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
const paginated = subjectId === representativeSubjectId;
const continued = paginated && url.searchParams.get("cursor") === memoryPageCursor;
const items = continued
? [memorySummary("memory-page-two", 1, "lesson", "active", "Memory on the next page")]
? [memorySummary("memory-page-two", "change-original", "lesson", "active", "Memory on the next page")]
: memoriesFor(subjectId);
return json({
items,
@@ -528,12 +527,14 @@ Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
}
if (parts.length >= 3 && parts[1] === "memories") {
const memoryId = parts[2];
if (parts.length === 4 && parts[3] === "revisions") {
return json(revisions(memoryId, url.searchParams.get("cursor")));
if (parts.length === 4 && parts[3] === "changes") {
return json(changes(memoryId, url.searchParams.get("cursor")));
}
if (parts.length === 3) {
const requested = url.searchParams.get("revision");
return json(memoryDetail(memoryId, requested === null ? null : Number(requested)));
const requested = url.searchParams.get("change_id");
const knownChanges = memoryId === derivedMemoryId ? ["change-parent"] : ["change-original", "change-earlier", "change-current"];
if (requested !== null && !knownChanges.includes(requested)) return json({}, 404);
return json(memoryDetail(memoryId, requested));
}
}
}
@@ -323,18 +323,18 @@ Deno.test("production subject Memory and Worker launch shells preserve exact sco
assertEquals(await detailPage.getByText("candidate-release-decision-0001").count(), 1);
assertEquals(await detailPage.getByText("Memory information design source").count(), 1);
assertEquals(await detailPage.getByText("memory-derived-source-0002").count(), 1);
assertEquals(await detailPage.getByRole("heading", { name: "Revision history" }).count(), 1);
assertEquals(await detailPage.getByText("Memory revision 3", { exact: true }).count(), 1);
assertEquals(await detailPage.getByText("Memory revision 2", { exact: true }).count(), 1);
assertEquals(await detailPage.getByText("Memory revision 1", { exact: true }).count(), 0);
await detailPage.getByRole("navigation", { name: "Revision history pages" }).getByRole(
assertEquals(await detailPage.getByRole("heading", { name: "Change history" }).count(), 1);
assertEquals(await detailPage.getByText("Memory change change-current", { exact: true }).count(), 1);
assertEquals(await detailPage.getByText("Memory change change-earlier", { exact: true }).count(), 1);
assertEquals(await detailPage.getByText("Memory change change-original", { exact: true }).count(), 0);
await detailPage.getByRole("navigation", { name: "Change history pages" }).getByRole(
"link",
{ name: "Next page →" },
).click();
await detailPage.getByText("Memory revision 1", { exact: true }).waitFor();
assert(new URL(detailPage.url()).searchParams.has("revision_cursor"));
await detailPage.getByText("Memory change change-original", { exact: true }).waitFor();
assert(new URL(detailPage.url()).searchParams.has("change_cursor"));
assertEquals(
await detailPage.getByRole("navigation", { name: "Revision history pages" }).getByRole(
await detailPage.getByRole("navigation", { name: "Change history pages" }).getByRole(
"link",
{ name: "First page" },
).count(),
@@ -497,7 +497,7 @@ Deno.test("production subject Memory and Worker launch shells preserve exact sco
path: join(createInteractionArtifacts, "behavior-edit-desktop-light.png"),
});
await createPage.getByRole("button", { name: "Save behavior" }).click();
await createPage.getByText(/Behavior storage confirmed at revision 1/).waitFor();
await createPage.getByText(/Behavior storage confirmed/).waitFor();
assert(
(await createPage.getByRole("status").filter({
hasText: "This page does not confirm application",
@@ -1,3 +1,4 @@
import { createHash } from "node:crypto";
// Synthetic API fixture serving the production static UI; no Worker/Git execution.
import { extname, join, normalize } from "jsr:@std/path@1.1.4";
import {
@@ -11,6 +12,11 @@ const root = Deno.args[1];
const legacy = Deno.args.includes("--legacy");
const tickets = new Map<string, TicketDetail>();
const bodies: unknown[] = [];
function contentDigest(value: TicketDetail): string {
return createHash("sha256").update("ticket.content.sha256.v1\0").update(JSON.stringify([
value.id, value.title, value.body, value.targets.map(({ repository_key, ref_selector, access }) => ({ repository_key, ref_selector, access })),
])).digest("hex");
}
function ticket(key: string) {
if (!tickets.has(key)) {
const value: TicketDetail = fixtureDetail(key);
@@ -49,7 +55,9 @@ function ticket(key: string) {
}
tickets.set(key, value);
}
const value = structuredClone(tickets.get(key)!);
const stored = tickets.get(key)!;
stored.content_digest = contentDigest(stored);
const value = structuredClone(stored);
if (legacy) {
const wire = value as unknown as Record<string, unknown>;
wire.current_coder = wire.current_worker;
@@ -114,7 +122,6 @@ Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
kind: "git",
provider: "git",
source: { kind: "local_path", uri: "/fixture/main" },
source_revision: 1,
source_fingerprint: "fixture",
observed_status: "ready",
record_authority: "fixture",
@@ -252,8 +259,11 @@ Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
});
}
const stored = tickets.get(key) ?? (ticket(key), tickets.get(key)!);
if (body.expected_item_revision && body.expected_item_revision !== stored.item_revision) {
return Response.json({ message: "Ticket revision conflict; refresh" }, { status: 409 });
if (body.expected_content_digest && body.expected_content_digest !== stored.content_digest) {
return Response.json({ message: "Ticket content conflict; refresh" }, { status: 409 });
}
if ((tm[2] === "/state" || tm[2] === "/close") && body.expected_state !== stored.state) {
return Response.json({ message: "Ticket state conflict; refresh" }, { status: 409 });
}
if (tm[2] === "/state") {
stored.state = body.state;
@@ -289,7 +299,7 @@ Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
if (body.title) stored.title = body.title;
if (body.body) stored.body = body.body;
}
stored.item_revision += "x";
stored.content_digest = contentDigest(stored);
stored.event_count = stored.events.length;
}
return Response.json(ticket(key));
@@ -166,7 +166,7 @@ Deno.test("Ticket progress, comment results and reopen work without Git while re
state: "planning",
reason: "Fixture reset",
expected_state: detail.state,
expected_item_revision: detail.item_revision,
expected_content_digest: detail.content_digest,
},
});
}
@@ -41,7 +41,6 @@ export function workdirFixture() {
workspace_id: workspace,
runtime_id: "fixture-runtime",
generated_at: "2026-01-01T00:00:00Z",
revision: "plan-1",
digest: "digest-1",
workers: [],
diagnostics: [],
@@ -113,7 +113,7 @@ Deno.test("ordinary removal refreshes failed and successful inventory, supports
assertEquals(await page.locator("thead th").allTextContents(), [
"Workdir",
"Repository",
"Revision",
"Checkout",
"Status",
"Cleanliness",
"Occupied by",
@@ -101,7 +101,16 @@ Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
diagnostics: [],
});
}
// Keyboard focus can preload the Tickets route before navigation.
// Keyboard focus can preload the Tickets and Workers routes before navigation.
if (path === "/workers") {
return json({
workspace_id: workspaceId,
items: [],
diagnostics: [],
limit: 100,
source: "fixture",
});
}
if (path === "/orchestrator") {
return json({
workspace_id: workspaceId,
@@ -228,7 +228,7 @@ Deno.test("production Workspace Settings separates read/edit, discloses details,
const reset = await context.request.put(`${url}/api/w/home-long/settings`, {
data: {
display_name: metadataFixture("home-long").display_name,
revision: current.revision,
expected_updated_at: current.updated_at,
},
});
assertEquals(reset.status(), 200);
+16 -19
View File
@@ -21,7 +21,7 @@ export type FixtureLog = {
method: string;
requestId: string | null;
nodeId: string | null;
expectedRevision: string | null;
expectedMutationId: string | null;
};
const encoder = new TextEncoder();
const rootId = "9007199254740993";
@@ -68,7 +68,7 @@ export function createDriveFixture() {
parent: parent === null ? null : ref(parent),
name,
kind,
revision: "1",
last_mutation_id: "1",
size: kind === "file" ? bytes.length : null,
content_type: contentType,
updated_by: "fixture-worker",
@@ -140,16 +140,13 @@ export function createDriveFixture() {
state.next = id.includes("paged") ? 300n : 100n;
return state;
}
function bump(node: Node) {
node.entry.revision = String(BigInt(node.entry.revision) + 1n);
}
function mutate(id: string, mutation: DriveMutation): DriveEntry | null {
function mutate(id: string, requestId: string, mutation: DriveMutation): DriveEntry | null {
const state = ws(id);
const target = "id" in mutation ? state.nodes.get(mutation.id.node_id) : null;
if ("id" in mutation && (mutation.id.workspace_id !== id || !target)) throw "not_found";
if (
target && "expected_revision" in mutation &&
target.entry.revision !== mutation.expected_revision
target && "expected_mutation_id" in mutation &&
target.entry.last_mutation_id !== mutation.expected_mutation_id
) throw "conflict";
if ("parent" in mutation) {
const parent = state.nodes.get(mutation.parent.node_id);
@@ -177,14 +174,14 @@ export function createDriveFixture() {
if (mutation.operation === "relocate") {
target!.entry.parent = mutation.parent;
target!.entry.name = mutation.name;
bump(target!);
target!.entry.last_mutation_id = requestId;
return target!.entry;
}
if (mutation.operation === "update_text") {
target!.bytes = encoder.encode(mutation.text);
target!.entry.size = target!.bytes.length;
target!.entry.content_type = mutation.content_type;
bump(target!);
target!.entry.last_mutation_id = requestId;
return target!.entry;
}
const nodeId = String(state.next++);
@@ -196,7 +193,7 @@ export function createDriveFixture() {
parent: mutation.parent,
name: mutation.name,
kind: mutation.operation === "create_folder" ? "folder" : "file",
revision: "1",
last_mutation_id: requestId,
size: mutation.operation === "create_folder" ? null : bytes.length,
content_type: mutation.operation === "create_text" ? mutation.content_type : null,
updated_by: "fixture-user",
@@ -233,7 +230,7 @@ export function createDriveFixture() {
method: request.method,
requestId: url.searchParams.get("request_id"),
nodeId: url.searchParams.get("id") ?? url.searchParams.get("parent_id"),
expectedRevision: url.searchParams.get("expected_revision"),
expectedMutationId: url.searchParams.get("expected_mutation_id"),
};
log.push(event);
if (id.includes("denied")) return failure("denied");
@@ -287,7 +284,7 @@ export function createDriveFixture() {
event.requestId = requestId;
if (payload) {
event.nodeId = payload.mutation.id?.node_id ?? payload.mutation.parent?.node_id ?? null;
event.expectedRevision = payload.mutation.expected_revision ?? null;
event.expectedMutationId = payload.mutation.expected_mutation_id ?? null;
}
if (state.receipts.has(requestId)) return Response.json(state.receipts.get(requestId));
const fault = faults.get(id);
@@ -296,7 +293,7 @@ export function createDriveFixture() {
return failure("conflict");
}
let entry: DriveEntry | null;
if (path === "/mutate") entry = mutate(id, payload.mutation);
if (path === "/mutate") entry = mutate(id, requestId, payload.mutation);
else {
const bytes = new Uint8Array(await request.arrayBuffer());
if (bytes.length > 16 * 1024 * 1024) return failure("limit");
@@ -308,17 +305,17 @@ export function createDriveFixture() {
) return failure("invalid");
const content_type = url.searchParams.get("content_type")!;
if (url.searchParams.get("operation") === "create") {
entry = mutate(id, {
entry = mutate(id, requestId, {
operation: "create_text",
parent: { workspace_id: id, node_id: url.searchParams.get("parent_id")! },
name: url.searchParams.get("name")!,
text: "",
content_type,
});
} else {entry = mutate(id, {
} else {entry = mutate(id, requestId, {
operation: "update_text",
id: { workspace_id: id, node_id: url.searchParams.get("id")! },
expected_revision: url.searchParams.get("expected_revision")!,
expected_mutation_id: url.searchParams.get("expected_mutation_id")!,
text: "",
content_type,
});}
@@ -370,8 +367,8 @@ export function createDriveFixture() {
}
if (path === "/download" || path === "/read-chunk") {
if (
url.searchParams.has("expected_revision") &&
url.searchParams.get("expected_revision") !== node.entry.revision
url.searchParams.has("expected_mutation_id") &&
url.searchParams.get("expected_mutation_id") !== node.entry.last_mutation_id
) return failure("conflict");
if (faults.get(id) === "image_error" && node.entry.content_type?.startsWith("image/")) {
return failure("storage_unavailable");
-1
View File
@@ -67,7 +67,6 @@ export function cleanupPlan(workspaceId: string, runtimeId: string): RuntimeClea
diagnostics: [],
digest: "fixture-cleanup",
generated_at: "2026-01-02T00:00:00Z",
revision: "1",
workers: [],
workdirs: [],
};
@@ -166,7 +166,6 @@ try {
workspace_id: "space",
generated_at: "2026-10-09",
runtime_id: "runtime-a",
revision: "plan-1",
digest: "digest-1",
diagnostics: [],
workdirs: [],
@@ -154,11 +154,11 @@
"id": "detail",
"label": "Committed Memory detail",
"path": "/w/memory-review/memory/release-coordination-subject-with-a-deliberately-long-stable-identity-for-responsive-review/memory-decision-with-a-deliberately-long-stable-identity-for-overflow-review-0000000001",
"goal": "Audit immutable Memory body, candidate evidence, source refs, derivations, and revision history.",
"dataState": "Current active revision with bounded typed provenance and active/resolved/retracted immutable history.",
"goal": "Audit immutable Memory body, candidate evidence, source refs, derivations, and change history.",
"dataState": "Current active change with bounded typed provenance and active/resolved/retracted immutable history.",
"ready": {
"kind": "selector",
"selector": "[data-memory-view='detail'] .revision-list"
"selector": "[data-memory-view='detail'] .change-list"
},
"capturePoints": [{ "id": "initial", "label": "Memory detail" }]
},
@@ -154,11 +154,11 @@
"id": "detail",
"label": "Committed Memory detail",
"path": "/w/memory-review/memory/release-coordination-subject-with-a-deliberately-long-stable-identity-for-responsive-review/memory-decision-with-a-deliberately-long-stable-identity-for-overflow-review-0000000001",
"goal": "Audit immutable Memory body, candidate evidence, source refs, derivations, and revision history.",
"dataState": "Current active revision with bounded typed provenance and active/resolved/retracted immutable history.",
"goal": "Audit immutable Memory body, candidate evidence, source refs, derivations, and change history.",
"dataState": "Current active change with bounded typed provenance and active/resolved/retracted immutable history.",
"ready": {
"kind": "selector",
"selector": "[data-memory-view='detail'] .revision-list"
"selector": "[data-memory-view='detail'] .change-list"
},
"capturePoints": [{ "id": "initial", "label": "Memory detail" }]
},
+4 -4
View File
@@ -46,7 +46,7 @@ export type CaptureOptions = {
headed?: boolean;
};
type SourceState = { revision: string | null; dirty: boolean | null };
type SourceState = { commitSha: string | null; dirty: boolean | null };
type ErrorCollector = { errors: CaptureError[]; observed: number; limit: number };
const CAPTURE_ERROR_LIMIT = 100;
@@ -88,18 +88,18 @@ function timestampId(): string {
async function sourceState(): Promise<SourceState> {
try {
const [revision, status] = await Promise.all([
const [commit, status] = await Promise.all([
new Deno.Command("git", { args: ["rev-parse", "HEAD"], stdout: "piped", stderr: "null" })
.output(),
new Deno.Command("git", { args: ["status", "--porcelain"], stdout: "piped", stderr: "null" })
.output(),
]);
return {
revision: revision.success ? new TextDecoder().decode(revision.stdout).trim() : null,
commitSha: commit.success ? new TextDecoder().decode(commit.stdout).trim() : null,
dirty: status.success ? new TextDecoder().decode(status.stdout).trim().length > 0 : null,
};
} catch {
return { revision: null, dirty: null };
return { commitSha: null, dirty: null };
}
}
+1 -1
View File
@@ -133,7 +133,7 @@ export type ReviewContext = {
schemaVersion: 1;
runId: string;
scenario: { id: string; title: string; sourcePath: string | null };
source: { revision: string | null; dirty: boolean | null };
source: { commitSha: string | null; dirty: boolean | null };
baseUrl: string;
browser: { name: "chromium"; version: string };
createdAt: string;