refactor(canvas): remove RUNTIME_PROFILES.hermes — value flows server-side now (#2054 phase 3)

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) <noreply@anthropic.com>
This commit is contained in:
rabbitblood 2026-04-26 07:12:44 -07:00
parent a8c9644618
commit 756aa00e1f
2 changed files with 39 additions and 27 deletions

View File

@ -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,
);
});

View File

@ -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<string, RuntimeProfile> = {
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<string, RuntimeProfile> = {};
/**
* Data fields the canvas can consult for per-workspace overrides. These