From 756aa00e1f9139945d8b3111031a746ca9245fb7 Mon Sep 17 00:00:00 2001 From: rabbitblood Date: Sun, 26 Apr 2026 07:12:44 -0700 Subject: [PATCH] =?UTF-8?q?refactor(canvas):=20remove=20RUNTIME=5FPROFILES?= =?UTF-8?q?.hermes=20=E2=80=94=20value=20flows=20server-side=20now=20(#205?= =?UTF-8?q?4=20phase=203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the canvas-side loop on #2054. Phases 1+2 plumbed provision_timeout_ms from template manifest → workspace API → canvas socket → node-data → ProvisioningTimeout resolver. The template-hermes manifest declares provision_timeout_seconds: 720 (filed as a separate template-repo PR). With that flow live, the canvas-side hardcoded RUNTIME_PROFILES.hermes entry is redundant. Removed: - RUNTIME_PROFILES.hermes (was 720000ms hardcoded in canvas/src/lib/runtimeProfiles.ts) Doc updates: - RUNTIME_PROFILES jsdoc explains the map is now empty by design — new runtimes that need a non-default cold-boot threshold should declare runtime_config.provision_timeout_seconds in their template manifest, NOT add an entry here. Tests updated (3): - "returns hermes override when runtime = hermes" → "hermes returns default — value moved server-side post-#2054 phase 3". Asserts RUNTIME_PROFILES.hermes is undefined. - The two server-override tests now compare against DEFAULT_RUNTIME_PROFILE since hermes no longer has a profile entry. 19/19 pass locally. The end-state for hermes: workspace-server reads template manifest at request time → workspace API includes provision_timeout_ms: 720000 → canvas hydrate populates node.data.provisionTimeoutMs → ProvisioningTimeout resolver picks it up via overrides. Same effective threshold (720s), now declarative and one-edit-point per runtime. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../__tests__/ProvisioningTimeout.test.tsx | 42 ++++++++++++------- canvas/src/lib/runtimeProfiles.ts | 24 ++++++----- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/canvas/src/components/__tests__/ProvisioningTimeout.test.tsx b/canvas/src/components/__tests__/ProvisioningTimeout.test.tsx index dedb1fb3..43f42d7b 100644 --- a/canvas/src/components/__tests__/ProvisioningTimeout.test.tsx +++ b/canvas/src/components/__tests__/ProvisioningTimeout.test.tsx @@ -226,13 +226,18 @@ describe("ProvisioningTimeout", () => { ); }); - it("returns hermes override when runtime = hermes", () => { + it("hermes returns default — value moved server-side post-#2054 phase 3", () => { + // RUNTIME_PROFILES.hermes was removed when template-hermes + // started declaring provision_timeout_seconds in its + // config.yaml. The value now flows server-side via the + // workspace API → WorkspaceData.provision_timeout_ms → + // resolver overrides path. With no override supplied, the + // resolver falls through to the default — same as any other + // runtime without a canvas-side override. expect(provisionTimeoutForRuntime("hermes")).toBe( - RUNTIME_PROFILES.hermes?.provisionTimeoutMs, - ); - expect(provisionTimeoutForRuntime("hermes")).toBeGreaterThanOrEqual( - DEFAULT_RUNTIME_PROFILE.provisionTimeoutMs * 5, + DEFAULT_RUNTIME_PROFILE.provisionTimeoutMs, ); + expect(RUNTIME_PROFILES.hermes).toBeUndefined(); }); it("server-side workspace override wins over runtime profile", () => { @@ -309,7 +314,7 @@ describe("ProvisioningTimeout", () => { expect(node?.data.provisionTimeoutMs).toBe(600_000); }); - it("absent provision_timeout_ms hydrates to null (falls through to runtime profile)", () => { + it("absent provision_timeout_ms hydrates to null (falls through to default post-cleanup)", () => { useCanvasStore.getState().hydrate([ makeWS({ id: "ws-default", name: "Default", status: "provisioning", runtime: "hermes" }), ]); @@ -317,27 +322,32 @@ describe("ProvisioningTimeout", () => { .getState() .nodes.find((n) => n.id === "ws-default"); expect(node?.data.provisionTimeoutMs).toBeNull(); - // And the resolver still returns hermes' profile value when - // no override is supplied — proves the fall-through stays intact. + // Post-#2054 phase 3: hermes no longer has a canvas-side + // RUNTIME_PROFILES entry. With no node override the resolver + // falls all the way through to DEFAULT_RUNTIME_PROFILE. In + // production the workspace-server-side template lookup + // populates node.provisionTimeoutMs to 720000 before this + // resolver runs (#2094); this test isolates the fall-through + // behavior when that population hasn't happened yet. expect( provisionTimeoutForRuntime("hermes", { provisionTimeoutMs: node?.data.provisionTimeoutMs ?? undefined, }), - ).toBe(RUNTIME_PROFILES.hermes.provisionTimeoutMs); + ).toBe(DEFAULT_RUNTIME_PROFILE.provisionTimeoutMs); }); - it("server override wins over runtime profile via the resolver path the component uses", () => { - // Mirrors ProvisioningTimeout.tsx:144 where node.provisionTimeoutMs - // is passed as overrides — verifies the resolver respects it - // even when the runtime has its own profile entry. - const override = 30_000; + it("server override wins over default via the resolver path the component uses", () => { + // Mirrors ProvisioningTimeout.tsx where node.provisionTimeoutMs + // is passed as overrides — verifies the resolver respects the + // override regardless of the runtime's profile state. + const override = 600_000; expect( provisionTimeoutForRuntime("hermes", { provisionTimeoutMs: override, }), ).toBe(override); - // Sanity — the runtime profile would have been much larger. - expect(RUNTIME_PROFILES.hermes.provisionTimeoutMs).toBeGreaterThan( + // Sanity — the override is the path that wins (default is much smaller). + expect(DEFAULT_RUNTIME_PROFILE.provisionTimeoutMs).toBeLessThan( override, ); }); diff --git a/canvas/src/lib/runtimeProfiles.ts b/canvas/src/lib/runtimeProfiles.ts index 68befd8a..ae33bf26 100644 --- a/canvas/src/lib/runtimeProfiles.ts +++ b/canvas/src/lib/runtimeProfiles.ts @@ -60,21 +60,23 @@ export const DEFAULT_RUNTIME_PROFILE: Required< /** * Named per-runtime overrides. Keep this map small and explicit — * each entry is a deliberate statement that this runtime's cold-boot - * behavior differs materially from the default. + * behavior differs materially from the default AND that the runtime's + * template manifest hasn't yet declared a server-side + * `provision_timeout_seconds` (the preferred path post-#2054). * * Each override must also ship with a comment explaining WHY the default * is wrong for this runtime. Unexplained numbers rot. + * + * Empty today — `hermes` previously lived here at 720_000ms, but + * Molecule-AI/molecule-ai-workspace-template-hermes now declares the + * value in its config.yaml manifest, so the value flows through the + * server (workspace API → WorkspaceData.provision_timeout_ms → resolver + * overrides) instead of being canvas-hardcoded. New runtimes that need + * a non-default cold-boot threshold should follow the same pattern: + * declare `runtime_config.provision_timeout_seconds` in their template + * manifest, NOT add an entry here. */ -export const RUNTIME_PROFILES: Record = { - hermes: { - // 12 min. Installs ripgrep + ffmpeg + node22 + builds hermes-agent - // from source + Playwright + Chromium (~300MB download). Measured - // cold boots on staging EC2 routinely land at 8-13 min. Aligns - // with SaaS E2E's PROVISION_TIMEOUT_SECS=900 (15 min) so the UI - // warning lands shortly before the backend itself gives up. - provisionTimeoutMs: 720_000, - }, -}; +export const RUNTIME_PROFILES: Record = {}; /** * Data fields the canvas can consult for per-workspace overrides. These