Repository navigation
Conversation
…dment 29) run_graph derives node keys before any node runs. gates.battery@1 raised when its binding registry or a binding's source closure changed after the kernel was built, and the executor filed that exception as a `fail` verdict under the unedited registry's key, so later runs of the correct registry on the same store were served the stale `fail` (#1125 review, finding 1). - errors.py: KernelIdentityChangedError, a NodeRejectedError, exported from microcosm.graph. - executor.py: re-raise it for any role instead of filing it; the run refuses, nothing for the node is stored, earlier work is kept. - gates.battery@1: raise it from implementation_hash and from run, which now checks before evaluating, before returning and when evaluation raises; an unreadable or unparseable closure file is a change too. - The executor deliberately does not re-derive hashes itself: most gate hashes re-read module files, and a rewrite under code already imported would refuse correct builds. - Amendment 28, transport-graph-kernels Gates section, changelog. - Tests: graph-level contract tests and a Hypothesis same-store vs cold-store differential; the review's three-run probe and closure-file changes through the real battery. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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.
A kernel that reports a changed identity no longer has that report filed as a gate verdict (graph amendment 29). This closes finding 1 of the independent review of #1125 (Subfleet job 20261007-081708).
The defect
run_graphderives every node key fromkernel.implementation_hash()before any node runs.gates.battery@1raises inrun()once its binding registry, or a file in a binding's source closure, changed after the kernel was built. The executor turned every gate-kernel exception into afailverdict (_failed_gate_result, amendment 7) and stored it under the already-derived key of the unedited registry. A later run with the correct registry on the same store was then served thatfailas a cache hit.Reproduced on
origin/main(9f0a2b9): the binding was edited from the population observer aftertoy.calibrate, then the toy graph was run throughtoy.gates.terminal:The fix
microcosm.graph.KernelIdentityChangedError, aNodeRejectedError, is added toerrors.pyand exported. A kernel raises it when state itsimplementation_hashdescribes has moved since that hash was fixed.except KernelIdentityChangedError: raisecomes before the generic handler, for any role (executor.py:2903). For a gate this replaces amendment 7's verdict, and F4's "becomesfail", in that one case. The run refuses and nothing for the node is applied or stored. The existing_settle_failed_runkeeps what the run had already completed, because sources are unchanged and those keys still name what computed them. The next run reuses that work and computes the gate afresh.gates.battery@1. It raises the new type, which used to beValueError, fromimplementation_hash()and fromrun().run()now checks before it evaluates, before it returns, and when evaluation raises, chained to that exception. A closure file that can no longer be read or parsed counts as a change: before, it surfaced as a rawSyntaxErrororFileNotFoundErrorand was filed as afail. The message now names the source closure, which was Add shared transport-graph kernels: targets, ordered calibration, gates, diagnostics and export (NZ v0, G5b) #1125 review nit 4.docs/graph-acceptance.mdgains amendment 29, which is runtime-only:decl.pyandkernel.pyare untouched and the interface lock is unchanged.docs/transport-graph-kernels.md(Gates) is updated, and there is a changelog fragment.Why the executor does not re-check hashes itself
The first draft of this branch also re-derived every gate's
implementation_hashright afterrun()and refused on any change. The internal review executed a probe against it, and that version is not what ships.Seven of the eight production gate kernels hash module files from disk on every call through
source_hash: the UK gates, both export readbacks andgeography.gate@1. A file rewritten under a running process does not change the code it already imported. That draft would therefore have aborted a correct UK build at its first gate after any save, checkout or pull that touched one of those files, at any point after key derivation.So the check belongs to the kernel, which knows whether its identity is in-memory state.
test_a_hash_that_moves_without_a_report_is_not_second_guessedpins that gates whose hash only re-reads files keep their current behaviour.Invariants
Each is scoped to what the tests execute.
resume="auto"and"forbid", for every behaviour (pass, fail, raise), every move (none, before the gate, inside its run) and any identity value, including one equal to the original. The Hypothesis property with a same-store vs cold-store differential istest_graph_executor_gate_identity.py:523.KernelIdentityChangedErrorif and only if the kernel reports a change. Otherwise its gate outcome is the unperturbed kernel's, and amendment 7 is untouched for every other exception (:426, and the property).resume="require"then raisesStoreMiss(:349, under auto and forbid).:396).test_transport_graph.py:257,:355,:415). After each refusal, a fresh registry derives the original key and computespassas a miss, every other node hits, and a cold store agrees.Not covered. A move undone before the kernel's next check is not seen. Only
gates.battery@1checks its own identity. Seven other kernels' hashes read attributes set at construction:uk_runtime/graph_kernels.pyreads transforms and stages;_engineinus_runtime/graph_kernels.py(two kernels) andframe/kernels.py;_adapter_classesinframe/rules_kernels.py.None of them is a gate, and none is re-checked, as before. Amendment 29 asks any kernel whose identity can move in process to check it and raise the new error.
Key movement
Executed by deriving node keys at 9f0a2b9 with and without this change, without running the graph, before rebasing onto #888. The rebase left this PR's hunks unchanged apart from one merged import line in
executor.py.gates.battery@1, whose own source changed, andexport.prepare@1andtransport.package@1, which hash the battery's source. On the toy transport graph the gate nodes andtoy.export.preparemove; every other node keeps its key.implementation_hashinpackages/*/srcreads an edited file; all 41 definitions were checked.failstored by the old code sits under a key this version never derives, so it is never served.Red, green and mutants
executor.pyfrom 9f0a2b9 (everything else at head), 12 of the new or changed tests in these files fail, including every closure-change case. All pass at head.resume="forbid"repair claim, and the F4 and amendment 7 carve-out.Tests run locally
Targeted files only, one at a time, with
-p no:cacheprovider --basetemp=.hub-scratch/pytest -o tmp_path_retention_policy=failed. The environment is the shared.venvwith this worktree's sources first on the path (hypothesis 6.167.1andjsonschema 4.26.0added from the lock into scratch). Whole suites go to CI.test_graph_executor_gate_identity.py(new)test_graph_executor.pytest_acceptance_f_gates.pytest_graph_explain.pytest_graph_interface_lock.pytest_graph_parity_pins.pytest_graph_executor_source_identity.pytest_graph_acceptance_burndown.pytest_graph_manifest.pytest_transport_graph.pytest_transport_gate_kernels.pytest_transport_terminal_kernels.pytest_transport_binding_identity.pytest_transport_target_kernels.pytest_ordered_kernels.pytest_nz_spec_package.py465 passed, 0 failed. Not run locally: the full suites, the US and UK engine jobs, and a wheel build.
Also run:
ruff checkandruff format --checkon the changed Python files (ruff 0.15.16, clean);tools/ci_test_plan.py verify(ok);tools/graph_acceptance_burndown.py --verify(ok). The acceptance suite files (test_acceptance_*.py) are not edited.Owners
executor.py. Max's sessions wrote 47 of its 50 commits. María Juaristi wrote one (507e2bd, the amendment renumbering and interface re-lock), and Anthony Volk wrote two, both in Export Microcosm schema metadata for Orrery #888 (52e5545 and e6279af, the Orrery export expansion validation).gate_kernels.py. Added by the NZ hub's sessions in Add shared transport-graph kernels: targets, ordered calibration, gates, diagnostics and export (NZ v0, G5b) #1125 (e84ba89, 90503f4).decl.pyandkernel.pyare untouched.This PR adds no executor line above line 2903, so nothing it cites moves. Separately,
packages/microcosm-build/src/microcosm/build/nz/benefit_unit_rule.jsoncitesexecutor.pylines 636-643 and 1550-1573. #888 has already moved those functions to 638 and 1531 onmain; I've raised that as a separate task.axiom: n/a: graph executor runtime contract, no policy
🤖 Generated with Claude Code