64fdfa6e77
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
CI / Python Lint & Test (pull_request) Successful in 4s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 2s
CI / Detect changes (pull_request) Successful in 9s
E2E Chat / detect-changes (pull_request) Successful in 8s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 3s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 8s
E2E API Smoke Test / detect-changes (pull_request) Successful in 11s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 8s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 7s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
sop-checklist / review-refire (pull_request_target) Has been skipped
gate-check-v3 / gate-check (pull_request_target) Successful in 7s
CI / Platform (Go) (pull_request) Successful in 2s
qa-review / approved (pull_request_target) Failing after 6s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: comprehensive-testing, local-postgres-e2
CI / Canvas (Next.js) (pull_request) Successful in 2s
sop-checklist / na-declarations (pull_request) N/A: (none)
E2E Chat / E2E Chat (pull_request) Successful in 2s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 1s
security-review / approved (pull_request_target) Failing after 13s
sop-checklist / all-items-acked (pull_request_target) Successful in 13s
sop-tier-check / tier-check (pull_request_target) Failing after 11s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 13s
CI / Canvas Deploy Status (pull_request) Has been skipped
CI / all-required (pull_request) Successful in 2s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m7s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 53s
qa-review / approved (pull_request_review) Has been skipped
security-review / approved (pull_request_review) Has been skipped
sop-tier-check / tier-check (pull_request_review) Failing after 4s
audit-force-merge / audit (pull_request_target) Successful in 5s
E2E Staging SaaS (full lifecycle) / pr-validate (pull_request) Waiting to run
E2E Staging SaaS (full lifecycle) / E2E Staging SaaS (pull_request) Waiting to run
E2E Staging SaaS (full lifecycle) / E2E Staging Platform Boot (pull_request) Waiting to run
E2E Staging Reconciler (heals terminated EC2) / pr-validate (pull_request) Waiting to run
E2E Staging Reconciler (heals terminated EC2) / E2E Staging Reconciler (pull_request) Waiting to run
The staging full-SaaS e2e provisioned a claude-code parent workspace with
the colon-namespaced model id `minimax:MiniMax-M2.7` (from
tests/e2e/lib/model_slug.sh), which is INTENTIONALLY unregistered for the
claude-code runtime: the claude-code adapter cannot strip the `minimax:`
prefix, so create-validation (provider-registry SSOT, internal#718) rejects
it 422 UNREGISTERED_MODEL_FOR_RUNTIME.
Evidence: real staging run job 295075 (main 797351bb) failed at
"5/11 Provisioning parent workspace" with:
{"code":"UNREGISTERED_MODEL_FOR_RUNTIME","error":"model
\"minimax:MiniMax-M2.7\" is not a registered model for runtime
\"claude-code\"; pick one of the runtime's registered models
(provider-registry SSOT, internal#718)"}
This 422 is correct, intentional product behavior, pinned by
workspace-server/internal/providers/derive_provider_matrix_test.go
(the #2263/#2274 colon-vs-slash-vs-bare MiniMax triple):
bare "MiniMax-M2.7" -> provider=minimax (BYOK)
slash "minimax/MiniMax-M2.7" -> provider=platform
colon "minimax:MiniMax-M2.7" -> UNREGISTERED (adapter can't strip minimax:)
The bare form is registered in claude-code's `minimax` arm
(registry_gen.go:88 Models=[MiniMax-M2,MiniMax-M2.7,MiniMax-M2.7-highspeed,
MiniMax-M3]) and derives provider=minimax BYOK via MINIMAX_API_KEY.
Test-only fix (zero production code):
- tests/e2e/lib/model_slug.sh: claude-code|seo-agent MiniMax-BYOK path now
emits the bare registered `MiniMax-M2.7`; rewrote the now-wrong comments
that claimed the colon form gives BYOK on claude-code (it doesn't — colon
is only the correct BYOK id on openclaw/hermes, which DO strip the prefix).
- tests/e2e/test_model_slug.sh: updated the three pins from the colon form to
the bare form (claude-code + minimax, both-keys priority, seo-agent).
- tests/e2e/test_priority_runtimes_e2e.sh: the live MiniMax arm directly
provisioned claude-code with the same colon id (same UNREGISTERED 422 class)
— switched to bare `MiniMax-M2.7` and corrected the "registry-skew" framing.
- tests/e2e/test_staging_full_saas.sh: corrected a stale diagnostic string.
Audit of other arms (no other UNREGISTERED mismatch found): hermes/codex
slash `openai/gpt-4o` and google-adk bare `gemini-2.5-pro` and the
test_peer_visibility `minimax/MiniMax-M2.7` slash form are all registered
for their runtimes per the matrix test; left unchanged. openclaw/hermes
colon-minimax is correct (those adapters strip the prefix) and is not
emitted by this helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
145 lines
8.0 KiB
Bash
Executable File
145 lines
8.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Per-runtime model slug dispatch for E2E provisioning.
|
|
#
|
|
# Different runtimes parse the model slug differently (PR #2571 incident,
|
|
# 2026-05-03):
|
|
#
|
|
# hermes → "openai/gpt-4o" (slash-form: derive-provider.sh splits
|
|
# on the prefix to set
|
|
# HERMES_INFERENCE_PROVIDER. Bare
|
|
# "gpt-4o" falls through to Anthropic
|
|
# default + 401, see PR #1714.)
|
|
#
|
|
# claude-code → auth-aware:
|
|
# E2E_MINIMAX_API_KEY → "MiniMax-M2.7"
|
|
# (BARE registered BYOK id — see the
|
|
# claude-code dispatch arm below for
|
|
# why bare, not the colon form)
|
|
# E2E_ANTHROPIC_API_KEY → "claude-sonnet-4-6"
|
|
# otherwise → "sonnet"
|
|
#
|
|
# claude-code provider routing is model-driven. The bare
|
|
# "sonnet" alias selects the OAuth provider, so it is only a
|
|
# good default when the canary is using Claude Code OAuth or
|
|
# intentionally exercising the missing-auth path. MiniMax and
|
|
# direct Anthropic API keys need model IDs that resolve to
|
|
# their provider entries, otherwise the workspace boots
|
|
# reachable but the first A2A call hits the wrong auth path.
|
|
#
|
|
# PLATFORM-MANAGED path (E2E_LLM_PATH=platform) — the moonshot/kimi
|
|
# NOT_CONFIGURED regression (RFC#340 Fix A #2187):
|
|
#
|
|
# The branches above all exercise BYOK: a tenant key (MINIMAX/ANTHROPIC/
|
|
# OPENAI) is injected as a workspace secret and the model id resolves to that
|
|
# vendor's *BYOK* provider entry. That path NEVER exercises the platform arm —
|
|
# the exact arm that booted "moonshot/kimi-k2.6" into NOT_CONFIGURED in prod,
|
|
# because the generated config.yaml lacked the derived `provider: platform`.
|
|
#
|
|
# E2E_LLM_PATH=platform selects a platform-managed model id (slash-namespaced,
|
|
# no tenant key — Molecule owns billing via the CP LLM proxy). The default is
|
|
# "moonshot/kimi-k2.6", the headline incident combo. Override the specific
|
|
# platform model with E2E_MODEL_SLUG. The provision branch in
|
|
# test_staging_full_saas.sh sends NO secrets for this path (platform-managed
|
|
# needs none), so the workspace must boot online purely on the proxy env the
|
|
# control plane injects + the manifest-derived `provider: platform` that Fix A
|
|
# stamps. That is the REAL boot-path assertion the deterministic unit test
|
|
# (workspace_provision_platform_boot_test.go) cannot make.
|
|
#
|
|
# When E2E_MODEL_SLUG is set, it overrides this dispatch entirely — useful when
|
|
# an operator dispatches the workflow to test a specific slug (or a specific
|
|
# platform model id).
|
|
#
|
|
# Unit tested by tests/e2e/test_model_slug.sh — every branch must stay
|
|
# pinned because regressions silently mask as "Could not resolve
|
|
# authentication method" + the synth-E2E gate goes red without naming
|
|
# the slug-format mismatch.
|
|
|
|
# Default platform-managed model for the platform-boot regression path. The
|
|
# exact id that booted NOT_CONFIGURED in prod. Must stay a member of the
|
|
# claude-code `platform` arm in workspace-server/internal/providers/providers.yaml
|
|
# (the deterministic suite TestEnsureDefaultConfig_StampsProviderForEverySSOTPlatformModel
|
|
# enforces every member of that arm derives provider=platform). Resolved INSIDE
|
|
# pick_model_slug via ${E2E_DEFAULT_PLATFORM_MODEL:-...} so callers can override
|
|
# it (or unset it) without tripping `set -u`.
|
|
E2E_DEFAULT_PLATFORM_MODEL_FALLBACK="moonshot/kimi-k2.6"
|
|
|
|
# Usage: pick_model_slug <runtime>
|
|
# stdout: the slug string
|
|
# E2E_MODEL_SLUG (env): if set + non-empty, used as-is (operator override)
|
|
# E2E_LLM_PATH=platform (env): select the platform-managed model id
|
|
# (E2E_DEFAULT_PLATFORM_MODEL) instead of a BYOK slug. Takes precedence over
|
|
# the per-key BYOK branches; E2E_MODEL_SLUG still wins over everything.
|
|
pick_model_slug() {
|
|
local runtime="${1:-}"
|
|
if [ -n "${E2E_MODEL_SLUG:-}" ]; then
|
|
printf '%s' "$E2E_MODEL_SLUG"
|
|
return 0
|
|
fi
|
|
# Platform-managed path: the slash-namespaced platform model, no tenant key.
|
|
# Exercises the arm the moonshot/kimi NOT_CONFIGURED bug shipped on.
|
|
if [ "${E2E_LLM_PATH:-}" = "platform" ]; then
|
|
printf '%s' "${E2E_DEFAULT_PLATFORM_MODEL:-$E2E_DEFAULT_PLATFORM_MODEL_FALLBACK}"
|
|
return 0
|
|
fi
|
|
case "$runtime" in
|
|
hermes) printf 'openai/gpt-4o' ;;
|
|
# seo-agent is a claude-code-adapter template VARIANT selected by
|
|
# template name (template="seo-agent"), not a distinct registry runtime
|
|
# (it is absent from manifest.json + runtime_registry.go). Its config.yaml
|
|
# declares `runtime: claude-code` and copies the claude-code `providers:`
|
|
# block (providers.yaml:21 "The same block is copy-pasted into the seo-agent
|
|
# template"), so its model dispatch is IDENTICAL to claude-code's: the BARE
|
|
# registered MiniMax BYOK id (the staging-default key path), else direct
|
|
# Anthropic, else the OAuth `sonnet` alias. Sharing the claude-code branch
|
|
# keeps the SSOT one place — a seo-agent run is just a claude-code run
|
|
# behind a productized template skin, and (because the runtime resolves to
|
|
# claude-code server-side) its model must be a *claude-code-registered* form.
|
|
claude-code|seo-agent)
|
|
if [ -n "${E2E_MINIMAX_API_KEY:-}" ]; then
|
|
# BARE registered BYOK id `MiniMax-M2.7`, NOT the colon form
|
|
# `minimax:MiniMax-M2.7`. On the claude-code runtime the three MiniMax
|
|
# spellings have three DISTINCT, intentional outcomes (provider-registry
|
|
# SSOT, internal#718; pinned by workspace-server/internal/providers/
|
|
# derive_provider_matrix_test.go, the #2263/#2274 "colon-vs-slash-vs-bare
|
|
# triple"):
|
|
# * bare "MiniMax-M2.7" -> provider=minimax (BYOK, MINIMAX_API_KEY)
|
|
# * slash "minimax/MiniMax-M2.7" -> provider=platform (CP proxy bills)
|
|
# * colon "minimax:MiniMax-M2.7" -> UNREGISTERED 422 (the claude-code
|
|
# adapter CANNOT strip the `minimax:` prefix, so the id is not a
|
|
# registered model for runtime claude-code; create-validation,
|
|
# internal#718, rejects it)
|
|
# The bare form is registered in the claude-code `minimax` arm
|
|
# (registry_gen.go:88 Models=[MiniMax-M2,MiniMax-M2.7,
|
|
# MiniMax-M2.7-highspeed,MiniMax-M3]) and derives provider=minimax (BYOK
|
|
# via MINIMAX_API_KEY), so it satisfies the #1994 byok-not-platform guard
|
|
# (test_staging_full_saas.sh) AND passes create-validation — unlike the
|
|
# colon form, which 422'd "5/11 Provisioning parent workspace" with
|
|
# UNREGISTERED_MODEL_FOR_RUNTIME on real staging (job 295075).
|
|
# NOTE: the colon form IS the correct BYOK-minimax id on openclaw/hermes
|
|
# (those adapters DO strip `minimax:` — matrix test), but this dispatch
|
|
# arm only emits for claude-code/seo-agent, where bare is the right form.
|
|
printf 'MiniMax-M2.7'
|
|
elif [ -n "${E2E_ANTHROPIC_API_KEY:-}" ]; then
|
|
printf 'claude-sonnet-4-6'
|
|
else
|
|
printf 'sonnet'
|
|
fi
|
|
;;
|
|
# google-adk: Gemini via two distinct provider arms in providers.yaml
|
|
# runtimes.google-adk:
|
|
# * platform arm → `platform:gemini-2.5-pro` (keyless Vertex via the CP
|
|
# LLM proxy + server-side WIF mint; the org-compliant PROD path). This
|
|
# id is selected via E2E_LLM_PATH=platform above, NOT here.
|
|
# * google arm (AI Studio BYOK) → bare `gemini-2.5-pro` with the tenant's
|
|
# own GOOGLE_API_KEY. This is the staging-exercisable path (no WIF
|
|
# provisioning needed) and is what this branch selects.
|
|
# The workflow may further override with E2E_MODEL_SLUG=google_genai:gemini-2.5-pro
|
|
# (the adapter's provider:model spelling) — E2E_MODEL_SLUG wins at the top
|
|
# of this function, so both forms are supported.
|
|
google-adk)
|
|
printf 'gemini-2.5-pro'
|
|
;;
|
|
*) printf 'openai/gpt-4o' ;; # safest fallback (matches hermes)
|
|
esac
|
|
}
|