Skip to content

gunbc test <label>: the target dispatch seam, one producer - #9336

Merged
briansrls merged 24 commits into
mainfrom
session/fierce-lynx-766-test-verb
Aug 27, 2026
Merged

briansrls merged 24 commits into
mainfrom
session/fierce-lynx-766-test-verb

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

gunbc --help listed compile, run, converge, serve. Nothing in this repository could run a target
by name. #9323 landed the binding vocabulary and #9332 the standing join; both are carriers with no
route into them. This is the route: one verb, one generic seam, one producer.

gunbc test //gunbc/instruments:heads-reading-differential

reaches the real heads-reading instrument, returns that producer's own standing vocabulary, and is
absent from //:required.

The four things this owns

1. A positional operand axis in the CLI model. gunbc.cli_dispatch_surface modelled named
options only. It now carries CliOperandRow with CliOperandValue = CliAbsoluteLabelOperand and
CliOperandArity = CliExactlyOneOperand. The interface is NOT respelled as gunbc test --target //...: an operand and an option differ in what the CLI grammar admits, not in which row shape was
convenient, and a surface that cannot say which input is positional cannot state this verb at all.
The operand names an AUTHORITY rather than a spelling -- extdeps.bazel.label Label -- so target
patterns (..., :all, :*) refuse there, with TargetPattern as the cause, and no second and
weaker label grammar is written at the CLI layer.

The arity is its own coproduct rather than CliOptionArity reused: two of that type's three arms
are meaningless positionally, and a variadic operand list should have to decide what it means
rather than inherit an option's answer.

2. No CliDelegatesToHostFn. That arm carries an authored Rust symbol as a String, which
binds the model to a realization by spelling -- the stringly hook this architecture replaces. The
verb sits on a new CliInvokesBoundTargetProducer, which carries NO payload deliberately: it
declares that the subject is resolved through the binding registry and realized by whatever that
registry binds, so the CLI authority names no realization at all. the_test_verb_names_no_host_function
goes red the moment someone re-homes it onto the stringly arm.

3. Real production rows, and the absence proof. gunbc.instrument_targets declares the actual
BuildTarget and TargetBinding for the differential -- #9323 defined carriers and fixture
examples and declared no addressable target.

The absence proof is measured over the DERIVED population, not asserted:
the_instrument_label_is_absent_from_the_derived_required_aggregate builds a census, derives
//:required from it through required_aggregate_from_census, and tests membership at identity
grain. The second conjunct is what makes it a check rather than a sentence -- the planned fixture
site's own label must be PRESENT in the same population, so an aggregate that lost every row, or a
membership predicate stuck at false, reds here instead of satisfying the absence claim perfectly.

A second witness plants the nearest site a discovery census could ever produce -- same package,
function name heads_reading_differential -- and asserts the near miss stays a miss: a discovered
site's target name is a .dag FUNCTION name, and heads-reading-differential is not an identifier
any function can carry, so the two naming axes cannot collide even by accident.

4. One generic invocation seam. route_target_invocation: operand -> parse_label -> build the
index -> exact lookup -> binding. It names no instrument, no producer and no host function, and it
takes its population as an ARGUMENT, so its behaviour is checkable against a fixture rather than
against a transcription of the rows it already reads. an_invented_population_routes_through_the_same_fold
runs a population this repository does not have through the same function with no edit anywhere.
There is no mode switch in main.rs -- the arm is a call -- and no coordinator with an arm per
instrument. The remaining three instruments are rows plus one classifier each.

One producer, deliberately. Landing four at once would have made a first failure ambiguous between
dispatch, subject reachability and output policy.

The output-policy trap, pinned from three sides at once

A direct invocation renders the producer's NATIVE standing. It does not consult
test_standing_verdict (that is //:required aggregate policy, which deliberately refuses
instrument producers as out-of-aggregate) and it does not consult the Blaze status export (which
deliberately refuses instrument standings). Both are correct code; either one here would report a
healthy instrument as a problem.

