Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e9d480c
Converge R2 entitlement + bucket existence for every allocated origin…
Sep 19, 2026
f820b55
Bucket ensure signs with a minted bucket-admin token; label spellings…
Sep 19, 2026
0f44d5c
Merge remote-tracking branch 'origin/main' into session/sharp-ant-20
Sep 19, 2026
328374f
Regenerate fleet-converge.yml: r2_bucket_admin_mint mode
Sep 19, 2026
c463ee8
Mint flow ensures its custody container; pin the executed bucket-admi…
Sep 19, 2026
b91d374
Name the container step for what it does: ensure, not observe (review…
Sep 19, 2026
0bb92b1
Cite cloudflare.R2Buckets in its new module (review 68571)
Sep 19, 2026
c3b1c62
Import the REST outcome constructors r2_bucket_ensure matches on (rev…
Sep 19, 2026
0e089c5
Merge remote-tracking branch 'origin/main' into session/sharp-ant-20
Sep 20, 2026
36d19e1
Merge remote-tracking branch 'origin/main' into session/sharp-ant-20
Sep 20, 2026
854e773
The two R2 modes declare no host SSH demand (fixes the post-merge com…
Sep 20, 2026
1cfe38c
Restore main's two SSH-demand arms the merge dropped
Sep 20, 2026
0ac0900
Regenerate fleet-converge.yml from the merged mode roster
briansrls Sep 20, 2026
e219d8a
Merge remote-tracking branch 'origin/main' into session/sharp-ant-20
Sep 20, 2026
ab841c6
Merge remote-tracking branch 'origin/session/sharp-ant-20' into sessi…
Sep 20, 2026
eb24535
Derive the jurisdiction parse from the wire table (review 69065)
Sep 20, 2026
c716d5f
Merge remote-tracking branch 'origin/main' into session/sharp-ant-20
Sep 20, 2026
bad2114
Merge remote-tracking branch 'origin/main' into session/sharp-ant-20
Sep 20, 2026
8255add
Merge remote-tracking branch 'origin/main' into sharp-ant-merge
Sep 20, 2026
e4db97c
Regenerate fleet-converge.yml with all three new modes after the main…
briansrls Sep 20, 2026
4995bf4
Merge remote-tracking branch 'origin/main' into sharp-ant-fix
Sep 20, 2026
b8167c3
Regenerate fleet-converge.yml with the approval-broker mode from main
briansrls Sep 20, 2026
e94729a
Merge remote-tracking branch 'origin/main' into sharp2
Sep 20, 2026
4a20129
Regenerate fleet-converge.yml with the microvm-controller mode from main
briansrls Sep 20, 2026
bc2608f
chore: regenerate drifted generated artifacts (ci auto-heal)
gunbai-bot[bot] Sep 20, 2026
6b50632
Regenerate ROADMAP.md, stale on this branch after the main merge
briansrls Sep 20, 2026
9fb0217
Merge remote-tracking branch 'origin/session/sharp-ant-20' into HEAD
briansrls Sep 20, 2026
7344492
Merge remote-tracking branch 'origin/main' into sharp3
Sep 21, 2026
f1ec9c9
Merge remote-tracking branch 'origin/main' into sharp3
Sep 21, 2026
91efb52
Regenerate fleet-converge.yml with the two R2 bucket modes (review 69…
Sep 21, 2026
70d32a1
Merge remote-tracking branch 'origin/main' into sharp3
Sep 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion .github/workflows/fleet-converge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
mode:
description: plan shows membership hunks; apply executes a prior plan artifact; org_actions_observe validates the org credential and refuses on runner-group drift; org_runner_roster_observe reads the organization's self-hosted runner registrations to exhaustion under the same token and refuses unless the roster is complete; app_control_plane_observe reads the gunbai-ci App registration and webhook config with a short-lived App JWT and refuses on hook-config drift; app_key_version_verify reads the gunbai-ci App private key at the EXACT Secret Manager version named by app_key_version, mints an installation token with it, and refuses unless GitHub accepts it and the key's rotation deadline has not passed -- no add, disable or destroy; microvm_host_converge installs the cited Firecracker release on the selected host and reads the kvm grant back, refusing by name when the grant has not landed (the grant itself is applied by the full-host apply spine); guest_image_observe reports the micro-VM base artifacts and their measured digests on the selected host and builds nothing; guest_image_converge builds the runner guest image there; microvm_boot_probe boots that image and reads its serial console; spark_grants and spark_bootstrap prepare the selected Spark; spark_serving_apply applies the promoted fabric groups' pair vLLM units over the password session, workers before heads; spark_native_serving_apply relaunches group B's native four-rank arm as ONE BOUNDED TRANSACTION -- an all-host preflight that mutates nothing and refuses on any unresolved artifact from an earlier run, the incumbent UNIT preserved (plus a diagnostic inspect of the incumbent container -- the container itself cannot outlive its unit, because these run --rm in the foreground under systemd), the head applied BEFORE the workers so every rank joins the head's new rendezvous store, then a readback of the COMPLETE realization through each container's own inspect -- every field the create spec states, the effective --node-rank off that container's argv, and NCCL's typed transport verdict on that incarnation -- plus the head's front door answering with this arm's served model, and then commit or a rollback of the whole arm -- and it is a separate mode because it reloads a 328 GB arm and nobody should reach it by asking for the pair units; spark_runtime_image_probe pulls the pinned runtime image on the selected Spark and reads its capabilities from inside its own digest, changing nothing; host_reset_return drives a rostered reset subject through its controller and measures the return FROM A PEER, and takes reset_observer rather than host because the observer must not be the subject; runner_host_file_observe reads the runner teardown drop-in, the needrestart deferral and the loaded teardown on the selected host as its administrator and writes nothing; runner_host_file_converge writes whichever of those two files differs, reloads systemd only if the drop-in changed, and refuses unless the manager then reports the declared teardown
required: true
options: [plan, launch_environment_plan, allocation_store_plan, apply, org_actions_observe, app_control_plane_observe, microvm_host_converge, guest_image_observe, guest_image_converge, microvm_boot_probe, spark_grants, spark_bootstrap, spark_serving_apply, spark_native_serving_apply, spark_runtime_image_probe, dashboard_deploy, approval_broker_dark_install, rlm_launch_deployment_receipt, host_reset_return, runner_host_file_observe, runner_host_file_converge, site_pxe_edge_observe, site_pxe_edge_converge, runner_password_session_tool_converge, r2_mint_preflight, r2_object_write_mint, org_runner_roster_observe, approval_keyring_converge, mtcollins1_boot, mtcollins1_fan_observe, mtcollins1_census_image_publish, microvm_controller_app_key_converge, app_key_version_verify]
options: [plan, launch_environment_plan, allocation_store_plan, apply, org_actions_observe, app_control_plane_observe, microvm_host_converge, guest_image_observe, guest_image_converge, microvm_boot_probe, spark_grants, spark_bootstrap, spark_serving_apply, spark_native_serving_apply, spark_runtime_image_probe, dashboard_deploy, approval_broker_dark_install, rlm_launch_deployment_receipt, host_reset_return, runner_host_file_observe, runner_host_file_converge, site_pxe_edge_observe, site_pxe_edge_converge, runner_password_session_tool_converge, r2_mint_preflight, r2_object_write_mint, org_runner_roster_observe, approval_keyring_converge, mtcollins1_boot, mtcollins1_fan_observe, mtcollins1_census_image_publish, microvm_controller_app_key_converge, app_key_version_verify, r2_bucket_ensure, r2_bucket_admin_mint]
type: choice
target:
description: "Spark target host for the spark_* modes, naming the administrator credential the run materializes (any rostered Spark: srv5, srv6, srv7, srv8, srv9, srv10, srv11, srv12); apply reads the subject off the plan artifact"
Expand Down Expand Up @@ -599,6 +599,45 @@ jobs:
retention-days: 30
if: github.event.inputs.mode == 'r2_mint_preflight'
timeout-minutes: 10
- name: R2 bucket-admin token mint (AccountTokens.Create + Secret Manager custody)
id: r2_bucket_admin_mint
run: |
ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
"$ROOT/target/release/gunbc" run --source-root "$ROOT/dag" --source-root "$ROOT/src/v2" --entry dag/gunbc/cloudflare/r2_token_mint_run.dag --function run_bucket_admin
env:
WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }}
if: github.event.inputs.mode == 'r2_bucket_admin_mint'
timeout-minutes: 5
- name: Upload R2 bucket-admin mint receipt (token id + custody version resource; no secret)
id: r2_bucket_admin_mint_receipt_upload
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: r2-bucket-admin-mint-receipt
path: /tmp/r2-mint-receipts/${{ github.run_id }}-${{ github.run_attempt }}/r2-*-bucket-admin-mint-receipt.txt
if-no-files-found: warn
retention-days: 30
if: always() && github.event.inputs.mode == 'r2_bucket_admin_mint'
timeout-minutes: 10
- name: "R2 bucket ensure: entitlement + bucket existence per allocated purpose (create on absence, readback)"
id: r2_bucket_ensure
run: |
ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
"$ROOT/target/release/gunbc" run --source-root "$ROOT/dag" --source-root "$ROOT/src/v2" --entry dag/gunbc/cloudflare/r2_bucket_ensure.dag --function ensure
cat "$ROOT/target/r2-bucket-ensure-receipt.txt"
env:
WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }}
if: github.event.inputs.mode == 'r2_bucket_ensure'
timeout-minutes: 5
- name: Upload R2 bucket ensure receipt
id: r2_bucket_ensure_receipt_upload
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: r2-bucket-ensure
path: target/r2-bucket-ensure-receipt.txt
if-no-files-found: warn
retention-days: 30
if: always() && github.event.inputs.mode == 'r2_bucket_ensure'
timeout-minutes: 10
- name: R2 object-write token mint (AccountTokens.Create + Secret Manager custody)
id: r2_object_write_mint
run: |
Expand Down
70 changes: 69 additions & 1 deletion dag/extdeps/cloudflare/client_v4.dag
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
module extdeps.cloudflare.client_v4

