merge: compose current auth migration with Ticket case schema 91

This commit is contained in:
2026-10-11 02:21:52 +09:00
64 changed files with 5196 additions and 4551 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:6f5ab2a613fca9996680d3a08962dc580563457811e68679574ff4325c86ec4e)
// Input contract: 0.1.0 (sha256:2e8dcd75d5d7b70c5d820999afcabf3d1377896732c7a3e8c3839e73a994974c)
// 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:6f5ab2a613fca9996680d3a08962dc580563457811e68679574ff4325c86ec4e)
// Input contract: 0.1.0 (sha256:2e8dcd75d5d7b70c5d820999afcabf3d1377896732c7a3e8c3839e73a994974c)
// 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:6f5ab2a613fca9996680d3a08962dc580563457811e68679574ff4325c86ec4e)
// Input contract: 0.1.0 (sha256:2e8dcd75d5d7b70c5d820999afcabf3d1377896732c7a3e8c3839e73a994974c)
// 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:6f5ab2a613fca9996680d3a08962dc580563457811e68679574ff4325c86ec4e)
// Input contract: 0.1.0 (sha256:2e8dcd75d5d7b70c5d820999afcabf3d1377896732c7a3e8c3839e73a994974c)
// 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-workdir-openapi-typescript@1. Do not edit.
// Input: openapi/server-api.json
// Input contract: 0.1.0 (sha256:6f5ab2a613fca9996680d3a08962dc580563457811e68679574ff4325c86ec4e)
// Input contract: 0.1.0 (sha256:2e8dcd75d5d7b70c5d820999afcabf3d1377896732c7a3e8c3839e73a994974c)
// Options: workdir-operation-schema-closure,openapi-3.1,strict-integer-ranges,sorted-output
// Output: web/workspace/src/lib/generated/workdir-api.ts
// Regenerate/check: cargo run -q -p server-api --example generate_workdir_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:6f5ab2a613fca9996680d3a08962dc580563457811e68679574ff4325c86ec4e)
// Input contract: 0.1.0 (sha256:2e8dcd75d5d7b70c5d820999afcabf3d1377896732c7a3e8c3839e73a994974c)
// 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,