fix(runner): code_sha is refused when unmeasured, never the all-zero placeholder - #208
Conversation
…placeholder
alpha-engine-config-I10454: every v2 manifest a dispatched box wrote carried
code_sha as forty zeros. `_code_sha()` fell back to it whenever `git
rev-parse HEAD` failed (a wheel install has no git checkout), and the value
validated against code_sha's `^[0-9a-f]{40}$` pattern the same as a real
commit sha -- half of `explain`'s answer to "why did it do that" was
silently absent on every box-produced manifest.
- crucible.runner.resolve_code_sha replaces _code_sha(): $CRUCIBLE_CODE_SHA
wins when set (the box's dispatcher will export this from the same
releases/current sha it already reads CRUCIBLE_RELEASE_SHA from -- see
the paired nous-ergon-ops PR), else `git rev-parse HEAD` off the tree
this module ships from (the laptop/CI path). Either producing something
other than a real sha raises CodeShaError, resolved once at the top of
run_job -- before the trading day, before any work -- mirroring
resolve_run_mode's existing shape, so the refusal happens where a raise
cannot collide with "manifest or it did not happen": the process never
reaches a job that would need one.
- RunManifestV2 gains _code_sha_is_not_the_placeholder (a model_validator,
not a pattern -- pydantic-core's regex engine has no look-around),
mirrored into the published schema's allOf by
_run_manifest_v2_json_schema_extra, same shape as the status/reason
cross-field rule. Both the model and the plain-jsonschema file refuse
the placeholder on their own.
- release_sha is untouched: it is real today on every manifest measured,
and this issue is about code_sha specifically.
Proof of red: a standalone reproduction (kept out of the diff, evidence in
the PR body) showed the pre-fix runner writing code_sha="0"*40 on a
simulated no-git box and the manifest validating clean; re-run against this
fix, the same scenario now raises CodeShaError before any manifest is
written. tests/test_manifest_schema.py::TestCodeShaRefusesTheAllZeroPlaceholder
and tests/test_runner.py::TestCodeShaIsMeasuredNotPlaceholder are the
committed proof, both new and both failing against the pre-fix code.
Full suite green (uv run --frozen pytest -q --ignore=tests/acceptance),
ruff clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WARhC81oatq9czVeyaM66L
|
HOLD — this PR must not merge until the The ordering hazard is real and this PR's own body names it honestly:
Clearing it, in order:
This is the same class as |
What / why
Closes:
alpha-engine-config-I10454.Every v2 manifest a dispatched box wrote carried
code_shaas forty zeros.crucible.runner._code_sha()fell back to the all-zero placeholder whenevergit rev-parse HEADfailed — a wheel install on a spot box has no git checkout — and that value satisfiedcode_sha's own^[0-9a-f]{40}$pattern, identical to a real commit sha.crucible/AGENTS.mdcallscode_sha"half ofexplain's answer to 'why did it do that'" — that half was silently absent on every box-produced manifest, and nothing refused it.Where
code_shawas meant to come from, and why the box can't derive it: the box installs a wheel, not a checkout — there is no.gitto read. The commit that produced the wheel IS the shareleases/{sha}/is addressed by (the same sha the box already resolvesCRUCIBLE_RELEASE_SHAfrom). Socode_shahas to be carried IN the release, the same pathrelease_shaalready travels — not derived on the box, and not a second value to compute. The companion PR (nous-ergon-ops-PR1203) exportsCRUCIBLE_CODE_SHAfrom that same already-resolved$SHA.The fix here (crucible side):
crucible.runner.resolve_code_shareplaces_code_sha().$CRUCIBLE_CODE_SHAwins when set and is validated as a real 40-hex sha (not the placeholder); otherwisegit rev-parse HEADoff the tree this module ships from (the laptop/CI path, where the env var is normally unset). Either source producing anything else raisesCodeShaError.run_job— before the trading day, before any work — mirroringresolve_run_mode's existing shape exactly. This is deliberate:code_shais required in every manifest the runner writes, including the_minimal_failed_manifestfallback-of-fallbacks, so a raise from inside the write path would collide with "manifest or it did not happen" (rule 1) — the write path can never be the place this fails. Raising beforerun_jobdoes anything means the process never reaches a job that would need a manifest, the same way an undeclaredrun_modealready refuses.RunManifestV2gains_code_sha_is_not_the_placeholder, amodel_validator(not a tightenedpattern— pydantic-core's regex engine has no look-around:SchemaError: look-around, including look-ahead and look-behind, is not supported, measured against pydantic-core 2.46.5). Mirrored into the published schema'sallOfby_run_manifest_v2_json_schema_extra, same shape as the existing status/reason cross-field rule, so a consumer with no Python import gets the same refusal.release_shais untouched. It is real on every manifest measured today; this issue is aboutcode_shaspecifically (the issue body explicitly separates the two questions).Found and NOT fixed here (filed separately, see below):
crucible/deploy.pyandcrucible/promote.pycarry the identical all-zero-sha-as-fallback shape (_UNKNOWN_SHA = "0" * 40"indeploy.py;code_sha or "0" * 40inpromote.py, feedingChampionPointer). Neither is in this PR's owned files (runner.py,manifest.py,release.py,explain.py), and touching them risks colliding with the sibling session workingtrack_f.py/gate.py/config.py. Filed asalpha-engine-config-I10506— see the tracker note below.crucible explain: already readscode_shastraight off the manifest (explain.py'sLineage.to_dict/render) — no code change needed there. It was already correctly wired; it just displayed zeros because the producer wrote zeros. It now displays the real value automatically.Proof of red
Reproduced against the PRE-FIX code (temporarily reverted
crucible/models.py,crucible/runner.py,crucible/schemas/run_manifest.v2.jsonviagit checkout --, then restored viagit applyof the saved diff — not part of this diff):Pre-fix: 1 passed — the manifest validated clean with
code_sha == "0"*40. Post-fix (this diff), the identical scenario: 1 failed,crucible.runner.CodeShaError: $CRUCIBLE_CODE_SHA is unset and \git rev-parse HEAD` could not run ([Errno 2] No such file or directory: 'git')` — no manifest written at all.The committed proof lives in two new test classes, both written and seen failing before the fix:
tests/test_runner.py::TestCodeShaIsMeasuredNotPlaceholder(5 tests: env override used, malformed/placeholder env refused, git-unavailable refused with no manifest written, git used when env absent, failed-run path still carries the real value)tests/test_manifest_schema.py::TestCodeShaRefusesTheAllZeroPlaceholder(4 tests: model refuses it, published schema file alone refuses it with no Python import, a real-looking-but-mostly-zero sha still validates,release_shais unaffected)Test plan
uv run --frozen ruff check .— cleanuv run --frozen ruff format --check .— cleanuv run --frozen pytest -q --ignore=tests/acceptance— full blocking suite green, run BEFORE opening this PRtests/acceptanceunaffected (still RED by design, unrelated to this change)Cross-repo
Paired PR:
nous-ergon-ops-PR1203(branchfix/export-crucible-code-sha-i10454) exportsCRUCIBLE_CODE_SHAfrom the crucible-v2 dispatcher's already-resolved release sha. Neither PR depends on the other's merge to pass its own CI — this PR'sresolve_code_shaalready handles the "no env var, no git" case by raising, which is correct even before the box exports the variable (a box without the env var will simply refuse to run until the companion PR is live, which is the intended fail-loud behavior rather than a silent zero).Out-of-scope finding filed
alpha-engine-config-I10506—crucible/deploy.pyandcrucible/promote.pycarry the same all-zero-sha placeholder fallback class this issue fixes inrunner.py; both are outside this PR's owned files.Prepared by: Claude Opus 5 (1M context) via Claude Code