Skip to content

Repair the test -e absence probe in runner_host_file_converge (metadata failure read as absent) - #11845

Merged
briansrls merged 3 commits into
mainfrom
session/bold-moth-41
Sep 21, 2026
Merged

briansrls merged 3 commits into
mainfrom
session/bold-moth-41

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

The defect

gunbc.runner_host_file_converge observe_runner_host_file_content observed a desired host file with ["test", "-e", <path>] over fleet ssh, and classify_host_file_presence mapped exit_code == 1 to HostFileAbsent.

GNU test -e is stat(path) == 0. ENOENT, EACCES on an ancestor directory, ELOOP, ENOTDIR and EIO all answer exit 1 with an empty stderr, so "the file is not there" and "I could not look" arrived as one value — and they license opposite actions. HostFileAbsent is the arm that walks into write-admission and the comparison path for a file this module exists to keep root-owned and non-injectable.

The repair — inhabiting an existing model

  • The probe is extdeps.tools.stat stat_file_type_command, built through the sealed argv_command mint rather than spelled at the call site. That operation's own annotation states this exact defect as the reason it exists, and gunbc.fabric.fabric_cell_acquire already consumes it in production.
  • The read has two honest outcomes and absence is not one of them. classify_host_file_metadata yields HostFileMetadataRead { file_type } or HostFileMetadataUnread { cause }. An exit-zero read that printed nothing is unread, not a nameless file type — exit success is a fact about the call, never about whether the answer arrived.
  • Absence is established by the parent, never by the probe's silence. classify_host_file_path joins the reading with gunbc.live_deploy.member_observe entry_presence — the landed authority for establishing absence from a parent listing over a transport, which also returns EntryPresenceIndeterminate when the parent itself cannot be listed. Consumed rather than restated, so no second absence vocabulary is minted beside the containment rule of absence_proof_whose_premise_the_success_destroys (gunbc#11823).
  • Three distinct outcomes: HostFilePathPresent / HostFilePathAbsent / HostFilePathUnreadable. runner_host_file_settled_standing maps them to the standing grain: unreadable reaches HostFileUnobservable carrying the cause and can no longer reach HostFileAbsent; present settles nothing and continues into the existing byte comparison.
  • The parent is enumerated only when the read failed (observe_runner_host_file_path), because that is the only outcome whose meaning the parent decides. entry_presence is a remote leg that recurses up the ancestor chain on a refusal, so asking it beside a read that already answered buys a standing the classifier discards.

classify_host_file_presence is deleted, not left standing beside its replacement.

The REDs

In test.claim.runner_host_file_converge_witness_test:

  • an_unread_path_reaches_unobservable_and_never_absent — the control that is red for this reason. One unread cause, held constant, must reach HostFileUnobservable beside a parent that holds the entry and HostFileAbsent only beside a parent that lacks it. Collapsing them again — which is exactly what test -e did — reds on the first row. A control that merely separated exit 0 from exit 1 would pass every row here, which is why the cause is held constant across all three.
  • a_metadata_read_that_failed_is_absent_only_when_the_parent_lacks_the_entry — the same discrimination at the path-standing grain, including the indeterminate parent.
  • a_metadata_read_that_answered_is_present_whatever_the_parent_said — the parent cannot turn a read file unreadable.
  • a_stat_that_exited_zero_and_printed_nothing_is_unread — exit success is not an answer.
  • the_production_probe_sends_the_cited_stat_metadata_words — inhabitance (DESIGN §3): the words the observer sends, over the path a production desired file produces, equal argv_words(stat_file_type_command(...)) and contain no boolean test. The remote leg is not hermetically executable (gunbc.rung_drop runner_host_file_apply_route_hermetic), so this is the deepest live route to the new probe.

Test plan

CI run 35513652407 on head e8aef59a447: clippy, compiler, floor, witnesses all pass. The floor job shows the claims executed rather than skipped —

[floor-phase] phase=touched-entry-compile-subject seeds=2
  modules=["gunbc.runner_host_file_converge", "test.claim.runner_host_file_converge_witness_test"]
[changed-witness] an_unread_path_reaches_unobservable_and_never_absent   outcome=passed
[changed-witness] a_metadata_read_that_failed_is_absent_only_when_…      outcome=passed
[changed-witness] a_metadata_read_that_answered_is_present_whatever_…    outcome=passed
[changed-witness] a_stat_that_exited_zero_and_printed_nothing_is_unread  outcome=passed
[changed-witness] the_production_probe_sends_the_cited_stat_metadata_…   outcome=passed

