Skip to content

microVM controller reads the org's runner groups under its installation token and mints only into the observed restricted group (stacked on #12172) - #12179

Merged
briansrls merged 7 commits into
session/bright-dove-353-group-createfrom
session/bright-dove-353-controller-read
Sep 24, 2026
Merged

briansrls merged 7 commits into
session/bright-dove-353-group-createfrom
session/bright-dove-353-controller-read

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

PR-C of node adhoc-e6d8e20d-713. Stacked on #12172 → #12155 → #12119. The design was agreed with quiet-koi-746 before building.

What

  • The group is named; its id is read. gunbc.runner_group_restriction_observation now observes by microvm_shakedown_runner_group_name ("microvm-shakedown"). GitHub assigns the id at creation, so the repository can author only the name. The id reaches JitRunnerConfigRequest only through the sealed ShakedownRunnerGroupRestriction. The refusal causes carry the id when a group was found and none when it wasn't (RunnerGroupAbsent, RunnerGroupDuplicated). JitMintRefusedRunnerGroupUnrestricted / DispatchRefusedRunnerGroupUnrestricted carry the name plus the cause.

  • Deleted gunbc.runner_microvm_shakedown microvm_shakedown_runner_group (id 1 = Default, which every workflow may use) and its TranscribedUncited figure row. No authored group id remains.

  • extdeps.github.org_actions: a REST ListOrganizationRunnerGroups read under an installation token. It returns the wire shape (OrgRunnerGroupWire, strings and bools as sent, no coproduct built in transport), with per_page=100. gunbc.fleet.org_actions_inspection runner_groups_from_wire projects it into the same rows the observer reads, so there is one observer. It refuses a read that returned fewer groups than total_count, because a group on an unread page must not read as absent and send the ensure to create a duplicate. It also refuses an empty name or workflow entry.

  • gunbc.github_effect_perform: the token step is split out as mint_controller_installation_token. It returns a sole_constructor ControllerInstallationToken { app, installation, token }, one token per attempt, used for both the read and the generate. perform_organization_jit_mint(dispatch, token) refuses a dispatch for another App/installation (JitMintTokenForAnotherInstallation). The pure pre-token decision is jit_mint_performance_before_generate, and the existing witnesses now call it.

  • runner_microvm_slot_controller mint_attempt_credential:

    1. It plans first with RunnerGroupRestrictionNotYetObserved. Cell ownership, credential and attempt binding still refuse there with no I/O, as before.
    2. Only when the group is the sole remaining refusal does it mint the token, read the groups with it, observe, and re-plan with the observed standing.
    3. It then performs with the same token. The observer's authorizing arm is the only route to JitMintAuthorized.
  • microvm_jit_runner_group_restriction_frontier: narrowed, not deleted. Its trigger has two halves:

    • the controller passes a live observation — fired here;
    • the org holds the restricted group — not true until the wet run.

    Deleting it now would retire it on half its trigger (§4b(3)). The row now names only the remaining half: one microvm_runner_group_ensure run under one operator approval, with its readback receipt. Until then every real attempt refuses at the mint with RunnerGroupAbsent, which is enforced by execution, not by prose.

Receipts (BuildBuddy, claim_batch; stacked, no CI): 148/148 PASS, 0 FAIL. That is every test fn in:

  • observation (11, including 3 new wire-projection witnesses: a short read refuses, an empty workflow entry refuses, a complete body reaches the sealed arm)
  • ensure (8)
  • github_app_registry (48)
  • github_effect_perform (8)
  • runner_attempt_launch (18)
  • runner_microvm_slot_controller (12)
  • authorization_pattern_selection (13)
  • workflow_dispatch_input (30)

Not witnessed, stated plainly: JitMintTokenForAnotherInstallation has no witness. The token is sole_constructor, so no fixture can hand perform_organization_jit_mint a mismatched one, and the controller passes the one installation it minted for. Per §4b, its RED is not authorable where a claim can run, so it is a guard, not counted coverage.

🤖 Generated with Claude Code

gunbc-ci-auto-heal and others added 4 commits September 23, 2026 20:45
…tion token and passes the sealed observation to the mint

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

gunbai-bot Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

Receipt after merging #12119's fixed head (e803d33; main's emit_yaml change) down the stack. BuildBuddy, claim_batch: 118/118 PASS, 0 FAIL.

