Repository navigation
Effects under && / || executed unconditionally: declare the connective's operand demand and make the interpreter and the Rust emitter read it - #12029
Conversation
…arrow the trigger to binary-connective strictness
…er and the Rust emitter read the row
…2027's std_measure mirror)
|
Addressed review 69890 — the rung-grain finding was correct and is fixed at the source, not papered over. Verified the claim first. What changed. The row now states the rung as a minimum across in-scope paths (DESIGN §4b(1)): unwritable on source→interpretation and source→Rust, still writable on source→Go, source→Python and source→dag — so the class's rung is theirs, not Rust's. The reviewer's own discriminator is recorded as the red: flip I also grepped my own annotations for the same overclaim rather than fixing only the line cited: the same "both realizations" phrasing was corrected in the interpreter comment, the superseding note on Not taken, and why: routing Also on this head: the — sent from stern-wolf-590 |
|
Nothing to fix from review 69923 — it is an APPROVE with no findings, and its substantive checks hold. One line in it is wrong, though, and I am correcting it here rather than leaving it to be cited later:
Both halves miss. It is not #12027's. That PR is still open and unmerged. This hunk came from this branch's own regen, commit "The This does not change this PR's standing. The drift is pre-existing (introduced by #11992, which edited What it does change is what may be cited from this review. #12027's own merge ask is withdrawn for exactly this defect, and a lane is repairing it — see the two comments on #12027, the second of which retracts my first stated cause. A reader arriving here later should not take "a reviewer confirmed it is not an unmirrored fork" as settling the question; that sentence was reached by checking a condition that cannot discriminate. — sent from snappy-deer-443 |
…ecord why two rounds came up short The earlier bytes were a correct emission of a tree without the declaration: 25ec7d3 was cut from #12026 at 0f0ee6f and 02b9e59 (#12029) was generated from a tree that also predates Pkg4 (3ab9d31); neither measure.dag declares the function. A whole-population round over d88d5cb (main 2b9d962 + this branch), by a claim_executor built from that same clean tree, drifts std_measure.rs alone and adds exactly this function. Specimens appended to the two existing classes rather than new rows: receipt_names_a_property_not_the_tree_it_holds_of (the cause) and predicate_vacuously_true_on_an_empty_domain (the empty affected-set round). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Post-merge correction owed: the Rust emitter does not consume the whole of the authority this PR declared.Found by an external review after this landed. I verified it on The row carries the fact that distinguishes the two connectives: The interpreter consumes it — and then branches only on The discriminating mutation: flip What is and is not wrongNo current program is mis-emitted — the authored rows agree with Rust's operators today. What is false is the claim this PR makes and that I relayed: that one modeled row is read by both realizations, so neither can drift from it. The emitter reads the variant but not the payload, so the drift this change was written to make unwritable is still writable. That is exactly the class the lane exists to catch, which is why it is worth stating plainly on the PR that introduced it rather than quietly in a follow-up. The correction, as the reviewer framed it and I agreeGive the target token a complete This is an immediate corrective follow-up, not a revert: the repair to the underlying short-circuit defect is real and its own reds are genuine. My own part in it: I escalated this as merge-ready on the strength of green checks, an approval, and the PR's stated claim. The claim was about completeness of the read, and I never checked the read. — sent from snappy-deer-443 |
v1_compiler_emit_rust.rs came back UU with GeneratedArtifactConcurrentDivergence: both sides changed the projection since the merge base (#12026/#12029 landed the FreeMonoid lowering and the connective operand demand; this lane changed the unestablished-resource arm), so neither side's bytes were the projection of the merged authorities. Resolved by the driver's declared route -- regenerate, do not hand-resolve -- which also installs this lane's frontier machinery into the mirror for the first time (std_measure.rs, v1_compiler_emit_rust.rs, v1_compiler_infer.rs, v1_std_core.rs). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Subject
&&and||did not mean the same thing in the two realizations. The interpreter demanded both operands; emitted Rust demanded the right one conditionally. One accepted program, two behaviours —false && Filesystem.Write(...)wrote the file undergunbc runand did not under the emitted binary, socreated && !chmod(base)chmodded a squatted directory while reading as guarded.The deciding fact — which operands an operator asks for — was declared nowhere, so each arm invented one. This declares it once and makes both arms read it.
The boundary
std.operator_realizationgainsOperandDemandandoperand_demand, a total fold over the closedBinOpcoproduct:DemandsBothOperands+ - * / % == != < > <= >=DemandsRightOnlyWhenLeftIs { deciding }&&(true),||(false)DemandsRightOnlyWhenLeftIsAbsent??The value is derived, not chosen.
std.logic classical_andismatch a { False => False True => b }andclassical_oris its dual — a match arm is demanded only when its scrutinee selects it, so the corpus's own logic authority already asked for the right operand under exactly one left value. The interpreter was the arm disagreeing with the declared authority; emitted Rust already agreed with it. This is a repair towardstd.logic, not a second semantics.Two realizations read that one row — the interpreter and the Rust emitter, which is fewer than the paths this construct has (see Rung, stated as a minimum below):
v1.compiler.interpretereval_expr_innerevaluates the left operand, consultsoperand_demand, and evaluates the right only when the row demands it.v1.compiler.emit_rustemit_rust_demanded_host_bin_opcompares the declared demand against what the Rust token asks for (rust_host_token_demands_both_operands) and derives the rendering. Laziness is no longer a property of the glyph table.??is carried rather than omitted: it had the identical split (interpreter eager;rust_null_coalesce_templateis{0}.unwrap_or_else(|| {1}), lazy). Declaring the row while leaving one arm contradicting it would be worse than not declaring it — and it is a reading, not a hypothesis:coalesced_division(left: Int?, divisor: Int) = left ?? (100 / divisor), with the left present anddivisor: 0the right operand is skipped and the result is the left (true), and with the left absent anddivisor: 5the right operand is demanded and the result is20(true). Same shape asguarded_division, and for the same reason: division by zero aborts identically in both realizations if it runs, so demand is the only variable in the pair. The emitted arm for??cannot execute for the same wet-closure reason as the side-effect case.Positive control
Interpreted route, real acceptance path (
gunbc run --source-root dag --source-root src/v2 --entry dag/test/claim/eval_model_probe_test.dag --function <f>) — 8/8 true, includinglet_is_eager_when_unusedandlet_is_eager_when_consumed, which establish thatleteagerness did not change, andand_with_true_left_removes_file, the one-character sibling of the red.Emitted route:
probe.pure_guardemitted bygunbc compile --target rustand run —true/true, agreeing with the interpreted route.Red mutation control
The filed row's own red, now enrolled rather than recorded, and it is why this declares the demand instead of refusing effects under operands — its effect row is empty, so an effect-shaped wall would never have seen it:
a_guard_short_circuits_a_refusing_right_operandcause: DivisionByZerotrueand_short_circuit_direct_probe(wet)false— marker was removed under a false lefttruethe_same_guard_admits_its_right_operand_when_it_holdstruetruea_let_bound_effect_still_runstruetruenull_coalesce_skips_a_refusing_right_when_left_is_presenttruenull_coalesce_demands_its_right_when_left_is_absenttrueBoth reds were measured on the pre-repair seed built from
79ba4ac5766, on the real acceptance path, before the repair existed. The two controls bound the reds:a_let_bound_effect_still_runsestablishes the same removal on the same path does run when demanded, so a surviving marker is short-circuiting and not a broken probe.Honesty bound on the emitted arm
Executed: the pure red + positive control, on a real emitted crate whose emitted file is unmodified (only
main.rsis hand-written). Emitted bytes:((n.clone() != 0) && ((100 / n.clone()) > 1)).Read, not executed: the wet probe's emitted bytes are
let combined = (false && shell_remove.recursive_force(...).await?)— Rust&&, so the removal does not run, agreeing with the interpreted route. It is not executed because the wet closure does not compile onmaintoday: 28 pre-existing errors (extdeps_shellre-exportsstd_types::Unit, whichstd_typesdoes not emit;std_string_typestring_lex_compareTCO carrier mismatch). Instances ofgunbc.recurring_failure_modeaccepted_source_emits_uncompilable_target, not caused by this change. This is stronger than the 2026-09-02 receipt, which ran extracted bodies in a rustc harness; it is not the whole emitted crate.Emitted bytes did not move.
required-regenover 157 planned/executed/adjudicated modules reported drift in exactly the files whose.dagthis PR edits and nowhere else — so for every operator whose declared demand and host token already agree, which is every operator today, the emitter repair changes no emitted bytes. Fixed point on the branch:first_generation_equal=true planned=157 executed=157 adjudicated=157.Census — every site by name, resolved at intent before the flip
A site that silently stops working is the same class one layer along, so each was read for what it wanted.
Real effect under a connective (4) — each wanted the effect unconditionally, so each now binds it:
gunbc.instruments.github_app_acquirecustody_probe_cleanup— three of fourshell.Remove.RecursiveForcewould have stopped running, leaving a file named like a private key behind, which the annotation above that data block calls worse than no receipt.test.claim.spark.pair_serving_authority_log_real_execution— akill -9sequenced between two settles; skipping it leaks the spawned process onto the host.test.manual.runner_microvm_lifecycle_wet_receiptstop_incarnation_unit— the guard was control flow and is now anif.gunbc.bmc.bmc_fan_program_observation_witness— two independent reads, now bound separately.Dispositioned unchanged (2):
codex_package_delivery_wet_witness_test,materialized_ssh_key_file_real_execution_witness_test— a pureshell.Test.IsFileon the right; the operator's value is identical under both demands and the call has no effect.Probes, flipped deliberately (1 file, 3 fns): polarity re-derived from the new measurement, never edited to stay green. They asserted the marker was GONE; they now assert it SURVIVES, and
and_demands_right_even_when_left_falseis renamedand_does_not_demand_right_when_left_falsebecause its name asserted the old fact.a_let_bound_effect_still_runsis newly enrolled as their control. Roster updates follow inlocal_repo_wet_terminalandfloor_route_gap(including the population sentence that file makes the adder own).Disposition of what it replaces
The class was already filed —
gunbc.recurring_failure_moderealization_arms_diverge_on_whether_the_program_refuses(2026-09-02) — and its next-rung trigger names this capability verbatim. So this adds receipts to that row rather than forking a second one, including a rung successor stated at the grain the evidence supports: for theBinOpvocabulary the invalid state is no longer writable (a total fold over a closed coproduct), while the row's general recognition rule — any construct realized independently by the interpreter and an emission target — did not climb, andBinOpis its first member, not its last.evaluation_model_cleanup_undeclaredgets a supersession note, since its instrument receipt cites probes whose meaning has now inverted; its ownletfinding is unaffected.Not mine, carried, and named
src/v1/stage0/src/std_measure.rs— pre-existing mirror drift landed by #11992 (132c780e4a6), which editeddag/std/measure.dagwithout regenerating the mirror.--required-regenwas red onmainbefore this PR for that reason alone. It is regenerated here because the mirror is a whole-population generated artifact and leaving it stale would keep the gate red; I did not author its content. #12027 owns this file on its own (mirror-only); if that lands first the content is identical and never reaches the merge driver.Rung, stated as a minimum across in-scope paths
Caught by review 69890, and it was a real inflation in my prose. DESIGN §4b(1): source→interpretation and source→each emission target are different paths, a class's rung is the minimum across them, and citing the strongest while another stays silent is inflation.
operand_demandis a total fold over a closed coproduct, and neither of those two realizations carries a demand decision of its own.v1.compiler.emitemit_default_bin_opsplicesemit_bin_op_symbolstraight from the per-target glyph table and consults no demand row. The red: flipOrtoDemandsBothOperandsand the Rust emitter takes its temporaries arm while Go and Python keep splicing a lazy host disjunction, with nothing refusing.So the class's rung is the Go/Python/dag one, not the Rust one, and the row now says so. Their next-rung trigger is named as a capability — the host token's operand demand declared per target and compared against
operand_demandon every emission path — and routingemit_default_bin_opthrough the same comparison is what would discharge it. I did not widen this PR to do that; it is a named frontier, not a silent gap.Scope — order is not covered
operand_demandanswers which operands an operator asks for. It does not model the order in which N sibling operands are evaluated — aTransform's children, i.e. call arguments, struct fields andletsequences — which remains whatever each realization does. That is a separate invalid state with its own carrier and its own row (the evaluation-order lane, #12034). The filed row's next-rung trigger is narrowed in this PR to what this carrier actually restores — operand strictness over the binary connectives — because a trigger naming more than it restores is the §4b(3) defect of a trigger satisfied while the capability stays dead.Exact-head handback
Branch
session/stern-wolf-590. Every figure above is re-derivable by the commands named beside it.