feat(workspace): allow creation without an initial repository

Keep bootstrap, signing identity, config and resource-key allocation atomic with optional Repository intent. Preserve repository-present fingerprints for saved receipts; replay reads current authority at the initial key and never recreates missing assets.

Provide explicit repository-free CLI creation, optional TUI/Web creation inputs, nullable generated contracts, and valid empty catalog/Worker paths. Retain existing authenticated non-owner viewing policy pending the T-717 authorization decision.

Validation: root cargo check; server tests 639 lib + 11 bin; server-api 70 default / 77 typescript; client 52 lib + 2 integration; tui 337; yoi 11 lib + 120 bin. OpenAPI/TS freshness, cargo fmt and diff checks pass. Web check/build, 18 model tests, 268 component tests and Chromium 8 width/theme combinations pass; before/after visual evidence is documented in web/workspace/test/visual-review. Full Web suite has one independently reproduced baseline Ticket source-policy failure.
This commit is contained in:
2026-10-08 18:34:44 +09:00
parent e939cd4e4b
commit 7919616db5
37 changed files with 2335 additions and 354 deletions
+20 -29
View File
@@ -1,12 +1,7 @@
declare const Deno: {
test(name: string, fn: () => void | Promise<void>): void;
readTextFile(path: URL): Promise<string>;
};
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}
function assertEquals(actual: unknown, expected: unknown): void {
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
throw new Error(
@@ -35,6 +30,26 @@ import {
loadWorkspaceCatalog,
WorkspaceCatalogError,
} from "../src/lib/workspace/api/workspace-catalog.ts";
import { creationResponse } from "../src/lib/workspace/api/workspace-catalog.test-fixtures.ts";
Deno.test("workspace creation accepts omitted and null repository requests without fabricating a repository", async () => {
for (const repository of [undefined, null]) {
const request = {
operation_key: "empty-create",
display_name: "New Workspace",
...(repository === null ? { repository } : {}),
};
const fetcher = async (url: string | URL | Request, init?: RequestInit) => {
assertEquals(url, "/api/workspaces");
assertEquals(init?.method, "POST");
assertEquals(JSON.parse(String(init?.body)), request);
return Response.json(creationResponse());
};
const response = await createWorkspace(fetcher as typeof fetch, request);
assertEquals(response.repository, null);
assertEquals(response.workspace.workspace_id, "home-empty");
}
});
Deno.test("workspace catalog enriches each visible workspace without dropping siblings", async () => {
const fetcher = (input: string | URL | Request) => {
@@ -118,27 +133,3 @@ Deno.test("workspace creation preserves caller-owned operation key across retry"
);
assertEquals(bodies, [request, request]);
});
Deno.test("workspace creation form starts with an explicit required Repository key", async () => {
const source = await Deno.readTextFile(
new URL("../src/routes/+page.svelte", import.meta.url),
);
assert(
source.includes('let repositoryKey = $state("");'),
"the form must start without an implicit Repository key",
);
assert(
source.includes("<input bind:value={repositoryKey} required"),
"the Browser must require creator input for the Repository key",
);
assert(
source.includes("repository_key: normalized.repositoryKey"),
"the Browser must submit the creator-provided Repository key",
);
assert(
!source.includes('repositoryKey || "main"') &&
!source.includes("repositoryKey || 'main'"),
"the Browser must not fall back to main",
);
});