Skip to content

microVM network observe: read a host's slot network into a typed receipt (parked; installs nothing) - #11751

Merged
briansrls merged 50 commits into
mainfrom
session/merry-ibex-866
Sep 21, 2026
Merged

briansrls merged 50 commits into
mainfrom
session/merry-ibex-866

Conversation

@gunbai-bot

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

Copy link
Copy Markdown
Contributor

UNPARKED AND MERGEABLE — 2026-09-20, head f8f760f. main merged twice (no rebase), both source conflicts resolved at their authority, and the generated-artifact drift that was the sole CI failure is repaired by regeneration rather than by hand. All four checks pass; the dashboard reports ready: true, MERGEABLE, CLEAN, checks_state: passing, one approval, no change requests. Nothing here makes the floor job select the microVM path.

FINDING — stage0-mirrors fails on main itself, and CI does not see it. Running the required build lane locally against a clean origin/main checkout (87c6658) fails generated-artifact stage0-mirrors: std_integer.rs, and also drifts ROADMAP.md in registry-projections (47/48) -- phases_failed=2. This branch fails only the first (phases_failed=1, registry-projections clean 48/48), and dag/std/integer.dag plus src/v1/stage0/src/std_integer.rs are byte-identical to main here while this branch changes no seed file, so the drift is main's and not this PR's. The regenerated candidate for that mirror contains compile_error!("operator realization: host operator '+' on structural operand std.integer.UInt8 -- the declaration has no host realization"), so the mirror cannot currently be regenerated into a seed that compiles: main's recent UInt8/QualifiedOctets work declares arithmetic the emitter cannot realize. The second half of the finding is the sharper one -- this is a required-lane phase that is red on main while CI reports the lane green at the same revision, which is DESIGN section 5's invisible-to-the-floor shape: the refusal is real and does not reach the gate. It is reported, not fixed here; fixing it belongs with whoever owns the UInt8 realization, and it blocks nothing in this PR.

What this PR is now

gunbc.runner_microvm_network_converge, dispatched as the fleet-converge mode microvm_network_observe. It reads a host's microVM network and records it as a ConvergedSlotNetwork receipt. It installs nothing. It began as a converge that also installed; the install half was withdrawn for the security reason below, and the mode was renamed because a mode named for a convergence it cannot perform is a §3 meaning fork.

What it reads, all of it live: each slot tap's disable_ipv6 from procfs, its address and route from ip -o -4, the host's ip_forward, the installed nft table's listing digest, and helper standing across all three surfaces a helper can attach through (kernel auto-assignment, every nft table, legacy xtables, userspace) plus the expectation table.

Every probe carries its own absence. HelperSurfaceReading, IpForwardStanding, SlotProbeReading, TapIpv6Standing, AutoAssignmentUnread: a probe that did not answer is Unread with a reason, never a zero and never a host fault. Four consecutive reviews found instances of the opposite; this is the shape that closed them.

The receipt is a value, not prose. The artifact leads with a schema line and the wire block; the operator's report follows, #-commented. parse_converged_slot_network reconstructs the sealed value, verifies the derivable per-slot fields against what the fleet derives, and refuses a receipt describing another host.

The security finding that changed the shape (review 69104)

The withdrawn install half granted ghrunner root install from a path ghrunner itself stages, beside systemctl enable/restart on the unit installed from it. That is root-equivalent: anything running in a CI job could write its own ExecStart= into the staged file and have root execute it. Same for the nft ruleset and the sysctl files. Spelling the source path in full narrows nothing when the source is grantee-writable, and digesting it from that same principal is a TOCTOU.

226 lines of mutating grants were withdrawn across srv1/srv3/srv4. Five read grants remain (nft list table, nft list ruleset, iptables-legacy-save, nfct helper list, conntrack -L expect).

microvm_network_host_mutation_principal_frontier names what the mutating half waits on: a principal the job user cannot impersonate — administrator-over-the-fleet-key, as runner_host_file_converge already does. It also records that the mode's FleetSshKeyNotConsumed classification flips when that lands, so nobody reads today's row as a standing fact.

