Repository navigation
DeclarationRetires: the trigger shape the corpus writes in prose because no variant could hold it - #8829
Merged
Conversation
…use no variant could hold it std.dissolution modeled exactly one trigger, DeclarationAppears, which fires when a named declaration is FOUND. Every trigger built from it is therefore a forward reference to a carrier that does not exist yet. That is the wrong shape for the retirement this corpus overwhelmingly records. The `dissolve-on:` marker names the SUBJECT -- the declaration that must go away (sol_console_send_script, configfs_op_line, nbd_server_config_text and their kin are live declarations whose disappearance IS the retirement) -- not a carrier that must arrive. With only the forward shape available an author had two moves: bind forward to a symbol nobody has declared, which gunbc.runner_spec_from_offer refuses by name as the DESIGN section 3 fabricated-citation class, or write the condition into a description string. The second move won, and a description is unreadable to dissolution_status by construction -- so the debt's retirement condition became something no mechanism can see. DeclarationRetires fires when the named declaration is ABSENT, over exactly the same present_decls evidence the forward shape already consumes. Its ref names something that exists TODAY, so unlike its sibling it is verifiable at the moment it is written. Three production conditions migrate from unbound_dissolution to retires_dissolution: extdeps.bmc.serial_console, extdeps.linux.usb_gadget and extdeps.storage.nbd. Their rationale moves to // annotations per DESIGN section 4c; the condition itself is now a trigger, so dissolution_is_bound is TRUE for all three where it was FALSE before. Evidence, green by execution against dag/test/claim/dissolution_census_mechanism_witness_test.dag: the pending-while-present and fires-once-gone pair both return true, and both flip to false under an inverted DeclarationRetires arm while the forward-trigger sibling stays true -- so the RED discriminates the retirement arm specifically. The stage0 mirror is regenerated (required-regen first_generation_equal=true) and cargo fmt --all --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls
pushed a commit
that referenced
this pull request
Aug 22, 2026
# Conflicts: # dag/test/claim/dissolution_census_mechanism_witness_test.dag
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Aug 22, 2026
…rop the two unconsumed census helpers FLOOR RUN 32539059752 REFUSED opposite_trigger_directions_agree_on_one_live_population at 5510ms against the 5000ms deadline and it went UNDECIDED, which is worse than a failure: nothing then claims the row is broken, and the figure reported is the interrupt point rather than the row's cost. Cause was the pool root — dag/std cost a 2.6s module_path_index fill for a question about the resolver, which is the cost-shape defect of pricing a mechanism at a production directory's breadth. The present/absent pair now lives in dag/test/fixture/dissolution_live_discriminator, independently authored: one declaration present, one name deliberately never authored, so both resolver answers are facts about that file rather than about whatever the corpus holds on the day the witness runs. Green by execution, and misspelling the present subject still flips it to false. Review 54537 (both findings actioned, neither blocking): dissolution_census_report_against had no caller and is deleted; dissolution_census_refused_keys now has one — census_verdict_is_answered_over_the_live_tree asserts the refused set is empty AT IDENTITY GRAIN, so a run that starts refusing names which roster refs the resolver could not decide instead of reporting a bare false. Merges main after #8829 squash-merged. All five live-tree witnesses re-verified green after the merge; required-regen first_generation_equal=true planned=132 executed=132. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls
pushed a commit
that referenced
this pull request
Aug 22, 2026
…line green could not fail (#8834) * DeclarationRetires: the trigger shape the corpus writes in prose because no variant could hold it std.dissolution modeled exactly one trigger, DeclarationAppears, which fires when a named declaration is FOUND. Every trigger built from it is therefore a forward reference to a carrier that does not exist yet. That is the wrong shape for the retirement this corpus overwhelmingly records. The `dissolve-on:` marker names the SUBJECT -- the declaration that must go away (sol_console_send_script, configfs_op_line, nbd_server_config_text and their kin are live declarations whose disappearance IS the retirement) -- not a carrier that must arrive. With only the forward shape available an author had two moves: bind forward to a symbol nobody has declared, which gunbc.runner_spec_from_offer refuses by name as the DESIGN section 3 fabricated-citation class, or write the condition into a description string. The second move won, and a description is unreadable to dissolution_status by construction -- so the debt's retirement condition became something no mechanism can see. DeclarationRetires fires when the named declaration is ABSENT, over exactly the same present_decls evidence the forward shape already consumes. Its ref names something that exists TODAY, so unlike its sibling it is verifiable at the moment it is written. Three production conditions migrate from unbound_dissolution to retires_dissolution: extdeps.bmc.serial_console, extdeps.linux.usb_gadget and extdeps.storage.nbd. Their rationale moves to // annotations per DESIGN section 4c; the condition itself is now a trigger, so dissolution_is_bound is TRUE for all three where it was FALSE before. Evidence, green by execution against dag/test/claim/dissolution_census_mechanism_witness_test.dag: the pending-while-present and fires-once-gone pair both return true, and both flip to false under an inverted DeclarationRetires arm while the forward-trigger sibling stays true -- so the RED discriminates the retirement arm specifically. The stage0 mirror is regenerated (required-regen first_generation_equal=true) and cargo fmt --all --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * The dissolution census asserted over an empty population, so its headline green could not fail gunbc.dissolution_census folded census_closure_frontier_rows against present_decls: []. Under an empty population no DeclarationAppears trigger can ever be found, so every bound row reported Pending, fired_still_present_count was 0 BY CONSTRUCTION, and frontier_expiry_clean -- the census's headline assertion, and the whole point of the carrier -- was a predicate with one reachable answer. Two production bound rows exist (extdeps.dhcp.v4 and extdeps.network.ipv6) and nothing could ever have observed either firing. std.dissolution was a correct model with no observer. The population is now the live declaration index. The mechanism half lands as gunbc.dissolution_observation, which imports no roster, so a consumer wanting the mechanism does not acquire the census's ~20-module extdeps fan-in to get it; the census keeps the roster and supplies it. A trigger ref is answered through v2.std.decl_ref_resolution resolve_declaration_ref rather than by list membership, so field grain is decided by the resolver instead of being silently missed by declaration_ref_eq, and the present population is the roster's OWN refs that resolved -- no qualified-name reprojection stands between the resolver's answer and the fold. The failure arm refuses rather than widening or narrowing (DESIGN section 5). A ref the resolver could not decide is neither present nor absent, and answering it either way would silently fire one trigger direction while silencing the other -- the hazard is symmetric now that DeclarationRetires exists. Undecided covers ambiguity and, derived rather than pattern-matched on a path convention, a module the declaration index does not cover at all: decl_facts skips test .dag files while module_declaration_facts still reports their modules, so a ref into a witness module resolves DeclarationAbsent for a reason that has nothing to do with the tree. EVIDENCE, green by execution over the live tree: census_verdict_is_answered_over_the_live_tree, census_counters_exhaustively_partition_the_rows, bound_and_unbound_partition_the_same_rows, census_holds_on_the_live_roster and nothing_is_fired_and_still_present_today all return true. THE DISCRIMINATING RED IS THE POINT OF THE CHANGE: adding fn parse_ipv6_address to extdeps.network.ipv6 -- the exact declaration the ipv6 row's trigger names -- flips nothing_is_fired_and_still_present_today AND census_holds_on_the_live_roster to false. Under the empty population that was unreachable. The probe was reverted. A second discriminator runs per-PR in the mechanism witness and is deliberately not a count: one subject KNOWN present and one KNOWN absent, asserted in opposite directions in the same run against one resolver answer, over a dag/std pool root rather than the corpus. Misspelling the present subject flips it to false. required-regen first_generation_equal=true; cargo fmt --all --check clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Budget-refused discriminator moves to a purpose-built fixture pool; drop the two unconsumed census helpers FLOOR RUN 32539059752 REFUSED opposite_trigger_directions_agree_on_one_live_population at 5510ms against the 5000ms deadline and it went UNDECIDED, which is worse than a failure: nothing then claims the row is broken, and the figure reported is the interrupt point rather than the row's cost. Cause was the pool root — dag/std cost a 2.6s module_path_index fill for a question about the resolver, which is the cost-shape defect of pricing a mechanism at a production directory's breadth. The present/absent pair now lives in dag/test/fixture/dissolution_live_discriminator, independently authored: one declaration present, one name deliberately never authored, so both resolver answers are facts about that file rather than about whatever the corpus holds on the day the witness runs. Green by execution, and misspelling the present subject still flips it to false. Review 54537 (both findings actioned, neither blocking): dissolution_census_report_against had no caller and is deleted; dissolution_census_refused_keys now has one — census_verdict_is_answered_over_the_live_tree asserts the refused set is empty AT IDENTITY GRAIN, so a run that starts refusing names which roster refs the resolver could not decide instead of reporting a bare false. Merges main after #8829 squash-merged. All five live-tree witnesses re-verified green after the merge; required-regen first_generation_equal=true planned=132 executed=132. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Bind the verdict once: the row scanned the whole corpus twice to ask two questions about one answer Review 54622, non-blocking, fixed anyway. census_verdict_is_answered_over_the_live_tree called dissolution_census_verdict() twice — once for the refused keys, once for the match — and each call re-derives decl_facts and module_declaration_facts over the whole corpus. Two scans for two questions about one answer. DESIGN's bare-minimum-cost rule is explicit that a proven cost-shape defect is always fixed regardless of the realized n, precisely because "n is small here" is not a time-stable fact, and a long-lane row is exactly where a doubled corpus scan would go unnoticed. A let binder is the whole fix. Green by execution, unchanged verdict. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls
pushed a commit
that referenced
this pull request
Aug 22, 2026
…nflation, with its consumer population measured (#8843) * File the systemd property/directive overlap as a firing carrier, with its consumer population measured extdeps.systemd's SystemdUnitProperty and extdeps.systemd.unit_file's section-scoped directive types both name MemoryMax, MemoryHigh, MemorySwapMax and TasksMax, because systemd spells one cgroup knob the same way whether it is written into a unit or read back through `systemctl show`. That looks like the §3 nicknaming this tree removes, and the obvious repair -- delete the write-side vocabulary, reuse the show surface as the directive type -- IS A REGRESSION: SystemdUnitProperty carries settable knobs and read-only observations in one flat coproduct, so reusing it makes `MemoryCurrent=` and `ActiveState=` writable into a unit file. It buys reuse by making an invalid state representable, the inverse of what the per-section directive types exist for. So the overlap is recorded rather than dissolved, and recorded where a mechanism can see it rather than in prose. The carrier classifies every member of the coproduct as SettableDirective or ObservationOnly -- total over the type, so an upstream addition cannot be left unclassified -- and asserts the conflation as BOTH CLASSES INHABITED rather than as a population size, so it survives a member being added and goes false only on the split that is its dissolution. The dissolution is DeclarationRetires (#8829) naming SystemdUnitProperty. The unification completes when the mixed coproduct is GONE, so retirement is the honest trigger; a forward DeclarationAppears naming a symbol nobody has declared would be the fabricated-citation class std.dissolution's own header cites as the reason the retirement variant exists. WHAT THE CENSUS FOUND THAT THE NAMING ARGUMENT DID NOT. The typed value barely travels. SystemdUnitProperty in typed form reaches only the show-argv builders (extdeps.systemd.systemctl, gunbc.systemctl_show_read, gunbc.host_hygiene_reaper_observe). Every other consumer takes a pre-stringified data row, and THREE of them recover the variant by string equality against that same row -- a match written as string comparison after a round trip through text that discarded the type (gunbc.host_converge_slice1, gunbc.runner_unit_live_read, gunbc.fleet_show_effective_read). So the write side never had the type at all, and part of the read side gives it up and re-derives it: a unification that only splits the coproduct would fix the smaller half. Two rows -- systemd_memory_current_property, systemd_tasks_current_property -- have no consumer anywhere, and are named rather than deleted because retiring an unconsumed cited row is a separate judgement about the upstream surface. The roster is recorded at IDENTITY grain, not as counts: DESIGN's oracle rule says a literal copied from the current tree is not an oracle and completeness is an identity join, so a re-derivation compares module NAMES and a difference says which module joined or left. Measured on origin/main at 90986d1. Six witnesses pass; three mutations were executed against them -- MemoryCurrent misclassified as settable, the classifier collapsed to one answer, and the dissolution naming a different declaration -- each reddening exactly the witnesses that own the claim and no others. This files the piece; it does not perform it. The unification is a refactor of a merged module with read-side consumers across converge, the runner unit live read and the fleet effective read, and is deliberately not smuggled into a renderer migration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Declare the member roster's half-wall: the classifier is total, the enumeration is not Review on #8843 read the hand-authored systemd_unit_property_members list as bounded by the classifier's total match. That covers one sequence and not the likelier one: adding a variant WITHOUT classifying it is a compile error, but adding it, classifying it, and forgetting the roster compiles clean and silently shrinks every wire projection derived from it -- and the author fixing the compile error is looking at the classifier, not at a list below it. No .dag facility enumerates a coproduct's variants (v2.std.node behavior_coproduct_variants hand-authors the same roster for the same reason), so this is declared at the mitigatable rung with its next-rung trigger rather than worked around. 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 finding
std.dissolutionmodeled exactly one trigger shape:It fires when the named declaration is found, so every trigger built from it is a forward reference to a carrier that does not exist yet.
That is the wrong shape for the retirement this corpus actually records. Read the corpus and the
dissolve-on:marker names the subject — the declaration that must go away — not a carrier that must arrive:sol_console_send_script— "…retires its concat-built transport"configfs_op_line— "…removing the shell leaf"nbd_server_config_text— "…renders NbdExport structurally instead of the inline join"With only the forward shape available an author had exactly two moves: bind forward to a symbol nobody has declared — which
gunbc.runner_spec_from_offerrefuses by name as the DESIGN §3 fabricated-citation class — or write the condition into adescriptionstring. The second move won. A description is unreadable todissolution_statusby construction, so the debt's retirement condition became something no mechanism can see.The missing variant is the defect, not the authors. This is the arity finding: a fork downstream of a row indicting the row's arity rather than its value.
The change
DeclarationRetires { ref }fires when the named declaration is absent, over exactly the samepresent_declsevidence the forward shape already consumes. Presence discharges the forward trigger; absence discharges the retirement trigger — onedissolution_trigger_firedfn, the only place the two shapes differ.It has a property its sibling lacks: its ref names something that exists today, so it is verifiable at the moment it is written rather than a promise about a name someone may or may not choose later. That is what makes it usable for this population without fabricating symbols.
Three production conditions migrate from
unbound_dissolutiontoretires_dissolution—extdeps.bmc.serial_console,extdeps.linux.usb_gadget,extdeps.storage.nbd. Their irreducible rationale moves to//annotations (DESIGN §4c); the condition is now a trigger, sodissolution_is_boundis TRUE for all three where it was FALSE before.Evidence — green by execution, with a discriminating RED
Subject: this branch. Ref:
4c05db315f(base6c3fbeb960). Producer:./target/release/gunbc run --source-root dag --source-root src/v2 --entry dag/test/claim/dissolution_census_mechanism_witness_test.dag --function <fn>, locally builtv1-compiler.a_retirement_trigger_is_pending_while_its_declaration_is_presenttruea_retirement_trigger_fires_once_its_declaration_is_gonetruemigrated_production_retirement_conditions_are_boundtruesupplying_a_bound_rows_declaration_reds_the_gate(pre-existing)trueunbound_rows_stay_unbound_under_any_present_decls(pre-existing)trueRED control — invert the
DeclarationRetiresarm ofdissolution_trigger_firedtopresent(the pre-change behaviour) and re-run:a_retirement_trigger_is_pending_while_its_declaration_is_presentfalsea_retirement_trigger_fires_once_its_declaration_is_gonefalsesupplying_a_bound_rows_declaration_reds_the_gatetrue(unchanged)Both new assertions flip while the forward-trigger sibling holds, so the perturbation discriminates the retirement arm specifically rather than breaking the fold globally. The inversion was reverted; the file is unchanged from the committed form.
required-regen:first_generation_equal=true planned=131 executed=131(thedeclared_divergent=1 [main.rs]row is pre-existing).cargo fmt --all --checkclean. The stage0 mirrorsrc/v1/stage0/src/std_dissolution.rsis the emitter's own candidate output, copied fromtarget/stage0-regen-candidate/src/, not hand-authored.Measured population (subject:
dag/, ref6c3fbeb960, producer: a row-grain python scanner over module-scopedata NAME: String|NonEmptyStrdeclarations)unbound_dissolution(appears at 772 sites against 5bound_dissolution(refsitesA caution about the cut, because it is the part that cannot be automated: I tried to classify the 308 by whether the token after
dissolve-on:resolves to exactly one live declaration. It yields 16 candidates, and reading them shows most are ordinary English words (carrier,named,witness,eval) that happen to collide with a declaration name. The marker is free prose and a mechanical bind is not sound over it — which is itself the argument against a sweep. What is always available is the direction this PR adds: the subject a debt row describes is a live declaration, soDeclarationRetireson that subject is expressible for this whole population without naming anything that does not exist.What this does NOT claim
It does not make prose dissolve-on unwritable. It removes the reason the corpus wrote prose — there was no expressible typed form for the retirement direction — but the wall against new prose rows is still
tools.prose_row_introduction_gate, still path-scoped bygunbc.prose_row_frontier prose_row_migration_scope, and none of these three modules is in that scope. Nor does it wire the migrated rows intogunbc.census_closure_frontier: that roster is folded againstpresent_decls: [], under which every retirement trigger reportsDissolutionFiredby construction, so enrolling retirement rows there requires the census to be given the live declaration population first. That is a separate change and is named here rather than left to be rediscovered.