fix: authenticate Runtime requests without persisted verification

Use the current Workspace signing identity for each request and remove binding key copies and verification handshake gates. Preserve explicit trust and revocation, migrate historical evidence, and align Runtime settings and connection tests. Retain request scope and durable replay protection with regression coverage.
This commit is contained in:
2026-10-11 00:52:24 +09:00
parent 6a12b6cc34
commit 4099359560
42 changed files with 2786 additions and 4411 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:fc25104c077ab8c5c43f77611f505171585d2d40fd54c6538774ac17aae6cce3)
// Input contract: 0.1.0 (sha256:a23a7b9989d37fd08459929a4e726396a33334489207799e02d45302e8d4c238)
// 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:fc25104c077ab8c5c43f77611f505171585d2d40fd54c6538774ac17aae6cce3)
// Input contract: 0.1.0 (sha256:a23a7b9989d37fd08459929a4e726396a33334489207799e02d45302e8d4c238)
// 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
+4 -12
View File
@@ -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:fc25104c077ab8c5c43f77611f505171585d2d40fd54c6538774ac17aae6cce3)
// Input contract: 0.1.0 (sha256:a23a7b9989d37fd08459929a4e726396a33334489207799e02d45302e8d4c238)
// 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
@@ -25,7 +25,7 @@ export type CompletionEntry = { description?: string | null; invocation?: Featur
export type CompletionKind = "file" | "feature" | "feature_argument";
export type CreateRemoteRuntimeRequest = { display_name?: string | null; endpoint: string; expected_binding_id?: string | null; public_bundle: RuntimePublicIdentityBundle; workspace_trust_id: string; };
export type CreateRemoteRuntimeRequest = { display_name?: string | null; endpoint: string; expected_binding_id?: string | null; public_bundle: RuntimePublicIdentityBundle; };
export type Diagnostic = { code: string; message: string; severity: DiagnosticSeverity; };
@@ -77,11 +77,9 @@ export type RuntimeConfigBundleResult = { availability?: unknown; diagnostics?:
export type RuntimeConfigBundleSyncRequest = { bundle: unknown; };
export type RuntimeConnectionDisplayState = "configured" | "verified" | "unavailable" | "revoked";
export type RuntimeConnectionTestFailureKind = "authentication" | "authorization" | "network_unreachable" | "timeout" | "tls_or_transport" | "malformed_response" | "protocol_version_mismatch" | "runtime_identity_mismatch" | "configuration";
export type RuntimeConnectionTestResponse = { actual_protocol_version?: number | null; binding_id: string; checked_at: string; connection_state: RuntimeConnectionDisplayState; diagnostics?: Array<Diagnostic>; expected_protocol_version: number; failure_kind?: RuntimeConnectionTestFailureKind | null; runtime_id: string; status: RuntimeConnectionTestStatus; verification?: RuntimeVerificationEvidenceSummary | null; workspace_id: string; };
export type RuntimeConnectionTestResponse = { actual_protocol_version?: number | null; binding_id: string; checked_at: string; diagnostics?: Array<Diagnostic>; expected_protocol_version: number; failure_kind?: RuntimeConnectionTestFailureKind | null; runtime_id: string; status: RuntimeConnectionTestStatus; workspace_id: string; };
export type RuntimeConnectionTestStatus = "compatible" | "failed";
@@ -121,10 +119,6 @@ export type RuntimeTrustKeyState = { binding_id?: string | null; created_at?: st
export type RuntimeTrustKeyStatus = "unconfigured" | "active" | "revoked";
export type RuntimeVerificationEvidenceSummary = { binding_id: string; last_checked_at: string; last_outcome: RuntimeVerificationOutcome; runtime_public_key_fingerprint: string; verified_at?: string | null; workspace_key_id: string; workspace_public_key_fingerprint: string; workspace_trust_id: string; };
export type RuntimeVerificationOutcome = "verified" | "challenge_issued" | "verification_failed" | "connectivity_failed";
export type RuntimeWorkerCompletionsRequest = { context?: CompletionContext | null; kind: CompletionKind; prefix?: string; request_id?: string | null; };
export type RuntimeWorkerCompletionsResult = { context?: CompletionContext | null; diagnostics?: Array<Diagnostic>; entries: Array<CompletionEntry>; kind: CompletionKind; prefix: string; request_id?: string | null; runtime_id: string; worker_id: string; };
@@ -217,9 +211,7 @@ export type WorkingDirectorySummary = { cleanliness?: string | null; cleanup_tar
export type WorkspaceConfigAccess = "read_only" | "read_write";
export type WorkspaceRuntimeBindingState = "configured" | "verified" | "revoked";
export type WorkspaceRuntimeBindingSummary = { binding_id: string; connection_state: RuntimeConnectionDisplayState; state: WorkspaceRuntimeBindingState; verification?: RuntimeVerificationEvidenceSummary | null; workspace_key_id?: string | null; workspace_trust_id?: string | null; };
export type WorkspaceRuntimeBindingSummary = { binding_id: string; revoked_at?: string | null; };
export type WorkspaceRuntimeDetail = { endpoint?: string | null; recent_audit?: Array<RuntimeTrustAuditEntry>; runtime: WorkspaceRuntimeResource; trust_key: RuntimeTrustKeyState; 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:fc25104c077ab8c5c43f77611f505171585d2d40fd54c6538774ac17aae6cce3)
// Input contract: 0.1.0 (sha256:a23a7b9989d37fd08459929a4e726396a33334489207799e02d45302e8d4c238)
// 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:fc25104c077ab8c5c43f77611f505171585d2d40fd54c6538774ac17aae6cce3)
// Input contract: 0.1.0 (sha256:a23a7b9989d37fd08459929a4e726396a33334489207799e02d45302e8d4c238)
// 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
@@ -2,15 +2,12 @@ import type {
Diagnostic,
RuntimeConnectionTestFailureKind,
RuntimeConnectionTestResponse,
RuntimeVerificationEvidenceSummary,
} from "#lib/generated/runtime-api.ts";
const RESPONSE_KEYS = [
"workspace_id",
"runtime_id",
"binding_id",
"connection_state",
"verification",
"checked_at",
"status",
"failure_kind",
@@ -108,27 +105,11 @@ export function parseRuntimeConnectionTestResponse(
return null;
}
const bindingId = value.binding_id;
const connectionState = parseConnectionState(value.connection_state);
const verification = parseVerificationEvidence(value.verification);
if (
!isBoundedString(bindingId) ||
connectionState === null ||
(value.verification !== null && verification === null) ||
(verification !== null &&
verification.binding_id !== bindingId) ||
(connectionState === "verified" && value.status !== "compatible") ||
(connectionState === "verified" && verification !== null &&
(verification.last_outcome !== "verified" ||
verification.verified_at === null))
) {
return null;
}
if (!isBoundedString(bindingId, 256)) return null;
return {
workspace_id: value.workspace_id,
runtime_id: value.runtime_id,
binding_id: bindingId,
connection_state: connectionState,
verification,
checked_at: value.checked_at,
status: value.status,
failure_kind: failureKind as RuntimeConnectionTestFailureKind | null,
@@ -138,60 +119,6 @@ export function parseRuntimeConnectionTestResponse(
};
}
function parseConnectionState(
value: unknown,
): "configured" | "verified" | "unavailable" | "revoked" | null {
return value === "configured" || value === "verified" ||
value === "unavailable" || value === "revoked"
? value
: null;
}
function parseVerificationEvidence(
value: unknown,
): RuntimeVerificationEvidenceSummary | null {
if (value === null) return null;
const keys = [
"verified_at",
"last_checked_at",
"last_outcome",
"binding_id",
"workspace_key_id",
"workspace_public_key_fingerprint",
"workspace_trust_id",
"runtime_public_key_fingerprint",
] as const;
if (!isRecord(value) || !hasExactKeys(value, keys)) return null;
if (
(value.verified_at !== null &&
(!isBoundedString(value.verified_at, 128) ||
Number.isNaN(Date.parse(value.verified_at)))) ||
!isBoundedString(value.last_checked_at, 128) ||
Number.isNaN(Date.parse(value.last_checked_at)) ||
(value.last_outcome !== "verified" &&
value.last_outcome !== "challenge_issued" &&
value.last_outcome !== "verification_failed" &&
value.last_outcome !== "connectivity_failed") ||
!isBoundedString(value.binding_id) ||
!isBoundedString(value.workspace_key_id, 128) ||
!isBoundedString(value.workspace_public_key_fingerprint) ||
!isBoundedString(value.workspace_trust_id) ||
!isBoundedString(value.runtime_public_key_fingerprint, 128)
) {
return null;
}
return {
verified_at: value.verified_at,
last_checked_at: value.last_checked_at,
last_outcome: value.last_outcome,
binding_id: value.binding_id,
workspace_key_id: value.workspace_key_id,
workspace_public_key_fingerprint: value.workspace_public_key_fingerprint,
workspace_trust_id: value.workspace_trust_id,
runtime_public_key_fingerprint: value.runtime_public_key_fingerprint,
};
}
export async function testRuntimeConnection(
workspaceId: string,
runtimeId: string,
@@ -3,7 +3,6 @@ import type {
Diagnostic,
RemoveRuntimeRequest,
RevokeRuntimeTrustKeyRequest,
RuntimeConnectionDisplayState,
RuntimeIdentityAuthority,
RuntimeManagementApiError,
RuntimeManagementSummary,
@@ -17,14 +16,13 @@ import type {
RuntimeTrustKeyRevealResponse,
RuntimeTrustKeyState,
RuntimeTrustKeyStatus,
RuntimeVerificationEvidenceSummary,
UpdateRemoteRuntimeRequest,
WorkspaceRuntimeBindingState,
WorkspaceRuntimeBindingSummary,
WorkspaceRuntimeDetail,
WorkspaceRuntimeListResponse,
WorkspaceRuntimeResource,
} from "#lib/generated/runtime-api.ts";
import type { RepositoryApiError } from "#lib/generated/repository-api.ts";
import { workspaceApiPath } from "./http.ts";
export type WorkspaceRuntimeList =
@@ -90,17 +88,6 @@ const CONFLICT_KINDS = new Set<RuntimeTrustConflictKind>([
"stale_binding",
"fingerprint_in_use",
]);
const BINDING_STATES = new Set<WorkspaceRuntimeBindingState>([
"configured",
"verified",
"revoked",
]);
const CONNECTION_STATES = new Set<RuntimeConnectionDisplayState>([
"configured",
"verified",
"unavailable",
"revoked",
]);
const REMOVAL_OPERATION_STATES = new Set<RuntimeRemovalOperationState>([
"pending",
"cleanup_pending",
@@ -326,140 +313,16 @@ function runtimeSource(value: unknown, path: string): RuntimeSourceSummary {
};
}
function runtimeVerification(
value: unknown,
path: string,
): RuntimeVerificationEvidenceSummary {
const item = object(value, path);
exactKeys(
item,
[
"verified_at",
"last_checked_at",
"last_outcome",
"binding_id",
"workspace_key_id",
"workspace_public_key_fingerprint",
"workspace_trust_id",
"runtime_public_key_fingerprint",
],
[],
path,
);
const verifiedAt = item.verified_at === null
? null
: boundedString(item.verified_at, `${path}.verified_at`, 128);
const lastOutcome = enumValue(
item.last_outcome,
`${path}.last_outcome`,
new Set(
[
"verified",
"challenge_issued",
"verification_failed",
"connectivity_failed",
] as const,
),
);
return {
verified_at: verifiedAt,
last_checked_at: boundedString(
item.last_checked_at,
`${path}.last_checked_at`,
128,
),
last_outcome: lastOutcome,
binding_id: boundedIdentity(
item.binding_id,
`${path}.binding_id`,
),
workspace_key_id: boundedString(
item.workspace_key_id,
`${path}.workspace_key_id`,
LIMITS.idBytes,
),
workspace_public_key_fingerprint: boundedIdentity(
item.workspace_public_key_fingerprint,
`${path}.workspace_public_key_fingerprint`,
),
workspace_trust_id: boundedIdentity(
item.workspace_trust_id,
`${path}.workspace_trust_id`,
),
runtime_public_key_fingerprint: boundedString(
item.runtime_public_key_fingerprint,
`${path}.runtime_public_key_fingerprint`,
LIMITS.fingerprintBytes,
),
};
}
function runtimeBinding(
value: unknown,
path: string,
requiresWorkspaceIdentity: boolean,
): WorkspaceRuntimeBindingSummary {
const item = object(value, path);
exactKeys(
item,
["state", "connection_state", "binding_id"],
["workspace_key_id", "workspace_trust_id", "verification"],
path,
);
const workspaceKeyId = optionalNullableString(
item.workspace_key_id,
`${path}.workspace_key_id`,
LIMITS.idBytes,
);
const workspaceTrustId = optionalNullableIdentity(
item.workspace_trust_id,
`${path}.workspace_trust_id`,
);
const state = enumValue(item.state, `${path}.state`, BINDING_STATES);
if (
requiresWorkspaceIdentity &&
state !== "revoked" &&
(workspaceKeyId == null || workspaceTrustId == null)
) {
return fail(path, "requires Workspace signing key identity metadata");
}
const connectionState = enumValue(
item.connection_state,
`${path}.connection_state`,
CONNECTION_STATES,
);
const binding_id = boundedIdentity(item.binding_id, `${path}.binding_id`);
const verification = item.verification === undefined
? undefined
: runtimeVerification(item.verification, `${path}.verification`);
if (
verification !== undefined && verification.binding_id !== binding_id
) {
return fail(path, "verification must match the current binding binding_id");
}
if (
requiresWorkspaceIdentity &&
connectionState === "verified" &&
(verification === undefined ||
verification.verified_at === null ||
verification.last_outcome !== "verified")
) {
return fail(
path,
"verified Workspace identity binding requires verification evidence",
);
}
exactKeys(item, ["binding_id"], ["revoked_at"], path);
return {
state,
connection_state: connectionState,
binding_id,
...(workspaceKeyId === undefined
? {}
: { workspace_key_id: workspaceKeyId }),
...(workspaceTrustId === undefined
? {}
: { workspace_trust_id: workspaceTrustId }),
...(verification === undefined ? {} : { verification }),
binding_id: boundedIdentity(item.binding_id, `${path}.binding_id`),
revoked_at:
optionalNullableTimestamp(item.revoked_at, `${path}.revoked_at`) ?? null,
};
}
@@ -483,7 +346,7 @@ function runtimeManagement(
const builtIn = boolean(item.built_in, `${path}.built_in`);
const binding = item.binding == null
? undefined
: runtimeBinding(item.binding, `${path}.binding`, !builtIn);
: runtimeBinding(item.binding, `${path}.binding`);
return {
built_in: builtIn,
config_managed: boolean(item.config_managed, `${path}.config_managed`),
@@ -763,7 +626,7 @@ export function parseRuntimeTrustConflict(
exactKeys(
response,
["error", "message"],
["current_binding_id", "current_fingerprint"],
["current_binding_id", "current_fingerprint", "diagnostics"],
"Runtime trust conflict",
);
return {
@@ -786,6 +649,15 @@ export function parseRuntimeTrustConflict(
"Runtime trust conflict.current_fingerprint",
LIMITS.fingerprintBytes,
),
...(response.diagnostics === undefined ? {} : {
diagnostics: array(
response.diagnostics,
"Runtime trust conflict.diagnostics",
LIMITS.diagnostics,
).map((entry, index) =>
diagnostic(entry, `Runtime trust conflict.diagnostics[${index}]`)
),
}),
};
}
@@ -891,6 +763,37 @@ async function readBoundedJson(response: Response): Promise<unknown> {
}
}
function repositoryApiError(value: unknown): RepositoryApiError {
const response = object(value, "Runtime trust error");
exactKeys(
response,
["error", "message"],
["diagnostics"],
"Runtime trust error",
);
return {
error: boundedString(
response.error,
"Runtime trust error.error",
LIMITS.idBytes,
),
message: boundedString(
response.message,
"Runtime trust error.message",
LIMITS.conflictMessageBytes,
),
...(response.diagnostics === undefined ? {} : {
diagnostics: array(
response.diagnostics,
"Runtime trust error.diagnostics",
LIMITS.diagnostics,
).map((entry, index) =>
diagnostic(entry, `Runtime trust error.diagnostics[${index}]`)
),
}),
};
}
function requestErrorFrom(
value: unknown,
status: number,
@@ -917,30 +820,13 @@ function requestErrorFrom(
),
);
}
exactKeys(
response,
["error", "message", "diagnostics"],
[],
"Runtime trust error",
);
const diagnostics = array(
response.diagnostics,
"Runtime trust error.diagnostics",
LIMITS.diagnostics,
).map((entry, index) =>
diagnostic(entry, `Runtime trust error.diagnostics[${index}]`)
);
const message = boundedString(
response.message,
"Runtime trust error.message",
LIMITS.conflictMessageBytes,
);
const field = diagnostics.some((entry) =>
const error = repositoryApiError(response);
const field = (error.diagnostics ?? []).some((entry) =>
entry.code.startsWith("runtime_public_key_")
)
? "public_key"
: null;
return new RuntimeTrustRequestError(message, field);
return new RuntimeTrustRequestError(error.message, field);
} catch {
return new RuntimeTrustRequestError(
`Runtime trust request failed (${status})`,
@@ -960,6 +846,21 @@ function throwRuntimeTrustConflict(payload: unknown): never {
throw new RuntimeTrustConflictError(conflict);
}
function throwRuntimeCreationConflict(payload: unknown): never {
let error: RepositoryApiError;
try {
error = repositoryApiError(payload);
} catch {
throw new RuntimeTrustRequestError(
"Runtime trust conflict response was invalid",
);
}
if (CONFLICT_KINDS.has(error.error as RuntimeTrustConflictKind)) {
throwRuntimeTrustConflict(error);
}
throw requestErrorFrom(error, 409);
}
async function finishMutation(
response: Response,
workspaceId: string,
@@ -990,17 +891,16 @@ export async function createRemoteRuntime(
request: CreateRemoteRuntimeRequest,
fetchImpl: typeof fetch = fetch,
): Promise<WorkspaceRuntimeResource> {
const enrollmentId = boundedIdentity(request.workspace_trust_id, "workspace_trust_id");
const response = await fetchImpl(
workspaceApiPath(workspaceId, "/runtimes"),
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ ...request, workspace_trust_id: enrollmentId }),
body: JSON.stringify(request),
},
);
const payload = await readBoundedJson(response);
if (response.status === 409) throwRuntimeTrustConflict(payload);
if (response.status === 409) throwRuntimeCreationConflict(payload);
if (!response.ok) throw requestErrorFrom(payload, response.status);
const runtime = runtimeResource(payload, "Runtime create response");
if (runtime.runtime_id !== request.public_bundle.identity_id) {
@@ -68,6 +68,6 @@ export function repositorySshProbeRuntimes(
runtime.management.endpoint_configured &&
runtime.management.binding !== undefined &&
runtime.management.binding !== null &&
runtime.management.binding.state !== "revoked"
runtime.management.binding.revoked_at == null
);
}
@@ -19,7 +19,6 @@
let { data }: PageProps = $props();
let runtimePublicBundle = $state('');
let workspaceTrustId = $state('');
let displayName = $state('');
let endpoint = $state('');
let runtimeFingerprint = $state<string | null>(null);
@@ -36,9 +35,7 @@
function connectionTestSummary(result: RuntimeConnectionTestResponse): string {
if (result.status === 'compatible') {
return result.connection_state === 'verified'
? `Verified · protocol v${result.actual_protocol_version}`
: `Compatible · ${result.connection_state} · protocol v${result.actual_protocol_version}`;
return `Signed ping succeeded · protocol v${result.actual_protocol_version}`;
}
switch (result.failure_kind) {
case 'authentication': return 'Authentication failed';
@@ -145,18 +142,16 @@
}
await createRemoteRuntime(data.workspaceId, {
public_bundle: publicBundle,
workspace_trust_id: workspaceTrustId,
display_name: displayName || null,
endpoint,
expected_binding_id: null,
});
runtimePublicBundle = '';
workspaceTrustId = '';
runtimeFingerprint = null;
displayName = '';
endpoint = '';
showAddRuntime = false;
requestNotice = 'Runtime registered for this Workspace. Run Test to complete authenticated verification.';
requestNotice = 'Runtime registered for this Workspace. Run Test to observe a signed ping.';
await invalidateAll();
} catch (error) {
requestError = error instanceof RuntimeTrustRequestError || error instanceof Error
@@ -171,7 +166,8 @@
runtime: WorkspaceRuntimeResource,
): RuntimeConnectionTestResponse | undefined {
const result = testResults[runtime.runtime_id];
return result?.binding_id === runtime.management?.binding?.binding_id
return result?.workspace_id === data.workspaceId &&
result?.binding_id === runtime.management?.binding?.binding_id
? result
: undefined;
}
@@ -179,12 +175,16 @@
async function testRuntime(runtime: WorkspaceRuntimeResource): Promise<void> {
const bindingId = runtime.management?.binding?.binding_id;
if (typeof bindingId !== 'string') return;
const workspaceId = data.workspaceId;
const generation = ++connectionTestGeneration;
requestError = null;
const { [runtime.runtime_id]: _previousResult, ...remainingResults } = testResults;
testResults = remainingResults;
busyRuntimeId = runtime.runtime_id;
try {
const result = await testRuntimeConnection(data.workspaceId, runtime.runtime_id);
if (
workspaceId !== data.workspaceId ||
generation !== connectionTestGeneration ||
result.binding_id !== bindingId
) {
@@ -261,15 +261,9 @@
</button>
<pre>yoi-runtime trust-workspace add --bundle {workspaceBundleFilename()}</pre>
<small>
Copy the Runtime-issued <code>workspace_trust_id</code> from <code>trust-workspace add/show</code>.
It identifies this enrollment, not the Workspace key fingerprint.
Pass the same <code>--fs-root</code> and <code>--fs-runtime-dir</code> options used by the Runtime
service. Existing Workspace trust entries are preserved.
</small>
<label>
Workspace trust ID
<input bind:value={workspaceTrustId} required autocomplete="off" spellcheck="false" />
</label>
{:else}
<p class="section-state">Loading Workspace public identity…</p>
{/if}
@@ -323,14 +317,14 @@
</div>
<p>
Registration stores this Workspace-scoped binding. After it appears in the list, run
<strong>Test</strong> to complete authenticated verification.
<strong>Test</strong> to observe a signed ping. Each request authenticates independently.
</p>
</section>
<div class="settings-action-row">
<button
type="submit"
disabled={busyRuntimeId !== null || !data.signingIdentity?.public_bundle || !runtimeFingerprint || !workspaceTrustId}
disabled={busyRuntimeId !== null || !runtimeFingerprint}
>Register Runtime</button>
<button type="button" disabled={busyRuntimeId !== null} onclick={() => showAddRuntime = false}>
Cancel
@@ -368,7 +362,7 @@
</thead>
<tbody>
{#each data.runtimes.items as runtime}
<tr class:inactive={runtime.status !== 'active'}>
<tr class:inactive={Boolean(runtime.management?.binding?.revoked_at) || currentTestResult(runtime)?.status === 'failed'}>
<td>
<strong>
<a class="inline-link" href={`/w/${encodeURIComponent(data.workspaceId)}/settings/runtimes/${encodeURIComponent(runtime.runtime_id)}`}>
@@ -379,7 +373,7 @@
</td>
<td>{runtime.kind}</td>
<td>
{runtime.management?.binding?.connection_state ?? runtime.status}
{runtime.management?.binding?.revoked_at ? 'Revoked' : currentTestResult(runtime) ? connectionTestSummary(currentTestResult(runtime)!) : 'Not tested'}
</td>
<td>{runtimePlatform(runtime)}</td>
<td>{managementLabel(runtime)}</td>
@@ -390,16 +384,14 @@
</td>
<td>
<div class="settings-action-row">
{#if runtime.management?.config_managed && runtime.management.binding?.connection_state !== 'revoked'}
{#if runtime.management?.config_managed && !runtime.management.binding?.revoked_at}
<button
type="button"
disabled={busyRuntimeId !== null}
onclick={() => testRuntime(runtime)}
>Test</button>
{/if}
{#if runtime.management?.binding?.state === 'configured'}
<span class="settings-muted-action">Verification required</span>
{:else if !runtime.management?.config_managed}
{#if !runtime.management?.config_managed}
<span class="settings-muted-action">Test unavailable</span>
{/if}
</div>
@@ -2,6 +2,7 @@
import { goto, invalidateAll } from '$app/navigation';
import type {
RevokeRuntimeTrustKeyRequest,
RuntimeConnectionTestResponse,
RuntimeTrustKeyStatus,
} from '#lib/generated/runtime-api.ts';
import {
@@ -17,6 +18,7 @@
RuntimeTrustRequestError,
type RuntimeTrustRouteOperation,
} from '#lib/workspace/api/runtime-management.ts';
import { testRuntimeConnection } from '#lib/workspace/api/runtime-connection.ts';
import type { PageProps } from './$types';
type TrustAction = 'create' | 'replace' | 'reactivate';
@@ -25,18 +27,19 @@
let showPublicKey = $state(false);
let revealedPublicKey = $state<string | null>(null);
let publicKey = $state('');
let workspaceTrustId = $state('');
let displayName = $state('');
let endpoint = $state('');
let editingMetadata = $state(false);
let deleteRuntimeConfirmation = $state('');
let busyAction = $state<'metadata' | 'trust' | 'revoke' | 'reveal' | 'copy' | 'delete' | null>(null);
let busyAction = $state<'metadata' | 'trust' | 'revoke' | 'reveal' | 'copy' | 'delete' | 'test' | null>(null);
let fieldError = $state<string | null>(null);
let deleteRuntimeError = $state<string | null>(null);
let requestError = $state<string | null>(null);
let successMessage = $state<string | null>(null);
let replacementFingerprint = $state<string | null>(null);
let replacementFingerprintError = $state<string | null>(null);
let connectionResult = $state<RuntimeConnectionTestResponse | null>(null);
let connectionError = $state<string | null>(null);
let fingerprintGeneration = 0;
const routeFence = new RuntimeTrustRouteFence();
const runtimeRemovalAttempt = new RuntimeRemovalAttempt();
@@ -47,10 +50,11 @@
if (nextGeneration === routeGeneration) return;
routeGeneration = nextGeneration;
fingerprintGeneration += 1;
connectionResult = null;
connectionError = null;
showPublicKey = false;
revealedPublicKey = null;
publicKey = '';
workspaceTrustId = '';
displayName = data.runtimeDetail?.runtime.label ?? '';
endpoint = data.runtimeDetail?.endpoint ?? '';
editingMetadata = false;
@@ -109,6 +113,28 @@
return new TextEncoder().encode(value).byteLength;
}
async function testConnection(): Promise<void> {
const bindingId = data.runtimeDetail?.runtime.management.binding?.binding_id;
if (busyAction !== null || !bindingId) return;
const operation = routeFence.capture(data.runtimeId);
const workspaceId = data.workspaceId;
busyAction = 'test';
connectionResult = null;
connectionError = null;
try {
const result = await testRuntimeConnection(workspaceId, operation.runtimeId);
if (!isCurrentRoute(operation) || workspaceId !== data.workspaceId ||
result.binding_id !== data.runtimeDetail?.runtime.management.binding?.binding_id) return;
connectionResult = result;
} catch (error) {
if (isCurrentRoute(operation) && workspaceId === data.workspaceId) {
connectionError = error instanceof Error ? error.message : String(error);
}
} finally {
if (isCurrentRoute(operation) && workspaceId === data.workspaceId) busyAction = null;
}
}
async function reloadAuthority(): Promise<void> {
await invalidateAll();
}
@@ -195,7 +221,7 @@
const binding = data.runtimeDetail.runtime.management.binding;
if (!binding || !data.runtimeDetail.endpoint) {
throw new RuntimeTrustRequestError(
'The Workspace identity binding and authoritative Runtime endpoint are required.',
'The Runtime binding and authoritative endpoint are required.',
);
}
await createRemoteRuntime(data.workspaceId, {
@@ -203,14 +229,12 @@
identity_id: operation.runtimeId,
public_key: key,
},
workspace_trust_id: workspaceTrustId,
display_name: data.runtimeDetail.runtime.label,
endpoint: data.runtimeDetail.endpoint,
expected_binding_id: binding.binding_id,
});
if (!isCurrentRoute(operation)) return;
publicKey = '';
workspaceTrustId = '';
showPublicKey = false;
revealedPublicKey = null;
successMessage = action === 'create'
@@ -396,8 +420,8 @@
{@const detail = data.runtimeDetail}
{@const runtime = detail.runtime}
{@const trust = detail.trust_key}
{@const verifiedTrust = runtime.management.binding?.state === 'verified'}
{@const currentAction = trustAction(trust.status)}
{@const latestResult = connectionResult?.workspace_id === data.workspaceId && connectionResult?.runtime_id === data.runtimeId && connectionResult?.binding_id === runtime.management.binding?.binding_id ? connectionResult : null}
<section class="runtime-detail-section" aria-labelledby="runtime-identity-heading">
<h2 id="runtime-identity-heading">Identity and binding</h2>
@@ -405,17 +429,32 @@
<div><dt>Runtime ID</dt><dd><code>{runtime.runtime_id}</code></dd></div>
<div><dt>Kind</dt><dd>{runtime.kind}</dd></div>
<div><dt>Endpoint</dt><dd>{detail.endpoint ?? 'Not configured'}</dd></div>
<div><dt>Status</dt><dd>{runtime.status}</dd></div>
<div><dt>Connection state</dt><dd>{runtime.management.binding?.connection_state ?? 'Not configured'}</dd></div>
<div><dt>Workspace signing key</dt><dd><code>{runtime.management.binding?.workspace_key_id ?? '—'}</code></dd></div>
<div><dt>Verified</dt><dd>{formatTimestamp(runtime.management.binding?.verification?.verified_at)}</dd></div>
<div><dt>Last verification check</dt><dd>{runtime.management.binding?.verification?.last_outcome ?? '—'} · {formatTimestamp(runtime.management.binding?.verification?.last_checked_at)}</dd></div>
<div><dt>Connection test</dt><dd>{runtime.management.binding?.revoked_at ? 'Revoked' : latestResult ? latestResult.status === 'compatible' ? 'Signed ping succeeded' : `Signed ping failed · ${latestResult.failure_kind}` : 'Not tested'}</dd></div>
<div><dt>Binding ID</dt><dd><code>{runtime.management.binding?.binding_id ?? '—'}</code></dd></div>
<div><dt>Runtime key status</dt><dd>{trust.status}</dd></div>
<div><dt>Fingerprint</dt><dd><code>{trust.fingerprint ?? '—'}</code></dd></div>
<div><dt>Created</dt><dd>{formatTimestamp(trust.created_at)}</dd></div>
<div><dt>Updated</dt><dd>{formatTimestamp(trust.updated_at)}</dd></div>
<div><dt>Revoked</dt><dd>{formatTimestamp(trust.revoked_at)}</dd></div>
</dl>
{#if runtime.management.config_managed && !runtime.management.binding?.revoked_at}
<div class="settings-action-row">
<button type="button" disabled={busyAction !== null} onclick={testConnection}>
{busyAction === 'test' ? 'Testing…' : 'Test'}
</button>
</div>
{/if}
{#if latestResult}
<p class="section-state" class:error={latestResult.status === 'failed'}>
Checked {formatTimestamp(latestResult.checked_at)}
{#each latestResult.diagnostics ?? [] as diagnostic}
<span>{diagnostic.message}</span>
{/each}
</p>
{/if}
{#if connectionError}
<p class="section-state error" role="alert">{connectionError}</p>
{/if}
{#if (runtime.diagnostics ?? []).length > 0}
<ul class="settings-diagnostics-list">
{#each runtime.diagnostics ?? [] as diagnostic}
@@ -495,25 +534,7 @@
{/if}
{/if}
{#if verifiedTrust}
<div class="runtime-public-key-readonly">
<strong>Runtime public key</strong>
<p>
This verified key is read-only. Revoke Workspace trust and register the Runtime again to use a different public key.
</p>
<code>{trust.fingerprint}</code>
</div>
{:else}
<form class="runtime-trust-form" onsubmit={saveTrustKey}>
<label for="runtime-workspace-trust-id-input">Workspace trust ID</label>
<input
id="runtime-workspace-trust-id-input"
bind:value={workspaceTrustId}
required
autocomplete="off"
spellcheck="false"
/>
<p>Copy the Runtime-issued <code>workspace_trust_id</code> from <code>trust-workspace add/show</code> for the current enrollment. Do not use a key fingerprint or a revoked enrollment ID.</p>
<form class="runtime-trust-form" onsubmit={saveTrustKey}>
<label for="runtime-public-key-input">Runtime public key</label>
<textarea
id="runtime-public-key-input"
@@ -548,8 +569,7 @@
{busyAction === 'trust' ? 'Saving…' : actionLabel(currentAction)}
</button>
</div>
</form>
{/if}
</form>
<div class="runtime-revoke-row">
<div>
@@ -10,7 +10,6 @@ import { afterEach, expect, test, vi } from "vitest";
import type { ComponentProps } from "svelte";
import RuntimeSettings from "./+page.svelte";
import RuntimeDetail from "./[runtimeId]/+page.svelte";
import { identityFixture } from "#lib/workspace/settings/identity.test-fixtures.ts";
const api = vi.hoisted(() => ({ create: vi.fn(), fingerprint: vi.fn() }));
vi.mock("$app/navigation", () => ({ invalidateAll: vi.fn(async () => {}) }));
@@ -27,14 +26,15 @@ afterEach(() => {
vi.resetAllMocks();
});
test("Runtime registration requires and preserves the Runtime-issued enrollment ID independently of the fingerprint", async () => {
test("Runtime registration uses public bundle without requiring copied Workspace enrollment identity", async () => {
api.fingerprint.mockResolvedValue("sha256:runtime-public-key");
api.create.mockResolvedValue({});
const data = {
workspaceId: "workspace-a",
workspace: { permissions: { manage_runtimes: true } },
runtimes: { items: [], diagnostics: [] },
signingIdentity: identityFixture("workspace-a"),
signingIdentity: null,
signingIdentityError: "Workspace public identity could not be loaded",
} as unknown as ComponentProps<typeof RuntimeSettings>["data"];
render(RuntimeSettings, {
props: { data, params: { workspaceId: "workspace-a" } },
@@ -61,11 +61,7 @@ test("Runtime registration requires and preserves the Runtime-issued enrollment
await waitFor(() =>
expect(screen.getByText("sha256:runtime-public-key")).toBeTruthy()
);
expect(submit).toHaveProperty("disabled", true);
expect(api.create).not.toHaveBeenCalled();
await fireEvent.input(screen.getByLabelText("Workspace trust ID"), {
target: { value: "Runtime-issued-enrollment-A" },
});
expect(screen.queryByLabelText("Workspace trust ID")).toBeNull();
expect(submit).toHaveProperty("disabled", false);
await fireEvent.submit(submit.closest("form")!);
await waitFor(() => expect(api.create).toHaveBeenCalledOnce());
@@ -74,14 +70,13 @@ test("Runtime registration requires and preserves the Runtime-issued enrollment
identity_id: "runtime-a",
public_key: "runtime-public-key",
},
workspace_trust_id: "Runtime-issued-enrollment-A",
display_name: null,
endpoint: "https://runtime.example",
expected_binding_id: null,
});
});
test("Runtime reactivation submits a new enrollment ID with the observed binding instead of reusing revoked trust", async () => {
test("Runtime reactivation uses the observed binding without copied Workspace identity", async () => {
api.fingerprint.mockResolvedValue("sha256:replacement-key");
api.create.mockResolvedValue({});
const data = {
@@ -100,10 +95,8 @@ test("Runtime reactivation submits a new enrollment ID with the observed binding
config_managed: true,
removable: true,
binding: {
state: "revoked",
connection_state: "revoked",
revoked_at: "2026-10-10T00:00:00Z",
binding_id: "observed-binding",
workspace_trust_id: "revoked-enrollment",
},
},
},
@@ -121,13 +114,7 @@ test("Runtime reactivation submits a new enrollment ID with the observed binding
params: { workspaceId: "workspace-a", runtimeId: "runtime-a" },
},
});
expect(screen.getByLabelText("Workspace trust ID")).toHaveProperty(
"value",
"",
);
await fireEvent.input(screen.getByLabelText("Workspace trust ID"), {
target: { value: "new-enrollment" },
});
expect(screen.queryByLabelText("Workspace trust ID")).toBeNull();
await fireEvent.input(screen.getByLabelText("Runtime public key"), {
target: { value: "yoi-ed25519-pub:v1:new-key" },
});
@@ -145,7 +132,6 @@ test("Runtime reactivation submits a new enrollment ID with the observed binding
identity_id: "runtime-a",
public_key: "yoi-ed25519-pub:v1:new-key",
},
workspace_trust_id: "new-enrollment",
display_name: "Runtime A",
endpoint: "https://runtime.example",
expected_binding_id: "observed-binding",
@@ -54,7 +54,8 @@ Deno.test("Repository SSH probe offers configured remote Runtimes regardless of
endpoint_configured: true,
endpoint_display: "https://arcadia.example",
binding: {
state: "verified",
binding_id: "observed-binding",
revoked_at: null,
},
},
} as unknown as WorkspaceRuntimeResource;
@@ -68,7 +69,10 @@ Deno.test("Repository SSH probe offers configured remote Runtimes regardless of
runtime_id: "revoked",
management: {
...configured.management,
binding: { state: "revoked" },
binding: {
binding_id: "revoked-binding",
revoked_at: "2026-10-10T00:00:00Z",
},
},
} as unknown as WorkspaceRuntimeResource;
const unbound = {
@@ -0,0 +1,308 @@
import { assert, assertEquals } from "jsr:@std/assert@1.0.19";
import { chromium } from "npm:playwright@1.59.1";
// Production static build plus loopback fixtures only; no live accounts or keys.
// --before retains the superseded wire shape solely for before/after visual evidence.
Deno.test("Runtime settings keep list, editing and testing accessible after signed-ping authentication failure", async () => {
const before = Deno.args.includes("--before");
const root = new URL("../../../../", import.meta.url).pathname;
const output = new URL(
`../../.svelte-kit/runtime-corrective/${before ? "before" : "after"}/`,
import.meta.url,
).pathname;
await Deno.mkdir(output, { recursive: true });
const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 });
const port = (listener.addr as Deno.NetAddr).port;
listener.close();
const base = `http://127.0.0.1:${port}`;
const server = new Deno.Command(Deno.execPath(), {
args: [
"run",
"--allow-net",
"--allow-read",
`${root}tools/web-ux/browser-tests/workspace_home_fixture_server.ts`,
String(port),
`${root}web/workspace/build`,
],
stdout: "null",
stderr: "null",
}).spawn();
try {
// Wait for an observable ready response, bounded by timeout.
await new Promise<void>((resolve, reject) => {
let stopped = false;
const deadline = setTimeout(() => {
stopped = true;
reject(new Error("fixture readiness timed out"));
}, 10000);
const probe = async () => {
if (stopped) return;
try {
const response = await fetch(`${base}/health`);
await response.body?.cancel();
if (response.ok) {
clearTimeout(deadline);
resolve();
return;
}
} catch { /* owned server is starting */ }
setTimeout(probe, 25);
};
void probe();
});
const browser = await chromium.launch({ headless: true });
const evidence: unknown[] = [];
try {
for (const colorScheme of ["light", "dark"] as const) {
for (const width of [1440, 768, 390]) {
for (const owner of [true, false]) {
const context = await browser.newContext({
viewport: { width, height: 900 },
colorScheme,
reducedMotion: "reduce",
});
try {
const page = await context.newPage();
const errors: string[] = [];
let documentStatus: number | null = null;
const navigate = async (url: string) => {
documentStatus = (await page.goto(url))?.status() ?? null;
assertEquals(documentStatus, 200);
};
page.on("response", (response) => {
if (response.status() >= 400) {
errors.push(`${response.status()} ${response.url()}`);
}
});
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()));
const workspace = owner ? "home-owner" : "home-member";
const binding = before
? {
binding_id: "binding-observed",
state: "configured",
connection_state: "unavailable",
workspace_key_id: "workspace-key",
workspace_trust_id: "enrollment",
}
: { binding_id: "binding-observed", revoked_at: null };
const runtime = {
runtime_id: "fixture-runtime",
label: "Development Runtime",
kind: "remote",
status: "unavailable",
source: {
kind: "remote_http",
status: "active",
identity_authority: "server_runtime_configuration",
note: "Fixture",
},
host_ids: [],
worker_creation_available: false,
os: "linux",
arch: "x86_64",
diagnostics: [{
code: "runtime_authentication_failed",
severity: "error",
message:
"The signed Runtime request was not authenticated. Update the endpoint or key and retry.",
}],
management: {
built_in: false,
config_managed: true,
removable: true,
endpoint_configured: true,
token_ref_configured: false,
binding,
},
};
await page.route(
"**/api/w/*/runtimes",
(route) =>
route.fulfill({
json: {
workspace_id: workspace,
limit: 200,
source: "fixture",
items: [runtime],
diagnostics: [],
},
}),
);
await page.route(
"**/api/w/*/runtimes/fixture-runtime",
(route) =>
route.fulfill({
json: {
workspace_id: workspace,
runtime,
endpoint: "https://runtime.example.test",
trust_key: {
status: "active",
binding_id: "binding-observed",
fingerprint: "sha256:runtime-public-key",
created_at: "2026-10-01T00:00:00Z",
updated_at: "2026-10-01T00:00:00Z",
revoked_at: null,
},
recent_audit: [],
},
}),
);
await page.route(
"**/api/w/*/runtimes/fixture-runtime/connection-tests",
(route) =>
route.fulfill({
json: {
workspace_id: workspace,
runtime_id: "fixture-runtime",
binding_id: "binding-observed",
...(before
? {
connection_state: "unavailable",
verification: null,
}
: {}),
checked_at: "2026-10-10T00:00:00Z",
status: "failed",
failure_kind: "authentication",
expected_protocol_version: 1,
actual_protocol_version: null,
diagnostics: [],
},
}),
);
const prefix = `${colorScheme}-${width}-${
owner ? "owner" : "member"
}`;
const capture = async (name: string) => {
await page.screenshot({
path: `${output}${prefix}-${name}.png`,
fullPage: true,
});
evidence.push({
colorScheme,
width,
owner,
name,
url: page.url(),
documentStatus,
screenshot: `${prefix}-${name}.png`,
errors: [...errors],
accessibility: await page.locator("main").ariaSnapshot(),
});
assertEquals(
await page.evaluate(
"document.documentElement.scrollWidth > innerWidth",
),
false,
prefix,
);
};
await navigate(`${base}/w/${workspace}/settings/runtimes`);
await page.getByRole("link", {
name: "Development Runtime",
exact: true,
}).waitFor();
await page.getByRole("button", { name: "Test", exact: true })
.click();
await page.getByText("Connection test: Authentication failed", {
exact: true,
}).waitFor();
await capture("list-failed");
assertEquals(
await page.getByRole("button", { name: "Test", exact: true })
.isEnabled(),
true,
);
if (owner) {
await page.getByRole("button", {
name: "Add Runtime",
exact: true,
}).click();
await page.getByLabel("Runtime public bundle", { exact: true })
.waitFor();
await capture("registration");
}
await navigate(
`${base}/w/${workspace}/settings/runtimes/fixture-runtime`,
);
await page.getByRole("heading", {
name: "Identity and binding",
exact: true,
}).waitFor();
await capture("detail");
if (!before) {
await page.getByRole("button", { name: "Test", exact: true })
.click();
await page.getByText("Signed ping failed · authentication", {
exact: true,
}).waitFor();
await capture("detail-failed");
assert(
await page.getByRole("button", { name: "Test", exact: true })
.isEnabled(),
);
}
assertEquals(
await page.getByRole("button", {
name: "Edit Runtime",
exact: true,
}).count(),
owner ? 1 : 0,
);
if (owner) {
await page.getByRole("button", {
name: "Edit Runtime",
exact: true,
}).click();
await page.getByLabel("Endpoint", { exact: true }).waitFor();
await capture("edit");
assert(
await page.getByRole("button", {
name: "Save Runtime settings",
exact: true,
}).isEnabled(),
);
if (!before) {
assertEquals(
await page.getByLabel("Workspace trust ID", { exact: true })
.count(),
0,
);
}
}
if (!before) {
Object.assign(binding, { revoked_at: "2026-10-10T00:00:00Z" });
await page.goto(`${base}/w/${workspace}/settings/runtimes`);
await page.getByRole("cell", { name: "Revoked", exact: true })
.waitFor();
assertEquals(
await page.getByRole("button", { name: "Test", exact: true })
.count(),
0,
);
await capture("revoked");
}
assertEquals(errors, []);
} finally {
await context.close();
}
}
}
}
} finally {
await browser.close();
}
await Deno.writeTextFile(
`${output}review-context.json`,
JSON.stringify(evidence, null, 2),
);
} finally {
server.kill("SIGTERM");
await server.status;
}
});
+24 -12
View File
@@ -20,8 +20,6 @@ function compatibleResponse(): Record<string, unknown> {
workspace_id: "workspace-a",
runtime_id: "runtime-a",
binding_id: "binding-3",
connection_state: "verified",
verification: null,
checked_at: "2026-09-01T12:00:00Z",
status: "compatible",
failure_kind: null,
@@ -63,16 +61,7 @@ Deno.test("runtime connection response rejects unknown fields and incoherent com
assertEquals(
parseRuntimeConnectionTestResponse({
...compatibleResponse(),
verification: {
verified_at: "2026-09-01T12:00:00Z",
last_checked_at: "2026-09-01T12:00:01Z",
last_outcome: "verified",
binding_id: "binding-2",
workspace_key_id: "WK-a",
workspace_public_key_fingerprint: "sha256:workspace",
workspace_trust_id: "trust-a",
runtime_public_key_fingerprint: "sha256:runtime",
},
verification: {},
}),
null,
);
@@ -120,3 +109,26 @@ Deno.test("runtime connection request rejects a mismatched response identity", a
"Connection test response did not match the selected Runtime",
);
});
Deno.test("runtime authentication failure remains an observed result without granting approval", async () => {
const failed = {
...compatibleResponse(),
status: "failed",
failure_kind: "authentication",
actual_protocol_version: null,
};
assertEquals(parseRuntimeConnectionTestResponse(failed), failed);
const fetched = await testRuntimeConnection(
"workspace-a",
"runtime-a",
(() => Promise.resolve(Response.json(failed))) as typeof fetch,
);
assertEquals(fetched, failed);
assertEquals(
parseRuntimeConnectionTestResponse({
...failed,
connection_state: "verified",
}),
null,
);
});
@@ -64,7 +64,7 @@ Deno.test("Runtime registration presents the complete multi-Workspace trust sequ
"yoi-runtime identity show --json",
"3. Register the connection",
"Register Runtime",
"Run Test to complete authenticated verification.",
"Run Test to observe a signed ping.",
]
) {
assert(
@@ -129,16 +129,14 @@ Deno.test("Runtime detail keeps trust controls owner-only and conflict-safe", as
const ownerGate = page.indexOf("data.workspace.permissions.manage_runtimes");
const reveal = page.indexOf("Reveal public key");
const verifiedKey = page.indexOf("{#if verifiedTrust}");
const mutation = page.indexOf('id="runtime-public-key-input"');
const metadataStart = page.indexOf("async function saveRuntimeMetadata");
const trustStart = page.indexOf("async function saveTrustKey");
const metadataMutation = page.slice(metadataStart, trustStart);
assert(ownerGate >= 0, "Runtime trust controls should use manage_runtimes");
assert(
verifiedKey >= 0 && verifiedKey < mutation &&
page.slice(verifiedKey, mutation).includes("{:else}"),
"verified Runtime public key must be read-only while unverified trust keeps key input",
!page.includes("verifiedTrust") && !page.includes("workspace_trust_id"),
"cached verification and copied enrollment identity must not gate key editing",
);
assert(
metadataMutation.includes("updateRemoteRuntime(") &&
@@ -150,7 +148,7 @@ Deno.test("Runtime detail keeps trust controls owner-only and conflict-safe", as
!metadataMutation.includes("publicKey") &&
!metadataMutation.includes("public_bundle") &&
!metadataMutation.includes("public_key"),
"verified Runtime metadata updates must not carry public key authority",
"Runtime metadata updates must not carry public key authority",
);
assert(
page.includes("Current fingerprint"),
@@ -195,7 +193,6 @@ Deno.test("Runtime detail keeps trust controls owner-only and conflict-safe", as
"saveRuntimeMetadata",
"Runtime settings",
"Edit Runtime",
"This verified key is read-only",
"publicKey = ''",
"requestError = null",
"successMessage = null",
@@ -246,3 +243,33 @@ Deno.test("Runtime detail uses flat sections instead of nested cards", async ()
"Runtime detail hierarchy should use flat section separators",
);
});
Deno.test("Runtime labels and recovery controls do not depend on persisted authentication approval", async () => {
for (const name of ["+page.svelte", "[runtimeId]/+page.svelte"]) {
const page = await Deno.readTextFile(
new URL(
"../src/routes/w/[workspaceId]/settings/runtimes/" + name,
import.meta.url,
),
);
for (
const field of [
"connection_state",
"verification",
"workspace_key_id",
"workspace_trust_id",
"Verification required",
]
) {
assert(!page.includes(field), "Runtime page retained " + field);
}
assert(
page.includes("revoked_at"),
"explicit revocation must remain visible",
);
assert(
page.includes("testRuntimeConnection("),
"actual test must remain available",
);
}
});
+246 -88
View File
@@ -2,6 +2,8 @@ declare const Deno: {
test(name: string, fn: () => void | Promise<void>): void;
};
import type { RepositoryApiError } from "../src/lib/generated/repository-api.ts";
import type { RuntimeManagementApiError } from "../src/lib/generated/runtime-api.ts";
import {
createRemoteRuntime,
parseRuntimeRemovalOperationResponse,
@@ -14,6 +16,7 @@ import {
revokeRuntimeTrustKey,
RuntimeRemovalAttempt,
RuntimeTrustConflictError,
RuntimeTrustRequestError,
RuntimeTrustRouteFence,
updateRemoteRuntime,
} from "../src/lib/workspace/api/runtime-management.ts";
@@ -44,21 +47,8 @@ function runtime() {
endpoint_configured: true,
token_ref_configured: false,
binding: {
state: "verified",
connection_state: "verified",
binding_id: "binding-3",
workspace_key_id: "WK-1",
workspace_trust_id: "trust-1",
verification: {
verified_at: "2026-09-01T13:00:00Z",
last_checked_at: "2026-09-01T13:00:00Z",
last_outcome: "verified",
binding_id: "binding-3",
workspace_key_id: "WK-1",
workspace_public_key_fingerprint: "sha256:workspace-key",
workspace_trust_id: "trust-1",
runtime_public_key_fingerprint: "SHA256:current",
},
revoked_at: null,
},
},
runtime_id: "arcadia",
@@ -117,8 +107,8 @@ Deno.test("Runtime list and detail parsers return generated Runtime DTO shapes",
);
assert(
list.items[0]?.management.binding?.state === "verified",
"binding state was not preserved",
list.items[0]?.management.binding?.binding_id === "binding-3",
"binding identity was not preserved",
);
const parsed = parseWorkspaceRuntimeDetail(detail());
@@ -142,9 +132,7 @@ Deno.test("Runtime list parser accepts the built-in Runtime's internal binding",
const binding = embedded.management.binding as Partial<
typeof embedded.management.binding
>;
delete binding.workspace_key_id;
delete binding.workspace_trust_id;
delete binding.verification;
delete binding.revoked_at;
const list = parseWorkspaceRuntimeList({
workspace_id: "workspace-a",
@@ -154,23 +142,51 @@ Deno.test("Runtime list parser accepts the built-in Runtime's internal binding",
diagnostics: [],
});
assert(
list.items[0]?.management.binding?.connection_state === "verified",
list.items[0]?.management.binding?.revoked_at === null,
"built-in Runtime binding was not preserved",
);
});
Deno.test("Runtime management parser rejects Workspace identity bindings without key metadata", () => {
const payload = detail();
const binding = payload.runtime.management.binding as Partial<
typeof payload.runtime.management.binding
Deno.test("Runtime list accepts migrated remote bindings without Workspace identity metadata", () => {
const migrated = runtime();
const binding = migrated.management.binding as Partial<
typeof migrated.management.binding
>;
delete binding.workspace_key_id;
delete binding.workspace_trust_id;
binding.state = "configured";
assertThrows(
() => parseWorkspaceRuntimeDetail(payload),
"requires Workspace signing key identity metadata",
delete binding.revoked_at;
const list = parseWorkspaceRuntimeList({
workspace_id: "workspace-a",
limit: 200,
items: [migrated],
source: "fixture",
diagnostics: [],
});
assert(
list.items[0]?.management.binding?.binding_id === "binding-3",
"migrated binding was rejected",
);
assert(
list.items[0]?.management.binding?.revoked_at === null,
"missing revocation should remain unrevoked",
);
});
Deno.test("Runtime binding rejects cached approval and copied Workspace identity fields", () => {
for (
const field of [
"state",
"connection_state",
"verification",
"workspace_key_id",
"workspace_trust_id",
]
) {
const payload = detail();
Object.assign(payload.runtime.management.binding, { [field]: "obsolete" });
assertThrows(
() => parseWorkspaceRuntimeDetail(payload),
"not part of the wire contract",
);
}
});
Deno.test("Runtime validators reject unknown object keys and enum variants", () => {
@@ -450,82 +466,225 @@ Deno.test("Runtime public key preview matches the Server fingerprint contract",
);
});
Deno.test("Runtime registration rejects empty or oversized enrollment identities before sending", async () => {
for (const enrollment of ["", "x".repeat(257)]) {
Deno.test("Runtime registration sends only Runtime connection data without Workspace identity copies", async () => {
const request = {
public_bundle: { identity_id: "arcadia", public_key: "test-key" },
endpoint: "https://runtime.example",
};
let body: unknown;
await createRemoteRuntime(
"workspace-a",
request,
((_input: string | URL | Request, init?: RequestInit) => {
body = JSON.parse(String(init?.body));
return Promise.resolve(Response.json(runtime()));
}) as typeof fetch,
);
assert(
JSON.stringify(body) === JSON.stringify(request),
"registration copied Workspace identity or approval fields",
);
});
Deno.test("Runtime trust replacement recognizes RepositoryApiError conflicts without retrying", async () => {
for (const errorKind of ["stale_binding", "fingerprint_in_use"] as const) {
for (
const diagnostics of [undefined, [], [{
code: errorKind,
severity: "error" as const,
message: "Reload Workspace trust before retrying.",
}]]
) {
const request = {
public_bundle: {
identity_id: "runtime-a",
public_key: "yoi-ed25519-pub:v1:test",
},
display_name: null,
endpoint: "https://runtime.example",
expected_binding_id: "observed-binding-id",
};
const conflict: RepositoryApiError = {
error: errorKind,
message: "Reload the authoritative Runtime trust.",
...(diagnostics === undefined ? {} : { diagnostics }),
};
let calls = 0;
const fetchImpl = ((input: RequestInfo | URL, init?: RequestInit) => {
calls++;
assert(
String(input) === "/api/w/workspace-a/runtimes",
"wrong trust route",
);
assert(
init?.body === JSON.stringify(request),
"observed binding ID changed",
);
return Promise.resolve(Response.json(conflict, { status: 409 }));
}) as typeof fetch;
try {
await createRemoteRuntime("workspace-a", request, fetchImpl);
throw new Error("expected trust replacement to reject");
} catch (error) {
assert(
error instanceof RuntimeTrustConflictError,
`binding conflict was hidden: ${String(error)}`,
);
assert(
error.message === conflict.message &&
JSON.stringify(error.conflict) === JSON.stringify(conflict),
"RepositoryApiError conflict was changed",
);
}
assert(calls === 1, "trust replacement was retried");
}
}
});
Deno.test("Runtime revoke preserves typed conflict binding identities and diagnostics", async () => {
const conflict: RuntimeManagementApiError = {
error: "stale_binding",
message: "Reload the authoritative Runtime trust.",
current_binding_id: "current-binding-id",
current_fingerprint: "sha256:current-key",
diagnostics: [],
};
try {
await revokeRuntimeTrustKey(
"workspace-a",
"runtime-a",
{ expected_binding_id: "observed-binding-id" },
"sha256:observed-key",
"sha256:observed-key",
() => Promise.resolve(Response.json(conflict, { status: 409 })),
);
throw new Error("expected revoke to reject");
} catch (error) {
assert(
error instanceof RuntimeTrustConflictError,
"revoke conflict was hidden",
);
assert(
JSON.stringify(error.conflict) === JSON.stringify(conflict),
"typed revoke conflict identity was changed",
);
}
});
Deno.test("Runtime create surfaces other RepositoryApiError 409 messages as request errors", async () => {
for (
const diagnostics of [undefined, [], [{
code: "runtime_binding_replacement_blocked",
severity: "error" as const,
message: "An active Worker occupies this Runtime.",
}]]
) {
const payload: RepositoryApiError = {
error: "conflict",
message: "Runtime binding replacement is blocked by an active Worker.",
...(diagnostics === undefined ? {} : { diagnostics }),
};
let calls = 0;
try {
await createRemoteRuntime("workspace-a", {
public_bundle: { identity_id: "runtime-a", public_key: "test-key" },
workspace_trust_id: enrollment,
endpoint: "https://runtime.example",
}, (() => { calls++; throw new Error("unexpected request"); }) as typeof fetch);
throw new Error("expected enrollment validation to reject");
}, () => {
calls++;
return Promise.resolve(Response.json(payload, { status: 409 }));
});
throw new Error("expected occupancy conflict to reject");
} catch (error) {
assert(error instanceof Error && error.message.includes("workspace_trust_id"), "enrollment validation was bypassed");
}
assert(calls === 0, "invalid enrollment reached the Server");
}
});
Deno.test("Runtime trust replacement preserves binding conflicts without retrying", async () => {
for (const errorKind of ["stale_binding", "fingerprint_in_use"] as const) {
const request = {
public_bundle: {
identity_id: "runtime-a",
public_key: "yoi-ed25519-pub:v1:test",
},
display_name: null,
endpoint: "https://runtime.example",
workspace_trust_id: "runtime-issued-enrollment-id",
expected_binding_id: "observed-binding-id",
};
const conflict = {
error: errorKind,
message: "Reload the authoritative Runtime trust.",
current_binding_id: "current-binding-id",
current_fingerprint: "sha256:current-key",
};
let calls = 0;
const fetchImpl = ((input: RequestInfo | URL, init?: RequestInit) => {
calls++;
assert(String(input) === "/api/w/workspace-a/runtimes", "wrong trust route");
assert(init?.body === JSON.stringify(request), "observed binding ID changed");
return Promise.resolve(Response.json(conflict, { status: 409 }));
}) as typeof fetch;
try {
await createRemoteRuntime("workspace-a", request, fetchImpl);
throw new Error("expected trust replacement to reject");
} catch (error) {
assert(error instanceof RuntimeTrustConflictError, "binding conflict was hidden");
assert(
JSON.stringify(error.conflict) === JSON.stringify(conflict),
"authoritative conflict identity was changed",
error instanceof RuntimeTrustRequestError,
"occupancy error was misclassified",
);
assert(error.message === payload.message, "occupancy message was hidden");
}
assert(calls === 1, "trust replacement was retried");
assert(calls === 1, "occupancy conflict was retried");
}
});
Deno.test("Runtime trust replacement rejects unknown fields and missing binding identity in conflict responses", async () => {
const fetchImpl = (() => Promise.resolve(Response.json({
error: "stale_binding",
message: "Stale trust",
unexpected_field: true,
}, { status: 409 }))) as typeof fetch;
Deno.test("Runtime create bounds legitimate RepositoryApiError 409 messages", async () => {
const message = "Runtime is occupied. ".repeat(30);
try {
await createRemoteRuntime("workspace-a", {
public_bundle: { identity_id: "runtime-a", public_key: "test-key" },
endpoint: "https://runtime.example",
workspace_trust_id: "runtime-issued-enrollment-id",
expected_binding_id: "observed-binding-id",
}, fetchImpl);
throw new Error("expected malformed conflict to reject");
}, () =>
Promise.resolve(Response.json(
{
error: "conflict",
message,
} satisfies RepositoryApiError,
{ status: 409 },
)));
throw new Error("expected occupancy conflict to reject");
} catch (error) {
assert(!(error instanceof RuntimeTrustConflictError), "legacy conflict was accepted");
assert(
error instanceof Error && error.message === "Runtime trust conflict response was invalid",
"malformed conflict did not fail closed",
error instanceof RuntimeTrustRequestError,
"occupancy error was misclassified",
);
assert(
error.message === message.slice(0, 256),
"bounded backend message was lost",
);
}
});
Deno.test("Runtime create rejects malformed RepositoryApiError conflicts without exposing their message", async () => {
const validDiagnostic = {
code: "conflict",
severity: "error",
message: "detail",
};
for (const errorKind of ["stale_binding", "fingerprint_in_use", "conflict"]) {
const valid = {
error: errorKind,
message: "Do not expose malformed payload text",
};
for (
const payload of [
{ ...valid, unexpected_field: true },
{ ...valid, current_binding_id: "not-in-creation-contract" },
{ ...valid, current_fingerprint: "not-in-creation-contract" },
{ ...valid, error: 409 },
{ ...valid, error: "" },
{ ...valid, error: "x".repeat(257) },
{ error: errorKind },
{ ...valid, message: null },
{ ...valid, message: "" },
{ ...valid, message: "é".repeat(513) },
{ ...valid, diagnostics: null },
{ ...valid, diagnostics: {} },
{ ...valid, diagnostics: Array(65).fill(validDiagnostic) },
{
...valid,
diagnostics: [{ ...validDiagnostic, severity: "unknown" }],
},
{ ...valid, diagnostics: [{ ...validDiagnostic, code: 1 }] },
{ ...valid, diagnostics: [{ ...validDiagnostic, extra: true }] },
{
...valid,
diagnostics: [{ ...validDiagnostic, message: "x".repeat(2049) }],
},
]
) {
try {
await createRemoteRuntime("workspace-a", {
public_bundle: { identity_id: "runtime-a", public_key: "test-key" },
endpoint: "https://runtime.example",
expected_binding_id: "observed-binding-id",
}, () => Promise.resolve(Response.json(payload, { status: 409 })));
throw new Error("expected malformed conflict to reject");
} catch (error) {
assert(
error instanceof RuntimeTrustRequestError &&
error.message === "Runtime trust conflict response was invalid",
`malformed conflict did not fail closed: ${JSON.stringify(payload)}`,
);
}
}
}
});
@@ -551,7 +710,6 @@ Deno.test("Runtime create surfaces bounded Settings error details", async () =>
},
display_name: null,
endpoint: "https://runtime.example",
workspace_trust_id: "runtime-issued-enrollment-id",
expected_binding_id: null,
},
fetchImpl,