Skip to content

DeclarationRetires: the trigger shape the corpus writes in prose because no variant could hold it - #8829

Merged
briansrls merged 1 commit into
mainfrom
session/sleek-tern-222
Aug 22, 2026
Merged

briansrls merged 1 commit into
mainfrom
session/sleek-tern-222

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

The finding

std.dissolution modeled exactly one trigger shape:

type DissolutionTrigger = DeclarationAppears { ref: DeclarationRef }

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"
  • and their kin: "or the crate deletes", "the directory dissolves into generated emit", "delete this module … together"

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_offer refuses by name as the DESIGN §3 fabricated-citation class — or write the condition into a description string. The second move won. A description is unreadable to dissolution_status by 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 same present_decls evidence the forward shape already consumes. Presence discharges the forward trigger; absence discharges the retirement trigger — one dissolution_trigger_fired fn, 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_dissolution to retires_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, so dissolution_is_bound is TRUE for all three where it was FALSE before.

Evidence — green by execution, with a discriminating RED

Subject: this branch. Ref: 4c05db315f (base 6c3fbeb960). 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 built v1-compiler.

function result
a_retirement_trigger_is_pending_while_its_declaration_is_present true
a_retirement_trigger_fires_once_its_declaration_is_gone true
migrated_production_retirement_conditions_are_bound true
supplying_a_bound_rows_declaration_reds_the_gate (pre-existing) true
unbound_rows_stay_unbound_under_any_present_decls (pre-existing) true

RED control — invert the DeclarationRetires arm of dissolution_trigger_fired to present (the pre-change behaviour) and re-run:

function result under inversion
a_retirement_trigger_is_pending_while_its_declaration_is_present false
a_retirement_trigger_fires_once_its_declaration_is_gone false
supplying_a_bound_rows_declaration_reds_the_gate true (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 (the declared_divergent=1 [main.rs] row is pre-existing). cargo fmt --all --check clean. The stage0 mirror src/v1/stage0/src/std_dissolution.rs is the emitter's own candidate output, copied from target/stage0-regen-candidate/src/, not hand-authored.

Measured population (subject: dag/, ref 6c3fbeb960, producer: a row-grain python scanner over module-scope data NAME: String|NonEmptyStr declarations)

  • 388 rows whose value states a retirement condition, in 252 files
  • of those, 308 rows in 214 files sit in a module with no typed dissolution carrier at all
  • corpus-wide, unbound_dissolution( appears at 772 sites against 5 bound_dissolution(ref sites

A 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, so DeclarationRetires on 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 by gunbc.prose_row_frontier prose_row_migration_scope, and none of these three modules is in that scope. Nor does it wire the migrated rows into gunbc.census_closure_frontier: that roster is folded against present_decls: [], under which every retirement trigger reports DissolutionFired by 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.

…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
briansrls merged commit 0d31564 into main Aug 22, 2026
1 check passed
@briansrls
briansrls deleted the session/sleek-tern-222 branch August 22, 2026 00:35
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant