From ce2980ab6744ea799a120896e520332e8b63ef3d Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer B (MiniMax)" Date: Wed, 3 Jun 2026 05:20:03 +0000 Subject: [PATCH 1/4] feat(workspace-server): cp#469 managed-tenant boot assertion for LLM proxy env CTO directive 24164847. Makes managed SaaS tenants fail boot with MISSING_CP_LLM_ENV if MOLECULE_LLM_USAGE_TOKEN / MOLECULE_LLM_URL / MOLECULE_LLM_BASE_URL / ANTHROPIC_BASE_URL are missing after refreshEnvFromCP. Self-hosted (no orgID/adminToken) is exempt. Implementation: - internal_refreshPath: workspace-server/cmd/server/cp_config.go - new requiredLLMEnvVars var (4 keys) - new assertManagedTenantHasLLMEnv() function - main.go: add the assertion between refreshEnvFromCP() and crypto.InitStrict(); log.Fatal on MISSING_CP_LLM_ENV - cp_config_test.go: 2 new tests: - TestRefreshEnvFromCP_ManagedTenantRequiresLLMKeys (watch-fail-first per Researcher Task #46) - TestAssertManagedTenantHasLLMEnv_NotManagedIsNoop Out of scope: controlplane-side docker-run env-var injection (molecule-controlplane, separate PR per CTO directive). PM chose refresh-mandatory path-forward (DEV-B dispatch 24c8bf37). Researcher e12a2737 dispatched in parallel to verify controlplane files (tenant_config.go:119-174, ec2.go:2398-2419). --- workspace-server/cmd/server/cp_config.go | 38 +++++++++++++ workspace-server/cmd/server/cp_config_test.go | 56 +++++++++++++++++++ workspace-server/cmd/server/main.go | 10 ++++ 3 files changed, 104 insertions(+) diff --git a/workspace-server/cmd/server/cp_config.go b/workspace-server/cmd/server/cp_config.go index d1021c22f..d2ee19cd3 100644 --- a/workspace-server/cmd/server/cp_config.go +++ b/workspace-server/cmd/server/cp_config.go @@ -105,3 +105,41 @@ func refreshEnvFromCP() error { log.Printf("CP env refresh: applied %d values from %s/cp/tenants/config", applied, base) return nil } + +// requiredLLMEnvVars is the set of LLM proxy env vars a managed SaaS +// tenant must have populated after refreshEnvFromCP. cp#469 (tenant +// proxy-env delivery) — guaranteed CP-delivered creds reach the +// tenant process env on boot. Per Researcher Task #37 / Spec 2 and +// Task #46 (watch-fail-first test). +var requiredLLMEnvVars = []string{ + "MOLECULE_LLM_USAGE_TOKEN", + "MOLECULE_LLM_URL", + "MOLECULE_LLM_BASE_URL", + "ANTHROPIC_BASE_URL", +} + +// assertManagedTenantHasLLMEnv verifies that, when running as a +// managed SaaS tenant (MOLECULE_ORG_ID + ADMIN_TOKEN both set), all +// required LLM proxy env vars are populated after refreshEnvFromCP. +// +// Self-hosted (no orgID/adminToken) is exempt — dev must not be +// blocked here. Managed tenants with missing LLM keys fail with +// MISSING_CP_LLM_ENV so they do not silently boot with broken proxy +// creds. Caller in main.go decides whether to log and continue or +// log.Fatalf depending on deployment context. +func assertManagedTenantHasLLMEnv() error { + if os.Getenv("MOLECULE_ORG_ID") == "" || os.Getenv("ADMIN_TOKEN") == "" { + // Self-hosted dev / not yet provisioned — not a managed tenant. + return nil + } + var missing []string + for _, k := range requiredLLMEnvVars { + if os.Getenv(k) == "" { + missing = append(missing, k) + } + } + if len(missing) > 0 { + return fmt.Errorf("MISSING_CP_LLM_ENV: required LLM proxy keys not set after refreshEnvFromCP: %v", missing) + } + return nil +} diff --git a/workspace-server/cmd/server/cp_config_test.go b/workspace-server/cmd/server/cp_config_test.go index fddceddea..23dfc1f36 100644 --- a/workspace-server/cmd/server/cp_config_test.go +++ b/workspace-server/cmd/server/cp_config_test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httptest" "os" + "strings" "testing" ) @@ -47,6 +48,61 @@ func TestRefreshEnvFromCP_AppliesCPResponse(t *testing.T) { } } +// TestRefreshEnvFromCP_ManagedTenantRequiresLLMKeys: watch-fail-first +// per Researcher Task #46. When running as a managed tenant +// (MOLECULE_ORG_ID + ADMIN_TOKEN set), missing LLM proxy env vars +// after refreshEnvFromCP MUST surface as MISSING_CP_LLM_ENV, not be +// silently accepted. Without this guard, a CP that loses its LLM +// creds (e.g. during an incident) would let a tenant boot and then +// fail later at first LLM call — worse than a loud refusal here. +func TestRefreshEnvFromCP_ManagedTenantRequiresLLMKeys(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Stub CP returns a CP response WITHOUT any of the required + // LLM keys — simulates the failure mode where the CP side + // dropped or never had the LLM creds for this org. + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, `{"MOLECULE_CP_SHARED_SECRET":"x","MOLECULE_CP_URL":"https://api.moleculesai.app"}`) + })) + defer srv.Close() + + t.Setenv("MOLECULE_ORG_ID", "org-managed-1") + t.Setenv("ADMIN_TOKEN", "admin-tok") + t.Setenv("MOLECULE_CP_URL", srv.URL) + // Clear all LLM keys to simulate the boot-without-LLM-env failure mode. + t.Setenv("MOLECULE_LLM_USAGE_TOKEN", "") + t.Setenv("MOLECULE_LLM_URL", "") + t.Setenv("MOLECULE_LLM_BASE_URL", "") + t.Setenv("ANTHROPIC_BASE_URL", "") + + // refreshEnvFromCP itself should succeed — CP is reachable, returned 200. + if err := refreshEnvFromCP(); err != nil { + t.Fatalf("refreshEnvFromCP: %v", err) + } + // The boot assertion must catch the missing LLM keys. + err := assertManagedTenantHasLLMEnv() + if err == nil { + t.Fatal("expected MISSING_CP_LLM_ENV error for managed tenant without LLM keys, got nil") + } + if !strings.Contains(err.Error(), "MISSING_CP_LLM_ENV") { + t.Errorf("expected error to contain MISSING_CP_LLM_ENV, got: %v", err) + } +} + +// TestAssertManagedTenantHasLLMEnv_NotManagedIsNoop: self-hosted +// (no orgID/adminToken) must NOT block on missing LLM keys — dev +// ergonomics matter and the assertion's contract is "managed only". +func TestAssertManagedTenantHasLLMEnv_NotManagedIsNoop(t *testing.T) { + t.Setenv("MOLECULE_ORG_ID", "") + t.Setenv("ADMIN_TOKEN", "") + t.Setenv("MOLECULE_LLM_USAGE_TOKEN", "") + t.Setenv("MOLECULE_LLM_URL", "") + t.Setenv("MOLECULE_LLM_BASE_URL", "") + t.Setenv("ANTHROPIC_BASE_URL", "") + if err := assertManagedTenantHasLLMEnv(); err != nil { + t.Errorf("self-hosted (not managed) must not block, got: %v", err) + } +} + // TestRefreshEnvFromCP_CPUnreachableDoesNotFailBoot: network errors must // return non-nil BUT main.go treats that as warn-and-continue. We assert // the function returns an error (not a panic) so the caller can log. diff --git a/workspace-server/cmd/server/main.go b/workspace-server/cmd/server/main.go index d93f13255..bb3d3b7ef 100644 --- a/workspace-server/cmd/server/main.go +++ b/workspace-server/cmd/server/main.go @@ -56,6 +56,16 @@ func main() { log.Printf("CP env refresh: %v (continuing with baked-in env)", err) } + // Managed-tenant boot assertion (cp#469 — tenant proxy-env delivery). + // If we're a managed SaaS tenant (orgID + adminToken set), all required + // LLM proxy env vars must be present after refresh. Missing keys block + // the tenant from booting with broken LLM creds — silent-fail is worse + // than a loud refusal. Self-hosted (no orgID/adminToken) short-circuits + // inside the assertion, so this never fires for dev. + if err := assertManagedTenantHasLLMEnv(); err != nil { + log.Fatalf("Managed tenant boot assertion: %v", err) + } + // Secrets encryption. In MOLECULE_ENV=prod, boot refuses to start // without a valid SECRETS_ENCRYPTION_KEY (fail-secure — Top-5 #5). // In any other environment, missing keys just log a warning and -- 2.52.0 From 14c6c8761aa3fb37174ac8d61e68175d776d806c Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer B (MiniMax)" Date: Wed, 3 Jun 2026 05:31:07 +0000 Subject: [PATCH 2/4] =?UTF-8?q?fix(workspace-server):=20cp#469=20iterate?= =?UTF-8?q?=20=E2=80=94=20correct=20LLM=20env=20var=20name=20(LLM=5FURL=20?= =?UTF-8?q?=E2=86=92=20LLM=5FUSAGE=5FURL)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL fix per Researcher review of bcb79cc: requiredLLMEnvVars listed MOLECULE_LLM_URL, but the verified CP emission in controlplane tenant_config.go:119-174 emits MOLECULE_LLM_USAGE_URL. v1's gate would never have caught a missing USAGE_URL because it was checking the wrong name — silent acceptance of a half- configured tenant. Test files updated to match (both TestRefreshEnvFromCP_ManagedTenant RequiresLLMKeys and TestAssertManagedTenantHasLLMEnv_NotManagedIsNoop). Re-research: OPENAI_BASE_URL / OPENAI_API_KEY / ANTHROPIC_API_KEY / MOLECULE_LLM_ANTHROPIC_BASE_URL are out of scope for cp#469 (they're direct-to-provider fallbacks, not the LLM proxy). The 4 keys retained are the LLM-proxy subset of the 8 CP-emitted keys. Co-Authored-By: Claude Opus 4.7 --- workspace-server/cmd/server/cp_config.go | 10 +++++++++- workspace-server/cmd/server/cp_config_test.go | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/workspace-server/cmd/server/cp_config.go b/workspace-server/cmd/server/cp_config.go index d2ee19cd3..33205d817 100644 --- a/workspace-server/cmd/server/cp_config.go +++ b/workspace-server/cmd/server/cp_config.go @@ -111,9 +111,17 @@ func refreshEnvFromCP() error { // proxy-env delivery) — guaranteed CP-delivered creds reach the // tenant process env on boot. Per Researcher Task #37 / Spec 2 and // Task #46 (watch-fail-first test). +// +// Key set reconciled against Researcher's verified emission in +// controlplane tenant_config.go:119-174 (PM 2026-06-03 controlplane +// verify result). The four keys below are the LLM-proxy subset of +// the 8 CP-emitted keys; OPENAI_BASE_URL / OPENAI_API_KEY / +// ANTHROPIC_API_KEY / MOLECULE_LLM_ANTHROPIC_BASE_URL are out of +// scope for cp#469 (different feature surfaces — direct-to-provider +// fallbacks, not the proxy). var requiredLLMEnvVars = []string{ "MOLECULE_LLM_USAGE_TOKEN", - "MOLECULE_LLM_URL", + "MOLECULE_LLM_USAGE_URL", // CRITICAL fix: was MOLECULE_LLM_URL in v1; not in CP emission "MOLECULE_LLM_BASE_URL", "ANTHROPIC_BASE_URL", } diff --git a/workspace-server/cmd/server/cp_config_test.go b/workspace-server/cmd/server/cp_config_test.go index 23dfc1f36..b9615350f 100644 --- a/workspace-server/cmd/server/cp_config_test.go +++ b/workspace-server/cmd/server/cp_config_test.go @@ -70,7 +70,7 @@ func TestRefreshEnvFromCP_ManagedTenantRequiresLLMKeys(t *testing.T) { t.Setenv("MOLECULE_CP_URL", srv.URL) // Clear all LLM keys to simulate the boot-without-LLM-env failure mode. t.Setenv("MOLECULE_LLM_USAGE_TOKEN", "") - t.Setenv("MOLECULE_LLM_URL", "") + t.Setenv("MOLECULE_LLM_USAGE_URL", "") t.Setenv("MOLECULE_LLM_BASE_URL", "") t.Setenv("ANTHROPIC_BASE_URL", "") @@ -95,7 +95,7 @@ func TestAssertManagedTenantHasLLMEnv_NotManagedIsNoop(t *testing.T) { t.Setenv("MOLECULE_ORG_ID", "") t.Setenv("ADMIN_TOKEN", "") t.Setenv("MOLECULE_LLM_USAGE_TOKEN", "") - t.Setenv("MOLECULE_LLM_URL", "") + t.Setenv("MOLECULE_LLM_USAGE_URL", "") t.Setenv("MOLECULE_LLM_BASE_URL", "") t.Setenv("ANTHROPIC_BASE_URL", "") if err := assertManagedTenantHasLLMEnv(); err != nil { -- 2.52.0 From 007be71d7fa453b3a543af28fc3a479fe8c4ece7 Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer B (MiniMax)" Date: Wed, 3 Jun 2026 05:38:12 +0000 Subject: [PATCH 3/4] =?UTF-8?q?fix(workspace-server):=20cp#469=20iteratev3?= =?UTF-8?q?=20=E2=80=94=202nd=20env=20name=20fix=20+=20test=20adequacy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL fix #2 (missed in v2): requiredLLMEnvVars listed ANTHROPIC_BASE_URL, but the verified CP emission in tenant_config.go:140-144 emits MOLECULE_LLM_ANTHROPIC_BASE_URL. Bare ANTHROPIC_BASE_URL is a separate CP-emitted key for direct-provider use, not the LLM proxy. v2 would have bricked all platform-managed workspaces in production — same failure mode as v1's MOLECULE_LLM_URL typo, just a different key. Researcher's full iterate body (3987f59c) spelled out all 4 byte-correct names; v2 had only fixed 1 of 2 typos. This commit fixes the 2nd. Test additions per Researcher TEST ADEQUACY note: - TestRefreshEnvFromCP_ManagedTenantHappyPath: CP returns all 4 keys, gate must pass (no MISSING_CP_LLM_ENV). Watch-fail counterpart to the all-missing test. - TestRefreshEnvFromCP_ManagedTenantPartialEnv: CP returns 3 of 4 keys, gate must still catch + name the missing one. Production failure mode is usually "one key dropped" not "all keys dropped". Final env-name byte-match confirmation (per PM "reply with byte- match" ask): 1. MOLECULE_LLM_USAGE_TOKEN — CP tenant_config.go:140 ✓ 2. MOLECULE_LLM_USAGE_URL — CP tenant_config.go:141 ✓ 3. MOLECULE_LLM_BASE_URL — CP tenant_config.go:142 ✓ 4. MOLECULE_LLM_ANTHROPIC_BASE_URL — CP tenant_config.go:143 ✓ Watch-fail-first command (per Researcher): cd workspace-server && go test ./cmd/server -run 'TestRefreshEnvFromCP_ManagedTenantRequiresLLMKeys|TestAssertManagedTenantHasLLMEnv' -count=1 (Cannot run locally — no go binary in this workspace. Researcher 876788c3 to verify on their side.) Co-Authored-By: Claude Opus 4.7 --- workspace-server/cmd/server/cp_config.go | 22 ++--- workspace-server/cmd/server/cp_config_test.go | 81 ++++++++++++++++++- 2 files changed, 92 insertions(+), 11 deletions(-) diff --git a/workspace-server/cmd/server/cp_config.go b/workspace-server/cmd/server/cp_config.go index 33205d817..acbac0ac8 100644 --- a/workspace-server/cmd/server/cp_config.go +++ b/workspace-server/cmd/server/cp_config.go @@ -112,18 +112,22 @@ func refreshEnvFromCP() error { // tenant process env on boot. Per Researcher Task #37 / Spec 2 and // Task #46 (watch-fail-first test). // -// Key set reconciled against Researcher's verified emission in -// controlplane tenant_config.go:119-174 (PM 2026-06-03 controlplane -// verify result). The four keys below are the LLM-proxy subset of -// the 8 CP-emitted keys; OPENAI_BASE_URL / OPENAI_API_KEY / -// ANTHROPIC_API_KEY / MOLECULE_LLM_ANTHROPIC_BASE_URL are out of -// scope for cp#469 (different feature surfaces — direct-to-provider -// fallbacks, not the proxy). +// Key set byte-matched against Researcher's verified emission in +// controlplane tenant_config.go:140-144 (Researcher REQUEST_CHANGES +// iterate body, 3987f59c). The four keys below ARE the LLM-proxy +// subset of the 8 CP-emitted keys; OPENAI_BASE_URL / OPENAI_API_KEY / +// ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY are out of scope for cp#469 +// (different feature surfaces — direct-to-provider fallbacks, not +// the proxy). v2 fix: MOLECULE_LLM_USAGE_TOKEN, MOLECULE_LLM_USAGE_URL, +// MOLECULE_LLM_BASE_URL, MOLECULE_LLM_ANTHROPIC_BASE_URL — note the +// 4th key is namespaced MOLECULE_LLM_ANTHROPIC_BASE_URL, NOT bare +// ANTHROPIC_BASE_URL. Bare ANTHROPIC_BASE_URL is a separate CP-emitted +// key for direct-provider use, not the LLM proxy. var requiredLLMEnvVars = []string{ "MOLECULE_LLM_USAGE_TOKEN", - "MOLECULE_LLM_USAGE_URL", // CRITICAL fix: was MOLECULE_LLM_URL in v1; not in CP emission + "MOLECULE_LLM_USAGE_URL", // CRITICAL fix v2: was MOLECULE_LLM_URL in v1 "MOLECULE_LLM_BASE_URL", - "ANTHROPIC_BASE_URL", + "MOLECULE_LLM_ANTHROPIC_BASE_URL", // CRITICAL fix v3: was ANTHROPIC_BASE_URL in v2 (different key!) } // assertManagedTenantHasLLMEnv verifies that, when running as a diff --git a/workspace-server/cmd/server/cp_config_test.go b/workspace-server/cmd/server/cp_config_test.go index b9615350f..9a2ec4607 100644 --- a/workspace-server/cmd/server/cp_config_test.go +++ b/workspace-server/cmd/server/cp_config_test.go @@ -72,7 +72,7 @@ func TestRefreshEnvFromCP_ManagedTenantRequiresLLMKeys(t *testing.T) { t.Setenv("MOLECULE_LLM_USAGE_TOKEN", "") t.Setenv("MOLECULE_LLM_USAGE_URL", "") t.Setenv("MOLECULE_LLM_BASE_URL", "") - t.Setenv("ANTHROPIC_BASE_URL", "") + t.Setenv("MOLECULE_LLM_ANTHROPIC_BASE_URL", "") // refreshEnvFromCP itself should succeed — CP is reachable, returned 200. if err := refreshEnvFromCP(); err != nil { @@ -88,6 +88,83 @@ func TestRefreshEnvFromCP_ManagedTenantRequiresLLMKeys(t *testing.T) { } } +// TestRefreshEnvFromCP_ManagedTenantHappyPath: when the CP returns +// all 4 LLM-proxy keys, the gate must PASS — no MISSING_CP_LLM_ENV +// for a properly-configured managed tenant. Watch-fail counterpart +// to TestRefreshEnvFromCP_ManagedTenantRequiresLLMKeys: if THIS test +// ever fires MISSING_CP_LLM_ENV on the byte-correct key set, the +// requiredLLMEnvVars list has drifted from the CP emission again. +// Per Researcher REQUEST_CHANGES TEST ADEQUACY note. +func TestRefreshEnvFromCP_ManagedTenantHappyPath(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + // Return ALL 4 LLM-proxy keys — names byte-matched to + // tenant_config.go:140-144 CP emission. + fmt.Fprint(w, `{"MOLECULE_LLM_USAGE_TOKEN":"tok-1","MOLECULE_LLM_USAGE_URL":"https://llm.example.com/usage","MOLECULE_LLM_BASE_URL":"https://llm.example.com","MOLECULE_LLM_ANTHROPIC_BASE_URL":"https://llm.example.com/anthropic"}`) + })) + defer srv.Close() + + t.Setenv("MOLECULE_ORG_ID", "org-managed-happy") + t.Setenv("ADMIN_TOKEN", "admin-tok") + t.Setenv("MOLECULE_CP_URL", srv.URL) + // Pre-clear so we can verify the refresh actually populated them. + t.Setenv("MOLECULE_LLM_USAGE_TOKEN", "") + t.Setenv("MOLECULE_LLM_USAGE_URL", "") + t.Setenv("MOLECULE_LLM_BASE_URL", "") + t.Setenv("MOLECULE_LLM_ANTHROPIC_BASE_URL", "") + + if err := refreshEnvFromCP(); err != nil { + t.Fatalf("refreshEnvFromCP: %v", err) + } + // Sanity: refresh actually applied the keys. + if got := os.Getenv("MOLECULE_LLM_USAGE_TOKEN"); got != "tok-1" { + t.Errorf("refresh did not apply USAGE_TOKEN: got %q", got) + } + // The boot assertion must pass — no MISSING_CP_LLM_ENV. + if err := assertManagedTenantHasLLMEnv(); err != nil { + t.Errorf("managed happy path must not MISSING_CP_LLM_ENV, got: %v", err) + } +} + +// TestRefreshEnvFromCP_ManagedTenantPartialEnv: when the CP returns +// 3 of 4 LLM-proxy keys (one missing), the gate must STILL catch it +// and the error must name the missing key. Per Researcher +// REQUEST_CHANGES TEST ADEQUACY note — partial-env coverage is +// critical because the production failure mode is usually "one +// key dropped" not "all keys dropped". +func TestRefreshEnvFromCP_ManagedTenantPartialEnv(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + // 3 of 4 — MOLECULE_LLM_ANTHROPIC_BASE_URL is missing. + fmt.Fprint(w, `{"MOLECULE_LLM_USAGE_TOKEN":"tok-1","MOLECULE_LLM_USAGE_URL":"https://llm.example.com/usage","MOLECULE_LLM_BASE_URL":"https://llm.example.com"}`) + })) + defer srv.Close() + + t.Setenv("MOLECULE_ORG_ID", "org-managed-partial") + t.Setenv("ADMIN_TOKEN", "admin-tok") + t.Setenv("MOLECULE_CP_URL", srv.URL) + // Pre-clear all 4 so the 3 that come back from CP are the only + // ones set; the 4th (MOLECULE_LLM_ANTHROPIC_BASE_URL) stays empty. + t.Setenv("MOLECULE_LLM_USAGE_TOKEN", "") + t.Setenv("MOLECULE_LLM_USAGE_URL", "") + t.Setenv("MOLECULE_LLM_BASE_URL", "") + t.Setenv("MOLECULE_LLM_ANTHROPIC_BASE_URL", "") + + if err := refreshEnvFromCP(); err != nil { + t.Fatalf("refreshEnvFromCP: %v", err) + } + err := assertManagedTenantHasLLMEnv() + if err == nil { + t.Fatal("expected MISSING_CP_LLM_ENV for partial env (3 of 4 keys), got nil") + } + if !strings.Contains(err.Error(), "MISSING_CP_LLM_ENV") { + t.Errorf("expected error to contain MISSING_CP_LLM_ENV, got: %v", err) + } + if !strings.Contains(err.Error(), "MOLECULE_LLM_ANTHROPIC_BASE_URL") { + t.Errorf("expected error to name the missing key MOLECULE_LLM_ANTHROPIC_BASE_URL, got: %v", err) + } +} + // TestAssertManagedTenantHasLLMEnv_NotManagedIsNoop: self-hosted // (no orgID/adminToken) must NOT block on missing LLM keys — dev // ergonomics matter and the assertion's contract is "managed only". @@ -97,7 +174,7 @@ func TestAssertManagedTenantHasLLMEnv_NotManagedIsNoop(t *testing.T) { t.Setenv("MOLECULE_LLM_USAGE_TOKEN", "") t.Setenv("MOLECULE_LLM_USAGE_URL", "") t.Setenv("MOLECULE_LLM_BASE_URL", "") - t.Setenv("ANTHROPIC_BASE_URL", "") + t.Setenv("MOLECULE_LLM_ANTHROPIC_BASE_URL", "") if err := assertManagedTenantHasLLMEnv(); err != nil { t.Errorf("self-hosted (not managed) must not block, got: %v", err) } -- 2.52.0 From d9e6ce5792b52904df157f14b62cb1a913b92c87 Mon Sep 17 00:00:00 2001 From: Molecule AI Root-Cause Researcher Date: Wed, 3 Jun 2026 06:08:13 +0000 Subject: [PATCH 4/4] fix(ci): remove unused canvas message type --- workspace-server/internal/handlers/a2a_proxy_helpers.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/workspace-server/internal/handlers/a2a_proxy_helpers.go b/workspace-server/internal/handlers/a2a_proxy_helpers.go index 98c51bb7d..11916e6b1 100644 --- a/workspace-server/internal/handlers/a2a_proxy_helpers.go +++ b/workspace-server/internal/handlers/a2a_proxy_helpers.go @@ -407,15 +407,6 @@ func validateCallerToken(ctx context.Context, c *gin.Context, callerID string) e // matching (the wsauth errors are typed for the invalid case). var errInvalidCallerToken = errors.New("missing caller auth token") -// canvasUserMessage holds the extracted user message extracted from an -// A2A canvas request body for broadcasting to other sessions. -type canvasUserMessage struct { - Message string `json:"message,omitempty"` - Parts []map[string]interface{} `json:"parts,omitempty"` - MessageID string `json:"messageId,omitempty"` - Attachments []map[string]interface{} `json:"attachments,omitempty"` -} - // extractCanvasUserMessage parses an A2A JSON-RPC request body and extracts // the user-authored text and attachments from a canvas-initiated message/send. // Returns nil when the body is not a canvas user message (empty, malformed, -- 2.52.0