CI is re-running at the current head, which carries review 69169's fix and a merge of main.

Out of scope, deliberately untouched

gunbc.typed_remote_file_write exit 1 (cmp reporting a genuine byte difference), extdeps.shell exec ExitZeroOrOneSucceeds (a declared policy), and the runner_host_path_ancestors ownership guard.

🤖 Generated with Claude Code

gunbc.runner_host_file_converge observed a desired host file with
["test", "-e", path] and mapped exit 1 to HostFileAbsent. GNU test -e is
stat(path) == 0, so ENOENT, EACCES on an ancestor directory, ELOOP,
ENOTDIR and EIO all answer 1 with an empty stderr: "the file is not
there" and "I could not look" arrived as one value, and HostFileAbsent
is the arm that plans a write into a file this module exists to keep
root-owned and non-injectable.

The probe is now extdeps.tools.stat's own stat_file_type_command, whose
annotation states this defect as the reason the operation exists, built
through the sealed argv_command mint rather than spelled here. The read
has two honest outcomes -- read, or not read -- and absence is settled
by gunbc.live_deploy.member_observe entry_presence, the landed authority
for establishing absence from a parent listing over a transport, rather
than by the probe's silence. A metadata read that failed beside a parent
that holds the entry reaches HostFileUnobservable carrying the cause and
can no longer reach HostFileAbsent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 20, 2026 14:28
gunbc-ci-auto-heal and others added 2 commits September 20, 2026 15:17
review 69169: the parent listing was executed on every observation and
discarded on the common path where stat answered. entry_presence is a
remote leg -- ls -1 -A of the parent over fleet ssh, recursing up the
ancestor chain when a listing refuses -- so that was several discarded
round trips per desired file per host (DESIGN section 2, and section 6's
bare minimum cost).

The in-diff justification it replaced was wrong: section 3c asks who
consumes a declaration and by what executing route, and computing the
parent standing inside the unread arm keeps HostFilePathAbsent and
HostFilePathUnreadable inhabited by exactly the production route that
can reach them. classify_host_file_path stays total and pure, and the
witnesses over it are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

Addressed review 69169 in 192921f.

The finding is correct and my in-diff justification was wrong. DESIGN §3c asks who consumes a declaration and by what executing route; computing the parent standing inside the HostFileMetadataUnread arm keeps HostFilePathAbsent and HostFilePathUnreadable inhabited by exactly the production route that can reach them, and classify_host_file_path stays total and pure, so the witnesses over it are unchanged. The unconditional call was buying a remote ls -1 -A — recursing up the ancestor chain on a refusal — whose result the common HostFileMetadataRead path discarded, which is §2 and §6's bare-minimum-cost rule regardless of the realized n.

observe_runner_host_file_path now carries the uses net leg and calls entry_presence only on the unread arm; the annotation that made the bad §3c argument is deleted rather than reworded.

Also in this push: a merge of main (7f9e624), and the PR body replaced — it was still the auto-opened TODO template because my gh pr create was a silent no-op against the dashboard's already-open draft.

The discriminating RED is being executed on a throwaway branch (#11868, draft, not for merge): it collapses the unreadable arm back into HostFileAbsent and an_unread_path_reaches_unobservable_and_never_absent must go red. Local execution was not available — claim_batch in this container refuses under MemoryStallRefusedPageThrash (and exits 0 while doing it), and srv1 is not reachable from this session.

— sent from bold-moth-41

@briansrls
briansrls added this pull request to the merge queue Sep 20, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 20, 2026
@briansrls
briansrls added this pull request to the merge queue Sep 21, 2026
Merged via the queue into main with commit 6c24fb2 Sep 21, 2026
4 checks passed
@briansrls
briansrls deleted the session/bold-moth-41 branch September 21, 2026 03:15
gunbai-bot Bot pushed a commit that referenced this pull request Sep 21, 2026
…ng cleanup, ancestors-before-mutation) and #11845 (parent-listing absence) into the custody roster

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 21, 2026
…ence chain, not the deleted classify_host_file_presence