import std.types { NonEmptyStr, String, Int }
import std.types { NonEmptyStr, String, Int, List }
import extdeps.languages.json.emit { JsonValue, JsonNull, JsonBool, JsonNumber, JsonString, JsonArray, JsonObject }
import extdeps.languages.json.parse {
parse_json_document, JsonDocumentParsed, JsonDocumentUnreadable, json_document_gap_text,
json_object_unique_member, JsonMemberFound, JsonMemberAbsent, JsonMemberDuplicated, JsonMemberNotAnObject,
}
import std.decl_ref { DeclarationRef, WholeDeclaration }
import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef }
import extdeps.uri { Uri, Https }
Expand Down Expand Up @@ -37,3 +42,66 @@ type CloudflareApiError {
code: Int
message: String
}

// THE ERROR ENVELOPE'S CODES, READ RATHER THAN MATCHED AS SUBSTRINGS. Every client/v4 response
// carries `errors: [{ code, message }]` (the envelope this module's anchor documents), and a refused
// status's body is the only place the service says WHICH refusal it made -- a 403 is an
// authorization refusal and an entitlement refusal alike. A substring search for a code would
// accept the digits inside a message or an id; reading the array is the discriminator.
//
// THE CODES STAY LEXEMES. The envelope's code is a JSON number whose meaning is its identity, not
// its magnitude, and every consumer compares it to a cited code; parsing it to an Int would add a
// conversion no consumer demands.
type CloudflareErrorCodesRead
= CloudflareErrorCodesListed { codes: List<String> }
| CloudflareErrorEnvelopeUnreadable { cause: NonEmptyStr }

