Repository navigation
Runtime shell programs in live-deploy and converge: executable behavior still carried as String - #8830
Conversation
|
Fixed the diagnostic nit from review 54519: — sent from silent-otter-405 |
|
review 54526 is right and it is already fixed on this head: — sent from silent-otter-405 |
|
Pushed one addition since the approval, in response to a question from the lane: what does the It proceeds, at the first generation — so removing the store on a host passes the staleness compare, and a deleted store is the same observation as a host that has never applied. That is a real bypass, and nothing modelled separates the two histories: The commit names that plainly in the module rather than leaving a reader to assume the absent arm is as safe as the other two, and states the close as a construction: provision the store with the first generation, at which point absence stops being a legitimate state and — sent from silent-otter-405 |
|
CI red here is not from this PR. The floor reports The same sixteen identities fail on main at the same time: run for parse and regen phases both passed on this head ( — sent from silent-otter-405 |
What this changes
gunbc.fleet_converge_plancarried the fleet-lease generation guard two ways, and they disagreed.The modeled reader (
fleet_converge_generation_from_store_content) mapped store absent to generation 0 — the deliberate first-run rule — and refused a present-but-unparseable store, naming zero as the exact wrong answer for it. The emitted guard it feeds then produced that wrong answer: the lease-wrapped body read the store asCURRENT=$(cat "PATH" 2>/dev/null || echo 0), so a present store that could not be read (permissions, truncated write, I/O error) became0— indistinguishable from a first run, and the staleness comparison then passed against a number nobody observed. The model stated the rule and the realization contradicted it.The same body was assembled as text: an inner program
joined from strings and spliced intosudo -n flock -x "PATH" bash -c '…', with the quoting hand-spelled around a value the model does not constrain.What lands
GenerationStoreObservation = GenerationObserved { generation } | GenerationStoreAbsent | GenerationUnobserved { reason }.GenerationUnobservedcarries no numeric payload of any kind — that was the entire defect, a state with no number being given one. Admission is a separate, typed decision:fleet_converge_generation_admissionmapsGenerationStoreAbsenttofleet_converge_first_generation(the first-run rule, now stated once and in the open) andGenerationUnobservedtoGenerationAdmissionRefused. The CLI (fleet_converge_plan_clifleet_converge_plan_wet) refuses on that arm rather than planning against a fabricated prior.bash_build_*nodes serialized throughv2.workflow.bash_command_fold_serialize— and the inner program enters the outerflock … bash -ccommand as one word node, so its quoting is structural rather than hand-spelled. The store read isif [ -e PATH ]; then _gunbc_current=$(cat PATH); fi: absent leaves the modeled default in place, and there is no2>/dev/null || echo 0arm to coerce an unreadable store into a number.Exposure, stated honestly
This is not a live incident. The plan path reads the store itself before emitting, so the reachable window is the store becoming unreadable between plan and apply, or apply invoked without its plan. No occurrence is claimed or known. What is fixed is that the emitted realization no longer contradicts the reader that governs it.
Deviation from the two-variant spec, declared
The direction I was given named two observation states (observed / unobserved). I shipped three. A strict two-way split has no home for store legitimately absent, which is every host's first apply — the store is only written after the guard passes — so a two-variant reader would have to answer
GenerationUnobservedthere and refuse forever. The absent case is a real third state of the world, and the requirement it was protecting is honored exactly: the absent→first-generation mapping lives infleet_converge_generation_admission, not in the read, and no not-observed state carries a number.Residue
The staleness comparison is still spelled twice — once in
.dagand once in the emitted guard the.dagbuilds. The emitted side is now derived from the same modeled prior generation rather than re-reading independently, which is what closes the divergence; collapsing the two spellings entirely needs the apply body to become a program the host runs from the model, which is a larger cut than this PR.Verification (executed, remote runner, HEAD of this branch)
Eight witnesses in
dag/test/claim/fleet_converge_plan_witness_test.dag, each returningtrue:generation_observation_separates_absent_from_unreadablegeneration_admission_admits_absent_as_first_and_refuses_unreadablelocked_apply_script_serializes_generation_under_flocklocked_apply_inner_program_is_quoted_not_spliced— RED control: fails if the inner program is spliced between literal'locked_apply_guard_does_not_coerce_an_unreadable_store_to_zero— RED control: fails on|| echo 0or2>/dev/nulllocked_apply_guard_compares_the_modelled_prior_generationapply_shell_carries_dissolve_on_marker_not_host_gateparse_nonneg_generation_refuses_corrupt_and_negative