diff --git a/web/workspace/src/lib/workspace/settings/model.test.ts b/web/workspace/src/lib/workspace/settings/model.test.ts index e00f5721..a8ef3082 100644 --- a/web/workspace/src/lib/workspace/settings/model.test.ts +++ b/web/workspace/src/lib/workspace/settings/model.test.ts @@ -38,14 +38,36 @@ Deno.test("settings section navigation stays under the settings route", () => { } }); -Deno.test("settings shell advertises no fake browser admin model", () => { +Deno.test("settings shell advertises scoped account authority", () => { assert( - SETTINGS_PERMISSION_NOTICE.includes("no browser user, role, permission"), - "notice should explicitly deny a browser permission model", + SETTINGS_PERMISSION_NOTICE.includes("authenticated account authority"), + "notice should identify authenticated account authority", ); assert( - SETTINGS_PERMISSION_NOTICE.includes("does not create an admin role"), - "notice should not imply an admin role exists", + SETTINGS_PERMISSION_NOTICE.includes("current Workspace owner"), + "notice should state the Repository secret permission boundary", + ); + assert( + SETTINGS_PERMISSION_NOTICE.includes("does not expose secret material"), + "notice should not imply that stored secret material is readable", + ); +}); + +Deno.test("Repository access settings are editable and canonically routed", () => { + const section = SETTINGS_SECTIONS.find((entry) => + entry.id === "repository-access" + ); + assert( + section?.status === "editable", + "Repository Access should be editable", + ); + assert( + settingsSectionHref("repository-access") === "/settings/repository-access", + "Repository Access should have a dedicated settings route", + ); + assert( + section?.bullets.join("\n").includes("write-only"), + "Repository Access copy should preserve write-only secret semantics", ); }); diff --git a/web/workspace/src/lib/workspace/settings/model.ts b/web/workspace/src/lib/workspace/settings/model.ts index 2a89efd8..ed76fbf6 100644 --- a/web/workspace/src/lib/workspace/settings/model.ts +++ b/web/workspace/src/lib/workspace/settings/model.ts @@ -8,6 +8,7 @@ export type SettingsSectionId = | "runtime-connections" | "runtime-inventory" | "configuration-sources" + | "repository-access" | "profile-sources" | "backend-config" | "workspace-identity"; @@ -72,7 +73,7 @@ export type RemoteRuntimeTestResponse = { export const SETTINGS_ROUTE = "/settings"; export const SETTINGS_PERMISSION_NOTICE = - "Yoi currently has no browser user, role, permission, or multi-user authorization model. This local settings surface uses typed Backend APIs only; it does not create an admin role or grant broad mutation authority."; + "Workspace settings use authenticated account authority and Workspace-scoped typed Backend APIs. Repository secret management requires the current Workspace owner; this surface does not expose secret material or grant Runtime execution authority."; export const SETTINGS_SECTIONS: readonly SettingsSection[] = [ { @@ -111,6 +112,18 @@ export const SETTINGS_SECTIONS: readonly SettingsSection[] = [ "Profile launch data is projected from this active revision; remaining Skill, Prompt, and Plugin consumers migrate in their follow-up cutovers.", ], }, + { + id: "repository-access", + label: "Repository Access", + status: "editable", + summary: + "Manage Workspace-scoped SSH credentials and pinned host keys without exposing stored secret material.", + bullets: [ + "Private keys and passphrases are write-only; list and detail responses contain public metadata only.", + "Host trust requires an explicitly pinned key and never uses accept-new or TOFU.", + "Repository bindings are committed through the shared Workspace configuration editor and validated against these records.", + ], + }, { id: "profile-sources", label: "Profile Sources", @@ -175,6 +188,8 @@ export function settingsSectionHref(id: SettingsSectionId): string { return `${SETTINGS_ROUTE}/runtimes`; case "configuration-sources": return `${SETTINGS_ROUTE}/configuration`; + case "repository-access": + return `${SETTINGS_ROUTE}/repository-access`; case "profile-sources": return `${SETTINGS_ROUTE}/profiles`; case "workspace-identity": diff --git a/web/workspace/src/routes/w/[workspaceId]/repositories/[repositoryId]/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/repositories/[repositoryId]/+page.svelte index c06eb9c8..df07104d 100644 --- a/web/workspace/src/routes/w/[workspaceId]/repositories/[repositoryId]/+page.svelte +++ b/web/workspace/src/routes/w/[workspaceId]/repositories/[repositoryId]/+page.svelte @@ -31,6 +31,10 @@
owner only
Manage Workspace-scoped SSH credentials and pinned host keys. Private keys and passphrases are write-only and never returned by this page.
+ {#if message}{/if} + +No credentials configured.
{/if} + {#each credentials as credential (credential.credential_id)} +{credential.credential_id}
+ {credential.public_key_algorithm} · {credential.public_key_fingerprint} · revision {credential.current_revision}
+References: {credential.referenced_repositories.join(', ') || 'none'}
+No host trust records configured.
{/if} + {#each hostTrusts as hostTrust (hostTrust.host_trust_id)} +{hostTrust.host_trust_id}
+ {hostTrust.key_algorithm} · {hostTrust.fingerprint} · revision {hostTrust.current_revision}
+References: {hostTrust.referenced_repositories.join(', ') || 'none'}
+