fn cloudflare_error_code_lexemes(entry: JsonValue) -> List<String> {
match json_object_unique_member(v: entry, key: "code") {
JsonMemberFound { value: v } =>
match v {
JsonNumber { lexeme: lexeme } => [lexeme as String]
JsonNull => []
JsonBool { value: _ } => []
JsonString { value: _ } => []
JsonArray { elements: _ } => []
JsonObject { members: _ } => []
}
JsonMemberAbsent => []
JsonMemberNotAnObject => []
JsonMemberDuplicated { count: _ } => []
}
}

fn cloudflare_error_codes_from_entries(entries: List<JsonValue>) -> CloudflareErrorCodesRead {
let codes = entries |> flat_map(entry => cloudflare_error_code_lexemes(entry: entry))
if codes.length() == entries.length() {
CloudflareErrorCodesListed { codes: codes }
} else {
CloudflareErrorEnvelopeUnreadable { cause: "an errors entry carries no numeric code" as NonEmptyStr }
}
}

fn cloudflare_error_codes(body: String) -> CloudflareErrorCodesRead {
match parse_json_document(s: body) {
JsonDocumentUnreadable { gap: gap } =>
CloudflareErrorEnvelopeUnreadable {
cause: concat("the refused body is not JSON: ", json_document_gap_text(gap: gap)) as NonEmptyStr
}
JsonDocumentParsed { value: doc } =>
match json_object_unique_member(v: doc, key: "errors") {
JsonMemberFound { value: errors } =>
match errors {
JsonArray { elements: entries } => cloudflare_error_codes_from_entries(entries: entries)
JsonNull => CloudflareErrorEnvelopeUnreadable { cause: "errors is null" as NonEmptyStr }
JsonBool { value: _ } => CloudflareErrorEnvelopeUnreadable { cause: "errors is not an array" as NonEmptyStr }
JsonNumber { lexeme: _ } => CloudflareErrorEnvelopeUnreadable { cause: "errors is not an array" as NonEmptyStr }
JsonString { value: _ } => CloudflareErrorEnvelopeUnreadable { cause: "errors is not an array" as NonEmptyStr }
JsonObject { members: _ } => CloudflareErrorEnvelopeUnreadable { cause: "errors is not an array" as NonEmptyStr }
}
JsonMemberAbsent => CloudflareErrorEnvelopeUnreadable { cause: "the refused body carries no errors member" as NonEmptyStr }
JsonMemberNotAnObject => CloudflareErrorEnvelopeUnreadable { cause: "the refused body is not a JSON object" as NonEmptyStr }
JsonMemberDuplicated { count: _ } => CloudflareErrorEnvelopeUnreadable { cause: "the refused body names errors twice" as NonEmptyStr }
}
}
}
Loading