test(mission): guard that the controller model pin is EXPORTED, not just assigned (#696) - #741
Merged
Conversation
…ust assigned (#696) #696 reported that `mission-control.sh` never exported `MODEL`, leaving the skill's routing contract (`$MODEL`/`$MODEL_WHY`, and the #611 end-of-chain fallback) reading an always-empty variable. Ghost-disciplined at HEAD: the defect was REAL when filed and is FIXED by de0e410 (2026-08-15), whose `_mc_set_controller` exports both at :234 on all four `select_model` paths. Live-confirmed in a scheduled fire (MODEL=claude-opus-5, MODEL_WHY="probe ok"). Nothing guarded it, so the fix could regress silently. `test_mission_routing.sh` already sourced the real selector into a lab but read `MODEL_WHY` in the SAME shell — an assertion that passes whether or not `export` is present. The new arm reads the variables from a CHILD process (`/usr/bin/env`), the only observable the `export` keyword moves, with an in-call known-positive control (MC_EXPORT_CONTROL) so a broken pipeline cannot masquerade as a missing export. Mutation drills, both under /bin/bash 3.2.57 (the rig's shell; CI asserts 3.x): - Reproducing #696 (drop `MODEL MODEL_WHY` from the export list) — mutant LANDED (sha256 changed) and syntactically valid (`bash -n` rc=0): all NINE pre-existing arms stayed green, only the new arm failed, and the control still fired. So the new arm is the killer rather than a bystander, and prior coverage was genuinely hollow here. - Precondition-neutering: weakening the arm's own `unset` made it PASS under that same mutant, because a suite run by the mission loop inherits the controller's real MODEL/MODEL_WHY (which match the expected string exactly). The guard would have been vacuous precisely where it matters; the `unset` is what closes that. Driver restored from a copy and verified byte-identical (sha256 + `git diff --quiet`). Also gitignores the root-level `govulncheck-filter` build artifact, root-anchored so it cannot shadow the tracked `tools/govulncheck-filter/` source dir (same hazard the adjacent `/wasm` entry documents). It has sat untracked in the driver-pin worktree since 2026-08-15 and iterations 213/214/215 each had to note and route around it at Gate 0. Gates (darwin/arm64; ubuntu + windows legs unrun locally): `make test-launchd-drivers` rc=0 (10 passed, 0 failed) · `make check-changelog` rc=0 · `gofmt -l` 0 files. Baselined on pristine origin/dev first: rc=0, 9 passed / 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What
Adds a CI-gated regression guard proving the mission controller's model pin is exported, not merely assigned — and gitignores a stray root-level build artifact.
Why —
#696, ghost-disciplined at HEAD#696reported thattools/launchd/mission-control.shnever exportedMODEL, so the skill's routing contract ($MODEL/$MODEL_WHY, and the#611end-of-chain fallback every role terminates at) was reading an always-empty variable.The defect was REAL when filed (2026-08-13) and is FIXED at HEAD.
de0e41099(2026-08-15) introduced_mc_set_controller, whose singleexport CONTROLLER_PROVIDER CONTROLLER_ID MODEL MODEL_WHY MISSION_ANTHROPIC_AVAILABLE(:234) runs on all fourselect_modelpaths — env pin (:239), override file (:249), probe-ok (:257), Anthropic-unavailable Codex fallback (:267). Live-confirmed in a scheduled fire:MODEL=claude-opus-5,MODEL_WHY=probe ok.#696's own measurement was an over-anchored pattern (^\s*export MODEL=|^\s*export MODEL$) that cannot match a multi-variable bareexport, which is why the fix arriving incidentally went unnoticed.Nothing guarded it, so it could regress silently.
test_mission_routing.shalready sourced the real selector into a lab, but readMODEL_WHYin the same shell — an assertion that passes whether or notexportis present. Prior coverage was hollow for exactly this defect.The new arm
Reads the variables from a child process (
/usr/bin/env) — the only observable theexportkeyword moves — with an in-call known-positive control (MC_EXPORT_CONTROL) so a broken pipeline cannot masquerade as a missing export.Mutation drills (both under
/bin/bash3.2.57 — the rig's shell; CI asserts 3.x)#696: dropMODEL MODEL_WHYfrom the export list. Mutant LANDED (sha256 changed) and valid (bash -nrc=0)unset, keep the mutantMODEL/MODEL_WHY, which match the expected string exactly. Theunsetis what closes this.Driver restored from a copy (not
git checkout --) and verified byte-identical by sha256 +git diff --quiet.Also
/govulncheck-filteradded to.gitignore, root-anchored so it cannot shadow the trackedtools/govulncheck-filter/source dir — the same hazard the adjacent/wasmentry documents. Verified with four controls (root artifact ignored; tracked source not ignored;/wasmstill ignored; a random new file not ignored). It has sat untracked in the driver-pin worktree since 2026-08-15, and mission iterations 213/214/215 each had to note and route around it at Gate 0.Gates
Baselined on pristine
origin/devfirst (rc=0, 9 passed / 0 failed), so these measure the change rather than the repo:make test-launchd-drivers→ rc=0, 10 passed / 0 failedmake check-changelog→ rc=0gofmt -l→ 0 filesPlatform: green on darwin/arm64; the ubuntu and windows CI legs are unrun locally.
Fixes #696
🤖 Generated with Claude Code