File Result
observation 11/11
ensure 8/8
github_app_registry 48/48
github_effect_perform 8/8
runner_attempt_launch 18/18
runner_microvm_slot_controller 12/12
authorization_pattern_selection 13/13
workflow_dispatch_input not run

workflow_dispatch_input hit the 45-minute remote cap, so I report it as not run rather than green. This PR changes nothing it tests (no fleet-converge or ci_spec change). It passed 30/30 in the same-base run on #12172 (107/107 total).

Since then #12119 moved again (eb5059d: census row + claim for microvm-shakedown.yml, no code this PR touches). I merged it in cleanly; the head is now af991e6.

— sent from bright-dove-353

…e JSON decode, route inspection, ensure and controller through read_runner_groups (review 70883)

The gh-CLI ListRunnerGroupsJson fetched GitHub's default page and never compared total_count,
so the ensure could read the shakedown group as absent on a page boundary and plan a duplicate
create while the controller's REST read refused. Now there is one operation (REST, per_page 100),
one fold over its outcome (runner_groups_of_rest_read) and one projection with the completeness
and element refusals. perform_organization_jit_mint matches only on jit_mint_generate_gate, so
its pre-token arms are decided once and it carries no unreachable repeat.

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

gunbai-bot Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

Review 70883 addressed at 6dd9156. The finding was right, and it was a behavioral fork, not a naming one.

The fork is deleted, not bridged. extdeps.github.org_actions github.CliOrgRunnerGroups ListRunnerGroupsJson and the JSON-text decode are gone: decode_runner_groups_json, decode_runner_group and json_member_string_list. The runner-groups list has one operation, github.OrgRunnerGroupsRest ListOrganizationRunnerGroups (REST, per_page 100). It has one fold over its outcome, gunbc.fleet.org_actions_inspection runner_groups_of_rest_read, and one projection, runner_groups_from_wire, which holds the completeness refusal (count < total_count) and the element refusals. All three consumers go through read_runner_groups(token, org):

  • the org-actions inspection (bearer from GUNBC_ORG_ADMIN_TOKEN, which its step already mints)
  • the ensure run
  • the controller

So the ensure and the controller can no longer reach different answers about the group. A single REST handler with no forked gh copy is the shape extdeps.github.users already states for this repo. The per-group repositories read stays on the CLI service: it is a different operation and has no REST twin.

Smaller point: perform_organization_jit_mint now matches only on jit_mint_generate_gate (JitMintDecidedBeforeGenerate | JitMintProceedsToGenerate). The refused-plan and wrong-App arms are decided once, in one place, and there is no unreachable repeat. The witnesses execute that gate.

Witnesses now supply GitHub's typed wire records, not JSON text. The "body missing the restriction members" case can't be expressed as a typed record, because that failure is now the transport's RestBodyUndecodable. It is replaced by two reds through the real fold: a refused status (403) and an undecodable body each refuse as RunnerGroupListUnreadable, never as absent.

Receipt (BuildBuddy, claim_batch): 101/101 PASS, 0 FAIL. That is every test fn in:

  • observation (12)
  • ensure (8)
  • github_effect_perform (8)
  • org_admin_credential (7)
  • org_actions_standing (6)
  • runner_microvm_slot_controller (12)
  • github_app_registry (48)

Consequence for sequencing: #12172 alone still carries the CLI read in its ensure mode. The microvm_runner_group_ensure wet run should therefore be dispatched only after this PR is on main; I've told quiet-koi-746.

— sent from bright-dove-353

@gunbai-bot

gunbai-bot Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

On review 70900's one unchecked point, whether the lifecycle App's installation token may read org runner groups: the modeled answer is yes. The gunbai-ci App's required grant is organization_self_hosted_runners: write (gunbc.fleet.app_control_plane_inspection gunbai_ci_registration_goal, derived from GenerateOrganizationJitConfig). extdeps.github.org_admin_auth grant_supports maps FineGrainedOrganizationSelfHostedRunnersWrite to RunnerGroupsRead and RunnerGroupsWrite, among others.

It is not yet observed against GitHub. That observation is the wet run: its first step is this exact read under this token. The reviewer's point stands for the unhappy path: a missing permission comes back as a typed 403 RunnerGroupListUnreadable, witnessed by witness_runner_group_read_refused_by_status_refuses_as_unreadable, never as an absent group.

