Skip to content

One binary, two spellings: 'env'/'/usr/bin/env' and 'chmod'/'/bin/chmod' are section-3 nicknames at the transport layer - #8918

Merged
briansrls merged 2 commits into
mainfrom
session/still-seal-394
Aug 22, 2026
Merged

briansrls merged 2 commits into
mainfrom
session/still-seal-394

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

The defect this leads with

gunbc.roadmap_dashboard_instance_apply spelled "/usr/bin/chmod" twice — once as the program its serve-binary install step execs, once inside dashboard_required_target_executables, the roster whose entire job is to assert the required binaries exist before apply proceeds. Meanwhile extdeps.tools.chmod, the tool's authority, records "/bin/chmod" for the same fleet, and tools.build_step_transport runs that one.

Two absolute paths for one binary, in two modules, neither able to see the other. The preflight was gating a path no sanctioned caller execs. It passed only because these hosts are usrmerged — a host fact nobody declared, quietly holding both claims up, and the first non-usrmerged target turns it into a mystery. Both sites now read chmod_binary_path.

Underneath the chmod pair sits the same class one layer worse. env(1) had no upstream authority at all: gunbc.roadmap_dashboard_instance's dashboard_environment_binding_executable was the only declaration of env's path anywhere in the repository. A product module standing as the upstream authority for a coreutils binary is the §3 layer inversion that the nickname was sitting on. dag/extdeps/tools/env.dag now owns it and the product row derives from it.

The design decision that changed the answer

The tempting rule is the bare name is a decline to say which binary, so the absolute path is correct everywhere. That is half right and the wrong half is load-bearing. The two forms answer different questions:

  • the absolute path is a claim about a filesystem this repository has observed. It is what a sudoers NOPASSWD rule can name (which is why chmod.dag carries one at all), what a preflight can test for existence, and what refuses loudly where the claim is false.
  • the PATH-resolved name is correct where the executing host is not one we have observed — a machine reached over ssh, a runner image, a dev box. Asserting an absolute path there does not repair the decline, it fabricates a host fact (§5).

So each authority declares both rows, each with its cited reason, and every reachable call site cites one. The nickname dies — no site invents a spelling — without erasing the distinction, because a caller now has to declare which question it is asking. This correction propagated: it caused quick-newt-661 to revert nine tool rows that had been written to absolute paths on the older rule.

Populations (git grep over HEAD, dag/**/*.dag)

spelling count this PR
bare "env" 46 across 23 files 8 converted, 12 left to #8912, 2 excluded, ~24 out of reach or test rows
"/usr/bin/env" 52 the one declaration converted; 36 are pijul_upstream_model_execution_test fixtures
bare "chmod" 9 2 converted, 2 left to #8912, 4 in transport
"/usr/bin/chmod" 3 all 3 converted — the disagreement

Two of the "env" hits (extdeps/languages/yaml/gha_workflow) are a YAML mapping key, not the binary — counted and excluded, recorded here so a later reader does not re-derive it.

What is out of reach, named rather than missed

~15 sites sit inside transport shell { argv: [...] } (extdeps/{shell,python,gunbc,rust/cargo_build,tools/npm}, gunbc.cli_services). An identifier in that position is not name-resolved at compile time. Measured with three arms in one 0-diagnostic run, reading the emitted AST rather than the diagnostic count:

argv: [probe_bin, "0700", "{path}"]        -> ExprVar, binding_kind null, no inferred node
argv: ["chmod", probe_tail(path: path)]    -> ExprCall, unresolved
argv: [nowhere_declared_symbol, "{path}"]  -> ExprVar, unresolved, ZERO diagnostics

Same-file same-run control: an undefined variable in an ordinary fn body reds undefined variable.

The honest claim is "citing a symbol there is UNGUARDED", not "impossible" — the position parses expressions and may well resolve at realization; if it does not, it emits as a literal token and nothing refuses. That is worse than impossible, because impossible fails loudly. I first reported this as "cannot be dissolved by citation" and retracted it in the weakening direction. Filed as its own substrate row in gunbc#8916.

These ~15 are BLOCKED BY gunbc#8916, not residue of this lane and not a safety boundary (per the ruling on that row, which post-dates the first draft of this section and corrects it). The standing operation-argv note says the executable position must be a literal because letting an operation input choose the executable is unsafe. That argument is sound and it does not reach this case: a compile-time citation of a resolved declaration and a runtime caller-selected executable are different facts, and the ruling states they must not be collapsed —

program selected by a resolved declaration        -> allowed as a compile-time cited authority
program selected by arbitrary runtime operation input -> still refused

env_binary_path, env_path_resolved_program and chmod_binary_path are exactly the first case. So the blocker is a missing compiler path that today refuses the safe and unsafe cases alike because it cannot tell them apart — the obligation sits in the name-resolution layer, which the ruling assigns by name and says deserves a cut of its own. Nothing in this PR's design is what those sites are waiting on.

dag/gunbc/devboot/{build,produce}.dag are deliberately untouched: gunbc#8912 deletes ten of their twelve env sites by making GIT_INDEX_FILE an operation-owned transport prefix in extdeps.git.plumbing, which is a better answer than an env prefix.

Evidence