gunbc.microvm_controller_app_key_converge imported and called
classify_host_file_presence from gunbc.runner_host_file_converge. The
function was real (#11063) and this consumer bound it in #11679; #11845
deleted it because its exit-1 arm minted HostFileAbsent for every stat
failure (GNU `test -e` is stat(path) == 0), and touched only the sibling
module, so this consumer went dangling. Restoring the name would restore
the false-absence arm on the one file the module keeps root:root 0400.

observe_key_content now runs the sibling's stat `%F` probe over this
module's elevated leg and consumes classify_host_file_metadata,
classify_host_file_path and runner_host_file_settled_standing; the parent
listing that decides absence when the read failed runs elevated (the same
stated departure from member_observe's entry_presence that clear_staging
already carries, because the parent is root:root 0700), with the argv and
membership read still member_observe's.

Evidence: per-entry compile of the module (primary-precedence pool) on
#11941's head with the old file reports exactly the three errors (name not
found in module at the import, function not found in scope at the call,
effect summary incomplete); with this file it reports none, both runs
resolving the same 1563-source closure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 21, 2026
Two conflicts, and the second was load-bearing rather than mechanical.

.github/workflows/fleet-converge.yml is a generated projection and the driver
refused it rather than answering, since both sides changed it since the merge
base. Regenerated from the merged authorities instead of merged by hand; the
result carries this branch's host_credential_custody_converge mode AND main's
new modes (mtcollins1_census_image_publish, r2_bucket_ensure,
app_key_version_verify, r2_bucket_admin_mint), with no microvm_controller_app_key
surviving. A second gate run wrote nothing.

dag/gunbc/runner/microvm_controller_app_key_converge.dag was a modify/delete:
this branch deletes it as part of the rename to
gunbc.host_credential_custody_converge, while #11946 edited it on main. Taking
the delete is right, but the edit could not simply be discarded -- it relocated
executor_bootstrap_principal from gunbc.runner_host_grants to
gunbc.fleet_bootstrap_principal, and the replacement module still imported it
from the old home. Resolving delete-vs-modify without reading main's edit would
have produced a branch that merges cleanly and does not resolve. The import is
repointed, and a census over the branch confirms no other importer takes
executor_bootstrap_principal from runner_host_grants. The prose citation at the
privilege law is left alone: it names bootstrap_principal_is_not_the_job_user,
which did not move.

Measured rather than assumed: compiling the replacement module's closure at this
head reports 3 blocking errors, all 'shell' transport emission refusals in
extdeps.gunbc, and all present in the same closure family on main. Compiling the
deleted module ON main reports its own resolution failure
(classify_host_file_presence, deleted by #11845) plus annotation-grain errors --
so main currently carries a module that does not resolve, and this merge removes
it rather than inheriting it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 21, 2026
Third merge of main into this branch, and the first whose conflicts were not
all mechanical.

fleet_converge_workflow_modes: both sides edited the roster. This branch
replaced MicrovmControllerAppKeyConverge with HostCredentialCustodyConverge;
main's #11751 added MicrovmNetworkObserve. Kept both -- main's roster with the
rename applied -- rather than taking either side, which would have dropped the
other's mode. Checked after regeneration: the emitted workflow carries
host_credential_custody_converge and microvm_network_observe and no
microvm_controller_app_key.

microvm_controller_app_key_converge.dag: modify/delete again, and this time
main's edit was #11942, a real repair -- observe_key_content was calling
classify_host_file_presence, which #11845 deleted, and the repair rebuilds
presence from the sibling's chain so that "I could not look" can no longer walk
into the write path. The delete is still correct, and the repair is not lost by
taking it: this branch's replacement already reads presence from an elevated
parent listing and treats a listing that did not succeed as unobservable rather
than absent. Same semantics, reached independently, and verified in the
replacement rather than assumed from the port note.

Noted rather than fixed, because it is not this PR's subject: after #11942,
main's observe_key_parent_presence consumes runner_host_file_converge's
repaired chain while this module re-spells the listing branch inline. Both are
correct and fail-closed, and both use member_observe's argv and membership
read, so the shared primitives are shared -- but they are two spellings of one
decision and should converge on the sibling's chain in a follow-up.

Measured: compiling the workflow closure reports 11 blocking errors at this
head and 11 at origin/main, all in unrelated modules. The resolution introduces
none. Generated projections regenerated, not hand-merged; a second gate run
wrote nothing.

Co-Authored-By: Claude Opus 5 (1M context) <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