feat: add workspace runtime trust key management

This commit is contained in:
2026-09-06 05:06:37 +09:00
parent e7803d1aba
commit 78d571ed14
18 changed files with 3258 additions and 459 deletions
@@ -0,0 +1,227 @@
declare const Deno: {
test(name: string, fn: () => void | Promise<void>): void;
};
import {
parseRuntimeTrustConflict,
parseWorkspaceRuntimeDetail,
parseWorkspaceRuntimeList,
putRuntimeTrustKey,
RuntimeTrustConflictError,
} from "../src/lib/workspace/api/runtime-management.ts";
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}
function assertThrows(operation: () => unknown, expected: string): void {
try {
operation();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (message.includes(expected)) return;
throw new Error(
`expected error containing ${expected}, received ${message}`,
);
}
throw new Error("expected operation to throw");
}
function runtime() {
return {
management: {
built_in: false,
config_managed: true,
removable: false,
endpoint_configured: true,
token_ref_configured: false,
},
runtime_id: "arcadia",
label: "Arcadia",
kind: "remote",
status: "started",
source: {
kind: "remote_http",
status: "active",
identity_authority: "server_runtime_configuration",
note: "Configured by Server authority",
},
host_ids: ["host-a"],
worker_creation_available: true,
os: "linux",
arch: "x86_64",
diagnostics: [],
};
}
function detail() {
return {
workspace_id: "workspace-a",
runtime: runtime(),
endpoint: "https://runtime.example.test",
trust_key: {
status: "active",
public_key: "ssh-ed25519 AAAA-test",
fingerprint: "SHA256:current",
revision: 3,
created_at: "2026-09-01T12:00:00Z",
updated_at: "2026-09-01T13:00:00Z",
revoked_at: null,
},
recent_audit: [{
action: "created",
actor_account_id: "account-a",
old_fingerprint: null,
new_fingerprint: "SHA256:current",
revision: 3,
at: "2026-09-01T13:00:00Z",
}],
};
}
Deno.test("Runtime list and detail parsers return generated Runtime DTO shapes", () => {
const list = parseWorkspaceRuntimeList({
workspace_id: "workspace-a",
limit: 200,
items: [runtime()],
source: "workspace-control-plane",
diagnostics: [],
});
assert(
list.items[0]?.runtime_id === "arcadia",
"Runtime ID was not preserved",
);
const parsed = parseWorkspaceRuntimeDetail(detail());
assert(
parsed.trust_key.revision === 3,
"revision was not preserved as a safe integer",
);
assert(
parsed.recent_audit[0]?.revision === 3,
"audit revision was not normalized",
);
});
Deno.test("Runtime validators reject unknown object keys and enum variants", () => {
assertThrows(
() => parseWorkspaceRuntimeDetail({ ...detail(), head_tree: "stale" }),
"head_tree is not part",
);
const futureSource = structuredClone(detail());
futureSource.runtime.source.kind = "future_transport";
assertThrows(
() => parseWorkspaceRuntimeDetail(futureSource),
"contains an unknown enum value",
);
assertThrows(
() =>
parseRuntimeTrustConflict({
error: "future_conflict",
message: "conflict",
current_revision: 4,
current_fingerprint: "SHA256:new",
}),
"contains an unknown enum value",
);
});
Deno.test("Runtime validators reject unsafe revisions and bounded collection overflow", () => {
const unsafeRevision = structuredClone(detail());
unsafeRevision.trust_key.revision = Number.MAX_SAFE_INTEGER + 1;
assertThrows(
() => parseWorkspaceRuntimeDetail(unsafeRevision),
"must be a safe integer",
);
const tooMuchAudit = structuredClone(detail());
tooMuchAudit.recent_audit = Array.from(
{ length: 21 },
() => structuredClone(detail().recent_audit[0]),
);
assertThrows(
() => parseWorkspaceRuntimeDetail(tooMuchAudit),
"must contain at most 20 items",
);
const tooManyItems = Array.from({ length: 201 }, () => runtime());
assertThrows(
() =>
parseWorkspaceRuntimeList({
workspace_id: "workspace-a",
limit: 200,
items: tooManyItems,
source: "workspace-control-plane",
diagnostics: [],
}),
"must contain at most 200 items",
);
});
Deno.test("Runtime detail rejects unbounded strings and incoherent trust state", () => {
const largeKey = structuredClone(detail());
largeKey.trust_key.public_key = "x".repeat(16 * 1024 + 1);
assertThrows(
() => parseWorkspaceRuntimeDetail(largeKey),
"must be at most 16384 UTF-8 bytes",
);
const activeWithoutFingerprint = structuredClone(detail()) as Record<
string,
unknown
>;
(activeWithoutFingerprint.trust_key as Record<string, unknown>).fingerprint =
null;
assertThrows(
() => parseWorkspaceRuntimeDetail(activeWithoutFingerprint),
"must include fingerprint",
);
});
Deno.test("typed trust conflict is validated and preserves authoritative revision", async () => {
let sentBody: unknown = null;
const fetchImpl = ((_: RequestInfo | URL, init?: RequestInit) => {
sentBody = JSON.parse(String(init?.body)) as unknown;
return Promise.resolve(
new Response(
JSON.stringify({
error: "stale_revision",
message: "Runtime trust changed",
current_revision: 4,
current_fingerprint: "SHA256:new",
}),
{ status: 409, headers: { "content-type": "application/json" } },
),
);
}) as typeof fetch;
try {
await putRuntimeTrustKey(
"workspace-a",
"arcadia",
{ public_key: "ssh-ed25519 AAAA-new", expected_revision: 3 },
fetchImpl,
);
throw new Error("expected mutation to reject");
} catch (error) {
assert(
error instanceof RuntimeTrustConflictError,
"expected typed conflict",
);
assert(
error.conflict.current_revision === 4,
"authoritative revision was lost",
);
}
assert(
JSON.stringify(sentBody) ===
JSON.stringify({
public_key: "ssh-ed25519 AAAA-new",
expected_revision: 3,
}),
"request should serialize the generated bigint revision as a safe JSON integer",
);
});