fix: runtime retain restore facts when Stop supersedes publication

This commit is contained in:
2026-10-26 11:50:54 +09:00
parent ceb01f6eaa
commit 9e3f87ac52
18 changed files with 314 additions and 77 deletions
@@ -1,6 +1,6 @@
// @generated by yoi-companion-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:5de3f3b716555717d3274b8c6a385fb66624585941628a53b8b3050cb58f8ab7)
// Input contract: 0.1.0 (sha256:4159d6baa918c9d7d3cb6ae02207aad877a69ad1dd4207beb63b271deb24176e)
// Options: companion-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/companion-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_companion_api_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-repository-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:5de3f3b716555717d3274b8c6a385fb66624585941628a53b8b3050cb58f8ab7)
// Input contract: 0.1.0 (sha256:4159d6baa918c9d7d3cb6ae02207aad877a69ad1dd4207beb63b271deb24176e)
// Options: repository-operation-schema-closure,openapi-3.1,format:uint64=safe-number,strict-safe-integers,sorted-output
// Output: web/workspace/src/lib/generated/repository-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_repository_openapi_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-runtime-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:5de3f3b716555717d3274b8c6a385fb66624585941628a53b8b3050cb58f8ab7)
// Input contract: 0.1.0 (sha256:4159d6baa918c9d7d3cb6ae02207aad877a69ad1dd4207beb63b271deb24176e)
// Options: runtime-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/runtime-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_runtime_api_types -- --check
@@ -181,7 +181,7 @@ export type WorkerRestoreResponse = { result: WorkerRestoreResult; runtime_id: s
export type WorkerRestoreResult = { diagnostics?: Array<Diagnostic>; state: WorkerRestoreState; worker?: WorkerSummary | null; };
export type WorkerRestoreState = "accepted" | "rejected" | "rolled_back" | "reconciliation_required";
export type WorkerRestoreState = "accepted" | "rejected" | "rolled_back" | "reconciliation_required" | "superseded";
export type WorkerRetentionResponse = { pinned: boolean; retention_state: string; runtime_id: string; worker_id: string; workspace_id: string; };
@@ -1,6 +1,6 @@
// @generated by yoi-ticket-browser-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:5de3f3b716555717d3274b8c6a385fb66624585941628a53b8b3050cb58f8ab7)
// Input contract: 0.1.0 (sha256:4159d6baa918c9d7d3cb6ae02207aad877a69ad1dd4207beb63b271deb24176e)
// Options: ticket-objective-merge-request-browser-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/ticket-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_ticket_api_types -- --check
@@ -1,6 +1,6 @@
// @generated by yoi-worker-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:5de3f3b716555717d3274b8c6a385fb66624585941628a53b8b3050cb58f8ab7)
// Input contract: 0.1.0 (sha256:4159d6baa918c9d7d3cb6ae02207aad877a69ad1dd4207beb63b271deb24176e)
// Options: worker-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/worker-launch-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_worker_launch_api_types -- --check
@@ -363,8 +363,13 @@ export function parseWorkerRestoreResponse(
exactKeys(result, ["state"], ["diagnostics", "worker"], `${path}.result`);
const state = boundedString(result.state, `${path}.result.state`);
if (
!["accepted", "rejected", "rolled_back", "reconciliation_required"]
.includes(state)
![
"accepted",
"rejected",
"rolled_back",
"superseded",
"reconciliation_required",
].includes(state)
) {
fail(`${path}.result.state`, "has an unknown value");
}
@@ -427,7 +427,12 @@ test.each(["switch", "dispose"] as const)(
);
function restoreResponse(
state: "accepted" | "rejected" | "rolled_back" | "reconciliation_required",
state:
| "accepted"
| "rejected"
| "rolled_back"
| "superseded"
| "reconciliation_required",
extra = {},
) {
return Response.json({
@@ -461,7 +466,13 @@ test.each(["stopped", "idle", "running", "paused", "unavailable"] as const)(
);
test.each(
["accepted", "rejected", "rolled_back", "reconciliation_required"] as const,
[
"accepted",
"rejected",
"rolled_back",
"superseded",
"reconciliation_required",
] as const,
)(
"Restore classifies %s through the shared helper, without treating HTTP 200 as success",
async (state) => {
@@ -470,10 +481,11 @@ test.each(
const unsubscribe = workspaceAlerts.subscribe((value) => {
alerts = value;
});
const request = vi.fn(async (input: RequestInfo | URL) =>
String(input).endsWith("/restore")
? restoreResponse(state)
: Response.json(plan())
const request = vi.fn(
async (input: RequestInfo | URL, _init?: RequestInit) =>
String(input).endsWith("/restore")
? restoreResponse(state)
: Response.json(plan()),
);
vi.stubGlobal("fetch", request);
render(WorkersPage, { data: data() });
@@ -498,6 +510,16 @@ test.each(
screen.queryByRole("button", { name: "Retry Restore Catalog worker" }),
),
).toBe(state === "reconciliation_required");
expect(
request.mock.calls.filter(([url]) => String(url).endsWith("/restore")),
).toHaveLength(1);
expect(
request.mock.calls
.filter(([, init]) => init?.method && init.method !== "GET")
.map(([url]) => String(url)),
).toEqual([
"/api/w/page-test/runtimes/runtime-a/workers/worker-a/restore",
]);
// The response is never projected into the catalog by the action.
expect(screen.getByRole("cell", { name: "stopped" })).toBeTruthy();
unsubscribe();
@@ -305,6 +305,7 @@ Deno.test("Restore classification never promotes nonaccepted results and preserv
"accepted",
"rejected",
"rolled_back",
"superseded",
"reconciliation_required",
] as const
) {
+2 -1
View File
@@ -173,12 +173,13 @@ function restoredWorker() {
restore_observation_token: "generation-b",
};
}
Deno.test("Restore parser preserves all four states and optional Workspace summary", () => {
Deno.test("Restore parser preserves typed outcomes and optional Workspace summary", () => {
for (
const state of [
"accepted",
"rejected",
"rolled_back",
"superseded",
"reconciliation_required",
]
) {