diff --git a/experimental/papyrus/server-tests/api.e2e.test.ts b/experimental/papyrus/server-tests/api.e2e.test.ts index 84b8516..9134fe0 100644 --- a/experimental/papyrus/server-tests/api.e2e.test.ts +++ b/experimental/papyrus/server-tests/api.e2e.test.ts @@ -80,7 +80,8 @@ describe("papyrus routes → silverwood (in-process, skip-mode)", () => { jsonInit("POST", { name: "blank", path: ["local-blank"], args: [] }), ); expect(status).toBe(200); - expect(body.checkoutState).toBe("none"); + // Checkout state is Basic-only, so a checkout-less kind reports none (undefined, omitted). + expect(body.checkoutState).toBeUndefined(); const ns = await nodes(); expect(ns).toHaveLength(1); diff --git a/experimental/papyrus/server-tests/silverwood-contract.test.ts b/experimental/papyrus/server-tests/silverwood-contract.test.ts index 919de92..a098f12 100644 --- a/experimental/papyrus/server-tests/silverwood-contract.test.ts +++ b/experimental/papyrus/server-tests/silverwood-contract.test.ts @@ -26,7 +26,7 @@ describe("silverwood wrapper contract (skip-mode, no network)", () => { const ws = await newSkipWs("alpha"); expect(ws.name).toBe("alpha"); expect(ws.status).toBe("active"); - expect(sw.checkoutState(ws)).toBe("initialized-without-checkout"); + expect(sw.basicCheckoutState(ws)).toBe("initialized-without-checkout"); const got = await sw.get(ws.id); expect(got.id).toBe(ws.id); @@ -137,7 +137,8 @@ describe("silverwood wrapper contract (skip-mode, no network)", () => { const ws = await sw.create({ name: "blank", path: ["local-blank"], args: [] }); expect(ws.kind).toBe("local-blank"); expect(ws.mode).toBeUndefined(); - expect(sw.checkoutState(ws)).toBe("none"); + // Checkout state is Basic-only; a checkout-less kind has none (not a fabricated "none"). + expect(sw.basicCheckoutState(ws)).toBeUndefined(); expect(sw.checkoutLocation(ws)).toBeTruthy(); // Round-trips through get, and an empty blank removes cleanly (ground truth via CLI). diff --git a/experimental/papyrus/server/routes/api.ts b/experimental/papyrus/server/routes/api.ts index 38e6283..85d7b9c 100644 --- a/experimental/papyrus/server/routes/api.ts +++ b/experimental/papyrus/server/routes/api.ts @@ -86,7 +86,7 @@ apiRoutes.get("/session-schema", async (c) => { async function buildNode(ws: sw.Workstream) { const kv = ws.kv?.[sw.PAPYRUS_NS] || {}; const cwd = sw.checkoutLocation(ws) || ""; - const cstate = sw.checkoutState(ws); + const cstate = sw.basicCheckoutState(ws); // Live PTYs belonging to this workstream, from the registry. const runtimes = [...sessions].filter(([, s]) => s.workstreamId === ws.id); @@ -335,7 +335,7 @@ apiRoutes.post("/sessions", async (c) => { sessionId: ws.id, nodeId: ws.id, cwd, - checkoutState: sw.checkoutState(ws), + checkoutState: sw.basicCheckoutState(ws), }); }); @@ -361,8 +361,8 @@ apiRoutes.post("/sessions/:wsId/sessions/connect", async (c) => { return c.json({ error: e.message }, 404); } const cwd = sw.checkoutLocation(ws) || ""; - const checkoutState = sw.checkoutState(ws); - if (!cwd || (checkoutState !== "ready" && checkoutState !== "none")) { + const checkoutState = sw.basicCheckoutState(ws); + if (!cwd || (checkoutState !== "ready" && checkoutState !== undefined)) { return c.json({ error: "checkout not ready" }, 400); } @@ -438,8 +438,8 @@ apiRoutes.post("/sessions/:wsId/sessions", async (c) => { return c.json({ error: e.message }, 404); } const cwd = sw.checkoutLocation(ws) || ""; - const checkoutState = sw.checkoutState(ws); - if (!cwd || (checkoutState !== "ready" && checkoutState !== "none")) { + const checkoutState = sw.basicCheckoutState(ws); + if (!cwd || (checkoutState !== "ready" && checkoutState !== undefined)) { return c.json({ error: "checkout not ready" }, 400); } const sessionId = randomUUID(); diff --git a/experimental/papyrus/server/services/silverwood.ts b/experimental/papyrus/server/services/silverwood.ts index 1691375..aafd1b7 100644 --- a/experimental/papyrus/server/services/silverwood.ts +++ b/experimental/papyrus/server/services/silverwood.ts @@ -274,11 +274,12 @@ export function checkoutLocation(ws: Workstream): string | undefined { return ws.location?.within.path || undefined; } -/// The provisioning state to surface to the canvas (carried by the checkout mode). -export function checkoutState( - ws: Workstream, -): "initialized-without-checkout" | "ready" | "pending" | "failed" | "none" { - return ws.mode?.state ?? "none"; +/// The provisioning state of a **Basic** workstream's checkout, or `undefined` for a kind +/// that has none (the `local-*` kinds). This is a Basic-only property — the checkout +/// lifecycle — not a universal workstream state, so a checkout-less kind reports `undefined` +/// rather than a fabricated "none". Surfaced to color the canvas node (pending/failed). +export function basicCheckoutState(ws: Workstream): CheckoutMode["state"] | undefined { + return ws.kind === "basic" ? ws.mode?.state : undefined; } /// Read a papyrus KV value, JSON-decoded, or `undefined` if absent/garbage.