Nine witnesses run by execution on a binary built from this tree, remote (ctrl-build --remote, one dispatch, build and run together):

  • seven in dag/test/claim/env_execution_authority_witness_test.dag — the arity of an assignment vs an unset, NAME= not collapsing into -u NAME, tail-producer/whole-vector agreement, the binary being absolute, and the new the_path_resolved_name_is_a_second_fact_not_the_binary_path
  • the_path_resolved_chmod_is_a_second_fact_not_the_binary_path (new) beside the existing chmod_argv_adds_the_execute_bit_rather_than_replacing_the_mode as a control
  • dashboard_target_preflight_derives_dispatch_platform_dependencies, whose /usr/bin/chmod literal moved to /bin/chmod — that literal is now the one independent statement of the value, so it reds if the authority moves without anyone looking there

The discriminating RED

Ten greens show the witnesses pass, not that they can fail. Mutation probe, one remote dispatch, ephemeral checkout, chmod_path_resolved_program "chmod" -> "chmodX":

witness unmodified mutated
the_mode_is_set_on_staging_before_the_rename true false
the_path_resolved_chmod_is_a_second_fact_not_the_binary_path true false
chmod_argv_adds_the_execute_bit_rather_than_replacing_the_mode (control) true true

The control is the load-bearing row. It pins the ABSOLUTE path, which the mutation does not touch, so its staying green is what establishes the mutation was targeted rather than broadly breaking the module -- two reds beside a red control would have measured nothing.

The two "second fact" witnesses are the ones that matter for durability: they red if a later editor decides one row of each pair is a stale duplicate and helpfully points it at the other. Both state their expected values as literals, so they are independent oracles rather than the module agreeing with itself.

Attribution

extdeps/tools/env.dag's EnvBinding-grounded producers and its witness file are eager-crane-282's, taken from origin/env-execution-authority rather than reinvented; their green is re-run here, not carried. #8915 is closed. The two-facts rows and annotation are mine. env_prefixed_args ships because quick-newt-661's ArgvCommand-seal lane declares a hard dependency on it — I had cut the producers as unconsumed, and that was the caller.

gunbc-ci-auto-heal and others added 2 commits August 22, 2026 17:02
…one of the spellings disagreed

extdeps.tools.env is new: env(1) had no upstream authority at all, so a product
module (gunbc.roadmap_dashboard_instance) held the only declaration of its path
in the repository -- a layer inversion sitting underneath the nickname. It now
derives from the tool authority instead of declaring it.

The sharp finding is chmod. gunbc.roadmap_dashboard_instance_apply spelled
"/usr/bin/chmod" twice -- once as the program its install step execs, once in
the required-executable roster its preflight tests for existence -- while
extdeps.tools.chmod, the tool's authority, records "/bin/chmod" for the same
fleet. Two absolute paths for one binary in two modules, neither able to see
the other, agreeing today only because these hosts are usrmerged. The preflight
was gating a path no sanctioned caller runs. Both sites now read the authority.

The two forms are two FACTS, not a right and a wrong spelling. An absolute path
is a claim about an observed filesystem; asserting one for a remote target this
repository has not observed would fabricate a host fact (DESIGN 5). So each
authority declares both, with its reason, and every reachable call site cites
one rather than inventing a literal.

Out of reach and named rather than missed: ~15 sites sit inside
`transport shell { argv: [...] }`, where an identifier is not name-resolved at
compile time -- measured with a same-file same-run control, an undefined
variable in a fn body reds while an undeclared identifier in a transport argv
produces zero diagnostics.

devboot's twelve env sites are deliberately untouched: gunbc#8912 removes ten
of them outright by making GIT_INDEX_FILE an operation-owned transport prefix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…he two-facts rows on them

quick-newt-661's ArgvCommand-seal lane declares a hard dependency on
env_prefixed_args, so cutting the argv producers would have blocked their PR to
keep this diff tidy. Taken wholesale from origin/env-execution-authority
(witnesses green by execution there; re-run here rather than carried), with
env_path_resolved_program added beside env_binary_path and the annotation
rewritten: the bare name is not a decline to say which binary, it is the correct
form for a host this repository has not observed.

Adds the two assertions that would red if a later editor decided one row of each
pair was a stale duplicate and pointed it at the other -- the repair that looks
like tidying and is a fabricated host fact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 22, 2026 17:42
@briansrls
briansrls merged commit abf7194 into main Aug 22, 2026
1 of 2 checks passed
@briansrls
briansrls deleted the session/still-seal-394 branch August 22, 2026 19:04
gunbai-bot Bot pushed a commit that referenced this pull request Aug 22, 2026
… imports

Resolutions:
- extdeps/tools/env.dag takes main's wholesale. My branch had accidentally
  committed the throwaway stub I was typechecking against (a git add -A picked
  up the untracked file); it is gone, and the authority is still-seal-394's.
- The two env call sites take my builder form. main converted the 'env' program
  WORD at both; env_prefixed_command carries the whole invocation, so the
  binding is a modeled EnvSet rather than a joined string and the word comes
  from the same row either way.
- chmod_set_mode_command now takes its program as a parameter, because main
  landed chmod's second spelling and the two are two facts: the observed
  /bin/chmod a sudoers rule names, and the PATH-resolved name for a host we
  have not read. fleet_converge_plan_cli keeps the resolved row main gave it,
  so this merge changes no emitted word there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhAfyPkuxnjZaTzPn5PyE1
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