Repository navigation
Read the org runner roster live; dissolve the pinned-corpus scaffold - #11711
Merged
Merged
Conversation
gunbc.apply (Plan) and gunbc.runner_capacity_realize now plan from
gunbc.fleet.organization_runner_roster_read organization_runner_roster_live_read:
GET /orgs/{org}/actions/runners through the gh CLI under the in-run gunbai-ci
installation token (gunbc_ci_org_admin_app_token_prelude), following RFC 8288
Link pages via std.page_fold, accounting by distinct runner id against
total_count. A failed later page keeps earlier members as RosterObservedPartial;
no credential answers RosterReadUnavailable, never the pinned corpus, which
survives only as the dated witness fixture incident_2026_08_06_roster_fixture.
Adds the org-scoped per-ID read (present / absent / unobserved), with 404 read
as absent only after a same-org listing answered 200. Adds fleet-converge mode
org_runner_roster_observe. GitHubSelfHostedRunnerRegistration.ephemeral and
.version become Optional, matching the upstream schema.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
OR-1: a rel=next target is admitted only as this organization's runners
listing (orgs/<login>/..., or organizations/<id>/... when the id was read
from GET /orgs/{org}); any other org, repo or resource refuses before fetch.
OR-2: visibility evidence carries its org and admitted route (GH_TOKEN from
the org-admin prelude); a 404 is absence only for that same org.
OR-3: repeated runner ids are reconciled over their full reading; a
disagreement withholds the id and makes the roster Partial.
Witnesses now drive the real pump through std.page_fold over scripted
responses. The page bound is re-grounded as a policy budget with the
receipt as its instrument; the observe mode probes captured ids from the
new runner_ids input, consuming the per-ID read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
Re review 68461, addressed at 68601cf:
The same head also addresses the side-chat holds OR-1..3 (continuation subject binding, org-scoped visibility, disagreeing repeated ids). Their witnesses drive the real pump through — sent from swift-moth-559 |
Per-ID absence now joins visibility derived from the roster walk the observe entry already performed, instead of a second first-page listing; the unused single-id wrapper is deleted; an unparsable runner_ids token refuses the step rather than being dropped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
Re review 68504, addressed at 2988af0:
The roster witness module passes 23/23 locally. — sent from swift-moth-559 |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # dag/gunbc/fleet/fleet_runner_connectivity.dag # dag/test/claim/runner/runner_capacity_plan_witness_test.dag # dag/test/claim/runner/runner_capacity_realize_witness_test.dag
…es; fail on unobserved probes OR-2: the route is admitted only when GH_TOKEN and GUNBC_ORG_ADMIN_TOKEN are both bound and agree; a mismatch or a missing binding refuses before any read. OR-3: repeated ids are compared field by field (optional presence as presence, labels through runner_label_sets_equal), not through a joined rendering. OR-4: the observe command exits non-zero when any requested probe is unobserved; negative and non-integer ids refuse. Adds a roster_page_size dispatch input (admitted by std.page_fold admit_page_size) and per-page receipt lines (target, total_count, Link field, ids). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Sep 20, 2026
main carries #11711 squashed (cf66aa2), so the roster read and its witness are resolved to main's content with only this PR's own hunks re-applied: the gunbc.host_cli_dependency import, org_runner_route_on_executor, and the gh-prerequisite witness rows. fleet-converge.yml was regenerated rather than hand-merged; the regenerated projection of the merged authorities is byte-identical to this branch's file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The runner-capacity planner no longer plans from the pinned 2026-08-06 incident corpus. Both production call sites (
gunbc.applyPlan arm andgunbc.runner_capacity_realize runner_capacity_apply_wet_exit) now callgunbc.fleet.organization_runner_roster_read organization_runner_roster_live_read. It returns the existing carrierOrganizationRunnerRosterEvidence.organization_runner_roster_producer_scaffoldand itsReadAbsentrow are deleted.GH_TOKEN/GUNBC_ORG_ADMIN_TOKENthatgunbc.ci_spec gunbc_ci_org_admin_app_token_preludemints in-run. This is the same routeorg_actions_convergealready uses for runner groups. No key is copied, no PAT is created, and no token is placed on argv. The organization is derived from the gunbai-ci App row (gunbc.runner_host_deploy github_app_runner_org(gunbc_ci_github_app)), not written as a literal.Link rel="next"targets verbatim, and every continuation decision (advance, cycle check, page budget, closure) is made bystd.page_fold. It is Observed only when three things hold: the listing closed, every page reported the sametotal_count, and the number of distinct runner ids equals it. Otherwise it is Partial. If a later page fails, the earlier members are kept and the result is Partial. A failed first page, or a missing credential, givesRosterReadUnavailable. It never falls back to the fixture.std.page_fold's outcome deliberately carries no items on refusal. The walk keeps its own copy of the pages because the roster carrier has a typed partial arm, and its consumers already decide per question whether a prefix may answer. The fold state remains the only authority on whether another request is owed.organization_runner_id_observation(org, runner_id)returnsRunnerIdPresent/RunnerIdAbsent/RunnerIdUnobserved, each carrying its org. It uses GET/orgs/{org}/actions/runners/{id}. A 404 counts as Absent only after a runners listing of the same org answered 200 under the same token; otherwise it is Unobserved. This was quiet-moth-644's requirement for Retire srv2 as a declared obligation, from an executor that is not the target (L2a-1) #11680: an absence caused by insufficient scope must never discharge a removal obligation.org_runner_roster_observe. It uses the same token prelude, writes a receipt, and exits non-zero unless the roster was complete. The workflow is regenerated viatools.generated_artifact_gate main_wet_one.extdeps.github.cli_api_includereads thegh api --includeframing (status line,Linkheader, body).GitHubSelfHostedRunnerRegistration.ephemeraland.versionare now Optional. GitHub's schema does not require them, andversionisstring|null.rel="next"target is admitted only as this organization's runners listing:orgs/<login>/actions/runners, ororganizations/<id>/actions/runnerswhen the id matches the one read from GET/orgs/{org}. Any other org, repository or resource refuses before fetch.std.page_foldprogressions over scripted responses, including a cross-org continuation and a disagreeing repeat: 20/20 PASS.incident_2026_08_06_roster_fixtureand is used by witnesses only.Evidence (local
claim_batch, this head's tree)organization_runner_roster_read_witness: 13/13 PASS. They cover Link continuation and closure, Optional fields, 403 as a failure, unreadable output, Observed, repeated ids giving Partial, a failed later page keeping members as Partial, a failed first page giving Unavailable, 404 with and without an established listing, and per-ID mismatch.fleet_runner_connectivity_witness: 22/22 PASS.runner_capacity_plan_witnessandrunner_capacity_realize_witness: all PASS except three that fail identically on unchanged origin/main 7d1d6c5:at_the_committed_width_additions_follow_purpose_not_count,apply_seam_plans_additions_at_the_committed_width_not_count_refusalandapply_seam_preserves_sibling_host_receipt_when_one_refuses. cool-dove-770 is fixing them.gunbc run --entry dag/gunbc/apply.dag --function apply_wetwith workflow=runner-capacity and mode=plan exits 1 with "runner roster read unavailable … GUNBC_ORG_ADMIN_TOKEN is absent … no pinned population is substituted".runner_ids=251463,251484,91529,91513), artifactorg-runner-roster. Receipt head line:organization=gunb-ai pages_read=1 roster=observed count=100 disputed_ids= note=listing closed; distinct runner ids compared against total_count251463 srv2-02-1789774588-3693107and251484 srv2-05-1789775039-286764, both offline and not busy.max_per_page. Observed rests on two independent API facts, norel="next"andtotal_count= 100 = distinct ids, but the coincidence is worth a second run before anything is discharged from it. Per the side-chat hold, this read is evidence and is not yet used to discharge retirement obligations.roster_page_size=50, same four probes). Receipt:page_size=50 pages_read=2 roster=observed count=92 disputed_ids= note=listing closed.orgs/gunb-ai/actions/runners?per_page=50,total_count=92,Link: <https://api.github.com/organizations/244123794/actions/runners?per_page=50&page=2>; rel="next", …; rel="last".rel="next"URL, used verbatim and admitted because its numeric org id matched the id read from GET/orgs/gunb-ai.total_count=92;Linkcarries onlyprev/first, so the listing closed.total_count92. The population changed from 100 between the two runs, which is expected while registrations churn; totals are not required to match across runs.ghis absent on that runner (see findings). A srv1 run queued for about 40 min under org-wide load and was cancelled.Findings
ghCLI is an unmodeled executor dependency. The gh-CLI service family (github.CliOrgRunnerGroups,github.CliOrgSelfHostedRunners,github.CliOrganizations) needsghon the executing runner, and nothing declares that.gunbc.host_toolchain_componentsmodels only rustup components. srv4, a retained runner host, lacksgh(run 35448023511:failed to execute 'gh': No such file or directory); srv1 has/usr/bin/gh. It is not fixed here.TypeError. The failure is fail-closed but untyped: the shell operation's typed outputs (success=falseplus stderr) never form, so the caller's typed refusal arm (RosterPageFailed, and so Partial or Unavailable) cannot fire and no receipt is written. Location:v1_interpreter.rsdispatch_shell(thefailed to execute '{argv[0]}'spawn-error arms). It is an interpreter shell-transport gap and is not fixed here.main). A spawn failure becomesextdeps.transports.shellShellOutcome.ShellSpawnRefused { program, cause }for an operation that declares anoutcome: ShellOutcomeoutput. Declared follow-up for this read, triggered by Shell transport: spawn failure is a typed ShellSpawnRefused, not an evaluation-aborting TypeError #11737 landing: addoutcome: ShellOutcometoListOrgRunnersPage,GetOrgRunnerandGetOrganization; carry it throughRosterTransportResponse; and mapShellSpawnRefusedtoRosterPageFailed, or toRunnerIdUnobservedfor per-ID reads. That is sufficient for a missingghto yield Unavailable, or Partial with earlier pages kept, plus a written receipt, not an aborted run.gha declared prerequisite of the executor that runs the read, refused before the first call, and stops loading the fleet SSH key for the two API-only org modes.gunbc.runner_slot_allocation gunbc_runner_slots_per_hostmapsDerivedRunnerWidthUnresolved { cause }to0and drops the cause. So when srv2's commitment is unestablished,runner_capacity_planrefuses with "no committed fleet slot", which conflates unknown with zero. The fix is to return the unresolved result as its own typed case and give the planner a separate refusal for it. It belongs in the slot-allocation/planner lane (cool-dove-770), not in the roster.jit_mint_effect_transport_frontierremains a real, distinct gap. It is a POST whose response is a secret-bearing JIT config, which must reach neither argv, logs nor receipts. An env-authenticatedgh apiread does not discharge it, and it does not block this read.🤖 Generated with Claude Code