Findings that contradict or extend the brief — the part that would otherwise be lost

  1. "Host mutation only through modeled converge entries" holds, but the PRINCIPAL cannot be the job user. The brief did not anticipate that the converge principal and the job principal are the same account on these hosts. Any design that stages content as ghrunner and has root consume it is root-equivalent regardless of modelling. The microVM cutover needs an administrator-principal actuation path before any host mutation in this lane can land.
  2. The brief's attempt-owned tap is wrong for this fleet. Ruling A (sunny-ant-606, 2026-09-19): the tap and nft table are slot/host-scoped converge state, not attempt-owned, because a per-attempt ip tuntap grows the sudoers surface and does not survive reboot. Sanitation therefore proves the tap unheld, not absent. microVM guest network: tap-grain default-deny egress, slot network readback, workspace staging gate #11675 carries that.
  3. The guest image configures no DNS resolver at all. A guest cannot resolve github.com even with egress rules in place. microVM guest network: tap-grain default-deny egress, slot network readback, workspace staging gate #11675 names one resolver and passes it as the kernel ip= dns0 field; the guest image must still make /etc/resolv.conf follow it (guest_resolver_configuration_frontier). This is a gate for the cutover, owned by the guest-image lane.
  4. Guest egress was never established and still is not. This PR can read whether a ruleset is installed; nothing here has ever run on a host.
  5. Main was red under its own newly landed Optional checker, confirmed by running pure origin/main with a current binary: fabric_required_build_cell (landed separately as Main repair: stale egress-grain import in the qualification witness; Optional first() in the build-cell decoder #11839, whose fix I took) and workflow_dispatch_input_witness_test's trigger index (fixed here).
  6. Evidence provenance is load-bearing. A witness verdict is only as current as the binary that produced it; /cargo-target and sibling worktrees are shared and relinked underneath you. A set of my own greens was invalidated by a binary 8 seed commits stale that predated the checker refusing main's defect.

State at park

Head: 3cfea8efed2. CI on the previous head (88b7671) was RED: the floor's structural class, caused by my merge resolution breaking SystemdServiceDirective — the annotation move left a second two-arm header and orphaned ~25 arms (review 69165). The fix is in 3cfea8efed2 and is exactly what that review prescribed: one header ahead of the full arm list, RemainAfterExit as one more arm.

That fix now has partial evidence, from CI rather than from me. I labelled it unverified because my local run refused with MemoryStallRefusedPageThrash and the park instruction forbids fleet-host runs. CI has since run at this head (run 35518108259): clippy and compiler pass, and the floor job proceeded past corpus resolution into its generated-artifact step — which it could not do if SystemdServiceDirective were still truncated, since that was the structural refusal at 88b7671. So the repair holds at the resolution level. The witnesses themselves were never reached at this head, so claim-level evidence is still absent.

Evidence, bound to heads — read the head, not the number:

  • At bf587c2cb5a (binary: this worktree's target/release/gunbc, built 2026-09-20 12:23:35 from bf587c2cb5a): converge 13/13, network 32/32, launch 12/12, kernel config 6/6, workflow_dispatch 20/20, artifact gate rc=0 no drift. Superseded — three commits have landed since, including a structural fix.
  • Everything before bf587c2 came from a binary 8 seed commits stale and does not stand.
  • At 3cfea8efed2: no evidence. Nothing has been run at this head.

At resume, do these as ONE motion before reading the diff: merge main, resolve, then re-run gunbc.instruments.generated_artifact_gate main_wet and commit its bytes, then run the witnesses. Two specific traps from my resolutions: this branch and main both edit fleet_converge_workflow_modes (keep both sides' modes — main's plus MicrovmNetworkObserve), and extdeps/systemd/unit_file.dag carries main's closed SystemdServiceMode enum and this branch's RemainAfterExit arm — they are not alternatives.

Open review, unaddressed: none known beyond the above. Reviews 68963, 69021, 69041, 69063, 69082, 69104, 69132, 69152 and 69165 are each answered in a commit on this branch.

Not built, deliberately: the plan mode previewing the exact operation list before a first wet run (sunny-ant-606 suggested it; I kept it out rather than growing this PR further).

The two frontier rows still say honestly that nothing has executed on a host, and the first real run remains an operator decision, not mine.

🤖 Generated with Claude Code

gunbc-ci-auto-heal and others added 23 commits September 19, 2026 02:32
…adback, workspace staging gate

- runner_microvm_network: tap-scoped egress rules in the forward chain (established; v6 drop;
  private/link-local/multicast denials above the grants; DNS and TCP 443; final tap drop). Chain
  policy stays accept for dockerd coexistence, so runner_host_filtered_egress DefaultDrop is honoured
  at tap grain, not host grain.
- SlotNetworkReadback (Confirmed / Refuted{fact} / Unobservable{fact}) over tap unheld, guest
  conntrack empty after flush, and nft listing digest == converged digest; plus the conntrack
  flush/list and nft list commands that produce the observations.
- runner_attempt_launch: staging_verdict gates the jailer on a read-back workspace (create exit,
  mke2fs exit, dumpe2fs superblock magic and size); every failure refuses under its own name.
- extdeps: nft daddr/dport/nfproto matches, nft list table, conntrack(8), dumpe2fs -h.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…he present-tense consumption claim

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ector instead of two singleton-list folds

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…booted resolver; filtered-egress consumes the policy

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…es in their own direction

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ts through content_hash_eq_cryptographic

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nverge as its installer

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

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d image; denials precede every guest-side accept; tap v6 refused before conntrack

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…etwork receipt; v6 disabled on tap and guest; neighbour and expectation readbacks; helper standing over the effective host policy

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…pelling on every host

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rection each v6 rule covers

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…me drop on the shared hosts

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ng, then read the host back into a receipt

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	dag/gunbc/fleet/fleet_converge_workflow.dag
…s its ssh-key demand

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…croVM network mode

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title microVM guest networking + attempt workspace microVM network converge: install the slot taps, ruleset and forwarding on srvN, then read the host back into a receipt Sep 20, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 20, 2026 06:53
gunbc-ci-auto-heal and others added 2 commits September 20, 2026 08:01
…e requested host, and persist the whole receipt

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

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

The four side-chat blockers are fixed in 68021c1. Evidence ran on srv1 (my container thrashes on this corpus).

1. Helper standing could be falsely clean. All three surfaces are now read, and an unread one cannot be spelled as an empty one.

  • HelperStanding's counts are HelperSurfaceReading = HelperSurfaceCounted { count } | HelperSurfaceUnread { reason }. An Int field could not say "I did not look", so the only thing a failed probe could write was a zero.
  • nft list ruleset now populates nft_ct_helper_statements (it was stored under the xtables field). iptables-legacy-save and nfct helper list are new granted operations, so the xtables and userspace surfaces are actually observed.
  • helpers_excluded is false for any unread surface, and each refuses under its own name.
  • The sysctl's presence now comes from a directory listing joined to the read via filesystem_file_observation, so a permission or I/O error is AutoAssignmentUnread, not AbsentFromKernel. present/readable are no longer the same value.
  • REDs: an_unread_helper_surface_refuses_rather_than_counting_zero (all four surfaces) and a_subject_refuses_when_a_helper_surface_was_unread.

2. The route readback expected the wrong thing — you were right, and my witnesses couldn't see it. clean_slot built its fixture with the same renderer the readback matched against, so producer and oracle shared the defect. Now: runner_slot_subnet derives an Ipv4Prefix ending in .0 beside the address authority, and the fixtures are authored ip -o -4 strings. RED the_route_readback_accepts_the_network_and_refuses_the_host_address: 172.30.1.0/30 proto kernel scope link src 172.30.1.1 is accepted, 172.30.1.1/30 is refused.

3. The requested host is now joined to the mutated host. bind_requested_host compares FLEET_CONVERGE_EXPECTED_HOST against the machine's own hostname before any staging write and before any privileged operation. HostBindingRefused names both ("expected srv1, observed srv3"), and HostBindingUnnamed covers an unset variable or an unnamed machine. The refusal text states that nothing was staged and no privileged operation ran.

4. The receipt carries the whole record. converged_receipt_text renders host, generation, digest, and every slot's subject, host address, route and v6 standing, plus each helper reading including its unread reason.

On the plan mode you suggested: not in this commit. I'd rather land it as its own change than grow this one further; say the word and it's next.

Evidence on srv1, clean checkout, systemd scope with a real memory bound: converge 9/9, network 32/32, launch 12/12, kernel config 6/6, and the generated-artifact gate rc=0 with its bytes committed (the new grants are /usr/sbin/iptables-legacy-save and /usr/sbin/nfct helper list).

— sent from merry-ibex-866

…unread

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

Review 69021 is right, and it is the same class as the blocker before it: the strongest reading asserted on the weakest evidence. Fixed in d1b7d8e.

uname -r refusing produced an empty kernel_release, and AutoAssignmentAbsentFromKernel then claimed there is no setting left to turn back on while naming no kernel. Every sibling probe carried an unread arm; this one did not.

  • New absent_from_kernel_or_unread(kernel_release, kernel_read): AutoAssignmentUnread when the probe did not answer or printed an empty release, AbsentFromKernel only with a release to name.
  • read_host_wet threads the uname ProcessOutcome's ran/refused fact in, the same way ruleset_digest is guarded by outcome_ran.
  • RED an_absent_helper_sysctl_names_its_kernel_or_is_unread, pinning all three cases. You were right that nothing went red before: the old witness only ever supplied a populated release, so the defect was invisible to it.

srv1, clean checkout: converge witness 10/10.

— sent from merry-ibex-866

@gunbai-bot gunbai-bot Bot changed the title microVM network converge: install the slot taps, ruleset and forwarding on srvN, then read the host back into a receipt microVM network observe: read a host's slot network into a typed receipt (parked; installs nothing) Sep 20, 2026
@gunbai-bot

gunbai-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

This PR is parked by operator decision (program wound down mid-review; sunny-ant-606 instructed: push, record state, stop — no regen, no verification runs, no fleet-host access). So this is a reply, not a commit. Both items verified against the current code:

1. DissolutionCondition/unbound_dissolution without a local import — not a defect, and I have execution evidence. The declaration exists at bf587c2cb5a with no import std.dissolution in that module, and at that exact head, on srv1, the converge witness passed 13/13 and generated_artifact_gate main_wet returned rc=0 — a run that resolves the whole corpus. A name that did not resolve would have refused there. Review 69152 reached the same conclusion independently ("resolve without a local import, as many non-test modules in this tree do the same").

That said, your consistency point stands on its own: I checked the gunbc modules using unbound_dissolution and every one of them imports it explicitly, so this module is the outlier. Adding the line would be right on style grounds — it is not the compiler-floor break the finding describes, and it is not worth an unverifiable push while parked. It is a one-liner for whoever resumes.

2. The fleet-converge.yml drift is real, and worse than stale — it deletes landed work. Confirmed: grep -c spark_native_serving_apply returns 0 in the YAML and 12 in fleet_converge_workflow.dag on this head. The cause is mine and is already recorded in the PR body: the last merge resolved that file by taking one side, and the regen was not re-run afterwards, so the emitted artifact contradicts its authority and drops #11565's step.

I am not fixing it here, because the only correct fix is to re-run gunbc.instruments.generated_artifact_gate main_wet and commit its bytes — a full corpus sweep, which is precisely what the park instruction forbids. Hand-editing generated YAML to restore the step would be worse: it would put a second author on a file whose whole point is that one authority emits it.

For whoever resumes: regenerate first, before reading anything else in the diff. The PR body says so, and this comment is the second witness to it. The parked-state body also carries the evidence binding (the green set at bf587c2cb5a is superseded; 3cfea8efed2 has no evidence at all, including the unverified SystemdServiceDirective repair).

— sent from merry-ibex-866

gunbc-ci-auto-heal and others added 5 commits September 20, 2026 19:30
…e the workflow

Three conflicts, each resolved at the authority rather than in the bytes.

fleet_converge_workflow_modes: both sides appended. Taking either side would
have deleted the other's modes while the match arms below -- which git merged
cleanly -- still named all of them, so the roster and the arms would have
disagreed. The roster is the union at 31 modes, and the type arms, the roster
and fleet_converge_mode_scope are now checked to agree exactly.

workflow_dispatch_input_witness_test: main landed the same Optional repair
independently and its annotation is the better one, citing
witness_that_fails_to_compile_is_absent_rather_than_red rather than describing
the fix locally. Main's side taken whole; the two controls this branch added
are untouched.

fleet-converge.yml is generated, and the merge driver refused it rather than
answering with a side -- correctly, because neither side's bytes are the
projection of the merged authorities. It is regenerated from the merged
sources, not hand-edited: the result differs from BOTH parents, restoring the
seven spark_native_serving_apply occurrences this branch had dropped to zero
while keeping this branch's FLEET_CONVERGE_EXPECTED_HOST step. Re-running the
gate changes nothing, so it is at its fixed point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ge and regenerate

Same two paths as the previous merge and the same resolution, because they are
the program's shared spine: #11765 and #11679 are sibling microVM lanes editing
fleet_converge_workflow.dag, so every lane in this program serializes on this
file and its generated projection.

Roster is the union at 32 modes -- this branch's MicrovmNetworkObserve beside
main's MicrovmControllerAppKeyConverge -- with the type arms, the roster and
fleet_converge_mode_scope checked to agree exactly. The workflow YAML is
regenerated from the merged sources and carries both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Third merge of the same two paths in one session, same resolution: the roster
is the union at 33 modes (this branch's MicrovmNetworkObserve beside main's
AppKeyVersionVerify), with the type arms, the roster and
fleet_converge_mode_scope checked to agree exactly, and the workflow YAML
regenerated from the merged sources rather than resolved as bytes.

That this is the third time is the finding, not the incident: every lane in
the microVM program edits this one flat roster and its generated projection,
so the lanes serialize on it and each pass costs a full regeneration.

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

Fourth merge of the same two paths. Roster is the union at 34 modes, with the
type arms, the roster and fleet_converge_mode_scope checked to agree, and the
workflow YAML regenerated from the merged sources.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review 69448 is right: parse_converged_slot_network and the eleven folds
around it have no production consumer -- git grep finds only the definition,
one prose comment and the witness file -- while the render half beside them is
genuinely consumed by receipt_text. Every other unconsumed surface in this
module already states that in a typed DissolutionCondition row
(host_ruleset_installer_frontier, converged_slot_network_producer_frontier,
slot_network_readback_consumer_frontier, guest_resolver_configuration_frontier);
the parse half rested on a // annotation, and DESIGN section 4c is explicit
that an annotation is never evidence that a machine claim holds, because no
Accepted program can read one.

So the gap now carries the same row shape the module already uses: it names
the consumer and why that consumer cannot avoid the parse (the lifecycle
controller runs in a LATER PROCESS than the converge that wrote the receipt,
so it cannot hold the in-memory value and must recover it from the artifact),
enumerates the twelve folds it covers, and states a trigger in the
capability form section 4b(3) requires, with an explicit NOT-satisfied-by
clause that rules out the three ways this could be declared discharged
without the controller existing.

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

gunbai-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Addressing review 69448.

Finding 1 — confirmed and fixed in cd2041c. You are right, and the check reproduces: git grep -n parse_converged_slot_network -- dag returns only the definition, the one prose comment in the converge module, and the witness file. The render half is genuinely consumed (receipt_text folds render_converged_slot_network into the persisted artifact), so the asymmetry you identified is real, and the module's own convention is the standard it failed: four other unconsumed surfaces here carry typed DissolutionCondition rows while the parse family rested on a // annotation.

converged_slot_network_parse_consumer_frontier now carries it in the same shape. It names the consumer and why that consumer cannot avoid the parse — the lifecycle controller runs in a later process than the converge that wrote the receipt, so it cannot hold the in-memory ConvergedSlotNetwork and must recover the value from the artifact, which is precisely why the parse exists — enumerates all twelve covered folds, and states the trigger in the §4b(3) capability form with a NOT satisfied by clause ruling out the render half having a consumer, a witness round-tripping render into parse, and the annotation itself.

Finding 2 — I could not act on it, and I would rather say so than guess. The relay of this review truncates mid-sentence at "The obse…", and the artifact body is not reachable from this session: /api/reviews/69448/artifacts/stdout.log returns empty on every host I tried, and dashboard-ops exposes no artifact subcommand. I am not going to infer a finding about the observe mode from four characters and "fix" something that was not raised. If the full text is re-relayed or pasted, I will address it immediately.

Evidence for this commit: the generated-artifact gate resolves the full corpus (1990 modules) at rc=0 with the row in the tree, and the row's shape matches its siblings exactly. Binary .target-merry/release/gunbc, built 2026-09-20T19:52Z from the merged head.

— sent from merry-ibex-866

@briansrls
briansrls added this pull request to the merge queue Sep 21, 2026
gunbai-bot Bot pushed a commit that referenced this pull request Sep 21, 2026
Written on operator instruction to wind down and prioritise v1 performance
and v2 migration. A state record, not a plan: what is true on origin/main,
what is owed, and what is blocked, so resuming a lane does not begin by
re-deriving it.

Every claim is verified against origin/main at 7a145ef or attributed to
the lane that produced it. The floor's per-phase fail-open reading is marked
CONTESTED with both readings and the discriminating test stated, rather than
asserted in either direction (DESIGN 4d: a bet is typed as a bet).

Corrects two premises of my own closeout instructions: both dynamic-memory
branches COMPILE and run green -- the uncompiled attempts are the two closed
PRs -- and #11751 is in the merge queue rather than parked.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Sep 21, 2026
Four conflicts, resolved at the authority rather than in the artifacts.

gunbc.fleet_converge_workflow fleet_converge_workflow_modes was a union
conflict: this branch adds MicrovmNetworkObserve, main added R2BucketEnsure
and R2BucketAdminMint. Resolved as the union, which is what both sides
intended -- neither removed a mode.

The other three paths are GENERATED artifacts and were not hand-merged. They
reached the low-level merge driver, which refuses rather than answering: it
leaves the ours side in the worktree with no conflict markers and marks the
path unmerged, so accepting what was there would have silently dropped main's
authority-derived bytes. Regenerated instead, through
tools.generated_artifact_gate main_wet, from the merged authority.

Verified the regenerated emission carries BOTH sides rather than either:
.github/workflows/fleet-converge.yml now contains microvm_network_observe
(this branch), r2_bucket_ensure and r2_bucket_admin_mint (main), and main's
org_actions_inspection entry-point rename; the srv3/srv4 sudoers carry main's
fabric-cell grants alongside this branch's rows. Hand-merging could not have
produced that -- main had moved entry points and added steps this branch
never saw.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot mentioned this pull request Sep 21, 2026
6 tasks
…route capture

All three findings verified against the merged head and all three hold. The
lane that authored this PR has archived, so these are mine.

1. MISSING IMPORT. runner_microvm_network_converge declared
   microvm_network_host_mutation_principal_frontier using DissolutionCondition
   and unbound_dissolution with no `import std.dissolution`. It bound anyway,
   because the required floor no longer refuses a cross-module reference whose
   module is not imported -- so this was a new row added to the population of
   a DECLARED 4b(3) drop that calls itself bounded and under repair. Silently
   growing a population declared bounded is the same move the drop exists to
   make visible. 330 other dag/ files carry the import; this one was the
   outlier. One line.

2. TWO NICKNAMES (DESIGN 3). addr_reading_carries and route_reading_carries
   had byte-identical bodies -- one predicate under two names. Consolidated to
   reading_carries_cidr, with the distinction stated where it actually lives:
   what differs between the call sites is the CIDR SUPPLIED, not the test
   applied. slot_field was a pure pass-through to wire_value; deleted, its 7
   call sites re-pointed to wire_value.

3. THE RAW ROUTE CAPTURE. guest_route carried the whole stdout of
   `ip -o -4 route show dev <tap>`, and `ip -o` guarantees one line per ROUTE
   OBJECT rather than one per device. A tap with a second route therefore made
   the slot record span two wire lines, and parse_converged_slot_line returned
   ConvergedSlotNetworkMalformed for a correctly converged host -- failing
   closed, so nothing fabricated, but making the receipt unparseable for a
   LEGAL host state. The field now carries the matched route line.

   This NARROWS an existing fallback rather than adding one: the whole-capture
   value was previously used unconditionally and is now used only when no line
   carries the expected prefix, which slot_refusal has already established
   cannot happen on a reading that reaches this function -- it refuses with
   SlotRouteNotHeld first.

EVIDENCE: the corpus typechecks clean with all three edits applied (gunbc run
over --source-root dag --source-root src/v2 reaches evaluation, so every
module resolved and typechecked). Witness execution is NOT available from this
container -- claim_batch page-thrashes here and srv1 refuses this session's
key -- so the witnesses are owed against CI rather than claimed here.

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

gunbai-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

All three findings in review 69484 verified against the merged head and all three fixed in 2b33365473. The authoring lane has archived, so these came to me.

The import finding is the one I'd have most regretted missing. DissolutionCondition/unbound_dissolution bound without import std.dissolution only because the required floor no longer refuses an unimported cross-module reference — which is a declared §4b(3) drop whose population calls itself bounded and under repair. So this was not a cosmetic omission; it was a silent addition to a population that is supposed to be shrinking, and silently growing something declared bounded is the same move the drop exists to make visible. 330 other dag/ files carry the import.

On the route capture — your reasoning is exactly right and the failure is quiet: ip -o guarantees one line per route object, not per device, so a tap with a second route makes the slot record span two wire lines and parse_converged_slot_line returns ConvergedSlotNetworkMalformed for a correctly converged host. It fails closed, but the receipt the lifecycle controller exists to read becomes unparseable for a legal host state.

Worth stating how I fixed it, because a fallback is the thing to be suspicious of: this narrows the existing one rather than adding one. The whole-capture value was used unconditionally before; it is now used only when no line carries the expected prefix — which slot_refusal has already established cannot happen on a reading that reaches converged_slot_of_reading, since it refuses with SlotRouteNotHeld first. Strictly closer to the intended value, never worse.

Evidence, stated at its real level. The corpus typechecks clean with all three edits — a full --source-root dag --source-root src/v2 run reaches evaluation, so every module resolved and typechecked. The witnesses are owed against CI, not claimed here: claim_batch page-thrashes in this container and srv1 refuses this session's key, so I have resolution evidence and not execution evidence. Please read the floor result on this head rather than my typecheck as the verdict.

Context: this PR also carries a merge of main, whose generated-artifact conflicts were resolved by regenerating from the merged authority rather than by taking a side — the merge driver leaves no conflict markers, so accepting the worktree would have silently dropped main's bytes.

— sent from sunny-ant-606

@gunbai-bot gunbai-bot Bot mentioned this pull request Sep 21, 2026
6 tasks
…thorities

The finding is correct and its sharpest form is internal: this same diff mints
cited extdeps.tools.uname and extdeps.iproute2.ip_show rows for `uname -r` and
`ip -o -4 addr show`, and replaces a bare "restart" literal with
systemctl_restart_verb -- and then leaves five read arms spelling their
upstream argv inline. Same move, left undone five times.

TWO AUTHORITIES ALREADY EXISTED AND WERE BEING FORKED. conntrack -L expect was
owned by extdeps.tools.conntrack conntrack_list_expectations_command, and
nft list table by extdeps.nftables.ruleset nft_list_table_command. The wire
words now live in one declaration each, consumed by BOTH the ArgvCommand
builder and the privileged read projection, so there is one source for each
upstream fact rather than two.

THREE HAD NO EXTDEPS ROW AT ALL. nft list ruleset gets one beside its sibling.
iptables-legacy-save and nfct get cited modules: extdeps.netfilter.iptables_legacy
and extdeps.netfilter.nfct, each carrying its upstream citation, its program
and its argument words. They are separate modules rather than spellings inside
conntrack because they are separate upstream programs -- nfct manages userspace
helper OBJECTS while conntrack reads the flow and expectation TABLES, and
iptables-legacy-save reads the xtables backend, a different kernel subsystem
from the one nft list ruleset can see.

WHAT I DELIBERATELY DID NOT ADD, and it is the same rule one layer on: no
ArgvCommand builder for the three new rows. The only consumer reaches these
programs through a privileged projection that prepends its own sudo binary
path, so it needs the ARGUMENT WORDS and would discard a command's program
field. A builder nothing calls is the dangling declaration DESIGN 3c refuses,
and it would also have required three new entries on argv_command's
admit_callers roster to authorize a call site that does not exist. Each new
module states that absence and what would end it.

EVIDENCE: the corpus resolves and typechecks clean with every edit applied --
the run reaches evaluation and fails only on a deliberately fake entry function.
Witness execution remains unavailable from this container, so the witnesses are
owed against CI rather than claimed here.

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

gunbai-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Review 69493's finding accepted and fixed in 676f745d2a. Your framing is what made it decisive: this same diff already does the right thing three times — cited extdeps.tools.uname and extdeps.iproute2.ip_show rows, and systemctl_restart_verb replacing a bare "restart" — and then leaves five read arms spelling upstream argv inline. Same move, left undone five times.

Two authorities existed and were being forked. conntrack -L expect and nft list table now have their wire words in one declaration each, consumed by both the ArgvCommand builder and the privileged read projection.

Three had no row at all. nft list ruleset gets one beside its sibling; iptables-legacy-save and nfct get cited modules. They are separate modules rather than spellings inside conntrack because they are separate upstream programs — nfct manages userspace helper objects while conntrack reads the flow and expectation tables, and iptables-legacy-save reads the xtables backend, a different kernel subsystem from the one nft list ruleset can see. That last distinction is why reading both is not redundant.

What I deliberately did not add, because it is this review's own rule one layer on: no ArgvCommand builder for the three new rows. My first attempt added them and argv_command's admit_callers roster refused the calls — which was the right refusal for the wrong reason I first assumed. The real problem is that the only consumer reaches these programs through a projection that prepends its own sudo path, so it needs the argument words and would discard a command's program field. A builder nothing calls is the dangling declaration §3c refuses, and registering one would have authorized a call site that does not exist. Each new module states that absence and what would end it.

Evidence at its real level, unchanged from my last comment: the corpus resolves and typechecks clean with every edit — the run reaches evaluation and fails only on a deliberately fake entry function. Witness execution is still unavailable from this container, so please read the floor on this head as the verdict rather than my typecheck.

— sent from sunny-ant-606

MY DEFECT, CAUGHT BY THE FLOOR LOG AND NOT BY THE GREEN CHECK. Both new
extdeps modules ended with a // block and no module item after it, which is
the DESIGN 4c module-item-grain violation: "source annotation names no
subject: no module item follows it." Ten parse FAILs across the two files.

The annotations say why there is no ArgvCommand builder, so they describe the
arguments row that stands in its place; they now sit above that declaration
rather than trailing the file.

WORTH RECORDING WHY I DID NOT SEE IT: the floor check reported SUCCESS on this
head while its own log said phases_run=2 phases_failed=2, and the run refused
with ArmSetConsumerPlanningUnavailable having selected no witness. The green
was the fail-open, not a reading. Every class= line in that job log is echoed
step script, so grepping the log for a class is not a reading either -- the one
real line is the required-ci summary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@briansrls
briansrls added this pull request to the merge queue Sep 21, 2026
Merged via the queue into main with commit 99c2d33 Sep 21, 2026
4 checks passed
@briansrls
briansrls deleted the session/merry-ibex-866 branch September 21, 2026 10:54
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