the_direct_standing_is_not_the_aggregate_verdict_nor_the_blaze_export asserts all three answers
about the SAME completed, held observation together, so a later change routing the direct result
through either policy reds in the witness rather than in production.

The rendering says neither PASSED nor FAILED -- those are the aggregate's words. It reports what the
differential measured. narrowed is reported and excluded from the verdict: it is the declared,
bounded scope narrowing, counted rather than absorbed. Three exit statuses rather than two, because
a caller that cannot separate "found a defect" from "never ran" has been handed the absorbing answer
§5 forbids: 0 held, 1 an observation that did not hold, 2 no observation.

The hand-Rust growth, enumerated

gunbc.target_invocation_seed_growth carries the obligation, 19 declarations at identity grain,
enrolled in seed_growth_justification_roster.

The producer leaf is Rust because its subject is a filesystem walk the hermetic floor refuses by
construction. The route around it is hand-mirrored for a different reason: none of the three modeled
modules is in the v1 seed's emitted closure -- src/gunbc_cli_dispatch_surface.rs is the only
gunbc.* mirror the emitter produces, because the emitter imports that one module to render its own
subcommand surface.

Rung: mitigatable, stated honestly. The host route and the modeled route can drift and nothing
detects it -- same rung and same reason required_regen_host sits there while v2.workflow.required_regen
holds its ordering by construction. What is claimed is that the growth is enumerated, not that the
mirror is checked. Dissolution trigger: the seam is EMITTED rather than authored, at which point only
the producer leaf stays behind; the label admission dissolves independently the moment
extdeps.bazel.label is emitted, being a pure function of a string.

Two alternatives were rejected and are recorded in the carrier: routing the verb through the
interpreter would make it load-bearing for a NEW capability while two lanes delete it, which is
exactly what main.rs's converge arm argues against; growing the emitter's closure to reach
extdeps.bazel.label is a larger seed change than the verb it would serve.

The host mirror deliberately omits the model's TargetHasNoProducer arm: the host registry is a
list of PAIRS, so a registered target with no producer has no representation there, and an arm
nothing can construct would be a decoration that reads as coverage (§4b). The .dag route keeps it,
where the two populations are separate lists and the state is reachable -- and a witness reaches it.

Sequencing