— sent from bright-dove-353

gunbc-ci-auto-heal and others added 2 commits September 24, 2026 15:54
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…a256 6807a921)

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

gunbai-bot Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

Receipt at 186fd14, after #12155 on main and #12172 were merged in (own diff applied three-way; the diff vs #12172 is unchanged at 16 files). All results are BuildBuddy claim_batch, 0 FAIL:

File Result
observation 12/12
ensure 8/8
github_effect_perform 8/8
org_admin_credential 7/7
org_actions_standing 6/6
runner_microvm_slot_controller 12/12
github_app_registry 48/48
authorization_pattern_selection 13/13
runner_attempt_launch 18/18

runner_attempt_launch ran separately because the stack-head run hit the remote cap before reaching it; it is counted above. workflow_dispatch_input (30/30) and the byte-exact fleet-converge.yml check (sha256 6807a921…) come from #12172's head. This PR changes neither's inputs and carries the same generated bytes.

— sent from bright-dove-353

@briansrls
briansrls merged commit c3326f9 into session/bright-dove-353-group-create Sep 24, 2026
@briansrls
briansrls deleted the session/bright-dove-353-controller-read branch September 24, 2026 20:11
briansrls pushed a commit that referenced this pull request Sep 24, 2026
…(first approval-capability effect; stacked on #12155) (#12172)

* WIP PR-B: approval-routed microVM runner group ensure

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

* PR-B: annotations at module-item grain; escape the path braces in a refusal string

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

* PR-B: one annotation block above the service

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

* PR-B: regenerate fleet-converge.yml for the microvm_runner_group_ensure mode (main_wet_one, sha256 5c3e0d9f)

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

* PR-B: regenerate fleet-converge.yml over the rebuilt #12119 base (main_wet_one, sha256 3fbae87f)

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

* PR-B: the submission-MAC fetch refuses by name, not by the chmod that follows it (review 70675)

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

* PR-B: the named MAC-fetch refusal is this mode's prelude only; the keyring converge's shared-text line is not this PR's (regen: one hunk)

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

* PR-B: regenerate fleet-converge.yml over #12119 at e803d33 (main_wet_one, sha256 cbe53868)

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

* Regenerate fleet-converge.yml over main + the stack (main_wet_one, sha256 6807a921)

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

* microVM controller reads the org's runner groups under its installation token and mints only into the observed restricted group (stacked on #12172) (#12179)

* PR-C: the controller reads the org's runner groups under its installation token and passes the sealed observation to the mint

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

* One runner-groups read for every consumer: delete the CLI list and the JSON decode, route inspection, ensure and controller through read_runner_groups (review 70883)

The gh-CLI ListRunnerGroupsJson fetched GitHub's default page and never compared total_count,
so the ensure could read the shakedown group as absent on a page boundary and plan a duplicate
create while the controller's REST read refused. Now there is one operation (REST, per_page 100),
one fold over its outcome (runner_groups_of_rest_read) and one projection with the completeness
and element refusals. perform_organization_jit_mint matches only on jit_mint_generate_gate, so
its pre-token arms are decided once and it carries no unreachable repeat.

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

* Regenerate fleet-converge.yml over main + the stack (main_wet_one, sha256 6807a921)

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

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: gunbai-bot[bot] <289086189+gunbai-bot[bot]@users.noreply.github.com>
gunbai-bot Bot pushed a commit that referenced this pull request Sep 24, 2026
…and #12179)

Resolutions: ci_spec, fleet_converge_workflow and the authorization census witness
keep both sides' rows (main's mtcollins1 boot admission, this branch's runner-group
ensure); the workflow step list drops the mtcollins boot steps main moved into
fleet_converge_mtcollins1_boot_steps_around; fleet-converge.yml regenerated. The
runner-group census row gains main's new witness_discharge field (NoWitnessDischarge),
and its annotation moves above the census declaration (§4c: no body comments).

Local, fresh claim_batch: authorization_pattern_selection 19/19,
runner_group_restriction_observation 12/12, _ensure 8/8, github_effect_perform 8/8,
org_admin_credential 7/7, org_actions_standing 6/6, github_app_registry 48/48,
runner_attempt_launch 18/18, runner_microvm_slot_controller 12/12,
workflow_dispatch_input 30/30.

Co-Authored-By: Claude Opus 5.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