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
@@ -0,0 +1,65 @@
// Extends the existing Web UX fixture at the API boundary, never the production shell/CSS.
// Both loopback servers are owned and cleaned up by the Web UX capture harness.
const port = Number(Deno.args[0]);
const upstream = Deno.args[1];
if (!Number.isInteger(port) || !upstream) {
throw new Error("usage: server <port> <upstream-url>");
}
import { emptyLaunchOptions } from "../../src/lib/workspace/sidebar/worker-launch.test-fixtures.ts";
import { dashboardFixture } from "../../src/lib/workspace/home/dashboard.test-fixtures.ts";
Deno.serve({ hostname: "127.0.0.1", port }, async (request) => {
const url = new URL(request.url);
if (url.pathname === "/api/workspaces" && request.method === "POST") {
return Response.json({
message:
"Workspace display name was rejected by Backend validation. Choose another display name and retry; no Workspace was created.",
}, { status: 400 });
}
const match = url.pathname.match(/^\/api\/w\/([^/]+)(\/.*)$/);
if (match) {
const [, id, path] = match;
// The shared catalog includes a deliberate failure persona, which can be hover-preloaded.
// This scenario tests zero repositories, not that separate failure contract.
if (id === "home-error" && path === "/merge-requests") {
return Response.json(
dashboardFixture(path, url.searchParams, "home-empty"),
);
}
if (path === "/workers") {
return Response.json({
workspace_id: id,
limit: 100,
items: [],
source: "fixture",
diagnostics: [],
});
}
if (path === "/workers/launch-options") {
return Response.json(emptyLaunchOptions(id));
}
}
// Forward HTTP and bridge the existing workspace subscription fixture.
const target = new URL(url.pathname + url.search, upstream);
if (request.headers.get("upgrade") === "websocket") {
const { socket, response } = Deno.upgradeWebSocket(request);
target.protocol = "ws:";
const peer = new WebSocket(target);
const pending: string[] = [];
socket.onmessage = (event) =>
peer.readyState === WebSocket.OPEN
? peer.send(event.data)
: pending.push(String(event.data));
peer.onopen = () => {
for (const frame of pending) peer.send(frame);
};
peer.onmessage = (event) => {
if (socket.readyState === WebSocket.OPEN) socket.send(event.data);
};
socket.onclose = () => peer.close();
peer.onclose = () => socket.close();
return response;
}
return await fetch(new Request(target, request));
});