Repository navigation
The B4 door was one capability, not 40 diagnostics: the statement-form let has no body to lower from its own node - #8828
Merged
Conversation
…m let has no body to lower from its own node Boundary 4 of the seven-boundary product receipt refused with 40 diagnostics, of which exactly one rejects. A per-diagnostic ledger written by the transaction identifies it as dag_surface_let_expr -- the ordinary statement form, whose Bind body is the enclosing statement spine's remainder and so is not reachable from the let node at all. It is lowered at dag_surface_stmt_seq, the only node from which both halves of a Bind are visible. Executed: the door moves from body_lowering_reason_unsupported_form to normalized_tree_reason_wrapper_retention_not_normalized. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Aug 22, 2026
…loor failures The floor red on 77c9121 was 16 FAILs, all in v2.test.claim.fold_lowering (12) and v2.test.claim.body_lowering.statement_let_bind (4), every one 'non-exhaustive pattern match on OccurrenceId'. None of it is reachable from this branch's subject -- the diff touches the systemd unit-file grammar and the build-cache renderers. Diagnosed rather than assumed: main was GREEN at the same head class, and the two failing families map exactly to two commits this branch did not contain -- #8818, which routed the fold-lowering witnesses out of long/ and into the executing floor, and #8828, which is the statement-form let lowering. This branch carried the test files at their pre-#8818 home while the merged main-side routing executed them, so it ran witnesses against a compiler half a lane behind them. The remedy is the merge, not an edit: no file of mine is implicated, and the merge is clean (#8818 deletes src/v2/test/claim/long/fold_lowering_test.dag outright). Re-verified after merging: build_cache_ensure witness green, my grammar and renderer changes intact, no body-level annotation in the diff. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 22, 2026
briansrls
pushed a commit
that referenced
this pull request
Aug 22, 2026
…ad ever obeyed until one did (#8854) * The declaration was the lie: two OccurrenceId params that no caller had ever obeyed until one did MAIN IS RED at 67437fc — 16 witnesses (12 fold_lowering, 4 body_lowering.statement_let_bind) fail with `non-exhaustive pattern match on: OccurrenceId { value: 79 }`. Green PR, green base, red union: #8833's branch did not contain #8828, and neither head ever existed in the combination that fails. The mechanism is one layer below the obvious one. #8833 changed one call in `02_parse` `parse_expr_terminal` from `node_occurrence_minted(id: minted.id)` to `minted.id` — and it did that to OBEY the signature it was calling. `v2.extdeps.languages.dag` declares fn dag_int_literal_node_from_lexeme(lexeme: String, occurrence_id: OccurrenceId) fn dag_int_literal_node_from_magnitude(magnitude: DecimalMagnitude, occurrence_id: OccurrenceId) and stores that parameter straight into `Node.occurrence_id`, which is declared `NodeOccurrenceId`. Both signatures have said `OccurrenceId` since #6558. Every caller in the tree contradicted them by passing `SyntheticOccurrence`, which is why it stayed invisible. #8833's author was the first person to take the signature at its word. So the wrap is correct and the declaration is the defect. Fixed at the declaration, not just at the call site — repairing only the caller leaves the lie standing for the next caller to obey: - dag.dag: both parameters redeclared `NodeOccurrenceId`; the now-unused `import std.occurrence_identity { OccurrenceId }` dropped, `NodeOccurrenceId` added to the existing `v2.std.node` import. - 02_parse.dag: `node_occurrence_minted(id: minted.id)` restored. Caller census, read at call grain rather than by same-line grep — 17 sites across the two functions: 15 pass `SyntheticOccurrence`, 1 passes `node.occurrence_id`, 1 is dag.dag's own internal pass-through, and 1 passes the raw `minted.id`. Exactly one obedient caller existed and it is the one that reddened main, so no second detonation is waiting. Changing the declared type inverts which callers are correct, and by the finding below the typechecker cannot verify that for us — hence reading, not building. Green by execution, not by reasoning that the types now match: all 16 fold_lowering witnesses and all 4 statement_let_bind witnesses PASS. The discriminating RED is main itself. Also files one guarantee row, deliberately not fixed here. `v1.compiler.04_infer` gates `arg_compat_diags` on `module_skips_direct_call_arg_check`, which is true for every module named `v2.*`, keyed on the CALLER's module — so the direct-call argument-TYPE judgment is switched off across the entire active v2 corpus. That is why nothing complained in EITHER direction for two months. The row records the inversion that makes this class hard to see: a declaration that lies is inert while every caller contradicts it in the same direction, and detonates on the first caller who obeys it, so the at-risk population is callers who might get it right. * The row is a correction to DESIGN §4b, not an observation beside it: confinement was measured and then treated as safety DESIGN §4b names module_skips_direct_call_arg_check and reports it scoped entirely to the direct-call argument-type judgment, not reaching sole_constructor — a positive finding, axis retired. That finding is correct. It asked whether the exemption LEAKS; it never asked what the exemption COSTS inside the judgment it is scoped to, and that judgment is the argument-type check for the whole active v2 corpus. A reader who finds DESIGN's sentence and this row separately would conclude the question was already answered, so the row now says which half it is. Also records, against my own case: the arm is NOT unreasoned. direct_call_shape_wall_note states the TYPE judgment's false-positive classes are representation gaps (brand aliases, optionality's two forms, anonymous literals, expansion depth). That is a real stated reason, and it is why this row asks for a measurement rather than a deletion. What is unmeasured is whether those classes still fire in v2 and at what rate. * The corrected declaration is TRUE but INERT: the judgment that would enforce it is the one switched off Two changes, neither to the repair. RUNG HONESTY, and it is the point. Correcting the parameter to NodeOccurrenceId reads like the §5 construction move -- fix the type and the wrong call stops compiling -- but the judgment that would refuse the wrong call is `module_skips_direct_call_arg_check`, which exempts every `v2.*` caller from the direct-call argument-TYPE check. So this PR lands a wall that CANNOT FIRE. The declaration is now true, which is worth having on its own terms, but as enforcement it is inert, and shipping it while believing the class is closed is exactly the rung inflation §4b calls worse than sitting low. The class stays at its old rung; the next-rung trigger is the exemption's deletion, which the guarantee row already names. (Product-direction ruling, swift-badger-524.) ANNOTATIONS, grafted from gunbc#8856 (author's wording kept) with two additions of my own. With the type wall inert, a §4c annotation is the only thing standing at these two sites -- it is not evidence, and nothing mechanical reads it, which is precisely why it has to say why the shape is what it is. Added to the callee: a bare OccurrenceId is the PAYLOAD INSIDE one of NodeOccurrenceId's arms, never a member of the coproduct -- the modeling error in one line (smart-ram-730's phrasing, the clearest statement of it anyone produced). Added to the caller: that the exemption is why no mechanism stands there. Green by execution after the annotations: 16/16 fold_lowering witnesses PASS, 0 FAIL. --------- Co-authored-by: Brian Searls <briansearls1@gmail.com>
briansrls
added a commit
that referenced
this pull request
Aug 22, 2026
…on family (#8827) * Build-cache vertical: the systemd unit becomes a modeled value, not INI text gunbc spells itself gunbc.build_cache_unit hand-concatenated "[Unit]", "[Service]", "Environment=..." and "WantedBy=..." as string literals, which made gunbc a SECOND AUTHORITY for systemd's unit-file syntax beside extdeps.systemd.unit_file -- the §3 nicknaming that module's own header says it exists to prevent. Its systemd_medium_as_string_note declared the debt and named the wrong exit: the dissolve-on pointed at extdeps.languages, while the grammar had already landed at extdeps.systemd.unit_file. The trigger had effectively fired and nothing noticed. extdeps.systemd.unit_file grows the directives this consumer needs, each cited: Before=/Requires= in [Unit] (systemd.unit(5), already in scope) and Slice= plus a [Slice] section with MemoryMax=/MemoryHigh= (systemd.resource-control(5), a scope the module did not previously cite and now does). SliceUnitFile joins the unit-type coproduct, so a [Service] in a slice stays unwritable. Drop-ins get their own type rather than a unit file with empty sections: a fragment has no unit type, so SystemdDropIn is a LIST of known sections -- absence is absence from the list, and an empty [Service] stays distinct from no [Service] at all. The line view and the bytes are now one assembly read at two grains (systemd_unit_file_lines / serialize_systemd_unit_file), which is what let the build-cache render drop its own List<String> renderer instead of keeping one to serve consumers that want lines. The admissions stop carrying rendered text. EnsureBuildCacheAdmitted carried unit_lines: List<String> and EnsureCompilePoolAdmitted carried slice_lines -- a decision type holding a serialized artifact of one realization. Both now carry the modeled unit value, so bytes are produced by the single serializer at the moment of writing rather than frozen into the decision. NO HOST BYTES CHANGE, measured rather than reasoned. An earlier draft declared a one-byte behavioural change (a trailing newline); that was wrong -- the heredoc already supplied it. build_cache_ensure_daemon_body was rendered on this tree and on origin/main with one compiler, for the CI instance (absent capacity) and the session instance (present capacity), byte-identical both times, with a control confirming the two trees genuinely differ. The write site concatenates the heredoc terminator rather than joining it, which is what keeps that true. 95 witnesses across the six affected claim modules pass. Six new witnesses cover the added grammar, including exact bytes for a slice unit and the empty-vs-absent drop-in section. Three mutations were executed against the new walls -- a wrong MemoryHigh spelling, a line view that stops sharing the serializer's assembly, and a drop-in that always emits [Unit] -- each producing targeted reds. Does NOT wire realize_provision_build_cache onto the admission; that stays open and is zesty-wolf-464's lane on #8825. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Move the heredoc rationale to module-item grain: a body-level // refuses at the floor CI's floor phase refused this branch with four typed, located diagnostics -- "source annotation sits inside a declaration body. Only module-item grain is modeled" -- against the four-line comment I put inside build_cache_ensure_daemon_body's list literal. DESIGN section 4c says exactly this: the .dag realization admits only standalone leading // blocks attached to module-scope declarations, and body, trailing, unattached and block-comment forms refuse until separately modeled. The rationale is unchanged in substance and now sits above the declaration it describes, which is where section 4c puts it. WHY IT WAS NOT CAUGHT LOCALLY, recorded because the next person will reach for the same tool: `gunbc run` is eval-only and never applies the annotation-grain gate, so all 95 witnesses passed over source the floor refuses. `gunbc compile` is the strict path, but a whole-corpus compile does not finish inside a single local window (reconcile alone is 8-10 minutes, and the attempt timed out mid-emit), so the known-positive probe for the gate was INCONCLUSIVE rather than green -- stated that way rather than as a local check that passed. What was actually verified before pushing is narrower and is the decidable proxy for this class: the diff carries no indented // anywhere, checked mechanically across all seven changed files. That is a syntactic property, not a run of the gate, and CI remains the authority. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Merge origin/main: pick up #8818/#8828, whose witnesses were the 16 floor failures The floor red on 77c9121 was 16 FAILs, all in v2.test.claim.fold_lowering (12) and v2.test.claim.body_lowering.statement_let_bind (4), every one 'non-exhaustive pattern match on OccurrenceId'. None of it is reachable from this branch's subject -- the diff touches the systemd unit-file grammar and the build-cache renderers. Diagnosed rather than assumed: main was GREEN at the same head class, and the two failing families map exactly to two commits this branch did not contain -- #8818, which routed the fold-lowering witnesses out of long/ and into the executing floor, and #8828, which is the statement-form let lowering. This branch carried the test files at their pre-#8818 home while the merged main-side routing executed them, so it ran witnesses against a compiler half a lane behind them. The remedy is the merge, not an edit: no file of mine is implicated, and the merge is clean (#8818 deletes src/v2/test/claim/long/fold_lowering_test.dag outright). Re-verified after merging: build_cache_ensure witness green, my grammar and renderer changes intact, no body-level annotation in the diff. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Delete two _text projections that never gained a consumer runner_ordering_drop_in_text and compile_pool_slice_text were authored as the natural peers of their _lines projections and nothing ever called them -- not in this module, not in the tests, not in the realize path. Under DESIGN §6 that is "a new artifact with no final consumer", and the honest fix is a deletion rather than a note explaining why the surface exists. The _lines peers stay: render_compile_pool_slice reaches compile_pool_ensure's admission and runner_ordering_dropin_lines reaches the tests. build_cache_unit_text stays: host_effect_realize writes it into the heredoc. serialize_systemd_drop_in stays in the grammar module -- its byte-exact witnesses consume it -- and only this module's now-unused import of it is dropped. Found by review 54749 on #8827. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> 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.
The door was never 40 diagnostics. It was one missing capability.
Boundary 4 of the seven-boundary product receipt refused on the compiler's own 107-module closure with 40 diagnostics. b4 causal versus carried had already established, by reading the producers' return constructors, that 39 of those are produced on
Acceptedpaths and merely carried into theRejectedlist byrejected_with_pending— so exactly one rejects. What that reading could not do is name which one:body_lowering_reason_unsupported_formhas sixteen producer sites and the receipt renders only a tally of reason symbols.It is
let lm = dag_language_model(). More precisely, it is the class that line belongs to: the ordinary statement-formlet, which occurs at 104 sites in the tree.What it refuses, read from the producers rather than from message text
v2.compiler.body_lowering_foldbody_lower_let_exprcallsbody_lower_try_let_in_from_captured, whose first move isbody_lower_let_in_body_capture_optional— a walk for thedag_token_kw_inatom. With noinit returnsAccepted { value: Absent }, andbody_lower_let_exprrendered that absence asunsupported_form.Two independent confirmations that this is the statement form and not a failed spine walk, because the two have different repairs:
dag_grammar_let_expris a choice of two alternatives:let n = e in e, which carries its own body, andlet n [: T] = e, which does not.fn f() -> Int { let x = 1 \n x }dumped through the real grammar gives a right-nested statement spine —stmt_seq.capturedholds the let atgrammar_sequence_left_node_projectionand the continuation atgrammar_sequence_right_node_projection.So the statement form's Bind body is a sibling of the let, not a descendant of it.
body_lower_let_exprwas being handed a node that cannot in principle carry its own body. A refusal that fires on the ordinary case is a missing capability wearing a malformed-input costume.What changed
1. The capability (
body_lower_stmt_spine,body_lower_stmt_seq, reached from bothbody_lower_foldand the fn-body walker through one shared authority). A let-headed statement spine lowers tolower_bind(key, value, <remainder lowered>). It lives atdag_surface_stmt_seqbecause that is the only node from which both halves of aBindare reachable — a structural argument, not a convenience one.The guard is the whole overlap story: a spine is taken over only when it is headed by a statement-form let and has a following statement, which is exactly the case that has no lowering today. The
inform is untouched and keeps its own path. A non-final statement that binds nothing refuses with its own cause (body_lowering_reason_statement_precedes_without_binding) rather than fabricating a continuation.2. The cause split, which is worth landing on its own and would be even if the capability were bigger than it looked.
body_lower_let_exprhad one reason standing over "malformed let" and "well-formed let whose body this node cannot see". That conflation is what gave every reader the wrong first hypothesis and is why this took a session to find. It is #8801's split on the neighbouring form — spelled as two reasons rather than a coproduct-plus-projection because this result has exactly one consumer and it does not branch on the distinction; only the diagnostic does, and a coproduct whose sole consumer immediately collapses it would be a second representation of what the reason symbol already carries.3. The instrument that made the finding reachable. The transaction now writes
<txn>/b4-diagnostic-ledger.txtbeside the retention census: every diagnostic the refusal carries, in carriage order, attributed through the same locus channel and production-identity vocabulary the census already used, plus a bounded rendering of the refused subtree's atoms. Written from inside the transaction for the same reason the census is.CensusOutcomeis renamedB4EvidenceOutcomebecause boundary 4 now writes two evidence artifacts and one outcome type per artifact would be two names for one concept.A note on why the atoms and not a file: a
Nodedeliberately carries no filename —std.occurrence_identityoccurrence_identity_scope_lawnames filename as a forbidden identity input — so nothing here proposes putting one on it. The atoms are a locator, not an identity.The door moved — MOVED, and what it moved to
Both measurements are the same producer (
run_seven_boundary_product_receipt), same subject (src/v2/compiler/00_compile.dag, 107 files), same transport.parse_grammar_choice_overlap_residuebody_lowering_reason_wrapper_retained_emittedbody_lowering_reason_unsupported_form@dag_surface_let_exprnormalized_tree_reason_wrapper_retention_not_normalized@dag_surface_moduleThe
7residue invariant holds, as its mechanism predicts. The next first failure is at a different producer, not one rank down a queue of lets.And it changes the standing of the wrapper-retained population, which is the part a reader should not miss. The new holder is the normalized-tree admission refusing because retention is present. So the 37 are no longer merely carried beside the refusal — they are upstream of it. "Closing the 32 advances the door by zero boundaries" was true of the door as it stood and is not true of the door as it now stands; both prior probe documents are corrected in place rather than left to be reconciled by the next reader.
Evidence
src/v2/test/claim/body_lowering/statement_let_bind_test.dag, four claims written as invariants rather than as observations of the present frontier:statement_let_then_reference_lowers_holds— the positive controlstatement_let_chain_lowers_holds— nested bindssingle_statement_body_unchanged_holds— the path that already worked, unmovedunbound_statement_prefix_refuses_holds— the discriminating RED. A body whose non-final statement binds nothing has no continuation to bind and must refuse with its own cause. If this ever greens, the sequence lowering has begun inventing a body for a statement that produced none. It does not retire when the capability lands; it is the standing control that the capability stayed honest.Executed before enrolment, through the real grammar and the real
normalize: single-statementACCEPTED;let x = 1 \n xACCEPTED;let x = 1 \n let y = x \n yACCEPTED;1 \n 2REJECTED: body_lowering_reason_statement_precedes_without_binding. The first pre-repair run of the same four returnedunsupported_formon the two let cases.Subject discipline, which cost another lane a run tonight
Every measurement here was taken through
ctrl-build --remote, which fetches currentmainand applies the local worktree as a patch on top. The base commit is therefore not this branch's HEAD and differs between dispatches (three consecutive runs based on08be45b6,47ea349f,a9582f84). Pinning a sha would name a tree no run used, so the subject is stated as "current main at dispatch time, plus this branch's diff". A corollary worth carrying: a historical two-ref comparison is not available on this transport at all — positioning the worktree beforehand does not help, because the patch lands on whatevermainis current.