refactor(workspace-server): SSOT consolidation for BYO-compute meta-runtimes #2895

Merged
devops-engineer merged 1 commits from fix/byo-compute-meta-runtime-ssot into main 2026-06-15 06:17:14 +00:00
3 changed files with 188 additions and 25 deletions
@@ -26,6 +26,7 @@ package handlers
import (
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
@@ -67,25 +68,82 @@ type manifestFile struct {
WorkspaceTemplates []manifestEntry `json:"workspace_templates"`
}
// joinExternalLikeRuntimesForMessage returns the runtime list as it
// appears in user-facing error messages, e.g. `"external", "kimi", or
// "kimi-cli"`. Oxford-comma style for 3+ items, plain "or" for 2.
// Each item is Go-quoted (with surrounding double quotes) so the
// message reads naturally for an operator typing it.
//
// Used by workspace.go:400 (the "external workspaces must use
// runtime ..." error). Derived from the externalLikeRuntimes SSOT
// so adding a new BYO-compute meta-runtime only requires updating
// the SSOT in one place.
func joinExternalLikeRuntimesForMessage() string {
quoted := make([]string, len(externalLikeRuntimes))
for i, r := range externalLikeRuntimes {
quoted[i] = fmt.Sprintf("%q", r)
}
switch len(quoted) {
case 0:
return ""
case 1:
return quoted[0]
case 2:
return quoted[0] + " or " + quoted[1]
default:
return strings.Join(quoted[:len(quoted)-1], ", ") + ", or " + quoted[len(quoted)-1]
}
}
// externalLikeRuntimes is the SINGLE source of truth for the set of
// "BYO-compute meta-runtimes" (operator-managed, no platform-owned
// container or EC2). These runtimes share behavior around
// delivery_mode defaulting, plugin install, restart, and discovery,
// and are always available regardless of what manifest.json says
// (they have no template repo).
//
// Before this constant the same set was hardcoded in 3 separate
// places in this file (fallbackRuntimes, loadRuntimesFromManifest
// injection, isExternalLikeRuntime switch) + 1 string-literal in
// workspace.go:400. Adding a new BYO-compute meta-runtime required
// updating all 4 sites in lockstep; missing one was a silent drift
// surface. The TestExternalLikeRuntimesConsistent pin test in
// runtime_registry_test.go locks the shape across all 4 sites.
//
// "mock" is intentionally NOT in this set — it's a virtual
// workspace with hardcoded canned A2A replies (no container, no
// EC2, no template repo) but it's never user-selected (only the
// funding-demo org uses it), so it doesn't share the BYO-compute
// predicate behavior with external/kimi/kimi-cli.
var externalLikeRuntimes = []string{"external", "kimi", "kimi-cli"}
// fallbackRuntimes is used when manifest.json can't be loaded. Keeps
// tests + dev containers working even if the file isn't mounted.
// Kept slightly broader than the original hardcoded map so a stale
// manifest doesn't silently drop a runtime that was previously
// supported in the wild. "external" is always a valid runtime —
// manifest or not — because it has no template repo.
var fallbackRuntimes = map[string]struct{}{
"claude-code": {},
"hermes": {},
"openclaw": {},
"codex": {},
"external": {},
"kimi": {},
"kimi-cli": {},
// mock — virtual workspace with hardcoded canned A2A replies.
// No container, no EC2, no template repo. See mock_runtime.go
// for the full rationale (200-workspace funding-demo org).
"mock": {},
}
//
// The 3 externalLikeRuntimes + mock are derived from the SSOT
// (externalLikeRuntimes + the separate "mock" entry) so adding a
// new BYO-compute meta-runtime only requires updating
// externalLikeRuntimes above.
var fallbackRuntimes = func() map[string]struct{} {
out := map[string]struct{}{
"claude-code": {},
"hermes": {},
"openclaw": {},
"codex": {},
// mock — virtual workspace with hardcoded canned A2A replies.
// No container, no EC2, no template repo. See mock_runtime.go
// for the full rationale (200-workspace funding-demo org).
"mock": {},
}
for _, r := range externalLikeRuntimes {
out[r] = struct{}{}
}
return out
}()
// loadRuntimesFromManifest builds the runtime allowlist from
// manifest.json. Each workspace_templates[].name is normalized to its
@@ -106,20 +164,23 @@ func loadRuntimesFromManifest(path string) (map[string]struct{}, error) {
if err := json.Unmarshal(data, &m); err != nil {
return nil, err
}
// The 3 externalLikeRuntimes + mock are ALWAYS available
// regardless of what the manifest contains (they have no
// template repo, so the manifest doesn't know about them).
// Injected here from the SSOT (externalLikeRuntimes + the
// separate "mock" entry) so adding a new BYO-compute
// meta-runtime only requires updating externalLikeRuntimes
// above. See TestExternalLikeRuntimesConsistent for the
// pin test that locks this shape.
out := map[string]struct{}{
// external is ALWAYS available — it has no template repo, so
// the manifest doesn't know about it. Injected here so we
// don't need a special-case in every caller.
"external": {},
// kimi and kimi-cli are BYO-compute meta-runtimes (same shape
// as external). No template repo; injected like external.
"kimi": {},
"kimi-cli": {},
// mock is ALWAYS available for the same reason as external:
// virtual workspace, no template repo, never spawns a
// container. See mock_runtime.go.
"mock": {},
}
for _, r := range externalLikeRuntimes {
out[r] = struct{}{}
}
for _, e := range m.WorkspaceTemplates {
name := strings.TrimSpace(e.Name)
if name == "" {
@@ -139,10 +200,16 @@ func loadRuntimesFromManifest(path string) (map[string]struct{}, error) {
// (operator-managed, no platform-owned container or EC2). These runtimes
// share behavior around delivery_mode defaulting, plugin install, restart,
// and discovery.
//
// The set is derived from the externalLikeRuntimes SSOT (above) so
// adding a new BYO-compute meta-runtime only requires updating
// externalLikeRuntimes in one place — see
// TestExternalLikeRuntimesConsistent for the pin test.
func isExternalLikeRuntime(runtime string) bool {
switch runtime {
case "external", "kimi", "kimi-cli":
return true
for _, r := range externalLikeRuntimes {
if r == runtime {
return true
}
}
return false
}
@@ -14,6 +14,7 @@ package handlers
// must NOT be resolvable in the map after the next init).
import (
"fmt"
"os"
"path/filepath"
"strings"
@@ -320,3 +321,94 @@ func TestInitTemplateRepoByName_ReconcilesStaleEntries(t *testing.T) {
t.Errorf("templateIdentityForRuntime(hermes) should return (\"\", false), got (%q, %v)", id, ok)
}
}
// =============================================================================
// TestExternalLikeRuntimesConsistent — pin test for the
// externalLikeRuntimes SSOT consolidation. Locks the shape across
// all 4 sites that previously hardcoded the same set in 3 different
// shapes (fallbackRuntimes map, loadRuntimesFromManifest injection,
// isExternalLikeRuntime switch, workspace.go:400 error message).
//
// If anyone adds a new BYO-compute meta-runtime (e.g. "byo-cli"),
// they should:
// 1. add it to the externalLikeRuntimes slice in runtime_registry.go
// 2. run the test suite (this pin test still passes — same
// resolved shape)
// 3. the workspace.go:400 error message auto-includes it
//
// If anyone adds a new hardcoded list anywhere (drift surface),
// this test fails. The expected externalLikeRuntimes set is
// {"external", "kimi", "kimi-cli"} per the current production
// state — locked here so a future "we don't actually support kimi
// anymore" decision is a deliberate test update, not silent drift.
// =============================================================================
func TestExternalLikeRuntimesConsistent(t *testing.T) {
want := []string{"external", "kimi", "kimi-cli"}
if len(externalLikeRuntimes) != len(want) {
t.Fatalf("externalLikeRuntimes length = %d, want %d (drift surface: SSOT changed but test wasn't updated)",
len(externalLikeRuntimes), len(want))
}
for i, r := range want {
if externalLikeRuntimes[i] != r {
t.Errorf("externalLikeRuntimes[%d] = %q, want %q (SSOT shape changed without test update)",
i, externalLikeRuntimes[i], r)
}
}
// 1. fallbackRuntimes contains the SSOT (plus template-backed
// runtimes + mock). The SSOT MUST be a subset.
for _, r := range want {
if _, ok := fallbackRuntimes[r]; !ok {
t.Errorf("fallbackRuntimes missing externalLikeRuntimes entry %q (drift: SSOT says %q is BYO-compute but fallback allowlist doesn't include it)",
r, r)
}
}
// fallbackRuntimes ALSO contains the template-backed runtimes
// (claude-code, hermes, openclaw, codex) + mock — pin the
// resolved shape so a future edit doesn't silently drop them.
for _, r := range []string{"claude-code", "hermes", "openclaw", "codex", "mock"} {
if _, ok := fallbackRuntimes[r]; !ok {
t.Errorf("fallbackRuntimes missing expected entry %q (drift: a runtime was silently dropped from the fallback allowlist)",
r)
}
}
// 2. isExternalLikeRuntime returns true for each SSOT entry
// and false for the template-backed runtimes. (Locked because
// plugins.go / discovery.go / registry.go all switch on this
// predicate — silently flipping it would break BYO-compute
// behavior in 4 different files.)
for _, r := range want {
if !isExternalLikeRuntime(r) {
t.Errorf("isExternalLikeRuntime(%q) = false, want true (drift: predicate lost the SSOT entry)", r)
}
}
for _, r := range []string{"claude-code", "hermes", "openclaw", "codex", "mock", "unknown-runtime-xyz"} {
if isExternalLikeRuntime(r) {
t.Errorf("isExternalLikeRuntime(%q) = true, want false (drift: predicate now claims a template-backed runtime is BYO-compute)", r)
}
}
// 3. joinExternalLikeRuntimesForMessage produces the exact
// user-facing string the production error message uses. Pin
// the wire shape so a future edit doesn't silently change
// the user-facing 422 response.
wantMsg := `"external", "kimi", or "kimi-cli"`
if got := joinExternalLikeRuntimesForMessage(); got != wantMsg {
t.Errorf("joinExternalLikeRuntimesForMessage() = %q, want %q (drift: user-facing error string shape changed)",
got, wantMsg)
}
// 4. The full error message (the one workspace.go:400 sends in
// the 422 body) is the prefix + the joined SSOT. Pin it.
fullWant := `external workspaces must use runtime "external", "kimi", or "kimi-cli"`
// Reproduce the exact fmt.Sprintf call workspace.go:400 makes.
// We don't import workspace.go's Create (it has many other
// dependencies); we just rebuild the string the same way and
// assert the wire shape is preserved.
fullGot := fmt.Sprintf("external workspaces must use runtime %s", joinExternalLikeRuntimesForMessage())
if fullGot != fullWant {
t.Errorf("full error string drift:\n got: %q\n want: %q", fullGot, fullWant)
}
}
@@ -397,7 +397,11 @@ func (h *WorkspaceHandler) Create(c *gin.Context) {
if payload.External && !isExternalLikeRuntime(payload.Runtime) {
log.Printf("Create: FAIL-CLOSED — external workspace requested with non-external runtime %q", payload.Runtime)
c.JSON(http.StatusUnprocessableEntity, gin.H{
"error": "external workspaces must use runtime \"external\", \"kimi\", or \"kimi-cli\"",
// Build the runtime list from the externalLikeRuntimes SSOT
// (single source of truth) so adding a new BYO-compute
// meta-runtime only requires updating the SSOT in
// runtime_registry.go — see TestExternalLikeRuntimesConsistent.
"error": fmt.Sprintf("external workspaces must use runtime %s", joinExternalLikeRuntimesForMessage()),
"runtime": payload.Runtime,
"code": "RUNTIME_UNSUPPORTED",
})