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:
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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" }]
|
||||
},
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user