Three threads, all of which end at the same place: a mission whose verifier does
not share a model with the coder it reviews.
**GLM has a credential contract now.** `microvm_credential_for` returned one env
var name, which quietly assumed every provider reads its secret from the same
place Anthropic does. It returns a `Credential { source, target }` instead —
z.ai's key lives in the server's `ZAI_API_KEY` and Claude Code reads it as
`ANTHROPIC_AUTH_TOKEN`, and collapsing those two names is what forces a guess at
the other end. A wrong guess here sends one provider's credential to another
provider's endpoint.
`images/agent-glm` is the same CLI at the same pinned version as `agent-claude`
with `ANTHROPIC_BASE_URL` baked in. The split is deliberate: the ENDPOINT is a
property of the image, the CREDENTIAL is a property of the turn. That makes the
dangerous mix-up unrepresentable — a GLM VM cannot be handed an Anthropic
subscription token, and a claude VM cannot be pointed at z.ai. Asserted both
ways, because "the GLM VM must not carry CLAUDE_CODE_OAUTH_TOKEN" is the
property that costs a credential if it ever stops holding.
Kimi stays refused. `KIMI_API_KEY` is set and Moonshot serves an
Anthropic-compatible API, but I have not verified its base URL against the
running service, and this function is precisely where guessing a URL is
expensive. It becomes an arm the day someone measures it.
`api.z.ai` joins the node's default egress allow-list. A default that cannot
run the images we ship is a trap rather than a policy — the alternative is an
operator discovering it as a hung agent with no model access.
**Per-role models for claws** (migration 0071). `template_roles` had no model
column, so `mint_team_from_template` bound every role of every mission team to
one literal — a template whose whole point is an independent reviewer minted a
reviewer sharing a model with the coder. A role may now name its own; roles that
say nothing still take the mint's default, so every template written before this
behaves exactly as it did. The literal is now that default rather than a
hardcode.
**A harness scenario for the roster flow.** `verify-mission-delivery.sh roster`
runs the whole Slice 5 loop — planner proposes, human approves, mission runs —
and asserts the roster LANDED on the mission row rather than trusting the API's
answer. That distinction is not theoretical: the first live approval returned an
error while leaving the proposal marked approved.
Built and proven on tank ahead of the deploy: `clawmates/agent-glm:dev` reports
`2.1.223` and `BASE=https://api.z.ai/api/anthropic`, and
`fc-build-rootfs.sh … glm 8G` boots a VM from it that has git, can write
/mission, and answers `claude --version`.
533 tests pass, clippy clean. Migration 0071.
Co-Authored-By: Claude Opus 5 <[email protected]>
20 lines
1.1 KiB
SQL
20 lines
1.1 KiB
SQL
-- Which model a template role's claw runs on.
|
|
--
|
|
-- `mint_team_from_template` minted EVERY role of EVERY mission team on one
|
|
-- model, because a template had no way to say otherwise: `template_roles` had a
|
|
-- slot, a prompt, skills and a brain seed, and no model. So a team whose whole
|
|
-- point is an independent reviewer got a reviewer running the same model as the
|
|
-- coder it reviews — the correlated failure the cross-provider judge exists to
|
|
-- break, reintroduced one layer down.
|
|
--
|
|
-- NULL means "the mint's default", which is what every existing role gets: this
|
|
-- migration changes no behaviour on its own. A template that wants a cheap
|
|
-- summarizer or a different-provider reviewer can now say so.
|
|
--
|
|
-- No CHECK constraint and no FK to a model catalogue, for the same reason
|
|
-- `missions.backend` and `missions.validator_model` have none: which models a
|
|
-- deployment registered is configuration, not schema. An unknown alias is
|
|
-- refused where models are resolved, with a message naming what IS registered.
|
|
ALTER TABLE template_roles
|
|
ADD COLUMN IF NOT EXISTS model text;
|