feat: support workspace-managed SSH repository access

This commit is contained in:
2026-09-11 22:55:06 +09:00
parent 9d7ddcc04a
commit f6ce1df766
27 changed files with 3511 additions and 369 deletions
@@ -2,6 +2,7 @@ import {
parseRepositoryAccessProjection,
parseRepositorySshCredentials,
parseRepositorySshHostTrusts,
parseRepositorySshPublicKey,
RepositoryAccessSchemaError,
} from "../../src/lib/workspace/api/repository-access.ts";
@@ -59,6 +60,22 @@ const hostTrust = {
Deno.test("Repository Access parsers accept generated response contracts", () => {
assertEquals(parseRepositorySshCredentials([credential]), [credential]);
assertEquals(
parseRepositorySshPublicKey({
credential_id: "deploy-key",
current_revision: 2,
public_key_algorithm: "ssh-ed25519",
public_key_fingerprint: "SHA256:credential",
public_key: "ssh-ed25519 AAAA",
}),
{
credential_id: "deploy-key",
current_revision: 2,
public_key_algorithm: "ssh-ed25519",
public_key_fingerprint: "SHA256:credential",
public_key: "ssh-ed25519 AAAA",
},
);
assertEquals(parseRepositorySshHostTrusts([hostTrust]), [hostTrust]);
assertEquals(
parseRepositoryAccessProjection({
@@ -28,6 +28,7 @@ test("Repository Access Web code consumes workspace-api generated DTOs", () => {
assert(
loaderSource.includes("parseRepositorySshCredentials") &&
loaderSource.includes("parseRepositorySshHostTrusts") &&
loaderSource.includes("parseRepositorySshPublicKey") &&
loaderSource.includes("parseRepositoryAccessProjection"),
"loader should validate unknown JSON before exposing generated DTOs to Svelte",
);
@@ -66,6 +67,29 @@ test("Repository Access renders the shared access projection fields", () => {
}
});
test("Repository Access generates and copies selectable public keys", () => {
for (
const token of [
"/credentials/generate",
"/public-key",
"Generate Repository SSH credential",
"navigator.clipboard.writeText",
"publicKeys[credential.credential_id]",
"workspace-default",
"always offered during SSH clone",
]
) {
assert(
source.includes(token),
`missing generated public key flow ${token}`,
);
}
assert(
source.includes("binding.credential_id"),
"Repository bindings should identify the selected credential",
);
});
test("Repository credential submissions clear write-only fields in finally blocks", () => {
const createStart = source.indexOf("async function createCredential()");
const rotateStart = source.indexOf("async function rotateCredential(");
@@ -0,0 +1,106 @@
import { assert, assertEquals } from "jsr:@std/assert";
import type { WorkspaceRuntimeResource } from "../src/lib/generated/workspace-api.ts";
import { parseRepositorySshConnectionProbeResponse } from "../src/lib/workspace/api/workspace-model.ts";
import { repositorySshProbeRuntimes } from "../src/lib/workspace/repositories/ssh-connection.ts";
const root = new URL("../", import.meta.url);
const pageSource = await Deno.readTextFile(
new URL(
"./src/routes/w/[workspaceId]/repositories/[repositoryKey]/+page.svelte",
root,
),
);
const loaderSource = await Deno.readTextFile(
new URL(
"./src/routes/w/[workspaceId]/repositories/[repositoryKey]/+page.ts",
root,
),
);
Deno.test("Repository SSH probe parser preserves the confirmation contract", () => {
const response = {
workspace_id: "workspace-a",
repository_key: "main",
runtime_id: "runtime-a",
hostname: "example.test",
port: 22,
trust_state: "untrusted" as const,
host_trust_id: "tofu-example.test-22",
expected_host_trust_revision: null,
candidates: [
{
algorithm: "ssh-ed25519",
host_key: "ssh-ed25519 AAAA",
fingerprint: "SHA256:host",
},
],
};
assertEquals(parseRepositorySshConnectionProbeResponse(response), response);
});
Deno.test("Repository SSH probe offers configured remote Runtimes regardless of worker-style status", () => {
const configured = {
runtime_id: "arcadia",
label: "Arcadia",
kind: "remote_worker_runtime",
status: "idle",
diagnostics: [],
management: {
endpoint_configured: true,
endpoint_display: "https://arcadia.example",
binding: {
state: "verified",
},
},
} as unknown as WorkspaceRuntimeResource;
const embedded = {
...configured,
runtime_id: "embedded-worker-runtime",
kind: "embedded_worker_runtime",
} as unknown as WorkspaceRuntimeResource;
const revoked = {
...configured,
runtime_id: "revoked",
management: {
...configured.management,
binding: { state: "revoked" },
},
} as unknown as WorkspaceRuntimeResource;
const unbound = {
...configured,
runtime_id: "unbound",
management: {
...configured.management,
binding: undefined,
},
} as unknown as WorkspaceRuntimeResource;
assertEquals(
repositorySshProbeRuntimes([embedded, configured, revoked, unbound]).map((
runtime,
) => runtime.runtime_id),
["arcadia"],
);
});
Deno.test("Repository SSH connection test requires an explicit host-key confirmation", () => {
for (
const token of [
"Check SSH connection",
"selectedRuntimeId",
"candidate.fingerprint",
"Confirm and trust selected host key",
"expected_host_trust_revision",
"requestConnectionTest('POST'",
"requestConnectionTest('PUT'",
]
) {
assert(pageSource.includes(token), `missing SSH connection flow ${token}`);
}
});
Deno.test("Repository detail loads configured Workspace Runtimes for the connection test", () => {
assert(loaderSource.includes('workspaceApiPath(workspaceId, "/runtimes")'));
assert(loaderSource.includes("parseWorkspaceRuntimeList"));
});