Stacked on session/bright-moth-189 (#9323). When that merges, main is integrated with a merge
commit, never a rebase.


Verified by execution, not by CI

Every figure below was produced by running the thing, remotely, on this branch.

The acceptance case:

$ gunbc test //gunbc/instruments:heads-reading-differential
heads-reading-differential: compared=4034 divergent=0 narrowed=0 regressed=0 both_refused=0
heads-reading-differential: full_reading_parse_ms=7269 heads_reading_parse_ms=3541
exit 0

It reaches the REAL instrument -- 4034 modules read both ways -- and returns that producer's
own vocabulary. The four refusals all exit 2: :all (pattern), :heads-reading-differential
(relative), :no-such-instrument (unknown), :heads-reading (a prefix of a registered label,
which is a different target and is not silently resolved to the near one).

Baseline: 19/19 witnesses pass.

Three discriminating REDs, each with the failing set PREDICTED before the run:

Mutation Predicted Actual
heads_reading_differential_holds also requires narrowed empty 2 fail exactly narrowing_holds_while_divergence_does_not and the_direct_standing_is_not_the_aggregate_verdict_nor_the_blaze_export
verb re-homed onto CliDelegatesToHostFn { symbol: "test_verb" } 1 fail exactly the_test_verb_names_no_host_function; the other four pass
instrument target renamed heads-reading-differential -> heads_reading_differential 4 fail exactly those 4; the other ten pass

The third is the one worth reading. Renaming the target into the census's OWN naming axis is
what makes the two label populations able to collide at all, and the absence proof and the
near-miss witness go red TOGETHER when it does -- which is the evidence that the absence is a
property of the naming axes rather than of which sites the fixture happened to author.

Four things found by running it, not by reading it

  1. A single-arm coproduct whose variant has no record body parses as a type ALIAS.
    type CliOperandValue = CliAbsoluteLabelOperand produced unresolved type. The corpus's
    precedent is extdeps.standards.ieee_754_2019 Ieee7542019 {} -- the arm needs {}.

  2. ClaimDisposition gained PanickedBeforeVerdict and NotAttemptedAfterAbort on main
    after Model target producer bindings and instrument standings #9323 wrote gunbc.build_target's wildcard-free policy folds, so this branch is where
    they meet and refuse to compile. That is the fold doing exactly what its own comment says it
    must. Both classified UNSATISFIED; NotAttemptedAfterAbort is the damaging one to absorb --
    a required set treating "never attempted" as satisfied reports green over a run that stopped
    early. Its Blaze export takes the exact upstream member HALTED_BEFORE_TESTING rather than
    the tempting INCOMPLETE, which upstream already distinguishes.

  3. target_invocation_host.rs needed a seed-retained registration. Regen refused it as a
    committed mirror the emitter no longer produces. Registered has_pub_mod: false, matching
    required_regen_host and declaration_index, and its three generated artifacts moved with it.

  4. The emitted gunbc_cli_dispatch_surface.rs mirror is installed from the regen candidate
    rather than hand-written.

Review-noise note

The diff shows #9323 and #9332 content because this branch is built on
session/bright-moth-189 while the PR targets main -- which is what lets CI actually run
rather than being structurally dark. It resolves itself when #9323 merges. This branch also
merges 6db7a1b25f so it carries the sole_constructor seal on TargetBindingIndex; without
that merge it would have installed the unsealed carrier.


Update after integrating gunbc#9323's typed canonicality (review 56345)

review 56345 filed REQUEST_CHANGES against label_is_canonical. That function was authored on
session/bright-moth-189 (commit 56062764f25, gunbc#9323) and is absent from main — this PR
carried it only because it is branched from #9323 while targeting main. I did not edit another
lane's in-flight authority; I read the finding, judged it, and relayed it, and #9323's owner
agreed and fixed it at 0b1cb72c569.

The objection as filed does not hold — it asked to route through the canonical equality
surface, but package_eq renders both sides, so PackagePath(["a", "b"]) and
PackagePath(["a/b"]) compare equal, which is precisely what canonicality exists to catch.
Routing through it would defeat the function.

A different objection was right, and that is the one that got fixed: label_is_canonical
discarded a typed cause it already held, collapsing the whole LabelRefusal vocabulary AND the
structural-mismatch case — different facts with different repairs — into one false.

What this PR adds is the last leg of that chain. label_canonicality is useless if the cause
dies before reaching a caller, and it was dying here: InvocationLabelNotCanonical carried a bare
Label. It now carries LabelCanonicalityCause, threaded from TargetLookupLabelNotCanonical,
and the rendering surfaces it — so gunbc test tells a user WHY a label was non-canonical. A
typed cause nothing renders exists for programs and not for the person running the verb.

The inner LabelRefusal is carried but deliberately not decomposed in the rendering: that
vocabulary belongs to the label authority and has no renderer there, so writing one here would be
a second site deciding what DotSegment says to a human — the §3 fork arriving through a
rendering rather than a type. The typed cause stays intact for any consumer that descends.

Verified, 21/21 green, including two new witnesses proving the cause survives both the route
and the rendered text. RED4, predicted before running: replace the threaded cause with a
fabricated constant → both new witnesses fail, the acceptance route still passes. Exactly that.

The ci-verb merge, and why the mirror was regenerated

main deleted the ci verb (gunbc#9325). Two conflicts, two different rules:

  • the .dag source — took main's side; the row goes, five verbs remain.
  • the generated mirror — not hand-merged. The merge driver refused rather than answering
    true, and printed its recipe, so the recipe ran — including the step easy to skip: pass 1
    emits from a binary that PREDATES the change it emits, so a pass-1 green can be right for the
    wrong reason. Candidate installed, seed rebuilt from it, regen re-run:
    pass 1 first_generation_equal=false FAIL drift → pass 2 first_generation_equal=true, and the
    two passes' bytes are identical (sha256 fd2e29055c9d8b9d, 21336 bytes) — checked, since the
    pass-2 exit status alone cannot distinguish a real fixed point from the wrong-reason green.

gunbc-ci-auto-heal added 16 commits August 26, 2026 08:02
…dingIndex

Branching from c45e2dd left this branch carrying a TargetBindingIndex with no
sole_constructor. The diff includes target_binding.dag, so merging without this
would have installed the unsealed carrier -- a silent undo of the seal rather
than a stale branch, letting any module forge an index with hand-written maps and
bypass the canonicality, uniqueness and known-target admission the constructor
enforces.
The only conflict, and it is the one that module's own note predicts:
seed_growth_justification_roster is a CLOSED roster, so taking either side
whole silently deletes the other branch's authority obligation rather than
conflicting. main added claim_unwind, this branch added target_invocation;
both rows survive.
…e limit

The old --heads-reading-differential mode is removed in the same change that makes
the instrument addressable, so there is never an interval where two routes to one
producer both work. Its parse-wall figures survive as an explicitly unmodeled host
line: a replacement that silently drops a capability is not a replacement.

The producer executes against an unpinned live working tree and the carriers now
say so. InvocationOutcome stays an OUTCOME rather than a receipt, and both the
model and the host record that no digest field can close the gap -- the tree can go
A -> B -> A while the producer reads a mixed population and both hashes agree.
… written

PanickedBeforeVerdict and NotAttemptedAfterAbort landed on the floor's disposition
vocabulary after #9323 wrote gunbc.build_target's two wildcard-free policy folds, so
the merge of main into this branch is where they meet and refuse to compile. That is
the fold behaving exactly as its own comment says it must: a new arm fails to compile
rather than being inherited from whichever arm a wildcard named.

Both are UNSATISFIED for the aggregate. NotAttemptedAfterAbort is the one that would
have been damaging to absorb -- a required set treating 'never attempted' as satisfied
reports itself green over a run that stopped early, the empty-observation narrow
arriving as a green.

The Blaze export gives NotAttemptedAfterAbort its exact upstream member,
HALTED_BEFORE_TESTING, rather than the tempting INCOMPLETE that would have collapsed
it into a pile upstream already distinguishes. PanickedBeforeVerdict has no member and
joins FAILED beside RuntimeErroredBeforeVerdict.
…LI mirror

Two regen obligations the CLI model change created.

target_invocation_host.rs is a committed src/v1/stage0/src/*.rs the emitter does not
produce, so regen refused it as a mirror that stopped being emitted. It is declared
as a SeedRetainedIntrinsicRegistration with has_pub_mod: false, matching
required_regen_host and declaration_index -- it is wired by #[path] mod inside
cli_run.rs and contributes no pub mod line to the emitted lib.rs. The registration
moves three generated artifacts alongside it, as every prior registration has.

gunbc_cli_dispatch_surface.rs is the emitted mirror of the module that gained the
operand axis, installed from the regen candidate rather than hand-written: the two
new one-arm enums, CliOperandRow, the operands field on every subcommand, the
CliInvokesBoundTargetProducer arm and the test verb row.
@gunbai-bot gunbai-bot Bot changed the title gunbc test <label>: the target dispatch seam (one producer first) gunbc test <label>: the target dispatch seam, one producer Aug 26, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 26, 2026 17:40
gunbc-ci-auto-heal added 4 commits August 26, 2026 18:01
…er INCOMPLETE

#9343 landed on main fixing the same two ClaimDisposition arms this branch fixed
independently, so the two met in build_target.dag. Both halves resolved deliberately
rather than by taking a side whole.

THE IMPORT: main's build_target.dag has no gunbc.target_binding import because #9323
has not merged, so main's TestStanding carries three arms and this branch's carries
seven. Taking main's side would have deleted the four producer arms and every consumer
of them. HEAD's side survives.

THE EXPORT, AND THIS IS A DELIBERATE DIVERGENCE FROM #9343 RATHER THAN A MERGE
ARTEFACT. The aggregate policy is byte-identical between us -- both arms unsatisfied,
same causes. The Blaze export differs on one line: #9343 renders NotAttemptedAfterAbort
as INCOMPLETE, this keeps HALTED_BEFORE_TESTING.

The reason is upstream. NotAttemptedAfterAbort is constructed from NotAttempted
{ halted_by }, and Bazel's HALTED_BEFORE_TESTING means exactly 'the test did not run
because the build was halted'. It is an exact member for the fact, so the export loses
nothing; INCOMPLETE collapses it into the pile that module's own comment already
describes as lossy, beside HostToolUnresolvedBeforeVerdict and RouteGapBeforeVerdict.
Nothing reads the export back into a decision, so this is a fidelity choice and not a
correctness one -- a one-line revert if the reviewer prefers main's reading.
The export arm for NotAttemptedAfterAbort has now been decided twice, the other way:
gunbc#9343 chose INCOMPLETE on main while this branch chose HALTED_BEFORE_TESTING, and
the two met in a merge. Without the argument written down, the next reader diffing
against that history finds an unexplained divergence and 'fixes' it back.

The receipt is checked rather than recalled: extdeps.bazel.test_status carries
TestStatusHaltedBeforeTesting as wire number 8, BLAZE_HALTED_BEFORE_TESTING, and the
disposition is built from NotAttempted { halted_by }. Same fact, exact member.

Module-scope comment, above the declaration -- the annotation grain rule refuses one
inside the match body.
Both ClaimDisposition folds in gunbc.build_target carried two PanickedBeforeVerdict
arms and two NotAttemptedAfterAbort arms after the merge, and the export's second
NotAttemptedAfterAbort carried #9343's INCOMPLETE. Whichever way the compiler resolves
a duplicate, one of the two readings was wrong: first-match-wins reds the witness,
last-wins makes my change dead code and silently keeps the other value.

HOW I MISSED IT, because the check I ran looked sufficient and was not. After the merge
I grepped the diff against main and saw my HALTED_BEFORE_TESTING arm present, and
concluded the resolution was right. Presence is not exclusivity. A conflict git reports
gets resolved carefully; a hunk that applies CLEANLY beside an existing one does not
announce itself, and that is the direction this class always arrives from. Found by
cool-swift-307 reading my head, not by me and not by CI -- no checks had run.

Both folds are now one arm per member: 13 arms against ClaimDisposition's 13 members,
verified by counting rather than by eye, and every other match in the touched modules
swept for the same shape.

bazel_target_model_witness_test moved with the code rather than being left asserting
the value the code no longer produces. Its run/never-ran argument is UNCHANGED and
survives intact -- INCOMPLETE is in the never-ran family, so the split it tests was
never wrong, only vaguer than upstream allows. What the arm records now is which
never-ran member is exact, with the wire number and both PRs named.
Same conflict as the last main integration and the same resolution, because
seed_growth_justification_roster is a CLOSED roster: taking either side whole
silently deletes the other branch's authority obligation rather than conflicting.
main added qualified_pipe_callee, this branch has target_invocation; both rows and
both imports survive.

Checked the duplicate-arm hazard that the previous main merge produced -- arms
landing BESIDE existing ones rather than onto them, applying cleanly and announcing
nothing. It cannot have happened here: this merge leaves build_target.dag,
target_binding.dag, cli_dispatch_surface.dag, target_invocation.dag and
instrument_targets.dag byte-identical, verified per file rather than assumed from
the absence of a reported conflict.
@gunbai-bot

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

On review 56345 (REQUEST_CHANGES against dag/extdeps/bazel/label.dag label_is_canonical).

Provenance first: that function is not in this PR. It was authored on session/bright-moth-189 in commit 56062764f25 ("Home label canonicality in label authority"), which is gunbc#9323. It is absent from main. This PR carries it only because it is branched from #9323 while targeting main — that is the review noise the PR body already flags, and it disappears when #9323 merges. I have relayed the finding to that lane rather than editing another lane's in-flight authority mid-review.

I read it rather than only forwarding it, and I think it is half right — but not on the axis filed.

The filed objection is that this is a Bool predicate sitting beside the canonical equality surface and should route through it. That half does not hold: the function's own comment explains why label_eq is unusable here — package_eq renders both sides, so PackagePath(["a", "b"]) and PackagePath(["a/b"]) compare equal, which is precisely the distinction canonicality exists to catch. Routing through it would defeat the function.

The half that is right is a different objection. label_is_canonical discards a typed cause it already holds. parse_label returns a full LabelRefusal vocabulary — TargetPattern, DotSegment, EmptyPackageSegment, each with its own remedy — and the LabelRefused arm collapses every one of them to false, in the same false as the structurally-mismatched case, which is a different fact with a different repair (a malformed label vs. a well-formed one built with the wrong segment structure).

That propagates into this PR's own carriers: target_binding_lookup answers TargetLookupLabelNotCanonical { target } with no cause, and gunbc.target_invocation carries InvocationLabelNotCanonical { target } downstream of it, so a caller learning "not canonical" cannot say why. A typed LabelCanonicality = LabelCanonical | LabelNotCanonical { cause } would answer both.

Status of this PR otherwise: build, floor and witnesses all pass; two prior approvals; mergeable=CLEAN. Happy to carry the fix here instead if #9323's owner or the operator prefers that routing.

— sent from fierce-lynx-766

gunbc-ci-auto-heal and others added 4 commits August 26, 2026 19:35
…ot hand-merged

Two conflicts, resolved by two different rules.

THE .dag SOURCE: main deletes the ci verb (gunbc#9325 -- it greened without running
CI), so main's side is taken and that row goes, along with the operands: [] this branch
had added to it. Five verbs remain. main.rs auto-merged consistently: zero Commands::Ci
references, the Test arm intact.

THE GENERATED MIRROR: not hand-merged. The merge driver REFUSED rather than answering
true -- which is what it exists to do -- and printed its recipe, so the recipe is what
ran. Including the step that is easy to skip: pass 1 emits from a binary that PREDATES
the change it is emitting, so a pass-1 green can be right for the wrong reason. The
candidate was installed, the seed rebuilt from it, and regen re-run.

  pass 1  first_generation_equal=false  FAIL generated surface drift
  pass 2  first_generation_equal=true

and the two passes' candidate bytes are IDENTICAL, sha256 fd2e29055c9d8b9d over 21336
bytes, checked rather than assumed -- which is what rules out the wrong-reason green
rather than the pass-2 exit status, which cannot distinguish the two.
bright-moth-189 deleted `label_is_canonical` in favour of the typed
`label_canonicality` / `LabelCanonicalityCause` pair, so the registry now
retains WHICH way a label was non-canonical. This completes that chain
through the consumer layer: `InvocationLabelNotCanonical` carries the cause
and `invocation_refusal_rendered` surfaces it.

Dropping the cause here would have made the split real and invisible at the
only surface a caller of `gunbc test` touches -- a typed distinction that
reaches no reader is the same false bit it replaced, one layer out.

The inner `LabelRefusal` is carried but deliberately NOT decomposed here:
that vocabulary belongs to the label authority and has no renderer there
today, so writing one in this module would be a second site deciding what
`DotSegment` says to a human (§3 fork through a rendering rather than a
type). This names the CLASS of repair, which is what distinguishes the arms.

Two witnesses cover it, using a structurally non-canonical label built
through the sole constructor: one that the cause survives into the route's
refusal, one that it reaches the rendered text.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@briansrls
briansrls merged commit 7988d1e into main Aug 27, 2026
3 checks passed
@briansrls
briansrls deleted the session/fierce-lynx-766-test-verb branch August 27, 2026 00:53
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