From 30645e60305c3de45b18883000c639061bc5b434 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 16:08:12 +0000 Subject: [PATCH 1/3] Fleet-converge: r2_cache_object_read_mint / r2_cache_object_write_mint modes (mirror the workspace mints) Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/fleet-converge.yml | 40 ++++++++- dag/gunbc/ci/ci_spec.dag | 32 +++++++ dag/gunbc/fleet/fleet_converge_workflow.dag | 98 ++++++++++++++++++++- 3 files changed, 168 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fleet-converge.yml b/.github/workflows/fleet-converge.yml index 891570deec9..c94cd374a92 100644 --- a/.github/workflows/fleet-converge.yml +++ b/.github/workflows/fleet-converge.yml @@ -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; runner_browser_toolchain_converge installs the declared Playwright/Chromium toolchain (apt host libraries as the administrator, digest-pinned node, Playwright and Chromium archives into the job user's root) on the selected host, which must be in the pool that runs the floor job, and refuses unless every digest, version and host library reads back and headless Chromium renders a local page; 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); microvm_network_apply stages the slot and host network files the model renders at the named expected_revision as root:root 0600 in a root-only directory over the fleet SSH edge as the host's ADMINISTRATOR, installs them with the modeled operations, reloads networkd, systemd-sysctl and the nft loader unit, and reads the ruleset back -- the job user is granted none of it, because install plus systemctl over content that principal can write is arbitrary root for any pull request; 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; spark_wireless_link_converge reads the selected Spark's Wi-Fi power save (runtime via iw, persisted via the NetworkManager profile), link and kernel disconnect count, sets whichever realization differs from the declared intent, and refuses unless the readback decides Noop; spark_grants_observe reads every procured Spark's sudo grant listing as gunbc-automation and the bootstrap principal, and its sudoers drop-in shape, and writes nothing; spark_v41_checkpoint_materialize fetches the admitted published DeepSeek V4.1 files onto the selected Group A Spark (about 510 GB; spark_v41_row_store_encode encodes the eight Engram row stores from the verified shards on the selected Group A Spark and reads each store's sha256; spark_v41_row_store_readback reads those stores back at their header, first and last record and every rank seam, with the published source rows at the same rows, and writes nothing; spark_v41_engram_differential compares upstream's Engram lookup kernel with the design-B file-backed lookup over sampled real rows of every row store, byte for byte, and writes nothing; it states the requirement and refuses before fetching when the disk cannot hold it), publishes each only after its sha256 matches the manifest, leaves a present file with the right digest alone and refuses one with the wrong digest, and reads the storage-backed Engram spans from the verified shards; a transfer runs detached and a rerun reattaches; spark_v41_runtime_image_build PRODUCES the DeepSeek V4.1 image on the selected Spark -- it verifies the candidate's three FlashInfer wheels against the digests the candidate keys, converges the patched source tree, builds from it, reads the produced configuration digest back from inside the image through the probe route, and admits that digest against the candidate's own recipe, refusing a digest that does not recompute from it -- and it is a separate mode from the probe because it occupies one host for hours where the probe occupies it for minutes; spark_v41_runtime_image_distribute moves that produced image, named by the configuration digest its production receipt read back, from the host that receipt names to the selected Group A Spark -- save into its fabric blob root, pulled by the target straight over the fabric rail, load -- after stating its size against every filesystem a copy lands on, leaves a target already holding the digest untouched, refuses a target holding a different image under the tag, and refuses unless the target's image inspect Id reads back as that digest; spark_v41_group_a_launch reads the production image back under its tag on every Group A host, reads its registry inside its digest and every host's occupancy, and only when Group A is suspended for this candidate with every host held and vacant stages the Engram manifest and applies the V4.1 four-rank arm as one transaction at the capacity measurement's shape -- the target names only the session host; spark_v41_serving_load runs the shared serving-load runner against the V4.1 head (target must be srv6): one vllm bench serve step per capacity-measurement concurrency, metrics scraped around each, host pressure read on every Group A rank, and the staircase stop rules applied over the worst rank; it changes no unit and writes only its receipt; 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; microvm_controller_install writes the microVM slot controller's root-owned release locus (gunbc + sources + Firecracker + jailer) and the gunbc-microvm-slot@ template unit and the gunbc-microvm-slot-reserve broker unit on srv1 and starts neither; microvm_slot_reserve starts that broker unit once on srv1, which reserves the shakedown cell through the fabric broker route from inside the root process (slot, demand and offer derived from the model, never inputs), and uploads that invocation's reservation receipt, failing unless the reservation committed; microvm_slot_start starts the shakedown slot's controller unit once on srv1 (the instance is derived from the model, never an input), waits for it bounded by the unit's own stop timeout, and uploads that invocation's controller receipt; microvm_runner_group_ensure (srv1 only) reads the organization's runner groups and, only when the microvm-shakedown group is absent, files ONE operator approval, creates it restricted to the shakedown workflow on the default branch, and refuses unless the readback holds that restriction; mtcollins1_census_qemu_host_observe reads the selected host's KVM device and, under the job user's census QEMU root, the qemu-system-aarch64 build, its ldd libraries and the AAVMF images against their pins, and writes nothing; mtcollins1_census_qemu_toolchain_converge places that root as the job user (digest-pinned noble .debs unpacked with dpkg-deb -x, no apt, no Recommends) and refuses unless the same observe reads it ready; workspace_source_pack (host=srv1) enumerates the operator workspace as the job user, drops every path the credential exclusion row names and every build output, tars exactly the remainder, reads the archive back and refuses if any member is excluded, and puts it into the workspace bucket under its SHA-256, refusing by name when the runner cannot read a source root; workspace_checkpoint_measure (host=srv3, workspace_source_object = that SHA-256) refuses unless the job user's home is on btrfs, then times a read-only subvolume snapshot, a pinned kopia scan of it, the content-addressed chunk upload and the gated revision commit, and receipts every time with the 10 s / 60 s / 10 GB draft verdicts; workspace_checkpoint_restore (host=srv3) commits a small authored workspace, puts one chunk with no head advance, destroys the directory, restores it from the head closure and requires byte-equality with the uncommitted chunk absent -- both srv3 modes refuse their commit while the R2 head's CAS ground is uncited" required: true - options: [plan, launch_environment_plan, allocation_store_plan, workspace_commissioning_plan, apply, org_actions_observe, app_control_plane_observe, microvm_host_converge, microvm_network_observe, microvm_network_apply, guest_image_observe, guest_image_converge, microvm_boot_probe, spark_grants, spark_bootstrap, spark_serving_apply, spark_native_serving_apply, spark_runtime_image_probe, spark_wireless_link_converge, spark_grants_observe, spark_v41_checkpoint_materialize, spark_v41_row_store_encode, spark_v41_row_store_readback, spark_v41_engram_differential, spark_runtime_image_produce, spark_runtime_image_distribute, spark_arm_group_load, spark_arm_checkpoint_materialize, spark_arm_group_observe, spark_arm_group_launch_plan, spark_arm_group_launch, spark_v41_serving_load, dashboard_deploy, approval_broker_dark_install, microvm_controller_install, microvm_slot_reserve, microvm_slot_start, 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, runner_browser_toolchain_converge, r2_mint_preflight, r2_object_write_mint, org_runner_roster_observe, approval_keyring_converge, approval_device_enrolment_code_issue, mtcollins1_boot, mtcollins1_fan_observe, mtcollins1_ui_bundle_observe, mtcollins1_kvm_observer_observe, mtcollins1_census_image_publish, mtcollins1_census_member_readback, mtcollins1_census_qemu_host_observe, mtcollins1_census_qemu_toolchain_converge, host_credential_custody_converge, app_key_version_verify, r2_bucket_ensure, r2_bucket_admin_mint, r2_workspace_object_read_mint, r2_workspace_object_write_mint, workspace_source_pack, workspace_checkpoint_measure, workspace_checkpoint_restore, fabric_writer_identity_observe, pair_serving_d0, microvm_runner_group_ensure, gcp_iam_converge, namecheap_observe] + options: [plan, launch_environment_plan, allocation_store_plan, workspace_commissioning_plan, apply, org_actions_observe, app_control_plane_observe, microvm_host_converge, microvm_network_observe, microvm_network_apply, guest_image_observe, guest_image_converge, microvm_boot_probe, spark_grants, spark_bootstrap, spark_serving_apply, spark_native_serving_apply, spark_runtime_image_probe, spark_wireless_link_converge, spark_grants_observe, spark_v41_checkpoint_materialize, spark_v41_row_store_encode, spark_v41_row_store_readback, spark_v41_engram_differential, spark_runtime_image_produce, spark_runtime_image_distribute, spark_arm_group_load, spark_arm_checkpoint_materialize, spark_arm_group_observe, spark_arm_group_launch_plan, spark_arm_group_launch, spark_v41_serving_load, dashboard_deploy, approval_broker_dark_install, microvm_controller_install, microvm_slot_reserve, microvm_slot_start, 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, runner_browser_toolchain_converge, r2_mint_preflight, r2_object_write_mint, org_runner_roster_observe, approval_keyring_converge, approval_device_enrolment_code_issue, mtcollins1_boot, mtcollins1_fan_observe, mtcollins1_ui_bundle_observe, mtcollins1_kvm_observer_observe, mtcollins1_census_image_publish, mtcollins1_census_member_readback, mtcollins1_census_qemu_host_observe, mtcollins1_census_qemu_toolchain_converge, host_credential_custody_converge, app_key_version_verify, r2_bucket_ensure, r2_bucket_admin_mint, r2_workspace_object_read_mint, r2_workspace_object_write_mint, r2_cache_object_read_mint, r2_cache_object_write_mint, workspace_source_pack, workspace_checkpoint_measure, workspace_checkpoint_restore, fabric_writer_identity_observe, pair_serving_d0, microvm_runner_group_ensure, gcp_iam_converge, namecheap_observe] 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" @@ -1415,6 +1415,44 @@ jobs: retention-days: 30 if: always() && github.event.inputs.mode == 'r2_workspace_object_write_mint' timeout-minutes: 10 + - name: R2 cache-blobs object-read token mint (AccountTokens.Create + Secret Manager custody) + id: r2_cache_object_read_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_cache_object_read + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'r2_cache_object_read_mint' + timeout-minutes: 5 + - name: Upload R2 cache-blobs object-read mint receipt (token id + custody version resource; no secret) + id: r2_cache_object_read_mint_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: r2-cache-object-read-mint-receipt + path: /tmp/r2-mint-receipts/${{ github.run_id }}-${{ github.run_attempt }}/r2-*-object-read-mint-receipt.txt + if-no-files-found: warn + retention-days: 30 + if: always() && github.event.inputs.mode == 'r2_cache_object_read_mint' + timeout-minutes: 10 + - name: R2 cache-blobs object-write token mint (AccountTokens.Create + Secret Manager custody) + id: r2_cache_object_write_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_cache_object_write + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'r2_cache_object_write_mint' + timeout-minutes: 5 + - name: Upload R2 cache-blobs object-write mint receipt (token id + custody version resource; no secret) + id: r2_cache_object_write_mint_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: r2-cache-object-write-mint-receipt + path: /tmp/r2-mint-receipts/${{ github.run_id }}-${{ github.run_attempt }}/r2-*-object-write-mint-receipt.txt + if-no-files-found: warn + retention-days: 30 + if: always() && github.event.inputs.mode == 'r2_cache_object_write_mint' + timeout-minutes: 10 - name: "Workspace source pack: the operator workspace minus credentials and build output, into the workspace bucket by its SHA-256" id: workspace_source_pack run: |- diff --git a/dag/gunbc/ci/ci_spec.dag b/dag/gunbc/ci/ci_spec.dag index f808a7ae8f3..406fb2c622b 100644 --- a/dag/gunbc/ci/ci_spec.dag +++ b/dag/gunbc/ci/ci_spec.dag @@ -823,6 +823,16 @@ data gunbc_ci_r2_workspace_object_write_mint_target: GunbcRunStepTarget = GunbcR function: "run_workspace_object_write", } +data gunbc_ci_r2_cache_object_read_mint_target: GunbcRunStepTarget = GunbcRunStepTarget { + entry: "dag/gunbc/cloudflare/r2_token_mint_run.dag", + function: "run_cache_object_read", +} + +data gunbc_ci_r2_cache_object_write_mint_target: GunbcRunStepTarget = GunbcRunStepTarget { + entry: "dag/gunbc/cloudflare/r2_token_mint_run.dag", + function: "run_cache_object_write", +} + data gunbc_ci_workspace_source_pack_target: GunbcRunStepTarget = GunbcRunStepTarget { entry: "dag/gunbc/fabric/workspace_source.dag", function: "workspace_source_pack_wet", @@ -1368,6 +1378,8 @@ fn gunbc_run_step_targets() -> List { gunbc_ci_r2_object_write_mint_target, gunbc_ci_r2_workspace_object_read_mint_target, gunbc_ci_r2_workspace_object_write_mint_target, + gunbc_ci_r2_cache_object_read_mint_target, + gunbc_ci_r2_cache_object_write_mint_target, gunbc_ci_workspace_source_pack_target, gunbc_ci_workspace_checkpoint_measure_target, gunbc_ci_workspace_checkpoint_restore_target, @@ -2676,6 +2688,26 @@ fn gunbc_ci_r2_workspace_object_write_mint_invoke() -> String { ) } +fn gunbc_ci_r2_cache_object_read_mint_invoke() -> String { + gunbc_run_step_script( + source_roots: witness_layer_roots, + entry: gunbc_ci_r2_cache_object_read_mint_target.entry, + function: gunbc_ci_r2_cache_object_read_mint_target.function, + claim_run: false, + receipt_rel: none + ) +} + +fn gunbc_ci_r2_cache_object_write_mint_invoke() -> String { + gunbc_run_step_script( + source_roots: witness_layer_roots, + entry: gunbc_ci_r2_cache_object_write_mint_target.entry, + function: gunbc_ci_r2_cache_object_write_mint_target.function, + claim_run: false, + receipt_rel: none + ) +} + fn gunbc_ci_workspace_source_pack_invoke() -> String { gunbc_run_step_script( source_roots: witness_layer_roots, diff --git a/dag/gunbc/fleet/fleet_converge_workflow.dag b/dag/gunbc/fleet/fleet_converge_workflow.dag index e7af15aad59..48600bbb209 100644 --- a/dag/gunbc/fleet/fleet_converge_workflow.dag +++ b/dag/gunbc/fleet/fleet_converge_workflow.dag @@ -163,6 +163,8 @@ import gunbc.ci_spec { gunbc_ci_r2_object_write_mint_invoke, gunbc_ci_r2_workspace_object_read_mint_invoke, gunbc_ci_r2_workspace_object_write_mint_invoke, + gunbc_ci_r2_cache_object_read_mint_invoke, + gunbc_ci_r2_cache_object_write_mint_invoke, gunbc_ci_workspace_source_pack_invoke, gunbc_ci_workspace_checkpoint_measure_invoke, gunbc_ci_workspace_checkpoint_restore_invoke, @@ -344,6 +346,8 @@ type FleetConvergeWorkflowMode | R2ObjectWriteMint | R2WorkspaceObjectReadMint | R2WorkspaceObjectWriteMint + | R2CacheObjectReadMint + | R2CacheObjectWriteMint | WorkspaceSourcePack | WorkspaceCheckpointMeasure | WorkspaceCheckpointRestore @@ -419,6 +423,8 @@ fn fleet_converge_workflow_mode_wire(mode: FleetConvergeWorkflowMode) -> String R2ObjectWriteMint => "r2_object_write_mint" R2WorkspaceObjectReadMint => "r2_workspace_object_read_mint" R2WorkspaceObjectWriteMint => "r2_workspace_object_write_mint" + R2CacheObjectReadMint => "r2_cache_object_read_mint" + R2CacheObjectWriteMint => "r2_cache_object_write_mint" WorkspaceSourcePack => "workspace_source_pack" WorkspaceCheckpointMeasure => "workspace_checkpoint_measure" WorkspaceCheckpointRestore => "workspace_checkpoint_restore" @@ -462,7 +468,7 @@ fn fleet_converge_spark_target_description() -> String { ], "") } -data fleet_converge_workflow_modes: List = [FullHostPlan, LaunchEnvironmentPlan, AllocationStorePlan, WorkspaceCommissioningPlan, FleetApply, OrgActionsObserve, AppControlPlaneObserve, MicrovmHostConverge, MicrovmNetworkObserve, MicrovmNetworkApply, GuestImageObserve, GuestImageConverge, MicrovmBootProbe, SparkGrants, SparkBootstrap, SparkServingApply, SparkNativeServingApply, SparkRuntimeImageProbe, SparkWirelessLinkConverge, SparkGrantsObserve, SparkV41CheckpointMaterialize, SparkV41RowStoreEncode, SparkV41RowStoreReadback, SparkV41EngramDifferential, SparkRuntimeImageProduce, SparkRuntimeImageDistribute, SparkArmGroupLoad, SparkArmCheckpointMaterialize, SparkArmGroupObserve, SparkArmGroupLaunchPlan, SparkArmGroupLaunch, SparkV41ServingLoad, DashboardDeploy, ApprovalBrokerDarkInstall, MicrovmControllerInstall, MicrovmSlotReserve, MicrovmSlotStart, RlmLaunchDeploymentReceipt, HostResetReturn, RunnerHostFileObserve, RunnerHostFileConverge, SitePxeEdgeObserve, SitePxeEdgeConverge, RunnerPasswordSessionToolConverge, RunnerBrowserToolchainConverge, R2MintPreflight, R2ObjectWriteMint, OrgRunnerRosterObserve, ApprovalKeyringConverge, ApprovalDeviceEnrolmentCodeIssue, MtCollins1Boot, MtCollins1FanObserve, MtCollins1UiBundleObserve, MtCollins1KvmObserverObserve, MtCollins1CensusImagePublish, MtCollins1CensusMemberReadback, MtCollins1CensusQemuHostObserve, MtCollins1CensusQemuToolchainConverge, HostCredentialCustodyConverge, AppKeyVersionVerify, R2BucketEnsure, R2BucketAdminMint, R2WorkspaceObjectReadMint, R2WorkspaceObjectWriteMint, WorkspaceSourcePack, WorkspaceCheckpointMeasure, WorkspaceCheckpointRestore, FabricWriterIdentityObserve, PairServingD0, MicrovmRunnerGroupEnsure, GcpIamConverge, NamecheapObserve] +data fleet_converge_workflow_modes: List = [FullHostPlan, LaunchEnvironmentPlan, AllocationStorePlan, WorkspaceCommissioningPlan, FleetApply, OrgActionsObserve, AppControlPlaneObserve, MicrovmHostConverge, MicrovmNetworkObserve, MicrovmNetworkApply, GuestImageObserve, GuestImageConverge, MicrovmBootProbe, SparkGrants, SparkBootstrap, SparkServingApply, SparkNativeServingApply, SparkRuntimeImageProbe, SparkWirelessLinkConverge, SparkGrantsObserve, SparkV41CheckpointMaterialize, SparkV41RowStoreEncode, SparkV41RowStoreReadback, SparkV41EngramDifferential, SparkRuntimeImageProduce, SparkRuntimeImageDistribute, SparkArmGroupLoad, SparkArmCheckpointMaterialize, SparkArmGroupObserve, SparkArmGroupLaunchPlan, SparkArmGroupLaunch, SparkV41ServingLoad, DashboardDeploy, ApprovalBrokerDarkInstall, MicrovmControllerInstall, MicrovmSlotReserve, MicrovmSlotStart, RlmLaunchDeploymentReceipt, HostResetReturn, RunnerHostFileObserve, RunnerHostFileConverge, SitePxeEdgeObserve, SitePxeEdgeConverge, RunnerPasswordSessionToolConverge, RunnerBrowserToolchainConverge, R2MintPreflight, R2ObjectWriteMint, OrgRunnerRosterObserve, ApprovalKeyringConverge, ApprovalDeviceEnrolmentCodeIssue, MtCollins1Boot, MtCollins1FanObserve, MtCollins1UiBundleObserve, MtCollins1KvmObserverObserve, MtCollins1CensusImagePublish, MtCollins1CensusMemberReadback, MtCollins1CensusQemuHostObserve, MtCollins1CensusQemuToolchainConverge, HostCredentialCustodyConverge, AppKeyVersionVerify, R2BucketEnsure, R2BucketAdminMint, R2WorkspaceObjectReadMint, R2WorkspaceObjectWriteMint, R2CacheObjectReadMint, R2CacheObjectWriteMint, WorkspaceSourcePack, WorkspaceCheckpointMeasure, WorkspaceCheckpointRestore, FabricWriterIdentityObserve, PairServingD0, MicrovmRunnerGroupEnsure, GcpIamConverge, NamecheapObserve] // WHICH RELEASE PRODUCT A MODE'S JOB RUNS, NAMED PER MODE WITH NO WILDCARD (as the scope and fleet-key // matches above name theirs). A mode whose steps run only gunbc and claim_executor declares the // compiler pair (gunbc.fleet_release_bins_key compiler_pair_release_product): its build job then @@ -522,6 +528,8 @@ fn fleet_converge_mode_release_product(mode: FleetConvergeWorkflowMode) -> Fleet R2ObjectWriteMint => SixteenBinaryPack R2WorkspaceObjectReadMint => SixteenBinaryPack R2WorkspaceObjectWriteMint => SixteenBinaryPack + R2CacheObjectReadMint => SixteenBinaryPack + R2CacheObjectWriteMint => SixteenBinaryPack WorkspaceSourcePack => CompilerPairOnly WorkspaceCheckpointMeasure => CompilerPairOnly WorkspaceCheckpointRestore => CompilerPairOnly @@ -633,6 +641,8 @@ fn fleet_converge_mode_scope(mode: FleetConvergeWorkflowMode) -> FleetConvergeSc R2ObjectWriteMint => none R2WorkspaceObjectReadMint => none R2WorkspaceObjectWriteMint => none + R2CacheObjectReadMint => none + R2CacheObjectWriteMint => none WorkspaceSourcePack => none WorkspaceCheckpointMeasure => none WorkspaceCheckpointRestore => none @@ -733,6 +743,8 @@ fn fleet_converge_mode_fleet_ssh_key_demand(mode: FleetConvergeWorkflowMode) -> R2BucketAdminMint => FleetSshKeyNotConsumed R2WorkspaceObjectReadMint => FleetSshKeyNotConsumed R2WorkspaceObjectWriteMint => FleetSshKeyNotConsumed + R2CacheObjectReadMint => FleetSshKeyNotConsumed + R2CacheObjectWriteMint => FleetSshKeyNotConsumed WorkspaceSourcePack => FleetSshKeyNotConsumed WorkspaceCheckpointMeasure => FleetSshKeyNotConsumed WorkspaceCheckpointRestore => FleetSshKeyNotConsumed @@ -1451,6 +1463,8 @@ fn fleet_converge_mode_mutation_domain(mode: FleetConvergeWorkflowMode) -> Fleet R2ObjectWriteMint => ExecutorDomain R2WorkspaceObjectReadMint => ExecutorDomain R2WorkspaceObjectWriteMint => ExecutorDomain + R2CacheObjectReadMint => ExecutorDomain + R2CacheObjectWriteMint => ExecutorDomain WorkspaceSourcePack => ExecutorDomain WorkspaceCheckpointMeasure => ExecutorDomain WorkspaceCheckpointRestore => ExecutorDomain @@ -2274,6 +2288,8 @@ data fleet_converge_r2_bucket_admin_mint_step_if: String = fleet_converge_mode_s data fleet_converge_r2_object_write_mint_step_if: String = fleet_converge_mode_step_if(mode: R2ObjectWriteMint) data fleet_converge_r2_workspace_object_read_mint_step_if: String = fleet_converge_mode_step_if(mode: R2WorkspaceObjectReadMint) data fleet_converge_r2_workspace_object_write_mint_step_if: String = fleet_converge_mode_step_if(mode: R2WorkspaceObjectWriteMint) +data fleet_converge_r2_cache_object_read_mint_step_if: String = fleet_converge_mode_step_if(mode: R2CacheObjectReadMint) +data fleet_converge_r2_cache_object_write_mint_step_if: String = fleet_converge_mode_step_if(mode: R2CacheObjectWriteMint) // ── THE APP-KEY VERSION VERIFIER ───────────────────────────────────────────────────────────── // A MODE AND NOT A JOB, for the reason the R2 mint gives above: this job is already dispatch-only, @@ -3224,6 +3240,80 @@ fn fleet_converge_r2_workspace_object_write_mint_receipt_upload_step() -> Step { } } +fn fleet_converge_r2_cache_object_read_mint_step() -> Step { + RunStep { + name: Present { value: "R2 cache-blobs object-read token mint (AccountTokens.Create + Secret Manager custody)" }, + id: Present { value: "r2_cache_object_read_mint" }, + run: gunbc_ci_r2_cache_object_read_mint_invoke(), + shell: none, + env: Present { value: [ + kv(key: "WIF_ACCESS_TOKEN", value: yaml_string(s: "${{ steps.wif_auth.outputs.access_token }}")), + ] }, + working_directory: none, + if_condition: Present { value: fleet_converge_r2_cache_object_read_mint_step_if }, + continue_on_error: none, + timeout_minutes: Present { value: gunbc_ci_aux_step_timeout_minutes } + } +} + +fn fleet_converge_r2_cache_object_read_mint_receipt_upload_step() -> Step { + UsesStep { + name: Present { value: "Upload R2 cache-blobs object-read mint receipt (token id + custody version resource; no secret)" }, + id: Present { value: "r2_cache_object_read_mint_receipt_upload" }, + uses: upload_artifact_action, + with: Present { value: [ + kv(key: "name", value: yaml_string(s: "r2-cache-object-read-mint-receipt")), + kv(key: "path", value: yaml_string(s: r2_mint_receipt_glob_in( + scope: fleet_converge_r2_mint_receipt_scope(), + profile: R2OriginObjectRead, + ))), + kv(key: "if-no-files-found", value: yaml_string(s: "warn")), + kv(key: "retention-days", value: yaml_int(n: 30)), + ] }, + env: none, + if_condition: Present { value: join(["always() && ", fleet_converge_r2_cache_object_read_mint_step_if], "") }, + continue_on_error: none, + timeout_minutes: Present { value: gunbc_ci_artifact_transfer_step_timeout_minutes } + } +} + +fn fleet_converge_r2_cache_object_write_mint_step() -> Step { + RunStep { + name: Present { value: "R2 cache-blobs object-write token mint (AccountTokens.Create + Secret Manager custody)" }, + id: Present { value: "r2_cache_object_write_mint" }, + run: gunbc_ci_r2_cache_object_write_mint_invoke(), + shell: none, + env: Present { value: [ + kv(key: "WIF_ACCESS_TOKEN", value: yaml_string(s: "${{ steps.wif_auth.outputs.access_token }}")), + ] }, + working_directory: none, + if_condition: Present { value: fleet_converge_r2_cache_object_write_mint_step_if }, + continue_on_error: none, + timeout_minutes: Present { value: gunbc_ci_aux_step_timeout_minutes } + } +} + +fn fleet_converge_r2_cache_object_write_mint_receipt_upload_step() -> Step { + UsesStep { + name: Present { value: "Upload R2 cache-blobs object-write mint receipt (token id + custody version resource; no secret)" }, + id: Present { value: "r2_cache_object_write_mint_receipt_upload" }, + uses: upload_artifact_action, + with: Present { value: [ + kv(key: "name", value: yaml_string(s: "r2-cache-object-write-mint-receipt")), + kv(key: "path", value: yaml_string(s: r2_mint_receipt_glob_in( + scope: fleet_converge_r2_mint_receipt_scope(), + profile: R2OriginObjectWrite, + ))), + kv(key: "if-no-files-found", value: yaml_string(s: "warn")), + kv(key: "retention-days", value: yaml_int(n: 30)), + ] }, + env: none, + if_condition: Present { value: join(["always() && ", fleet_converge_r2_cache_object_write_mint_step_if], "") }, + continue_on_error: none, + timeout_minutes: Present { value: gunbc_ci_artifact_transfer_step_timeout_minutes } + } +} + fn fleet_converge_app_control_plane_receipt_upload_step() -> Step { UsesStep { name: Present { value: "Upload GitHub App control plane observation receipt" }, @@ -4330,6 +4420,10 @@ fn fleet_converge_job() -> Job { fleet_converge_r2_workspace_object_read_mint_receipt_upload_step(), fleet_converge_r2_workspace_object_write_mint_step(), fleet_converge_r2_workspace_object_write_mint_receipt_upload_step(), + fleet_converge_r2_cache_object_read_mint_step(), + fleet_converge_r2_cache_object_read_mint_receipt_upload_step(), + fleet_converge_r2_cache_object_write_mint_step(), + fleet_converge_r2_cache_object_write_mint_receipt_upload_step(), fleet_converge_workspace_source_pack_step(), fleet_converge_workspace_source_pack_receipt_upload_step(), fleet_converge_workspace_checkpoint_measure_step(), @@ -4592,6 +4686,8 @@ fn fleet_converge_mode_job_id(mode: FleetConvergeWorkflowMode) -> NonEmptyStr { R2BucketAdminMint => fleet_converge_shared_job_id R2WorkspaceObjectReadMint => fleet_converge_shared_job_id R2WorkspaceObjectWriteMint => fleet_converge_shared_job_id + R2CacheObjectReadMint => fleet_converge_shared_job_id + R2CacheObjectWriteMint => fleet_converge_shared_job_id WorkspaceSourcePack => fleet_converge_shared_job_id WorkspaceCheckpointMeasure => fleet_converge_shared_job_id WorkspaceCheckpointRestore => fleet_converge_shared_job_id From f349409fb978c716a8354f05809fc97aa225500e Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 16:45:55 +0000 Subject: [PATCH 2/3] Custody roster: the cache-blobs read/write secrets get the three mint cells each (six), so gcp_iam_converge provisions their containers Co-Authored-By: Claude Sonnet 5.5 --- dag/gunbc/cloudflare/r2_mint_secret_access.dag | 13 +++++++++++++ .../claim/r2_mint_secret_access_witness_test.dag | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/dag/gunbc/cloudflare/r2_mint_secret_access.dag b/dag/gunbc/cloudflare/r2_mint_secret_access.dag index 156c4d3ae09..0ebef7ca5ff 100644 --- a/dag/gunbc/cloudflare/r2_mint_secret_access.dag +++ b/dag/gunbc/cloudflare/r2_mint_secret_access.dag @@ -61,6 +61,8 @@ import gunbc.cloudflare.r2_origin { cloudflare_r2_bucket_admin_secret_id, fabric_workspace_read_secret_id, fabric_workspace_write_secret_id, + fabric_cache_blobs_read_secret_id, + fabric_cache_blobs_write_secret_id, } import gunbc.secret_provision { fleet_secret_ref } import std.types { List } @@ -175,6 +177,11 @@ fn r2_workspace_read_grant_for(secret_id: NonEmptyStr) -> SecretAccessGrant { secret_accessor_grant(target: r2_workspace_secret_target(secret_id: secret_id)) } +// THE CACHE-BLOBS CONTAINERS TAKE THE SAME THREE CELLS ON EACH OF THEIR TWO SECRETS (run_cache_object_read +// and run_cache_object_write perform the same three secret effects as the workspace mints, against their own +// container), so the cell constructors above are reused and the roster below names the two secrets. +// The bootstrap derives its secret population from this roster: a container missing here is never created. +// // THE ROSTER, ONE ROW PER CELL, IN EFFECT ORDER. gunbc.auth.gcp_iam_converge reads it as a grant set, // and the custody secret population (what the bootstrap creates and binds) is derived from its // targets rather than listed a second time. @@ -193,5 +200,11 @@ fn r2_mint_custody_grants() -> List { r2_workspace_container_read_grant_for(secret_id: fabric_workspace_write_secret_id), r2_workspace_version_add_grant_for(secret_id: fabric_workspace_write_secret_id), r2_workspace_read_grant_for(secret_id: fabric_workspace_write_secret_id), + r2_workspace_container_read_grant_for(secret_id: fabric_cache_blobs_read_secret_id), + r2_workspace_version_add_grant_for(secret_id: fabric_cache_blobs_read_secret_id), + r2_workspace_read_grant_for(secret_id: fabric_cache_blobs_read_secret_id), + r2_workspace_container_read_grant_for(secret_id: fabric_cache_blobs_write_secret_id), + r2_workspace_version_add_grant_for(secret_id: fabric_cache_blobs_write_secret_id), + r2_workspace_read_grant_for(secret_id: fabric_cache_blobs_write_secret_id), ] } diff --git a/dag/test/claim/r2_mint_secret_access_witness_test.dag b/dag/test/claim/r2_mint_secret_access_witness_test.dag index dfd2ceba3d3..48b32103538 100644 --- a/dag/test/claim/r2_mint_secret_access_witness_test.dag +++ b/dag/test/claim/r2_mint_secret_access_witness_test.dag @@ -25,11 +25,13 @@ import gunbc.cloudflare.r2_mint_secret_access { r2_mint_write_version_add_grant, r2_mint_write_readback_grant, r2_bucket_admin_container_read_grant, r2_bucket_admin_version_add_grant, r2_bucket_admin_read_grant, r2_workspace_container_read_grant_for, r2_workspace_version_add_grant_for, r2_workspace_read_grant_for, + r2_mint_custody_grants, } import gunbc.cloudflare.r2_origin { cloudflare_bootstrap_custody_secret_id, fabric_durable_origin_write_secret_id, cloudflare_r2_bucket_admin_secret_id, fabric_workspace_read_secret_id, fabric_workspace_write_secret_id, + fabric_cache_blobs_read_secret_id, fabric_cache_blobs_write_secret_id, } import std.types { String, NonEmptyStr } import gunbc.gcp_estate_observation { fleet_cloud_convergence_sa_email } @@ -174,3 +176,14 @@ test fn each_workspace_secret_gets_container_read_version_add_and_read_on_its_ow && (r2_workspace_read_grant_for(secret_id: fabric_workspace_read_secret_id).target.secret as String) != (cloudflare_bootstrap_custody_secret_id as String) && (r2_workspace_read_grant_for(secret_id: fabric_workspace_write_secret_id).target.secret as String) != (fabric_durable_origin_write_secret_id as String) } + +// THE CACHE-BLOBS CONTAINERS GET THE SAME THREE UNCONDITIONED CELLS EACH, AND THE ROSTER CARRIES ALL SIX +// (the bootstrap derives its containers from the roster, so a missing row is a container never created). +test fn each_cache_blobs_secret_gets_three_cells_on_its_own_container_and_the_roster_carries_them() -> Bool { + let targets = map(r2_mint_custody_grants(), g => g.target.secret as String) + workspace_cells_hold(id: fabric_cache_blobs_read_secret_id) + && workspace_cells_hold(id: fabric_cache_blobs_write_secret_id) + && (fabric_cache_blobs_read_secret_id as String) != (fabric_cache_blobs_write_secret_id as String) + && count(filter(targets, t => t == (fabric_cache_blobs_read_secret_id as String))) == 3 + && count(filter(targets, t => t == (fabric_cache_blobs_write_secret_id as String))) == 3 +} From a2c12b99a40561ced2441917f953c6e016879336 Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Wed, 7 Oct 2026 18:23:56 +0000 Subject: [PATCH 3/3] fleet-converge.yml regenerated after merge Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/fleet-converge.yml | 3612 ++++++++++++++++++++++++++ 1 file changed, 3612 insertions(+) diff --git a/.github/workflows/fleet-converge.yml b/.github/workflows/fleet-converge.yml index e69de29bb2d..bc9ca33550d 100644 --- a/.github/workflows/fleet-converge.yml +++ b/.github/workflows/fleet-converge.yml @@ -0,0 +1,3612 @@ +# Generated by gunbc.fleet_converge_workflow expected_fleet_converge_yml — do not hand-edit. +# Authority: gunbc.fleet_converge_workflow fleet_converge_workflow; regen via tools.generated_artifact_gate main_wet. +name: fleet-converge +run-name: fleet-converge ${{ inputs.mode }} ${{ inputs.host }} nonce=${{ inputs.transaction_nonce }} +on: + workflow_dispatch: + inputs: + host: + description: Executor fleet host (runner pinned here; plan mode observes this host) + required: true + options: [srv1, srv2, srv3, srv4] + type: choice + 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; runner_browser_toolchain_converge installs the declared Playwright/Chromium toolchain (apt host libraries as the administrator, digest-pinned node, Playwright and Chromium archives into the job user's root) on the selected host, which must be in the pool that runs the floor job, and refuses unless every digest, version and host library reads back and headless Chromium renders a local page; 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); microvm_network_apply stages the slot and host network files the model renders at the named expected_revision as root:root 0600 in a root-only directory over the fleet SSH edge as the host's ADMINISTRATOR, installs them with the modeled operations, reloads networkd, systemd-sysctl and the nft loader unit, and reads the ruleset back -- the job user is granted none of it, because install plus systemctl over content that principal can write is arbitrary root for any pull request; 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; spark_wireless_link_converge reads the selected Spark's Wi-Fi power save (runtime via iw, persisted via the NetworkManager profile), link and kernel disconnect count, sets whichever realization differs from the declared intent, and refuses unless the readback decides Noop; spark_grants_observe reads every procured Spark's sudo grant listing as gunbc-automation and the bootstrap principal, and its sudoers drop-in shape, and writes nothing; spark_v41_checkpoint_materialize fetches the admitted published DeepSeek V4.1 files onto the selected Group A Spark (about 510 GB; spark_v41_row_store_encode encodes the eight Engram row stores from the verified shards on the selected Group A Spark and reads each store's sha256; spark_v41_row_store_readback reads those stores back at their header, first and last record and every rank seam, with the published source rows at the same rows, and writes nothing; spark_v41_engram_differential compares upstream's Engram lookup kernel with the design-B file-backed lookup over sampled real rows of every row store, byte for byte, and writes nothing; it states the requirement and refuses before fetching when the disk cannot hold it), publishes each only after its sha256 matches the manifest, leaves a present file with the right digest alone and refuses one with the wrong digest, and reads the storage-backed Engram spans from the verified shards; a transfer runs detached and a rerun reattaches; spark_v41_runtime_image_build PRODUCES the DeepSeek V4.1 image on the selected Spark -- it verifies the candidate's three FlashInfer wheels against the digests the candidate keys, converges the patched source tree, builds from it, reads the produced configuration digest back from inside the image through the probe route, and admits that digest against the candidate's own recipe, refusing a digest that does not recompute from it -- and it is a separate mode from the probe because it occupies one host for hours where the probe occupies it for minutes; spark_v41_runtime_image_distribute moves that produced image, named by the configuration digest its production receipt read back, from the host that receipt names to the selected Group A Spark -- save into its fabric blob root, pulled by the target straight over the fabric rail, load -- after stating its size against every filesystem a copy lands on, leaves a target already holding the digest untouched, refuses a target holding a different image under the tag, and refuses unless the target's image inspect Id reads back as that digest; spark_v41_group_a_launch reads the production image back under its tag on every Group A host, reads its registry inside its digest and every host's occupancy, and only when Group A is suspended for this candidate with every host held and vacant stages the Engram manifest and applies the V4.1 four-rank arm as one transaction at the capacity measurement's shape -- the target names only the session host; spark_v41_serving_load runs the shared serving-load runner against the V4.1 head (target must be srv6): one vllm bench serve step per capacity-measurement concurrency, metrics scraped around each, host pressure read on every Group A rank, and the staircase stop rules applied over the worst rank; it changes no unit and writes only its receipt; 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; microvm_controller_install writes the microVM slot controller's root-owned release locus (gunbc + sources + Firecracker + jailer) and the gunbc-microvm-slot@ template unit and the gunbc-microvm-slot-reserve broker unit on srv1 and starts neither; microvm_slot_reserve starts that broker unit once on srv1, which reserves the shakedown cell through the fabric broker route from inside the root process (slot, demand and offer derived from the model, never inputs), and uploads that invocation's reservation receipt, failing unless the reservation committed; microvm_slot_start starts the shakedown slot's controller unit once on srv1 (the instance is derived from the model, never an input), waits for it bounded by the unit's own stop timeout, and uploads that invocation's controller receipt; microvm_runner_group_ensure (srv1 only) reads the organization's runner groups and, only when the microvm-shakedown group is absent, files ONE operator approval, creates it restricted to the shakedown workflow on the default branch, and refuses unless the readback holds that restriction; mtcollins1_census_qemu_host_observe reads the selected host's KVM device and, under the job user's census QEMU root, the qemu-system-aarch64 build, its ldd libraries and the AAVMF images against their pins, and writes nothing; mtcollins1_census_qemu_toolchain_converge places that root as the job user (digest-pinned noble .debs unpacked with dpkg-deb -x, no apt, no Recommends) and refuses unless the same observe reads it ready; workspace_source_pack (host=srv1) enumerates the operator workspace as the job user, drops every path the credential exclusion row names and every build output, tars exactly the remainder, reads the archive back and refuses if any member is excluded, and puts it into the workspace bucket under its SHA-256, refusing by name when the runner cannot read a source root; workspace_checkpoint_measure (host=srv3, workspace_source_object = that SHA-256) refuses unless the job user's home is on btrfs, then times a read-only subvolume snapshot, a pinned kopia scan of it, the content-addressed chunk upload and the gated revision commit, and receipts every time with the 10 s / 60 s / 10 GB draft verdicts; workspace_checkpoint_restore (host=srv3) commits a small authored workspace, puts one chunk with no head advance, destroys the directory, restores it from the head closure and requires byte-equality with the uncommitted chunk absent -- both srv3 modes refuse their commit while the R2 head's CAS ground is uncited" + required: true + options: [plan, launch_environment_plan, allocation_store_plan, workspace_commissioning_plan, apply, org_actions_observe, app_control_plane_observe, microvm_host_converge, microvm_network_observe, microvm_network_apply, guest_image_observe, guest_image_converge, microvm_boot_probe, spark_grants, spark_bootstrap, spark_serving_apply, spark_native_serving_apply, spark_runtime_image_probe, spark_wireless_link_converge, spark_grants_observe, spark_v41_checkpoint_materialize, spark_v41_row_store_encode, spark_v41_row_store_readback, spark_v41_engram_differential, spark_runtime_image_produce, spark_runtime_image_distribute, spark_arm_group_load, spark_arm_checkpoint_materialize, spark_arm_group_observe, spark_arm_group_launch_plan, spark_arm_group_launch, spark_v41_serving_load, dashboard_deploy, approval_broker_dark_install, microvm_controller_install, microvm_slot_reserve, microvm_slot_start, 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, runner_browser_toolchain_converge, r2_mint_preflight, r2_object_write_mint, org_runner_roster_observe, approval_keyring_converge, approval_device_enrolment_code_issue, mtcollins1_boot, mtcollins1_fan_observe, mtcollins1_ui_bundle_observe, mtcollins1_kvm_observer_observe, mtcollins1_census_image_publish, mtcollins1_census_member_readback, mtcollins1_census_qemu_host_observe, mtcollins1_census_qemu_toolchain_converge, host_credential_custody_converge, app_key_version_verify, r2_bucket_ensure, r2_bucket_admin_mint, r2_workspace_object_read_mint, r2_workspace_object_write_mint, r2_cache_object_read_mint, r2_cache_object_write_mint, r2_conditional_put_race_probe, workspace_source_pack, workspace_checkpoint_measure, workspace_checkpoint_restore, fabric_writer_identity_observe, pair_serving_d0, microvm_runner_group_ensure, gcp_iam_converge, namecheap_observe] + 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" + required: false + options: [srv5, srv6, srv7, srv8, srv9, srv10, srv11, srv12] + type: choice + runtime_image: + description: "spark_runtime_image_produce / spark_runtime_image_distribute only: which runtime image to produce on, or distribute to, the target Spark (gunbc.spark.runtime_image_produce_dispatch). A closed choice" + required: false + options: [v41-dsv41-gb10, glm53-kvplan-v2-gb10] + type: choice + checkpoint: + description: "spark_arm_checkpoint_materialize only: which checkpoint of the arm to acquire on the target Spark (gunbc.spark.arm_group_launch_dispatch). A closed choice" + required: false + options: [glm53-native-fp8, glm53-flash-nvidia-nvfp4] + type: choice + arm: + description: "spark_arm_group_launch / spark_arm_group_launch_plan only: which arm on which fabric group (gunbc.spark.arm_group_launch_dispatch). A closed choice; each arm's own inputs are its module's" + required: false + options: [v41-group-a, glm53-group-a] + type: choice + staircase_step: + description: "spark_arm_group_launch(_plan) with arm glm53-group-a only: the staircase step (gunbc.spark.glm53_arm_staircase), checkpoint and sequence ceiling over the cold 8 x 262144 floor. A closed choice; a step outside the staircase has no spelling. Must be empty for v41-group-a" + required: false + options: [fp8-8, fp8-12, fp8-16, fp8-24, fp8-32, fp8-64, fp8-128, nvfp4-8, nvfp4-16, nvfp4-24, nvfp4-32] + type: choice + incumbent: + description: "spark_arm_group_launch(_plan) with arm glm53-group-a only: the serve this step supersedes, as a DECLARED unit roster (never discovered): previous-step (this arm's own units from the step before) or none. Must be empty for v41-group-a" + required: false + options: [previous-step, none] + type: choice + reset_observer: + description: "host_reset_return only: the PEER that observes the reset. The subject is NOT an input -- it is derived from this observer through the modeled pairing, so a job cannot be pointed at the host it is resetting" + required: false + options: [srv1, srv2, srv3, srv4] + type: choice + credential: + description: "host_credential_custody_converge only: which rostered custody row to deliver (gunbc.host_credential_custody_converge). A closed choice, never a free-text secret name: each option carries its own SecretRef, path, owner, group, mode and directory, and a row scoped to one host refuses any other" + required: false + options: [controller_app_key, approval_ntfy_publisher_token, fabric_state_writer_key, claude_code_oauth_harness_token] + type: choice + d0_consent: + description: "pair_serving_d0 only (Cut D, Group A on srv1; expected_revision is required and frozen with the filing): the escalation id the consent is filed under. It names the transaction, and a rerun after a crash MUST name the same id to find its frozen filing and held claim -- a fresh id is a new consent" + required: false + type: string + app_key_version: + description: "app_key_version_verify only: the Secret Manager version NUMBER of ci-github-app-private-key to verify. The alias latest is refused -- it names whatever is newest when it resolves, so a mint through it proves nothing about a specific key" + required: false + type: string + plan_artifact_hash: + description: Fnv1a64Structural bundle digest of plan.txt + apply.sh — required for apply (printed by plan step). + required: false + type: string + plan_workflow_run_id: + description: Workflow run id that produced the plan artifact — required for apply, dashboard_deploy and rlm_launch_deployment_receipt + required: false + type: string + expected_revision: + description: R — the exact main revision this transaction installs and proves; every run of the transaction must execute at it (RLM_EXPECTED_REVISION). Required for dashboard_deploy and rlm_launch_deployment_receipt — and for microvm_network_apply, whose installed bytes are rendered from it + required: false + type: string + apply_workflow_run_id: + description: Workflow run id of the fleet apply — required for dashboard_deploy and rlm_launch_deployment_receipt + required: false + type: string + dashboard_workflow_run_id: + description: Workflow run id of the dashboard deploy — required for rlm_launch_deployment_receipt + required: false + type: string + runner_ids: + description: "org_runner_roster_observe only: comma-separated captured runner registration ids to observe individually (present / absent / unobserved) in the gunbai-ci organization" + required: false + type: string + roster_page_size: + description: "org_runner_roster_observe only: page size for the first roster request (1..100, default 100); later pages follow the authority's own continuation links" + required: false + type: string + workspace_source_object: + description: "workspace_checkpoint_measure only: the lower-hex SHA-256 the workspace_source_pack receipt printed as address= (the archive's own digest; the fetched bytes must hash to it)" + required: false + type: string + transaction_nonce: + description: "Operator-minted unique nonce for one RLM transaction; echoed into run-name so each dispatched run is API-selectable by its displayTitle, closing the run-id correlation gap (review 5062738052 B4). mtcollins1_boot only, when attempt_receipt is named: it is the boot attempt's identity, joined to the receipt's attempt and consumed once (gunbc.host_boot_attempt_admission), so a nonce admits one boot; [A-Za-z0-9_-] only" + required: false + type: string + attempt_receipt: + description: "mtcollins1_boot only: a committed artifacts/receipts/*.json naming this attempt's plan and the operator's inspection (gunbc.host_boot_attempt_admission). Empty records the configuration as not recorded; a named receipt that fails any check refuses the boot before power-on. Requires transaction_nonce" + required: false + type: string +jobs: + build: + runs-on: [self-hosted, linux, arm64] + timeout-minutes: 95 + if: github.event.inputs.mode != 'mtcollins1_boot' + permissions: + contents: read + actions: read + outputs: + release_bins_key: ${{ steps.release_bins_key.outputs.key }} + steps: + - name: Checkout + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + fetch-depth: 0 + ref: ${{ (github.event.inputs.mode == 'mtcollins1_boot' || github.event.inputs.mode == 'gcp_iam_converge' || github.event.inputs.mode == 'namecheap_observe') && github.sha || github.event.inputs.expected_revision || github.sha }} + - name: Isolate toolchain dirs + run: |- + rm -rf "$RUNNER_TEMP/rustup" "$RUNNER_TEMP/cargo" + echo "HOME=$RUNNER_TEMP" >> "$GITHUB_ENV" + echo "CARGO_HOME=$RUNNER_TEMP/cargo" >> "$GITHUB_ENV" + echo "RUSTUP_HOME=$RUNNER_TEMP/rustup" >> "$GITHUB_ENV" + echo "MAKEFLAGS=" >> "$GITHUB_ENV" + echo "CARGO_BUILD_JOBS=6" >> "$GITHUB_ENV" + if sccache --show-stats >/dev/null 2>&1; then + echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" + echo "CARGO_INCREMENTAL=0" >> "$GITHUB_ENV" + printf '%s\n' "CiSccacheProviderReceipt: provider=bound catalog=sccache_local" + else + printf '%s\n' "CiSccacheProviderReceipt: provider=SKIPPED catalog=sccache_local host=$(hostname) cause=daemon-unavailable; the release build runs UNCACHED — a counted degradation, not a supported mode (host_build_cache_provision P1b should have converged this host)" + fi + - name: Setup Rust + uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 + with: + components: rustfmt + cache: false + rustflags: -D warnings + env: + HOME: ${{ runner.temp }} + - name: Derive native-cache root (rustc-version segment; after setup-rust-toolchain) + run: |- + # 🟡 dissolve-on: ci_native_cache_root_script — orch-emitted foreign-executor prelude step deriving GUNBC_NATIVE_CACHE_ROOT from rustc -V after setup-rust-toolchain; leaf rustc/tr/mkdir/echo strings remain until typed toolchain probe lands on host_effect_apply + if ! GUNBC_TOOLCHAIN_SEG_RAW=$(rustc -V 2>/dev/null); then echo "::error::rustc -V failed after setup-rust-toolchain: cannot derive native-cache toolchain segment"; exit 1; fi + GUNBC_TOOLCHAIN_SEG=$(echo "$GUNBC_TOOLCHAIN_SEG_RAW" | tr ' ' '-') + if [ -z "$GUNBC_TOOLCHAIN_SEG" ]; then echo "::error::rustc -V produced an empty native-cache toolchain segment after setup-rust-toolchain"; exit 1; fi + mkdir -p "$RUNNER_TOOL_CACHE/gunbc-native/$GUNBC_TOOLCHAIN_SEG" + echo "GUNBC_NATIVE_CACHE_ROOT=$RUNNER_TOOL_CACHE/gunbc-native/$GUNBC_TOOLCHAIN_SEG" >> "$GITHUB_ENV" + - name: Pin rustup default (isolated RUSTUP_HOME has no default toolchain) + run: |- + # dissolve-on: ci_pin_rustup_default_script -- orch-emitted foreign-executor step selecting a rustup default toolchain inside an isolated RUSTUP_HOME, which starts with none, and resolving the cargo binary that selection implies. The leaf rustup/command/echo strings remain until a typed TOOLCHAIN-SELECTION effect lands on host_effect_apply -- NOT the filesystem-and-environment effect ci_toolchain_home_isolation_script waits on, which is why this is a separate obligation: that effect landing alone would leave this carrier standing + rustup default "$(rustup show active-toolchain | awk '{print $1; exit}')" + if [ -x "$CARGO_HOME/bin/cargo" ]; then CARGO_BIN="$CARGO_HOME/bin/cargo"; else CARGO_BIN="$(command -v cargo || true)"; fi + if [ -z "$CARGO_BIN" ]; then echo "::error::no cargo binary: neither the isolated $CARGO_HOME/bin/cargo shim nor PATH carries one"; exit 1; fi + echo "CARGO_BIN=$CARGO_BIN" >> "$GITHUB_ENV" + - name: Derive release-bins key (pre-materialization axes; sha256 at cross-run scope) + id: release_bins_key + run: | + if [ "${FLEET_CONVERGE_MODE:-}" = 'approval_device_enrolment_code_issue' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_source_pack' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_checkpoint_measure' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_checkpoint_restore' ]; then + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + DIRTY=$('git' '-C' "$ROOT" 'status' '--porcelain' '--untracked-files=no') + '[' '-z' "$DIRTY" ']' || ('echo' '::error title=release-bins SourceTreeDirty::tracked files differ from HEAD, so HEAD^{tree} does not name the build input; refusing to key'; exit 1) + 'env' '-0' > "$RUNNER_TEMP"'/env.nul' + if 'grep' '-zqE' '^(CARGO|RUST|CC|CXX|AR|CFLAGS|CXXFLAGS|LDFLAGS|TARGET_CC|HOST_CC)[A-Za-z0-9_]*(TOKEN|CREDENTIAL|SECRET|PASSWORD|PASSWD|AUTH)[A-Za-z0-9_]*=' "$RUNNER_TEMP"'/env.nul'; then ('echo' '::error title=release-bins CredentialInBuildEnvironment::a registry or toolchain credential is present in the build environment; credentials are not build inputs and must not reach a key or a published preimage, so the build is refused'; exit 1); fi + if 'grep' '-zqE' '^(RUSTC|RUSTC_WORKSPACE_WRAPPER|CARGO_BUILD_RUSTC|CARGO_BUILD_RUSTC_WRAPPER|CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER|CARGO_BUILD_TARGET|CARGO_BUILD_TARGET_DIR|CARGO_TARGET_DIR|CARGO_TARGET_[A-Z0-9_]*_(LINKER|RUNNER))=' "$RUNNER_TEMP"'/env.nul'; then ('echo' '::error title=release-bins BuildOverrideNotAdmitted::the environment overrides the compiler, linker, target or target directory Cargo would use; the key probes only the admitted defaults, so the build is refused rather than keyed wrongly'; exit 1); fi + 'sed' '-z' '-nE' '/^(CARGO_[A-Z0-9_]*|RUST[A-Z0-9_]*|CC|CXX|AR|CFLAGS|CXXFLAGS|LDFLAGS|CC_[A-Za-z0-9_]*|CFLAGS_[A-Za-z0-9_]*|TARGET_CC|HOST_CC)=/p' "$RUNNER_TEMP"'/env.nul' > "$RUNNER_TEMP"'/env-keyed.nul' + 'sed' '-z' '-E' '/^(CARGO_HOME|RUSTUP_HOME|CARGO_BIN|CARGO_TERM_COLOR|CARGO_BUILD_JOBS|RUSTC_WRAPPER)=/d' "$RUNNER_TEMP"'/env-keyed.nul' > "$RUNNER_TEMP"'/env-kept.nul' + 'sort' '-z' "$RUNNER_TEMP"'/env-kept.nul' > "$RUNNER_TEMP"'/env-sorted.nul' + 'sha256sum' "$RUNNER_TEMP"'/env-sorted.nul' > "$RUNNER_TEMP"'/AXIS_ENVIRONMENT_DIGEST.out' + AXIS_ENVIRONMENT_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/AXIS_ENVIRONMENT_DIGEST.out') + ANCESTOR_0="$ROOT" + ANCESTOR_CONFIG='' + ANCESTOR_1=$('dirname' "$ANCESTOR_0") + ('[' '!' '-e' "$ANCESTOR_1"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_1"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_1" + ANCESTOR_2=$('dirname' "$ANCESTOR_1") + ('[' '!' '-e' "$ANCESTOR_2"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_2"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_2" + ANCESTOR_3=$('dirname' "$ANCESTOR_2") + ('[' '!' '-e' "$ANCESTOR_3"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_3"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_3" + ANCESTOR_4=$('dirname' "$ANCESTOR_3") + ('[' '!' '-e' "$ANCESTOR_4"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_4"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_4" + ANCESTOR_5=$('dirname' "$ANCESTOR_4") + ('[' '!' '-e' "$ANCESTOR_5"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_5"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_5" + ANCESTOR_6=$('dirname' "$ANCESTOR_5") + ('[' '!' '-e' "$ANCESTOR_6"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_6"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_6" + ANCESTOR_7=$('dirname' "$ANCESTOR_6") + ('[' '!' '-e' "$ANCESTOR_7"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_7"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_7" + ANCESTOR_8=$('dirname' "$ANCESTOR_7") + ('[' '!' '-e' "$ANCESTOR_8"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_8"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_8" + ANCESTOR_9=$('dirname' "$ANCESTOR_8") + ('[' '!' '-e' "$ANCESTOR_9"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_9"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_9" + ANCESTOR_10=$('dirname' "$ANCESTOR_9") + ('[' '!' '-e' "$ANCESTOR_10"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_10"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_10" + ANCESTOR_11=$('dirname' "$ANCESTOR_10") + ('[' '!' '-e' "$ANCESTOR_11"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_11"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_11" + ANCESTOR_12=$('dirname' "$ANCESTOR_11") + ('[' '!' '-e' "$ANCESTOR_12"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_12"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_12" + ANCESTOR_13=$('dirname' "$ANCESTOR_12") + ('[' '!' '-e' "$ANCESTOR_13"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_13"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_13" + ANCESTOR_14=$('dirname' "$ANCESTOR_13") + ('[' '!' '-e' "$ANCESTOR_14"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_14"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_14" + ANCESTOR_15=$('dirname' "$ANCESTOR_14") + ('[' '!' '-e' "$ANCESTOR_15"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_15"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_15" + ANCESTOR_16=$('dirname' "$ANCESTOR_15") + ('[' '!' '-e' "$ANCESTOR_16"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_16"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_16" + if '[' '-n' "$ANCESTOR_CONFIG" ']'; then 'echo' 'release-bins: Cargo config found above the checkout in '"$ANCESTOR_CONFIG"; ('echo' '::error title=release-bins AncestorCargoConfigPresent::a Cargo config file exists in a directory above the checkout; it is outside the committed tree the key covers, so the build is refused'; exit 1); fi + '[' "$ANCESTOR_16" '=' '/' ']' || ('echo' '::error title=release-bins CheckoutPathTooDeep::the checkout is deeper than the ancestor Cargo-config scan covers, so ancestor configuration cannot be ruled out'; exit 1) + OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') + SOURCE_TREE=$('git' '-C' "$ROOT" 'rev-parse' 'HEAD^{tree}') + '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) + SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" + EXPECTED="$RUNNER_TEMP"'/release-bins.preimage.expected' + AXIS_PRODUCER=$("$CARGO_BIN" '-V') + '[' '-n' "$AXIS_PRODUCER" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_PRODUCER read back empty, so the key would not name the build; refusing to key'; exit 1) + 'rustc' '-vV' > "$RUNNER_TEMP"'/rustc-host.out' + AXIS_HOST=$('sed' '-n' 's/^host: //p' "$RUNNER_TEMP"'/rustc-host.out') + 'cc' '--version' > "$RUNNER_TEMP"'/AXIS_CC.out' + AXIS_CC=$('head' '-n' '1' "$RUNNER_TEMP"'/AXIS_CC.out') + 'ld' '--version' > "$RUNNER_TEMP"'/AXIS_LD.out' + AXIS_LD=$('head' '-n' '1' "$RUNNER_TEMP"'/AXIS_LD.out') + 'ldd' '--version' > "$RUNNER_TEMP"'/AXIS_LIBC.out' + AXIS_LIBC=$('head' '-n' '1' "$RUNNER_TEMP"'/AXIS_LIBC.out') + '[' '-n' "$AXIS_HOST" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_HOST read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$AXIS_CC" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_CC read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$AXIS_LD" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_LD read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$AXIS_LIBC" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_LIBC read back empty, so the key would not name the build; refusing to key'; exit 1) + 'rustc' '-vV' > "$RUNNER_TEMP"'/rustc-vV.out' + AXIS_TOOLCHAIN=$('paste' '-s' '-d' ';' "$RUNNER_TEMP"'/rustc-vV.out') + '[' '-n' "$AXIS_TOOLCHAIN" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_TOOLCHAIN read back empty, so the key would not name the build; refusing to key'; exit 1) + ('printf' 'kind=%s\n' 'gunbc.compiler_pair_release_bins'; 'printf' 'address=sha256 scope=cross_run\n'; 'printf' 'producer_compiler=%s\n' "$AXIS_PRODUCER"; 'printf' 'source_closure=%s\n' "$SOURCE_CLOSURE"; 'printf' 'target_model=%s;cc=%s;ld=%s;libc=%s\n' "$AXIS_HOST" "$AXIS_CC" "$AXIS_LD" "$AXIS_LIBC"; 'printf' 'toolchain=%s\n' "$AXIS_TOOLCHAIN"; 'printf' 'build_configuration=%s;embedded_build_identity=%s;environment=%s;roster=%s\n' 'cargo build --release -p v1-compiler --bin claim_executor --bin gunbc' "$SOURCE_CLOSURE" 'sha256:'"$AXIS_ENVIRONMENT_DIGEST" 'claim_executor gunbc'; 'printf' 'required_lens_contract=none (a seed cargo build admits no lens contract)\n') > "$EXPECTED" + 'sha256sum' "$EXPECTED" > "$RUNNER_TEMP"'/PREIMAGE_DIGEST.out' + PREIMAGE_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/PREIMAGE_DIGEST.out') + KEY='release-bins-'"$PREIMAGE_DIGEST" + 'rm' '-rf' "$ROOT"'/.release-bins-staging' + 'mkdir' '-p' "$ROOT"'/.release-bins-staging' + 'echo' 'release-bins request key '"$KEY"' over preimage:' + 'cat' "$EXPECTED" + 'echo' 'key='"$KEY" | 'tee' '-a' "$GITHUB_OUTPUT" > '/dev/null' + 'echo' 'GUNBC_MATERIALIZED_TREE_IDENTITY='"$SOURCE_CLOSURE" | 'tee' '-a' "$GITHUB_ENV" > '/dev/null' + + else + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + DIRTY=$('git' '-C' "$ROOT" 'status' '--porcelain' '--untracked-files=no') + '[' '-z' "$DIRTY" ']' || ('echo' '::error title=release-bins SourceTreeDirty::tracked files differ from HEAD, so HEAD^{tree} does not name the build input; refusing to key'; exit 1) + 'env' '-0' > "$RUNNER_TEMP"'/env.nul' + if 'grep' '-zqE' '^(CARGO|RUST|CC|CXX|AR|CFLAGS|CXXFLAGS|LDFLAGS|TARGET_CC|HOST_CC)[A-Za-z0-9_]*(TOKEN|CREDENTIAL|SECRET|PASSWORD|PASSWD|AUTH)[A-Za-z0-9_]*=' "$RUNNER_TEMP"'/env.nul'; then ('echo' '::error title=release-bins CredentialInBuildEnvironment::a registry or toolchain credential is present in the build environment; credentials are not build inputs and must not reach a key or a published preimage, so the build is refused'; exit 1); fi + if 'grep' '-zqE' '^(RUSTC|RUSTC_WORKSPACE_WRAPPER|CARGO_BUILD_RUSTC|CARGO_BUILD_RUSTC_WRAPPER|CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER|CARGO_BUILD_TARGET|CARGO_BUILD_TARGET_DIR|CARGO_TARGET_DIR|CARGO_TARGET_[A-Z0-9_]*_(LINKER|RUNNER))=' "$RUNNER_TEMP"'/env.nul'; then ('echo' '::error title=release-bins BuildOverrideNotAdmitted::the environment overrides the compiler, linker, target or target directory Cargo would use; the key probes only the admitted defaults, so the build is refused rather than keyed wrongly'; exit 1); fi + 'sed' '-z' '-nE' '/^(CARGO_[A-Z0-9_]*|RUST[A-Z0-9_]*|CC|CXX|AR|CFLAGS|CXXFLAGS|LDFLAGS|CC_[A-Za-z0-9_]*|CFLAGS_[A-Za-z0-9_]*|TARGET_CC|HOST_CC)=/p' "$RUNNER_TEMP"'/env.nul' > "$RUNNER_TEMP"'/env-keyed.nul' + 'sed' '-z' '-E' '/^(CARGO_HOME|RUSTUP_HOME|CARGO_BIN|CARGO_TERM_COLOR|CARGO_BUILD_JOBS|RUSTC_WRAPPER)=/d' "$RUNNER_TEMP"'/env-keyed.nul' > "$RUNNER_TEMP"'/env-kept.nul' + 'sort' '-z' "$RUNNER_TEMP"'/env-kept.nul' > "$RUNNER_TEMP"'/env-sorted.nul' + 'sha256sum' "$RUNNER_TEMP"'/env-sorted.nul' > "$RUNNER_TEMP"'/AXIS_ENVIRONMENT_DIGEST.out' + AXIS_ENVIRONMENT_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/AXIS_ENVIRONMENT_DIGEST.out') + ANCESTOR_0="$ROOT" + ANCESTOR_CONFIG='' + ANCESTOR_1=$('dirname' "$ANCESTOR_0") + ('[' '!' '-e' "$ANCESTOR_1"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_1"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_1" + ANCESTOR_2=$('dirname' "$ANCESTOR_1") + ('[' '!' '-e' "$ANCESTOR_2"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_2"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_2" + ANCESTOR_3=$('dirname' "$ANCESTOR_2") + ('[' '!' '-e' "$ANCESTOR_3"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_3"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_3" + ANCESTOR_4=$('dirname' "$ANCESTOR_3") + ('[' '!' '-e' "$ANCESTOR_4"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_4"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_4" + ANCESTOR_5=$('dirname' "$ANCESTOR_4") + ('[' '!' '-e' "$ANCESTOR_5"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_5"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_5" + ANCESTOR_6=$('dirname' "$ANCESTOR_5") + ('[' '!' '-e' "$ANCESTOR_6"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_6"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_6" + ANCESTOR_7=$('dirname' "$ANCESTOR_6") + ('[' '!' '-e' "$ANCESTOR_7"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_7"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_7" + ANCESTOR_8=$('dirname' "$ANCESTOR_7") + ('[' '!' '-e' "$ANCESTOR_8"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_8"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_8" + ANCESTOR_9=$('dirname' "$ANCESTOR_8") + ('[' '!' '-e' "$ANCESTOR_9"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_9"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_9" + ANCESTOR_10=$('dirname' "$ANCESTOR_9") + ('[' '!' '-e' "$ANCESTOR_10"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_10"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_10" + ANCESTOR_11=$('dirname' "$ANCESTOR_10") + ('[' '!' '-e' "$ANCESTOR_11"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_11"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_11" + ANCESTOR_12=$('dirname' "$ANCESTOR_11") + ('[' '!' '-e' "$ANCESTOR_12"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_12"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_12" + ANCESTOR_13=$('dirname' "$ANCESTOR_12") + ('[' '!' '-e' "$ANCESTOR_13"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_13"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_13" + ANCESTOR_14=$('dirname' "$ANCESTOR_13") + ('[' '!' '-e' "$ANCESTOR_14"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_14"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_14" + ANCESTOR_15=$('dirname' "$ANCESTOR_14") + ('[' '!' '-e' "$ANCESTOR_15"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_15"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_15" + ANCESTOR_16=$('dirname' "$ANCESTOR_15") + ('[' '!' '-e' "$ANCESTOR_16"'/.cargo/config.toml' ']' && '[' '!' '-e' "$ANCESTOR_16"'/.cargo/config' ']') || ANCESTOR_CONFIG="$ANCESTOR_16" + if '[' '-n' "$ANCESTOR_CONFIG" ']'; then 'echo' 'release-bins: Cargo config found above the checkout in '"$ANCESTOR_CONFIG"; ('echo' '::error title=release-bins AncestorCargoConfigPresent::a Cargo config file exists in a directory above the checkout; it is outside the committed tree the key covers, so the build is refused'; exit 1); fi + '[' "$ANCESTOR_16" '=' '/' ']' || ('echo' '::error title=release-bins CheckoutPathTooDeep::the checkout is deeper than the ancestor Cargo-config scan covers, so ancestor configuration cannot be ruled out'; exit 1) + OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') + SOURCE_TREE=$('git' '-C' "$ROOT" 'rev-parse' 'HEAD^{tree}') + '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) + SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" + EXPECTED="$RUNNER_TEMP"'/release-bins.preimage.expected' + AXIS_PRODUCER=$("$CARGO_BIN" '-V') + '[' '-n' "$AXIS_PRODUCER" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_PRODUCER read back empty, so the key would not name the build; refusing to key'; exit 1) + 'rustc' '-vV' > "$RUNNER_TEMP"'/rustc-host.out' + AXIS_HOST=$('sed' '-n' 's/^host: //p' "$RUNNER_TEMP"'/rustc-host.out') + 'cc' '--version' > "$RUNNER_TEMP"'/AXIS_CC.out' + AXIS_CC=$('head' '-n' '1' "$RUNNER_TEMP"'/AXIS_CC.out') + 'ld' '--version' > "$RUNNER_TEMP"'/AXIS_LD.out' + AXIS_LD=$('head' '-n' '1' "$RUNNER_TEMP"'/AXIS_LD.out') + 'ldd' '--version' > "$RUNNER_TEMP"'/AXIS_LIBC.out' + AXIS_LIBC=$('head' '-n' '1' "$RUNNER_TEMP"'/AXIS_LIBC.out') + '[' '-n' "$AXIS_HOST" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_HOST read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$AXIS_CC" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_CC read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$AXIS_LD" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_LD read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$AXIS_LIBC" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_LIBC read back empty, so the key would not name the build; refusing to key'; exit 1) + 'rustc' '-vV' > "$RUNNER_TEMP"'/rustc-vV.out' + AXIS_TOOLCHAIN=$('paste' '-s' '-d' ';' "$RUNNER_TEMP"'/rustc-vV.out') + '[' '-n' "$AXIS_TOOLCHAIN" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input AXIS_TOOLCHAIN read back empty, so the key would not name the build; refusing to key'; exit 1) + ('printf' 'kind=%s\n' 'gunbc.fleet_release_bins'; 'printf' 'address=sha256 scope=cross_run\n'; 'printf' 'producer_compiler=%s\n' "$AXIS_PRODUCER"; 'printf' 'source_closure=%s\n' "$SOURCE_CLOSURE"; 'printf' 'target_model=%s;cc=%s;ld=%s;libc=%s\n' "$AXIS_HOST" "$AXIS_CC" "$AXIS_LD" "$AXIS_LIBC"; 'printf' 'toolchain=%s\n' "$AXIS_TOOLCHAIN"; 'printf' 'build_configuration=%s;embedded_build_identity=%s;environment=%s;roster=%s\n' '"$CARGO_BIN" build -p v1-compiler --release --features text_lookup_work_counter,interp_test_witness,test_hooks --bin claim_executor --bin gunbc --bin discover_source_root_ingest --bin claim_batch --bin interp_recorded_fixture_witness --bin v1_src_dag_parse --bin auth_declared_but_unwired_witness --bin bootstrap_witness --bin dag_collect_fingerprint_witness --bin diagnostics_witness --bin effects_rest_transport_witness --bin infer_semantics_witness --bin parse_witness --bin cssl_assemble --bin namespace_structural_root_exposure_generated_witness --bin codex_app_server_stdio_session' "$SOURCE_CLOSURE" 'sha256:'"$AXIS_ENVIRONMENT_DIGEST" 'claim_executor gunbc discover_source_root_ingest claim_batch interp_recorded_fixture_witness v1_src_dag_parse auth_declared_but_unwired_witness bootstrap_witness dag_collect_fingerprint_witness diagnostics_witness effects_rest_transport_witness infer_semantics_witness parse_witness cssl_assemble namespace_structural_root_exposure_generated_witness codex_app_server_stdio_session'; 'printf' 'required_lens_contract=none (a seed cargo build admits no lens contract)\n') > "$EXPECTED" + 'sha256sum' "$EXPECTED" > "$RUNNER_TEMP"'/PREIMAGE_DIGEST.out' + PREIMAGE_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/PREIMAGE_DIGEST.out') + KEY='release-bins-'"$PREIMAGE_DIGEST" + 'rm' '-rf' "$ROOT"'/.release-bins-staging' + 'mkdir' '-p' "$ROOT"'/.release-bins-staging' + 'echo' 'release-bins request key '"$KEY"' over preimage:' + 'cat' "$EXPECTED" + 'echo' 'key='"$KEY" | 'tee' '-a' "$GITHUB_OUTPUT" > '/dev/null' + 'echo' 'GUNBC_MATERIALIZED_TREE_IDENTITY='"$SOURCE_CLOSURE" | 'tee' '-a' "$GITHUB_ENV" > '/dev/null' + + fi + env: + FLEET_CONVERGE_MODE: ${{ github.event.inputs.mode }} + timeout-minutes: 5 + - name: Look up release-bins by exact key under the admitted producer refs (cache API; a failed lookup is not absence) + id: release_bins_lookup + run: | + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + STANDING='absent' + ENTRY='none' + 'curl' '-sSf' '-H' 'Authorization: Bearer '"$GH_TOKEN" '-H' 'Accept: application/vnd.github+json' "$GITHUB_API_URL"'/repos/'"$GITHUB_REPOSITORY"'/actions/caches?per_page=100&key='"$RELEASE_BINS_KEY"'&ref=''refs/heads/main' '-o' "$ROOT"'/.release-bins-lookup-main.json' || ('echo' '::error title=release-bins LookupTransportFailed::the cache API lookup FAILED (unreachable or refused); this is not absence, so neither reuse nor rebuild is decided'; exit 1) + 'jq' '--raw-output' '--exit-status' '. as $answer | [inputs] | if length != 0 then error("the cache-API answer is not exactly one JSON document") else $answer | if type == "object" and (.total_count | type) == "number" and (.actions_caches | type) == "array" and .total_count == (.actions_caches | length) and .total_count <= 100 and all(.actions_caches[]; type == "object" and (.key | type) == "string" and (.ref | type) == "string") then [.actions_caches[] | select(.key == $ENV.RELEASE_BINS_KEY and .ref == "refs/heads/main")] | if length == 0 then "absent" elif length == 1 then "present\nid=\(.[0].id) ref=\(.[0].ref) version=\(.[0].version) created=\(.[0].created_at)" else error("duplicate exact entries") end else error("cache-API answer is not the admitted shape") end end' '.release-bins-lookup-main.json' > "$RUNNER_TEMP"'/lookup-main.decoded' || ('echo' '::error title=release-bins LookupAnswerUnusable::the cache API answer was unreadable or incomplete; it is not treated as absence'; exit 1) + DECODED_STANDING=$('head' '-n' '1' "$RUNNER_TEMP"'/lookup-main.decoded') + if '[' "$DECODED_STANDING" '=' 'present' ']'; then STANDING='present'; ENTRY=$('sed' '-n' '2p' "$RUNNER_TEMP"'/lookup-main.decoded'); else '[' "$DECODED_STANDING" '=' 'absent' ']' || ('echo' '::error title=release-bins LookupAnswerUnusable::the cache API answer was unreadable or incomplete; it is not treated as absence'; exit 1); fi + 'curl' '-sSf' '-H' 'Authorization: Bearer '"$GH_TOKEN" '-H' 'Accept: application/vnd.github+json' "$GITHUB_API_URL"'/repos/'"$GITHUB_REPOSITORY"'/actions/caches?per_page=100&key='"$RELEASE_BINS_KEY"'&ref='"$RELEASE_BINS_DISPATCH_SCOPE_REF" '-o' "$ROOT"'/.release-bins-lookup-dispatch.json' || ('echo' '::error title=release-bins LookupTransportFailed::the cache API lookup FAILED (unreachable or refused); this is not absence, so neither reuse nor rebuild is decided'; exit 1) + 'jq' '--raw-output' '--exit-status' '. as $answer | [inputs] | if length != 0 then error("the cache-API answer is not exactly one JSON document") else $answer | if type == "object" and (.total_count | type) == "number" and (.actions_caches | type) == "array" and .total_count == (.actions_caches | length) and .total_count <= 100 and all(.actions_caches[]; type == "object" and (.key | type) == "string" and (.ref | type) == "string") then [.actions_caches[] | select(.key == $ENV.RELEASE_BINS_KEY and .ref == $ENV.RELEASE_BINS_DISPATCH_SCOPE_REF)] | if length == 0 then "absent" elif length == 1 then "present\nid=\(.[0].id) ref=\(.[0].ref) version=\(.[0].version) created=\(.[0].created_at)" else error("duplicate exact entries") end else error("cache-API answer is not the admitted shape") end end' '.release-bins-lookup-dispatch.json' > "$RUNNER_TEMP"'/lookup-dispatch.decoded' || ('echo' '::error title=release-bins LookupAnswerUnusable::the cache API answer was unreadable or incomplete; it is not treated as absence'; exit 1) + DECODED_STANDING=$('head' '-n' '1' "$RUNNER_TEMP"'/lookup-dispatch.decoded') + if '[' "$DECODED_STANDING" '=' 'present' ']'; then STANDING='present'; ENTRY=$('sed' '-n' '2p' "$RUNNER_TEMP"'/lookup-dispatch.decoded'); else '[' "$DECODED_STANDING" '=' 'absent' ']' || ('echo' '::error title=release-bins LookupAnswerUnusable::the cache API answer was unreadable or incomplete; it is not treated as absence'; exit 1); fi + 'echo' 'standing='"$STANDING" | 'tee' '-a' "$GITHUB_OUTPUT" > '/dev/null' + 'echo' 'entry='"$ENTRY" | 'tee' '-a' "$GITHUB_OUTPUT" > '/dev/null' + 'echo' 'release-bins lookup: standing='"$STANDING"' entry='"$ENTRY" + env: + RELEASE_BINS_KEY: ${{ steps.release_bins_key.outputs.key }} + GH_TOKEN: ${{ github.token }} + RELEASE_BINS_DISPATCH_SCOPE_REF: ${{ startsWith(github.ref, 'refs/tags/') && format('refs/heads/{0}', github.ref) || github.ref }} + timeout-minutes: 5 + - name: Restore release-bins into isolated staging (transport only; publishes on a verified build) + id: release_bins_cache + uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 + with: + path: |- + .release-bins-staging/release-bins.tgz + .release-bins-staging/release-bins.preimage + .release-bins-staging/release-bins.manifest + key: ${{ steps.release_bins_key.outputs.key }} + timeout-minutes: 10 + - name: Decide release-bins outcome; verify a stored pack completely before any restored binary runs + id: release_bins_outcome + run: | + if [ "${FLEET_CONVERGE_MODE:-}" = 'approval_device_enrolment_code_issue' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_source_pack' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_checkpoint_measure' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_checkpoint_restore' ]; then + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + STAGE="$ROOT"'/.release-bins-staging' + EXPECTED="$RUNNER_TEMP"'/release-bins.preimage.expected' + if '[' "$LOOKUP_STANDING" '=' 'present' ']' && '[' "$CACHE_HIT" '=' 'true' ']'; then ':' 'no-op'; else if '[' "$LOOKUP_STANDING" '=' 'present' ']' && '[' "$CACHE_HIT" '!=' 'true' ']'; then ('echo' '::error title=release-bins StoreTransportDisagreePresentMiss::the cache API holds an exact admitted entry for this key but the transport did not restore it exactly; refusing rather than rebuilding over a present entry'; exit 1); else if '[' "$LOOKUP_STANDING" '=' 'absent' ']' && '[' "$CACHE_HIT" '=' 'true' ']'; then ('echo' '::error title=release-bins StoreTransportDisagreeAbsentHit::the cache API holds no exact admitted entry for this key but the transport reported an exact hit; the store and its transport disagree'; exit 1); else if '[' "$LOOKUP_STANDING" '=' 'absent' ']' && '[' "$CACHE_HIT" '!=' 'true' ']'; then 'rm' '-rf' "$STAGE"; 'mkdir' '-p' "$STAGE"; 'echo' 'outcome=build' | 'tee' '-a' "$GITHUB_OUTPUT" > '/dev/null'; exit 0; else ('echo' '::error title=release-bins OutcomeInputsMissing::the lookup standing or the transport exactness is missing, so no outcome can be decided'; exit 1); fi; fi; fi; fi + '[' '-f' "$STAGE"'/release-bins.preimage' ']' || ('echo' '::error title=release-bins StoredEntryIncomplete::the stored entry is incomplete: release-bins.preimage is missing'; exit 1) + '[' '-f' "$STAGE"'/release-bins.manifest' ']' || ('echo' '::error title=release-bins StoredEntryIncomplete::the stored entry is incomplete: release-bins.manifest is missing'; exit 1) + '[' '-f' "$STAGE"'/release-bins.tgz' ']' || ('echo' '::error title=release-bins StoredEntryIncomplete::the stored entry is incomplete: release-bins.tgz is missing'; exit 1) + 'cmp' '-s' "$EXPECTED" "$STAGE"'/release-bins.preimage' || ('diff' "$EXPECTED" "$STAGE"'/release-bins.preimage' || ':' 'no-op'; ('echo' '::error title=release-bins StoredPreimageDiffers::the stored preimage differs from this run'\''s independently derived preimage'; exit 1)) + 'sha256sum' "$STAGE"'/release-bins.preimage' > "$RUNNER_TEMP"'/STORED_DIGEST.out' + STORED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/STORED_DIGEST.out') + '[' 'release-bins-'"$STORED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins StoredPreimageKeyMismatch::the stored preimage does not hash to the probed key'; exit 1) + 'printf' '%s' 'claim_executor + gunbc + build_diagnostics + ' > "$RUNNER_TEMP"'/release-bins.roster' + 'tar' '-tzf' "$STAGE"'/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the stored pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) + 'cut' '-c' '67-' "$STAGE"'/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the stored manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$STAGE"'/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the stored manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + 'rm' '-rf' "$STAGE"'/unpacked' + 'mkdir' '-p' "$STAGE"'/unpacked' + 'tar' '-xzf' "$STAGE"'/release-bins.tgz' '-C' "$STAGE"'/unpacked' '--no-same-owner' + 'printf' '%s' 'claim_executor:regular file:1 + gunbc:regular file:1 + ' > "$RUNNER_TEMP"'/member-types.expected' + ('cd' "$STAGE"'/unpacked' && 'stat' '-c' '%n:%F:%h' 'claim_executor' 'gunbc') > "$RUNNER_TEMP"'/member-types.out' + 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the stored pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) + ('cd' "$STAGE"'/unpacked' && 'find' 'claim_executor' 'gunbc' '-maxdepth' '0' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' + '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the stored pack has a member that is not executable (listed above)'; exit 1)) + ('cd' "$STAGE"'/unpacked' && 'sha256sum' '--strict' '--quiet' '-c' "$STAGE"'/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the stored member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + 'mkdir' '-p' "$ROOT"'/target/release' + ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc') + ('cd' "$STAGE"'/unpacked' && 'cp' '-p' 'claim_executor' 'gunbc' "$ROOT"'/target/release/') + 'rm' '-rf' "$STAGE"'/unpacked' + 'echo' 'release-bins: stored pack verified and promoted' + 'sha256sum' "$STAGE"'/release-bins.manifest' > "$RUNNER_TEMP"'/OUTPUT_DIGEST.out' + OUTPUT_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/OUTPUT_DIGEST.out') + 'echo' 'ReleaseBinsReuseReceipt: request='"$RELEASE_BINS_KEY"' output=sha256:'"$OUTPUT_DIGEST"' dispatch='"$GITHUB_RUN_ID"'/'"$GITHUB_RUN_ATTEMPT"' producer=['"$LOOKUP_ENTRY"'] built=no' + 'echo' 'outcome=reuse' | 'tee' '-a' "$GITHUB_OUTPUT" > '/dev/null' + + else + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + STAGE="$ROOT"'/.release-bins-staging' + EXPECTED="$RUNNER_TEMP"'/release-bins.preimage.expected' + if '[' "$LOOKUP_STANDING" '=' 'present' ']' && '[' "$CACHE_HIT" '=' 'true' ']'; then ':' 'no-op'; else if '[' "$LOOKUP_STANDING" '=' 'present' ']' && '[' "$CACHE_HIT" '!=' 'true' ']'; then ('echo' '::error title=release-bins StoreTransportDisagreePresentMiss::the cache API holds an exact admitted entry for this key but the transport did not restore it exactly; refusing rather than rebuilding over a present entry'; exit 1); else if '[' "$LOOKUP_STANDING" '=' 'absent' ']' && '[' "$CACHE_HIT" '=' 'true' ']'; then ('echo' '::error title=release-bins StoreTransportDisagreeAbsentHit::the cache API holds no exact admitted entry for this key but the transport reported an exact hit; the store and its transport disagree'; exit 1); else if '[' "$LOOKUP_STANDING" '=' 'absent' ']' && '[' "$CACHE_HIT" '!=' 'true' ']'; then 'rm' '-rf' "$STAGE"; 'mkdir' '-p' "$STAGE"; 'echo' 'outcome=build' | 'tee' '-a' "$GITHUB_OUTPUT" > '/dev/null'; exit 0; else ('echo' '::error title=release-bins OutcomeInputsMissing::the lookup standing or the transport exactness is missing, so no outcome can be decided'; exit 1); fi; fi; fi; fi + '[' '-f' "$STAGE"'/release-bins.preimage' ']' || ('echo' '::error title=release-bins StoredEntryIncomplete::the stored entry is incomplete: release-bins.preimage is missing'; exit 1) + '[' '-f' "$STAGE"'/release-bins.manifest' ']' || ('echo' '::error title=release-bins StoredEntryIncomplete::the stored entry is incomplete: release-bins.manifest is missing'; exit 1) + '[' '-f' "$STAGE"'/release-bins.tgz' ']' || ('echo' '::error title=release-bins StoredEntryIncomplete::the stored entry is incomplete: release-bins.tgz is missing'; exit 1) + 'cmp' '-s' "$EXPECTED" "$STAGE"'/release-bins.preimage' || ('diff' "$EXPECTED" "$STAGE"'/release-bins.preimage' || ':' 'no-op'; ('echo' '::error title=release-bins StoredPreimageDiffers::the stored preimage differs from this run'\''s independently derived preimage'; exit 1)) + 'sha256sum' "$STAGE"'/release-bins.preimage' > "$RUNNER_TEMP"'/STORED_DIGEST.out' + STORED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/STORED_DIGEST.out') + '[' 'release-bins-'"$STORED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins StoredPreimageKeyMismatch::the stored preimage does not hash to the probed key'; exit 1) + 'printf' '%s' 'claim_executor + gunbc + discover_source_root_ingest + claim_batch + interp_recorded_fixture_witness + v1_src_dag_parse + auth_declared_but_unwired_witness + bootstrap_witness + dag_collect_fingerprint_witness + diagnostics_witness + effects_rest_transport_witness + infer_semantics_witness + parse_witness + cssl_assemble + namespace_structural_root_exposure_generated_witness + codex_app_server_stdio_session + ' > "$RUNNER_TEMP"'/release-bins.roster' + 'tar' '-tzf' "$STAGE"'/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the stored pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) + 'cut' '-c' '67-' "$STAGE"'/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the stored manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$STAGE"'/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the stored manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + 'rm' '-rf' "$STAGE"'/unpacked' + 'mkdir' '-p' "$STAGE"'/unpacked' + 'tar' '-xzf' "$STAGE"'/release-bins.tgz' '-C' "$STAGE"'/unpacked' '--no-same-owner' + 'printf' '%s' 'claim_executor:regular file:1 + gunbc:regular file:1 + discover_source_root_ingest:regular file:1 + claim_batch:regular file:1 + interp_recorded_fixture_witness:regular file:1 + v1_src_dag_parse:regular file:1 + auth_declared_but_unwired_witness:regular file:1 + bootstrap_witness:regular file:1 + dag_collect_fingerprint_witness:regular file:1 + diagnostics_witness:regular file:1 + effects_rest_transport_witness:regular file:1 + infer_semantics_witness:regular file:1 + parse_witness:regular file:1 + cssl_assemble:regular file:1 + namespace_structural_root_exposure_generated_witness:regular file:1 + codex_app_server_stdio_session:regular file:1 + ' > "$RUNNER_TEMP"'/member-types.expected' + ('cd' "$STAGE"'/unpacked' && 'stat' '-c' '%n:%F:%h' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') > "$RUNNER_TEMP"'/member-types.out' + 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the stored pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) + ('cd' "$STAGE"'/unpacked' && 'find' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' '-maxdepth' '0' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' + '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the stored pack has a member that is not executable (listed above)'; exit 1)) + ('cd' "$STAGE"'/unpacked' && 'sha256sum' '--strict' '--quiet' '-c' "$STAGE"'/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the stored member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + 'mkdir' '-p' "$ROOT"'/target/release' + ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') + ('cd' "$STAGE"'/unpacked' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') + 'rm' '-rf' "$STAGE"'/unpacked' + 'echo' 'release-bins: stored pack verified and promoted' + 'sha256sum' "$STAGE"'/release-bins.manifest' > "$RUNNER_TEMP"'/OUTPUT_DIGEST.out' + OUTPUT_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/OUTPUT_DIGEST.out') + 'echo' 'ReleaseBinsReuseReceipt: request='"$RELEASE_BINS_KEY"' output=sha256:'"$OUTPUT_DIGEST"' dispatch='"$GITHUB_RUN_ID"'/'"$GITHUB_RUN_ATTEMPT"' producer=['"$LOOKUP_ENTRY"'] built=no' + 'echo' 'outcome=reuse' | 'tee' '-a' "$GITHUB_OUTPUT" > '/dev/null' + + fi + env: + RELEASE_BINS_KEY: ${{ steps.release_bins_key.outputs.key }} + LOOKUP_STANDING: ${{ steps.release_bins_lookup.outputs.standing }} + LOOKUP_ENTRY: ${{ steps.release_bins_lookup.outputs.entry }} + CACHE_HIT: ${{ steps.release_bins_cache.outputs.cache-hit }} + FLEET_CONVERGE_MODE: ${{ github.event.inputs.mode }} + timeout-minutes: 5 + - name: Cache Cargo + uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 + with: + path: |- + ${{ runner.temp }}/cargo/registry/index/ + ${{ runner.temp }}/cargo/registry/cache/ + ${{ runner.temp }}/cargo/git/db/ + target/ + key: cargo-ci-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/Cargo.lock', 'rust-toolchain.toml') }} + restore-keys: | + cargo-ci-${{ runner.os }}-${{ runner.arch }}- + if: steps.release_bins_outcome.outputs.outcome != 'reuse' + - name: Build release bins (the dispatched mode's product) + id: release_build + run: | + if [ "${FLEET_CONVERGE_MODE:-}" = 'approval_device_enrolment_code_issue' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_source_pack' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_checkpoint_measure' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_checkpoint_restore' ]; then + cargo build --release -p v1-compiler --bin claim_executor --bin gunbc + else + ROOT=$('git' 'rev-parse' '--show-toplevel') + set -o pipefail + BUILD_LOG=$(mktemp) + if ! ("$CARGO_BIN" build -p v1-compiler --release --features text_lookup_work_counter,interp_test_witness,test_hooks --bin claim_executor --bin gunbc --bin discover_source_root_ingest --bin claim_batch --bin interp_recorded_fixture_witness --bin v1_src_dag_parse --bin auth_declared_but_unwired_witness --bin bootstrap_witness --bin dag_collect_fingerprint_witness --bin diagnostics_witness --bin effects_rest_transport_witness --bin infer_semantics_witness --bin parse_witness --bin cssl_assemble --bin namespace_structural_root_exposure_generated_witness --bin codex_app_server_stdio_session) 2>&1 | tee "$BUILD_LOG"; then + if grep -qiE 'Resource temporarily unavailable|failed to spawn|sccache: encountered fatal error|(exit status: 254)|sccache: error: failed to execute compile' "$BUILD_LOG"; then + echo "::warning::cargo/sccache EAGAIN under fleet pressure; cold retry CARGO_BUILD_JOBS=1 (keep sccache)" + CARGO_BUILD_JOBS=1 "$CARGO_BIN" build -p v1-compiler --release --features text_lookup_work_counter,interp_test_witness,test_hooks --bin claim_executor --bin gunbc --bin discover_source_root_ingest --bin claim_batch --bin interp_recorded_fixture_witness --bin v1_src_dag_parse --bin auth_declared_but_unwired_witness --bin bootstrap_witness --bin dag_collect_fingerprint_witness --bin diagnostics_witness --bin effects_rest_transport_witness --bin infer_semantics_witness --bin parse_witness --bin cssl_assemble --bin namespace_structural_root_exposure_generated_witness --bin codex_app_server_stdio_session + else + exit 1 + fi + fi + rm -f "$BUILD_LOG" + "$ROOT"'/target/release/claim_executor' '--verify-build-artifacts' "$ROOT"'/target/release/claim_executor' "$ROOT"'/target/release/gunbc' "$ROOT"'/target/release/discover_source_root_ingest' "$ROOT"'/target/release/claim_batch' "$ROOT"'/target/release/interp_recorded_fixture_witness' "$ROOT"'/target/release/v1_src_dag_parse' "$ROOT"'/target/release/auth_declared_but_unwired_witness' "$ROOT"'/target/release/bootstrap_witness' "$ROOT"'/target/release/dag_collect_fingerprint_witness' "$ROOT"'/target/release/diagnostics_witness' "$ROOT"'/target/release/effects_rest_transport_witness' "$ROOT"'/target/release/infer_semantics_witness' "$ROOT"'/target/release/parse_witness' "$ROOT"'/target/release/cssl_assemble' "$ROOT"'/target/release/namespace_structural_root_exposure_generated_witness' "$ROOT"'/target/release/codex_app_server_stdio_session' + 'sccache' '--show-stats' 2>/dev/null || 'true' + + fi + env: + FLEET_CONVERGE_MODE: ${{ github.event.inputs.mode }} + if: steps.release_bins_outcome.outputs.outcome != 'reuse' + timeout-minutes: 45 + - name: "Reset-return dispatch admission: refuse an unnamed observer before anything is scheduled" + id: reset_observer_dispatch_admission + 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/host/host_reset_return_run.dag --function host_reset_return_dispatch_admission_wet + env: + GUNBC_HOST_RESET_DISPATCH_MODE: ${{ github.event.inputs.mode }} + GUNBC_HOST_RESET_OBSERVER: ${{ github.event.inputs.reset_observer }} + if: github.event.inputs.mode == 'host_reset_return' + timeout-minutes: 5 + - name: Pack release bins with member manifest into staging (build outcomes only; tar keeps exec bits) + run: | + if [ "${FLEET_CONVERGE_MODE:-}" = 'approval_device_enrolment_code_issue' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_source_pack' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_checkpoint_measure' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_checkpoint_restore' ]; then + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + STAGE="$ROOT"'/.release-bins-staging' + 'mkdir' '-p' "$STAGE" + '[' '-s' "$ROOT"'/gunbc-floor-cmd.log' ']' || ('echo' '::error title=release-bins BuildDiagnosticsMissing::the product requires the build log gunbc-floor-cmd.log as a pack member and the build step left none; a pack without its diagnostics is incomplete, not smaller'; exit 1) + 'cp' "$ROOT"'/gunbc-floor-cmd.log' "$ROOT"'/target/release/build_diagnostics' + ('cd' "$ROOT"'/target/release' && 'sha256sum' 'claim_executor' 'gunbc' 'build_diagnostics') > "$STAGE"'/release-bins.manifest' + 'tar' '-czf' "$STAGE"'/release-bins.tgz' '-C' "$ROOT"'/target/release' 'claim_executor' 'gunbc' 'build_diagnostics' + 'cp' "$RUNNER_TEMP"'/release-bins.preimage.expected' "$STAGE"'/release-bins.preimage' + 'sha256sum' "$STAGE"'/release-bins.manifest' > "$RUNNER_TEMP"'/OUTPUT_DIGEST.out' + OUTPUT_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/OUTPUT_DIGEST.out') + 'echo' 'ReleaseBinsBuildReceipt: request='"$RELEASE_BINS_KEY"' output=sha256:'"$OUTPUT_DIGEST"' dispatch='"$GITHUB_RUN_ID"'/'"$GITHUB_RUN_ATTEMPT"' built=yes publish=requested-not-yet-persisted scope='"$GITHUB_REF" + + else + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + STAGE="$ROOT"'/.release-bins-staging' + 'mkdir' '-p' "$STAGE" + ('cd' "$ROOT"'/target/release' && 'sha256sum' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') > "$STAGE"'/release-bins.manifest' + 'tar' '-czf' "$STAGE"'/release-bins.tgz' '-C' "$ROOT"'/target/release' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' + 'cp' "$RUNNER_TEMP"'/release-bins.preimage.expected' "$STAGE"'/release-bins.preimage' + 'sha256sum' "$STAGE"'/release-bins.manifest' > "$RUNNER_TEMP"'/OUTPUT_DIGEST.out' + OUTPUT_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/OUTPUT_DIGEST.out') + 'echo' 'ReleaseBinsBuildReceipt: request='"$RELEASE_BINS_KEY"' output=sha256:'"$OUTPUT_DIGEST"' dispatch='"$GITHUB_RUN_ID"'/'"$GITHUB_RUN_ATTEMPT"' built=yes publish=requested-not-yet-persisted scope='"$GITHUB_REF" + + fi + env: + RELEASE_BINS_KEY: ${{ steps.release_bins_key.outputs.key }} + FLEET_CONVERGE_MODE: ${{ github.event.inputs.mode }} + if: steps.release_bins_outcome.outputs.outcome != 'reuse' + timeout-minutes: 5 + - name: Upload release-bins artifact + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: release-bins + path: |- + .release-bins-staging/release-bins.tgz + .release-bins-staging/release-bins.preimage + .release-bins-staging/release-bins.manifest + if-no-files-found: error + retention-days: 3 + compression-level: 0 + timeout-minutes: 10 + fleet-converge: + runs-on: ${{ fromJSON(format('["self-hosted","linux","arm64","{0}"]', github.event.inputs.host)) }} + needs: [build] + timeout-minutes: 295 + if: github.event.inputs.mode != 'mtcollins1_boot' && github.event.inputs.mode != 'gcp_iam_converge' && github.event.inputs.mode != 'namecheap_observe' + permissions: + contents: read + actions: read + id-token: write + concurrency: + group: ${{ github.event.inputs.mode == 'spark_serving_apply' && 'gunbc-host-mutation-srv1' || github.event.inputs.mode == 'spark_native_serving_apply' && 'gunbc-arm-mutation-glm-group-b-native' || github.event.inputs.mode == 'spark_v41_checkpoint_materialize' && 'gunbc-host-mutation-srv1' || github.event.inputs.mode == 'spark_v41_row_store_encode' && 'gunbc-host-mutation-srv1' || github.event.inputs.mode == 'spark_v41_row_store_readback' && 'gunbc-host-mutation-srv1' || github.event.inputs.mode == 'spark_v41_engram_differential' && 'gunbc-host-mutation-srv1' || github.event.inputs.mode == 'spark_runtime_image_produce' && format('gunbc-host-mutation-{0}', github.event.inputs.target) || github.event.inputs.mode == 'spark_runtime_image_distribute' && 'gunbc-host-mutation-srv1' || github.event.inputs.mode == 'spark_arm_group_load' && 'gunbc-host-mutation-srv1' || github.event.inputs.mode == 'spark_arm_checkpoint_materialize' && format('gunbc-host-mutation-{0}', github.event.inputs.target) || github.event.inputs.mode == 'spark_arm_group_observe' && format('gunbc-read-only-{0}', github.run_id) || github.event.inputs.mode == 'spark_arm_group_launch' && 'gunbc-host-mutation-srv1' || github.event.inputs.mode == 'spark_v41_serving_load' && 'gunbc-host-mutation-srv1' || github.event.inputs.mode == 'approval_device_enrolment_code_issue' && 'gunbc-subject-mutation-approval-device-store-srv1' || github.event.inputs.mode == 'r2_conditional_put_race_probe' && 'r2-conditional-put-race-probe' || github.event.inputs.mode == 'pair_serving_d0' && 'gunbc-host-mutation-srv1' || format('gunbc-host-mutation-{0}', github.event.inputs.host) }} + cancel-in-progress: false + steps: + - name: Checkout + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + fetch-depth: 0 + ref: ${{ (github.event.inputs.mode == 'mtcollins1_boot' || github.event.inputs.mode == 'gcp_iam_converge' || github.event.inputs.mode == 'namecheap_observe') && github.sha || github.event.inputs.expected_revision || github.sha }} + - name: Download release-bins artifact + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: release-bins + path: ${{ runner.temp }}/release-bins-download + timeout-minutes: 10 + - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) + id: release_bins + run: | + if [ "${FLEET_CONVERGE_MODE:-}" = 'approval_device_enrolment_code_issue' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_source_pack' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_checkpoint_measure' ] || [ "${FLEET_CONVERGE_MODE:-}" = 'workspace_checkpoint_restore' ]; then + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') + '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) + OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') + SOURCE_TREE=$('git' '-C' "$ROOT" 'rev-parse' 'HEAD^{tree}') + '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) + SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'printf' '%s' 'claim_executor + gunbc + build_diagnostics + ' > "$RUNNER_TEMP"'/release-bins.roster' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'printf' '%s' 'claim_executor:regular file:1 + gunbc:regular file:1 + ' > "$RUNNER_TEMP"'/member-types.expected' + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'stat' '-c' '%n:%F:%h' 'claim_executor' 'gunbc') > "$RUNNER_TEMP"'/member-types.out' + 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' 'claim_executor' 'gunbc' '-maxdepth' '0' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' + '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + 'mkdir' '-p' "$ROOT"'/target/release' + ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc') + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' "$ROOT"'/target/release/') + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + "$ROOT"'/target/release/claim_executor' '--verify-build-artifacts' "$ROOT"'/target/release/claim_executor' "$ROOT"'/target/release/gunbc' + + else + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') + '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) + OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') + SOURCE_TREE=$('git' '-C' "$ROOT" 'rev-parse' 'HEAD^{tree}') + '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) + SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'printf' '%s' 'claim_executor + gunbc + discover_source_root_ingest + claim_batch + interp_recorded_fixture_witness + v1_src_dag_parse + auth_declared_but_unwired_witness + bootstrap_witness + dag_collect_fingerprint_witness + diagnostics_witness + effects_rest_transport_witness + infer_semantics_witness + parse_witness + cssl_assemble + namespace_structural_root_exposure_generated_witness + codex_app_server_stdio_session + ' > "$RUNNER_TEMP"'/release-bins.roster' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'printf' '%s' 'claim_executor:regular file:1 + gunbc:regular file:1 + discover_source_root_ingest:regular file:1 + claim_batch:regular file:1 + interp_recorded_fixture_witness:regular file:1 + v1_src_dag_parse:regular file:1 + auth_declared_but_unwired_witness:regular file:1 + bootstrap_witness:regular file:1 + dag_collect_fingerprint_witness:regular file:1 + diagnostics_witness:regular file:1 + effects_rest_transport_witness:regular file:1 + infer_semantics_witness:regular file:1 + parse_witness:regular file:1 + cssl_assemble:regular file:1 + namespace_structural_root_exposure_generated_witness:regular file:1 + codex_app_server_stdio_session:regular file:1 + ' > "$RUNNER_TEMP"'/member-types.expected' + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'stat' '-c' '%n:%F:%h' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') > "$RUNNER_TEMP"'/member-types.out' + 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' '-maxdepth' '0' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' + '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + 'mkdir' '-p' "$ROOT"'/target/release' + ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + "$ROOT"'/target/release/claim_executor' '--verify-build-artifacts' "$ROOT"'/target/release/claim_executor' "$ROOT"'/target/release/gunbc' "$ROOT"'/target/release/discover_source_root_ingest' "$ROOT"'/target/release/claim_batch' "$ROOT"'/target/release/interp_recorded_fixture_witness' "$ROOT"'/target/release/v1_src_dag_parse' "$ROOT"'/target/release/auth_declared_but_unwired_witness' "$ROOT"'/target/release/bootstrap_witness' "$ROOT"'/target/release/dag_collect_fingerprint_witness' "$ROOT"'/target/release/diagnostics_witness' "$ROOT"'/target/release/effects_rest_transport_witness' "$ROOT"'/target/release/infer_semantics_witness' "$ROOT"'/target/release/parse_witness' "$ROOT"'/target/release/cssl_assemble' "$ROOT"'/target/release/namespace_structural_root_exposure_generated_witness' "$ROOT"'/target/release/codex_app_server_stdio_session' + + fi + env: + RELEASE_BINS_KEY: ${{ needs.build.outputs.release_bins_key }} + FLEET_CONVERGE_MODE: ${{ github.event.inputs.mode }} + timeout-minutes: 5 + - name: "Pair-serving D0 admission (credential-free): the executor is srv1, the consent and the expected revision are named" + id: pair_serving_d0_admit + 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/spark/pair_serving_d0_door.dag --function pair_serving_d0_admit_executor + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + GUNBC_D0_CONSENT: ${{ github.event.inputs.d0_consent }} + RLM_EXPECTED_REVISION: ${{ github.event.inputs.expected_revision }} + if: github.event.inputs.mode == 'pair_serving_d0' + timeout-minutes: 15 + - name: WIF auth (OIDC -> fleet-cloud-convergence SA, access token only) + id: wif_auth + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 + with: + workload_identity_provider: projects/582015116396/locations/global/workloadIdentityPools/github-actions/providers/github-oidc + service_account: fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com + token_format: access_token + create_credentials_file: false + timeout-minutes: 5 + - name: Materialize fleet key in-run (SM versions/1 pinned -> RUNNER_TEMP 0600 -> ssh-agent -> wipe file) + run: |- + # 🟡 dissolve-on: gunbc_ci_fleet_key_agent_script - orch-emitted foreign-executor (GitHub Actions run:) credential runner: WIF access token by env, one PINNED secret version fetched over curl, a 0600 key file under RUNNER_TEMP with a trap armed BEFORE the credential touches disk, fingerprint verified against the modeled authority, ssh-agent load, file wipe. The pipeline steps are modeled (gunbc_ci_fleet_key_agent_prelude) but the runner transport itself remains hand-shell; DISSOLVES WHEN the runner is authored as v2.extdeps.languages.bash_build nodes (typed, grammar-quoted words; command substitution; assignment; pipe; if/test; redirect; || true) emitted by v2.workflow.bash_emit bash_emit_stmts -- a route AVAILABLE today (#12422 retired the sibling pack runner on it), so this migration is unauthored, not blocked on a missing capability; set -euo pipefail, umask, export and trap are ordinary commands on that route, and the trap body is itself built from nodes and passed as one grammar-quoted word, so no new orchestration intent is a prerequisite + set -euo pipefail + umask 077 + KEY_FILE="$RUNNER_TEMP/fleet-automation-key" + HDR_FILE="$RUNNER_TEMP/fleet-automation-auth-header" + AGENT_STARTED=0 + cleanup() { rm -f "$KEY_FILE" "$HDR_FILE"; if [ "$AGENT_STARTED" = 1 ] && [ -n "${SSH_AGENT_PID:-}" ]; then ssh-agent -k >/dev/null 2>&1 || true; fi; } + trap cleanup EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + curl -sSf -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key/versions/1:access" | python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(sys.stdin)["payload"]["data"]))' > "$KEY_FILE" + rm -f "$HDR_FILE" + chmod 600 "$KEY_FILE" + EXPECTED_FP="SHA256:mGT7qJsh36VsVb8OPh3m8br3oHedQHxRukRkW5P0CoQ" + OBSERVED_FP="$(ssh-keygen -y -f "$KEY_FILE" | ssh-keygen -lf - | awk '{print $2}')" + if [ "$OBSERVED_FP" != "$EXPECTED_FP" ]; then echo "ProbeCredentialIdentityMismatch: fetched secret versions/1 fingerprint $OBSERVED_FP != modeled $EXPECTED_FP; contacting NO host" >&2; exit 1; fi + eval "$(ssh-agent -s)" >/dev/null + AGENT_STARTED=1 + ssh-add "$KEY_FILE" 2>/dev/null + rm -f "$KEY_FILE" + trap - EXIT + echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> "$GITHUB_ENV" + echo "SSH_AGENT_PID=$SSH_AGENT_PID" >> "$GITHUB_ENV" + echo "fleet-key: agent loaded (identity fleet-automation@gunbc; secret versions/1 pinned; fingerprint verified against modeled authority; key file wiped)" + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'plan' || github.event.inputs.mode == 'launch_environment_plan' || github.event.inputs.mode == 'allocation_store_plan' || github.event.inputs.mode == 'workspace_commissioning_plan' || github.event.inputs.mode == 'apply' || github.event.inputs.mode == 'app_control_plane_observe' || github.event.inputs.mode == 'microvm_host_converge' || github.event.inputs.mode == 'microvm_network_observe' || github.event.inputs.mode == 'microvm_network_apply' || github.event.inputs.mode == 'guest_image_observe' || github.event.inputs.mode == 'guest_image_converge' || github.event.inputs.mode == 'microvm_boot_probe' || github.event.inputs.mode == 'spark_grants' || github.event.inputs.mode == 'spark_bootstrap' || github.event.inputs.mode == 'spark_serving_apply' || github.event.inputs.mode == 'spark_native_serving_apply' || github.event.inputs.mode == 'spark_runtime_image_probe' || github.event.inputs.mode == 'spark_wireless_link_converge' || github.event.inputs.mode == 'spark_grants_observe' || github.event.inputs.mode == 'spark_v41_checkpoint_materialize' || github.event.inputs.mode == 'spark_v41_row_store_encode' || github.event.inputs.mode == 'spark_v41_row_store_readback' || github.event.inputs.mode == 'spark_v41_engram_differential' || github.event.inputs.mode == 'spark_runtime_image_produce' || github.event.inputs.mode == 'spark_runtime_image_distribute' || github.event.inputs.mode == 'spark_arm_group_load' || github.event.inputs.mode == 'spark_arm_checkpoint_materialize' || github.event.inputs.mode == 'spark_arm_group_observe' || github.event.inputs.mode == 'spark_arm_group_launch_plan' || github.event.inputs.mode == 'spark_arm_group_launch' || github.event.inputs.mode == 'spark_v41_serving_load' || github.event.inputs.mode == 'dashboard_deploy' || github.event.inputs.mode == 'approval_broker_dark_install' || github.event.inputs.mode == 'microvm_controller_install' || github.event.inputs.mode == 'rlm_launch_deployment_receipt' || github.event.inputs.mode == 'host_reset_return' || github.event.inputs.mode == 'runner_host_file_observe' || github.event.inputs.mode == 'runner_host_file_converge' || github.event.inputs.mode == 'site_pxe_edge_observe' || github.event.inputs.mode == 'site_pxe_edge_converge' || github.event.inputs.mode == 'runner_password_session_tool_converge' || github.event.inputs.mode == 'runner_browser_toolchain_converge' || github.event.inputs.mode == 'r2_mint_preflight' || github.event.inputs.mode == 'r2_object_write_mint' || github.event.inputs.mode == 'approval_keyring_converge' || github.event.inputs.mode == 'approval_device_enrolment_code_issue' || github.event.inputs.mode == 'mtcollins1_boot' || github.event.inputs.mode == 'mtcollins1_census_image_publish' || github.event.inputs.mode == 'mtcollins1_census_member_readback' || github.event.inputs.mode == 'host_credential_custody_converge' || github.event.inputs.mode == 'pair_serving_d0' + timeout-minutes: 5 + - name: Fleet converge plan (membership_reconcile → artifact) + id: plan + 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/fleet/fleet_converge_plan_cli.dag --function fleet_converge_plan_wet + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + RLM_EXPECTED_REVISION: ${{ github.event.inputs.expected_revision }} + if: github.event.inputs.mode == 'plan' + timeout-minutes: 15 + - name: Fleet converge plan — scope:launch-environment (legacy fleet-converge timer retirement) + id: launch_environment_plan + 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/fleet/fleet_converge_plan_cli.dag --function fleet_converge_launch_environment_plan_wet + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + RLM_EXPECTED_REVISION: ${{ github.event.inputs.expected_revision }} + if: github.event.inputs.mode == 'launch_environment_plan' + timeout-minutes: 5 + - name: Fleet allocation-store substrate plan + id: allocation_store_plan + 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/fleet/fleet_converge_plan_cli.dag --function fleet_converge_allocation_store_plan_wet + if: github.event.inputs.mode == 'allocation_store_plan' + timeout-minutes: 5 + - name: Fleet workspace slot commissioning plan + id: workspace_commissioning_plan + 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/fleet/fleet_converge_plan_cli.dag --function fleet_converge_workspace_commissioning_plan_wet + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + RLM_EXPECTED_REVISION: ${{ github.event.inputs.expected_revision }} + if: github.event.inputs.mode == 'workspace_commissioning_plan' + timeout-minutes: 15 + - name: Upload fleet converge plan artifact + id: plan_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: fleet-converge-plan + path: /tmp/fleet-converge-plan + if-no-files-found: error + retention-days: 30 + compression-level: 0 + if: github.event.inputs.mode == 'plan' || github.event.inputs.mode == 'launch_environment_plan' || github.event.inputs.mode == 'allocation_store_plan' || github.event.inputs.mode == 'workspace_commissioning_plan' + timeout-minutes: 10 + - name: Download fleet converge plan artifact + id: plan_download + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: fleet-converge-plan + path: /tmp/fleet-converge-plan + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.inputs.plan_workflow_run_id }} + if: github.event.inputs.mode == 'apply' + timeout-minutes: 10 + - name: Fleet converge apply (CAS + generated apply.sh) + id: apply + run: |- + set -euo pipefail + ACTUAL="$(cat /tmp/fleet-converge-plan/plan_content.hex)" + if [ -z "${EXPECTED_HASH:-}" ] || [ "$EXPECTED_HASH" != "$ACTUAL" ]; then echo "::error::PlanArtifactHashMismatch expected=$EXPECTED_HASH actual=$ACTUAL" >&2; exit 1; fi + 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/fleet/fleet_converge_plan_cli.dag --function fleet_converge_apply_wet + env: + EXPECTED_HASH: ${{ github.event.inputs.plan_artifact_hash }} + FLEET_CONVERGE_PLAN_RUN_ID: ${{ github.event.inputs.plan_workflow_run_id }} + RLM_EXPECTED_REVISION: ${{ github.event.inputs.expected_revision }} + if: github.event.inputs.mode == 'apply' + timeout-minutes: 60 + - name: Upload fleet converge apply receipt + id: apply_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: fleet-converge-apply-receipt + path: /tmp/fleet-converge-apply + if-no-files-found: error + retention-days: 90 + compression-level: 0 + if: github.event.inputs.mode == 'apply' + timeout-minutes: 10 + - name: Org Actions credential validation + read-only settings diff + id: org_actions_observe + run: |- + set -euo pipefail + umask 077 + KEY_FILE="$RUNNER_TEMP/org-admin-app-key" + HDR_FILE="$RUNNER_TEMP/org-admin-auth-header" + JWT_FILE="$RUNNER_TEMP/org-admin-app-jwt" + TOKEN_HEADERS="$RUNNER_TEMP/org-admin-token-headers" + TOKEN_BODY="$RUNNER_TEMP/org-admin-token-body" + cleanup() { rm -f "$KEY_FILE" "$HDR_FILE" "$JWT_FILE" "$TOKEN_HEADERS" "$TOKEN_BODY" "$RUNNER_TEMP/org-admin-app-key-sm-body"; unset GUNBC_ORG_ADMIN_TOKEN GH_TOKEN; } + trap cleanup EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + SM_BODY="$RUNNER_TEMP/org-admin-app-key-sm-body" + SM_CODE="$(curl -sS -o "$SM_BODY" -w '%{http_code}' -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/ci-github-app-private-key/versions/latest:access")" + rm -f "$HDR_FILE" + if [ "$SM_CODE" != 200 ]; then rm -f "$SM_BODY"; echo "OrgAdminAppKeyUnreadable: Secret Manager answered HTTP $SM_CODE for projects/gunbai-secrets/secrets/ci-github-app-private-key/versions/latest to the fleet-cloud-convergence principal. 403 means the version exists and is not readable OR does not exist -- REMEDY: run org_admin_app_key_access_converge_with_supplied_token (gunbc.fleet.org_actions_converge) with a control-plane token to bind roles/secretmanager.secretAccessor, and confirm the version exists. Minting NO token." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$SM_BODY" > "$KEY_FILE" + rm -f "$SM_BODY" + chmod 600 "$KEY_FILE" + NOW=$(date +%s) + b64url() { base64 -w0 | tr '+/' '-_' | tr -d '='; } + JWT_HEADER=$(printf '%s' '{"alg":"RS256","typ":"JWT"}' | b64url) + JWT_PAYLOAD=$(printf '{"iat":%d,"exp":%d,"iss":"%s"}' "$((NOW-60))" "$((NOW+540))" "2653532" | b64url) + JWT_SIG=$(printf '%s.%s' "$JWT_HEADER" "$JWT_PAYLOAD" | openssl dgst -sha256 -sign "$KEY_FILE" | b64url) + rm -f "$KEY_FILE" + printf 'Authorization: Bearer %s.%s.%s\n' "$JWT_HEADER" "$JWT_PAYLOAD" "$JWT_SIG" > "$JWT_FILE" + curl -sS -X POST -H @"$JWT_FILE" -H 'Accept: application/vnd.github+json' -D "$TOKEN_HEADERS" -o "$TOKEN_BODY" "https://api.github.com/app/installations/104134109/access_tokens" + rm -f "$JWT_FILE" + if ! head -n1 "$TOKEN_HEADERS" | grep -q ' 201 '; then echo "OrgAdminInstallationTokenRefused: GitHub answered $(head -n1 "$TOKEN_HEADERS" | tr -d '\r') for the gunbai-ci installation; a revoked key, a removed installation, or a missing Self-hosted-runners permission each look like this -- inspect the App's installation on the organization" >&2; exit 1; fi + GUNBC_ORG_ADMIN_TOKEN="$(python3 -c 'import sys,json; print(json.load(open(sys.argv[1]))["token"])' "$TOKEN_BODY")" + rm -f "$TOKEN_HEADERS" "$TOKEN_BODY" + export GUNBC_ORG_ADMIN_TOKEN + export GH_TOKEN="$GUNBC_ORG_ADMIN_TOKEN" + echo "org-admin: installation token minted in-run for app gunbai-ci installation 104134109 (one-hour lifetime; key wiped; token held in this step's environment only)" + 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/fleet/org_actions_inspection.dag --function org_actions_inspect_wet + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'org_actions_observe' + timeout-minutes: 5 + - name: Upload org Actions credential validation receipt + id: org_actions_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: org-actions-credential-validation + path: target/org-actions-credential-validation-receipt.txt + if-no-files-found: error + retention-days: 30 + if: github.event.inputs.mode == 'org_actions_observe' + timeout-minutes: 10 + - name: "Org runner roster: paginated read of GitHub self-hosted runner registrations" + id: org_runner_roster_observe + run: |- + set -euo pipefail + umask 077 + KEY_FILE="$RUNNER_TEMP/org-admin-app-key" + HDR_FILE="$RUNNER_TEMP/org-admin-auth-header" + JWT_FILE="$RUNNER_TEMP/org-admin-app-jwt" + TOKEN_HEADERS="$RUNNER_TEMP/org-admin-token-headers" + TOKEN_BODY="$RUNNER_TEMP/org-admin-token-body" + cleanup() { rm -f "$KEY_FILE" "$HDR_FILE" "$JWT_FILE" "$TOKEN_HEADERS" "$TOKEN_BODY" "$RUNNER_TEMP/org-admin-app-key-sm-body"; unset GUNBC_ORG_ADMIN_TOKEN GH_TOKEN; } + trap cleanup EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + SM_BODY="$RUNNER_TEMP/org-admin-app-key-sm-body" + SM_CODE="$(curl -sS -o "$SM_BODY" -w '%{http_code}' -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/ci-github-app-private-key/versions/latest:access")" + rm -f "$HDR_FILE" + if [ "$SM_CODE" != 200 ]; then rm -f "$SM_BODY"; echo "OrgAdminAppKeyUnreadable: Secret Manager answered HTTP $SM_CODE for projects/gunbai-secrets/secrets/ci-github-app-private-key/versions/latest to the fleet-cloud-convergence principal. 403 means the version exists and is not readable OR does not exist -- REMEDY: run org_admin_app_key_access_converge_with_supplied_token (gunbc.fleet.org_actions_converge) with a control-plane token to bind roles/secretmanager.secretAccessor, and confirm the version exists. Minting NO token." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$SM_BODY" > "$KEY_FILE" + rm -f "$SM_BODY" + chmod 600 "$KEY_FILE" + NOW=$(date +%s) + b64url() { base64 -w0 | tr '+/' '-_' | tr -d '='; } + JWT_HEADER=$(printf '%s' '{"alg":"RS256","typ":"JWT"}' | b64url) + JWT_PAYLOAD=$(printf '{"iat":%d,"exp":%d,"iss":"%s"}' "$((NOW-60))" "$((NOW+540))" "2653532" | b64url) + JWT_SIG=$(printf '%s.%s' "$JWT_HEADER" "$JWT_PAYLOAD" | openssl dgst -sha256 -sign "$KEY_FILE" | b64url) + rm -f "$KEY_FILE" + printf 'Authorization: Bearer %s.%s.%s\n' "$JWT_HEADER" "$JWT_PAYLOAD" "$JWT_SIG" > "$JWT_FILE" + curl -sS -X POST -H @"$JWT_FILE" -H 'Accept: application/vnd.github+json' -D "$TOKEN_HEADERS" -o "$TOKEN_BODY" "https://api.github.com/app/installations/104134109/access_tokens" + rm -f "$JWT_FILE" + if ! head -n1 "$TOKEN_HEADERS" | grep -q ' 201 '; then echo "OrgAdminInstallationTokenRefused: GitHub answered $(head -n1 "$TOKEN_HEADERS" | tr -d '\r') for the gunbai-ci installation; a revoked key, a removed installation, or a missing Self-hosted-runners permission each look like this -- inspect the App's installation on the organization" >&2; exit 1; fi + GUNBC_ORG_ADMIN_TOKEN="$(python3 -c 'import sys,json; print(json.load(open(sys.argv[1]))["token"])' "$TOKEN_BODY")" + rm -f "$TOKEN_HEADERS" "$TOKEN_BODY" + export GUNBC_ORG_ADMIN_TOKEN + export GH_TOKEN="$GUNBC_ORG_ADMIN_TOKEN" + echo "org-admin: installation token minted in-run for app gunbai-ci installation 104134109 (one-hour lifetime; key wiped; token held in this step's environment only)" + 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/fleet/organization_runner_roster_read.dag --function organization_runner_roster_observe_wet + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + GUNBC_RUNNER_ID_PROBES: ${{ github.event.inputs.runner_ids }} + GUNBC_RUNNER_ROSTER_PAGE_SIZE: ${{ github.event.inputs.roster_page_size }} + if: github.event.inputs.mode == 'org_runner_roster_observe' + timeout-minutes: 5 + - name: Upload org runner roster receipt + id: org_runner_roster_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: org-runner-roster + path: target/org-runner-roster-receipt.txt + if-no-files-found: error + retention-days: 30 + if: always() && github.event.inputs.mode == 'org_runner_roster_observe' + timeout-minutes: 10 + - name: "GitHub App control plane: registration + webhook config observation" + id: app_control_plane_observe + run: |- + set -euo pipefail + umask 077 + APP_KEY_FILE="$RUNNER_TEMP/app-jwt-key" + APP_SM_HDR="$RUNNER_TEMP/app-jwt-sm-header" + APP_SM_BODY="$RUNNER_TEMP/app-jwt-sm-body" + GUNBC_APP_JWT_HEADER_FILE="$RUNNER_TEMP/app-jwt-header" + app_jwt_cleanup() { rm -f "$APP_KEY_FILE" "$APP_SM_HDR" "$APP_SM_BODY" "$GUNBC_APP_JWT_HEADER_FILE"; unset GUNBC_APP_JWT_HEADER_FILE; } + trap app_jwt_cleanup EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$APP_SM_HDR" + APP_SM_CODE="$(curl -sS -o "$APP_SM_BODY" -w '%{http_code}' -H @"$APP_SM_HDR" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/ci-github-app-private-key/versions/latest:access")" + rm -f "$APP_SM_HDR" + if [ "$APP_SM_CODE" -ne 200 ]; then + rm -f "$APP_SM_BODY" + echo "AppJwtKeyUnreadable: Secret Manager answered HTTP $APP_SM_CODE for projects/gunbai-secrets/secrets/ci-github-app-private-key/versions/latest to the fleet-cloud-convergence principal. 403 means the version exists and is not readable OR does not exist -- REMEDY: run org_admin_app_key_access_converge_with_supplied_token (gunbc.fleet.org_actions_converge) with a control-plane token to bind roles/secretmanager.secretAccessor. Minting NO App JWT." >&2 + exit 1 + fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$APP_SM_BODY" > "$APP_KEY_FILE" + rm -f "$APP_SM_BODY" + chmod 600 "$APP_KEY_FILE" + APP_NOW=$(date +%s) + app_b64url() { base64 -w0 | tr '+/' '-_' | tr -d '='; } + APP_JWT_HEADER=$(printf '%s' '{"alg":"RS256","typ":"JWT"}' | app_b64url) + APP_JWT_PAYLOAD=$(printf '{"iat":%d,"exp":%d,"iss":"%s"}' "$((APP_NOW-60))" "$((APP_NOW+540))" "2653532" | app_b64url) + APP_JWT_SIG=$(printf '%s.%s' "$APP_JWT_HEADER" "$APP_JWT_PAYLOAD" | openssl dgst -sha256 -sign "$APP_KEY_FILE" | app_b64url) + rm -f "$APP_KEY_FILE" + printf 'Authorization: Bearer %s.%s.%s\n' "$APP_JWT_HEADER" "$APP_JWT_PAYLOAD" "$APP_JWT_SIG" > "$GUNBC_APP_JWT_HEADER_FILE" + chmod 600 "$GUNBC_APP_JWT_HEADER_FILE" + unset APP_JWT_HEADER APP_JWT_PAYLOAD APP_JWT_SIG + export GUNBC_APP_JWT_HEADER_FILE + echo "app-control-plane: short-lived App JWT minted in-run for app gunbai-ci (540s lifetime; key wiped; header file 0600, path exported, contents never in argv or environment)" + 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/fleet/app_control_plane_inspection.dag --function app_control_plane_inspect_wet + cat "$ROOT/target/app-control-plane-observation-receipt.txt" + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'app_control_plane_observe' + timeout-minutes: 5 + - name: Upload GitHub App control plane observation receipt + id: app_control_plane_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: app-control-plane-observation + path: target/app-control-plane-observation-receipt.txt + if-no-files-found: error + retention-days: 30 + if: always() && github.event.inputs.mode == 'app_control_plane_observe' + timeout-minutes: 10 + - name: "App key version verify: mint an installation token with the exact version" + id: app_key_version_verify_mint + run: |- + set -euo pipefail + : > "$APP_KEY_VERIFY_RECORD" + case "$APP_KEY_VERSION" in ''|0*|*[!0-9]*) echo "AppKeyVersionSelectorRefused: '$APP_KEY_VERSION' is not a Secret Manager version number; the alias is refused rather than resolved. Reading NO key." >&2; exit 1;; esac + set -euo pipefail + umask 077 + KEY_FILE="$RUNNER_TEMP/app-key-verify-app-key" + HDR_FILE="$RUNNER_TEMP/app-key-verify-auth-header" + JWT_FILE="$RUNNER_TEMP/app-key-verify-app-jwt" + TOKEN_HEADERS="$RUNNER_TEMP/app-key-verify-token-headers" + TOKEN_BODY="$RUNNER_TEMP/app-key-verify-token-body" + cleanup() { rm -f "$KEY_FILE" "$HDR_FILE" "$JWT_FILE" "$TOKEN_HEADERS" "$TOKEN_BODY" "$RUNNER_TEMP/app-key-verify-app-key-sm-body"; unset GUNBC_APP_KEY_VERIFY_TOKEN; } + trap cleanup EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + SM_BODY="$RUNNER_TEMP/app-key-verify-app-key-sm-body" + SM_CODE="$(curl -sS -o "$SM_BODY" -w '%{http_code}' -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/ci-github-app-private-key/versions/$APP_KEY_VERSION:access")" + rm -f "$HDR_FILE" + printf 'key_http_status %s\n' "$SM_CODE" >> "$APP_KEY_VERIFY_RECORD" + if [ "$SM_CODE" != 200 ]; then rm -f "$SM_BODY"; echo "AppKeyVersionUnreadable: Secret Manager answered HTTP $SM_CODE for projects/gunbai-secrets/secrets/ci-github-app-private-key/versions/$APP_KEY_VERSION; the verdict step below types the cause. Minting NO token." >&2; exit 1; fi + printf 'resolved_name %s\n' "$(python3 -c 'import sys,json; print(json.load(open(sys.argv[1]))["name"])' "$SM_BODY")" >> "$APP_KEY_VERIFY_RECORD" + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$SM_BODY" > "$KEY_FILE" + rm -f "$SM_BODY" + chmod 600 "$KEY_FILE" + NOW=$(date +%s) + b64url() { base64 -w0 | tr '+/' '-_' | tr -d '='; } + JWT_HEADER=$(printf '%s' '{"alg":"RS256","typ":"JWT"}' | b64url) + JWT_PAYLOAD=$(printf '{"iat":%d,"exp":%d,"iss":"%s"}' "$((NOW-60))" "$((NOW+540))" "2653532" | b64url) + JWT_SIG=$(printf '%s.%s' "$JWT_HEADER" "$JWT_PAYLOAD" | openssl dgst -sha256 -sign "$KEY_FILE" | b64url) + rm -f "$KEY_FILE" + printf 'Authorization: Bearer %s.%s.%s\n' "$JWT_HEADER" "$JWT_PAYLOAD" "$JWT_SIG" > "$JWT_FILE" + curl -sS -X POST -H @"$JWT_FILE" -H 'Accept: application/vnd.github+json' -D "$TOKEN_HEADERS" -o "$TOKEN_BODY" "https://api.github.com/app/installations/104134109/access_tokens" + rm -f "$JWT_FILE" + printf 'token_status_line %s\n' "$(head -n1 "$TOKEN_HEADERS" | tr -d '\r')" >> "$APP_KEY_VERIFY_RECORD" + if ! head -n1 "$TOKEN_HEADERS" | grep -q ' 201 '; then echo "AppKeyInstallationTokenRefused: GitHub answered $(head -n1 "$TOKEN_HEADERS" | tr -d '\r') to the exchange signed with this version; the verdict step below types the cause." >&2; exit 1; fi + GUNBC_APP_KEY_VERIFY_TOKEN="$(python3 -c 'import sys,json; print(json.load(open(sys.argv[1]))["token"])' "$TOKEN_BODY")" + rm -f "$TOKEN_HEADERS" "$TOKEN_BODY" + export GUNBC_APP_KEY_VERIFY_TOKEN + echo "app-key-verify: an installation token was minted with version $APP_KEY_VERSION for app gunbai-ci (key wiped; token unused and unset on exit)" + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + APP_KEY_VERSION: ${{ github.event.inputs.app_key_version }} + APP_KEY_VERIFY_RECORD: ${{ runner.temp }}/app-key-verify-record + if: github.event.inputs.mode == 'app_key_version_verify' + timeout-minutes: 5 + - name: "App key version verify: typed verdict and rotation-deadline standing" + id: app_key_version_verify_verdict + 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/auth/ci_app_key_rotation.dag --function ci_app_key_version_verify_wet + cat "$ROOT/target/app-key-version-verify-receipt.txt" + env: + APP_KEY_VERSION: ${{ github.event.inputs.app_key_version }} + APP_KEY_VERIFY_RECORD: ${{ runner.temp }}/app-key-verify-record + if: always() && github.event.inputs.mode == 'app_key_version_verify' + timeout-minutes: 5 + - name: Upload app key version verify receipt + id: app_key_version_verify_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: app-key-version-verify + path: target/app-key-version-verify-receipt.txt + if-no-files-found: error + retention-days: 90 + compression-level: 0 + if: always() && github.event.inputs.mode == 'app_key_version_verify' + timeout-minutes: 10 + - name: "Mt. Collins fan observation: IPMI SDR fan + temperature time series with power state (reads only)" + id: mtcollins1_fan_observe + run: |- + # 🟡 dissolve-on: gunbc_ci_mtcollins1_fan_observe_invoke - orch-emitted foreign-executor (GitHub Actions run:) credential runner: WIF access token by env, ONE pinned BMC secret version fetched over curl through the shared gunbc_ci_mtcollins1_bmc_credential_fetch_steps, a 0600 file under RUNNER_TEMP with a trap armed BEFORE the credential touches disk. The pipeline steps are modeled but each step body is a shell string built by concat, so the transport itself remains hand-shell; DISSOLVES WHEN the runner is authored as v2.extdeps.languages.bash_build nodes (typed, grammar-quoted words; command substitution; assignment; pipe; if/test; redirect; || true) emitted by v2.workflow.bash_emit bash_emit_stmts -- a route AVAILABLE today (#12422 retired the sibling pack runner on it), so this migration is unauthored, not blocked on a missing capability; set -euo pipefail, umask, export and trap are ordinary commands on that route, and the trap body is itself built from nodes and passed as one grammar-quoted word, so no new orchestration intent is a prerequisite. + set -euo pipefail + umask 077 + HDR_FILE="$RUNNER_TEMP/mtcollins1-fan-auth-header" + RESP_FILE="$RUNNER_TEMP/mtcollins1-fan-sm.json" + DEST_FILE="$RUNNER_TEMP/mtcollins1-bmc-credential" + trap 'rm -f "$HDR_FILE" "$RESP_FILE" "$DEST_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + curl -sSf --connect-timeout 10 --max-time 60 -o "$RESP_FILE" -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc/versions/2:access" && python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$DEST_FILE" + chmod 600 "$DEST_FILE" + export GUNBC_MTCOLLINS1_BMC_CREDENTIAL_FILE="$DEST_FILE" + 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/machine_intake/mtcollins1_fan_observe.dag --function mtcollins1_fan_observe_wet + cat "$ROOT/target/mtcollins1-fan-observation.txt" + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'mtcollins1_fan_observe' + timeout-minutes: 5 + - name: Upload Mt. Collins fan observation series + id: mtcollins1_fan_observe_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: mtcollins1-fan-observation + path: target/mtcollins1-fan-observation.txt + if-no-files-found: error + retention-days: 30 + if: github.event.inputs.mode == 'mtcollins1_fan_observe' + timeout-minutes: 10 + - name: "Mt. Collins served UI: firmware revision, then source.min.js, viewer.html and every script they load, each with its sha256 (reads only)" + id: mtcollins1_ui_bundle_observe + run: | + 'set' '-euo' 'pipefail' + 'umask' '077' + HDR_FILE="$RUNNER_TEMP"'/mtcollins1-ui-bundle-auth-header' + RESP_FILE="$RUNNER_TEMP"'/mtcollins1-ui-bundle-sm.json' + DEST_FILE="$RUNNER_TEMP"'/mtcollins1-ui-bundle-bmc-credential' + 'trap' ''\''rm'\'' '\''-f'\'' "$HDR_FILE" "$RESP_FILE" "$DEST_FILE" + ' 'EXIT' + 'printf' 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + 'curl' '-sSf' '--connect-timeout' '10' '--max-time' '60' '-o' "$RESP_FILE" '-H' '@'"$HDR_FILE" 'https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc/versions/2:access' && 'python3' '-c' 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$DEST_FILE" + 'chmod' '600' "$DEST_FILE" + 'export' 'GUNBC_MTCOLLINS1_BMC_CREDENTIAL_FILE='"$DEST_FILE" + 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/machine_intake/mtcollins1_ui_bundle_observe.dag --function mtcollins1_ui_bundle_observe_wet + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'mtcollins1_ui_bundle_observe' + timeout-minutes: 25 + - name: Upload Mt. Collins served UI files and receipt + id: mtcollins1_ui_bundle_observe_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: mtcollins1-ui-bundle + path: target/mtcollins1-ui-bundle-* + if-no-files-found: error + retention-days: 90 + compression-level: 0 + if: always() && github.event.inputs.mode == 'mtcollins1_ui_bundle_observe' + timeout-minutes: 10 + - name: "Mt. Collins KVM observer, no boot: under the unit hold, log in, establish the viewer, one still, release" + id: mtcollins1_kvm_observer_observe + run: | + 'set' '-euo' 'pipefail' + 'umask' '077' + HDR_FILE="$RUNNER_TEMP"'/mtcollins1-kvm-observer-auth-header' + RESP_FILE="$RUNNER_TEMP"'/mtcollins1-kvm-observer-sm.json' + DEST_FILE="$RUNNER_TEMP"'/mtcollins1-kvm-observer-bmc-credential' + 'trap' ''\''rm'\'' '\''-f'\'' "$HDR_FILE" "$RESP_FILE" "$DEST_FILE" + ' 'EXIT' + 'printf' 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + 'curl' '-sSf' '--connect-timeout' '10' '--max-time' '60' '-o' "$RESP_FILE" '-H' '@'"$HDR_FILE" 'https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc/versions/2:access' && 'python3' '-c' 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$DEST_FILE" + 'chmod' '600' "$DEST_FILE" + 'export' 'GUNBC_MTCOLLINS1_BMC_CREDENTIAL_FILE='"$DEST_FILE" + 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/machine_intake/mtcollins1_kvm_observer_observe.dag --function mtcollins1_kvm_observer_observe_wet + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'mtcollins1_kvm_observer_observe' + timeout-minutes: 18 + - name: "Mt. Collins KVM observer: release this run's observer and its unit hold (always; a no-op if nothing is held)" + id: mtcollins1_kvm_observer_release + run: | + 'set' '-euo' 'pipefail' + 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/machine_intake/mtcollins1_kvm_observer_observe.dag --function mtcollins1_kvm_observer_release_wet + if: always() && github.event.inputs.mode == 'mtcollins1_kvm_observer_observe' + timeout-minutes: 16 + - name: Upload Mt. Collins KVM observer journal, stills and receipt + id: mtcollins1_kvm_observer_observe_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: mtcollins1-kvm-observer + path: artifacts/mtcollins1-boot-kvm-probe-* + if-no-files-found: error + retention-days: 90 + compression-level: 0 + if: always() && github.event.inputs.mode == 'mtcollins1_kvm_observer_observe' + timeout-minutes: 10 + - name: "Fabric writer identity: the login this host presents at the fabric DB's served door (reads only)" + id: fabric_writer_identity_observe + 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/fabric/fabric_writer_identity_observe.dag --function fabric_writer_identity_observe_wet + cat "$ROOT/target/fabric-writer-identity-receipt.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'fabric_writer_identity_observe' + timeout-minutes: 5 + - name: Upload fabric writer identity receipt + id: fabric_writer_identity_observe_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: fabric-writer-identity + path: target/fabric-writer-identity-receipt.txt + if-no-files-found: error + retention-days: 90 + compression-level: 0 + if: always() && github.event.inputs.mode == 'fabric_writer_identity_observe' + timeout-minutes: 10 + - name: "Pair-serving D0 (Cut D): file the consent, wait for the operator, suspend the group's authority for the keyed successor under the admitted grant" + id: pair_serving_d0 + run: |- + # 🟡 dissolve-on: gunbc_ci_pair_serving_d0_invoke - orch-emitted foreign-executor (GitHub Actions run:) credential runner: WIF access token by env, ONE pinned submission-MAC secret version fetched over curl through the shared auth header and SM decode, a 0600 file under RUNNER_TEMP with a trap armed BEFORE the key touches disk. The pipeline steps are modeled (gunbc_ci_pair_serving_d0_prelude) but the runner transport itself remains hand-shell; DISSOLVES WHEN the runner is authored as v2.extdeps.languages.bash_build nodes (typed, grammar-quoted words; command substitution; assignment; pipe; if/test; redirect; || true) emitted by v2.workflow.bash_emit bash_emit_stmts -- a route AVAILABLE today (#12422 retired the sibling pack runner on it), so this migration is unauthored, not blocked on a missing capability; set -euo pipefail, umask, export and trap are ordinary commands on that route, and the trap body is itself built from nodes and passed as one grammar-quoted word, so no new orchestration intent is a prerequisite + set -euo pipefail + umask 077 + HDR_FILE="$RUNNER_TEMP/pair-serving-d0-auth-header" + RESP_FILE="$RUNNER_TEMP/pair-serving-d0-sm.json" + SUB_FILE="$RUNNER_TEMP/approval-submission-mac-key" + trap 'rm -f "$HDR_FILE" "$RESP_FILE" "$SUB_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + if DEST_FILE="$SUB_FILE"; curl -sSf --connect-timeout 10 --max-time 60 -o "$RESP_FILE" -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key/versions/1:access" && python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$DEST_FILE"; then chmod 600 "$SUB_FILE"; export GUNBC_APPROVAL_SUBMISSION_MAC_KEY_FILE="$SUB_FILE"; else rm -f "$SUB_FILE"; echo 'submission MAC key not materialized: a run that must file will refuse there; a held lifecycle resumes without it'; fi + 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/spark/pair_serving_d0_door.dag --function pair_serving_d0_ci_wet + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + GUNBC_D0_CONSENT: ${{ github.event.inputs.d0_consent }} + RLM_EXPECTED_REVISION: ${{ github.event.inputs.expected_revision }} + if: github.event.inputs.mode == 'pair_serving_d0' + timeout-minutes: 47 + - name: "Mt. Collins census image: over fleet SSH to srv2, reproduce the pinned image in scratch, then build and publish the current derivation create-only in /srv/bmc" + id: mtcollins1_census_image_publish + 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/machine_intake/mtcollins1_census_image_publish.dag --function mtcollins1_census_image_publish_wet + cat "$ROOT/target/mtcollins1-census-image.txt" + if: github.event.inputs.mode == 'mtcollins1_census_image_publish' + timeout-minutes: 30 + - name: Upload Mt. Collins census image receipt + id: mtcollins1_census_image_publish_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: mtcollins1-census-image + path: target/mtcollins1-census-image.txt + if-no-files-found: error + retention-days: 30 + if: always() && github.event.inputs.mode == 'mtcollins1_census_image_publish' + timeout-minutes: 10 + - name: "Mt. Collins census image: read the published kernel and initrd back against the stock medium" + id: mtcollins1_census_member_readback + 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/machine_intake/mtcollins1_census_member_readback.dag --function mtcollins1_census_member_readback_wet + cat "$ROOT/target/mtcollins1-census-member-readback.txt" + if: github.event.inputs.mode == 'mtcollins1_census_member_readback' + timeout-minutes: 30 + - name: Upload Mt. Collins census member readback receipt + id: mtcollins1_census_member_readback_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: mtcollins1-census-member-readback + path: target/mtcollins1-census-member-readback.txt + if-no-files-found: error + retention-days: 30 + if: always() && github.event.inputs.mode == 'mtcollins1_census_member_readback' + timeout-minutes: 10 + - name: "Mt. Collins census: read the QEMU, AAVMF and KVM standing of the selected host" + id: mtcollins1_census_qemu_host_observe + 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/machine_intake/mtcollins1_census_qemu_host_observe.dag --function mtcollins1_census_qemu_host_observe_wet + cat "$ROOT/target/mtcollins1-census-qemu-host.txt" + if: github.event.inputs.mode == 'mtcollins1_census_qemu_host_observe' + timeout-minutes: 30 + - name: Upload Mt. Collins census QEMU host standing receipt + id: mtcollins1_census_qemu_host_observe_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: mtcollins1-census-qemu-host + path: target/mtcollins1-census-qemu-host.txt + if-no-files-found: error + retention-days: 30 + if: always() && github.event.inputs.mode == 'mtcollins1_census_qemu_host_observe' + timeout-minutes: 10 + - name: "Mt. Collins census: place the pinned QEMU toolchain under the job user's root and read it back" + id: mtcollins1_census_qemu_toolchain_converge + 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/machine_intake/mtcollins1_census_qemu_toolchain_converge.dag --function mtcollins1_census_qemu_toolchain_converge_wet + cat "$ROOT/target/mtcollins1-census-qemu-host.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'mtcollins1_census_qemu_toolchain_converge' + timeout-minutes: 30 + - name: Upload Mt. Collins census QEMU toolchain converge receipt + id: mtcollins1_census_qemu_toolchain_converge_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: mtcollins1-census-qemu-toolchain + path: target/mtcollins1-census-qemu-host.txt + if-no-files-found: error + retention-days: 30 + if: always() && github.event.inputs.mode == 'mtcollins1_census_qemu_toolchain_converge' + timeout-minutes: 10 + - name: "R2 mint preflight: bootstrap read + account permission-group listing (reachability, no mutation)" + id: r2_mint_preflight + 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_permission_group_observe.dag --function observe_account_permission_groups_wet + cat "$ROOT/target/r2-mint-preflight-permission-groups.txt" + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'r2_mint_preflight' + timeout-minutes: 5 + - name: Upload R2 mint preflight receipt + id: r2_mint_preflight_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: r2-mint-preflight + path: target/r2-mint-preflight-permission-groups.txt + if-no-files-found: error + 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: |- + 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_object_write + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'r2_object_write_mint' + timeout-minutes: 5 + - name: Upload R2 object-write mint receipt (token id + custody version resource; no secret) + id: r2_object_write_mint_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: r2-object-write-mint-receipt + path: /tmp/r2-mint-receipts/${{ github.run_id }}-${{ github.run_attempt }}/r2-*-object-write-mint-receipt.txt + if-no-files-found: warn + retention-days: 30 + if: always() && github.event.inputs.mode == 'r2_object_write_mint' + timeout-minutes: 10 + - name: R2 workspace object-read token mint (AccountTokens.Create + Secret Manager custody) + id: r2_workspace_object_read_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_workspace_object_read + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'r2_workspace_object_read_mint' + timeout-minutes: 5 + - name: Upload R2 workspace object-read mint receipt (token id + custody version resource; no secret) + id: r2_workspace_object_read_mint_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: r2-workspace-object-read-mint-receipt + path: /tmp/r2-mint-receipts/${{ github.run_id }}-${{ github.run_attempt }}/r2-*-object-read-mint-receipt.txt + if-no-files-found: warn + retention-days: 30 + if: always() && github.event.inputs.mode == 'r2_workspace_object_read_mint' + timeout-minutes: 10 + - name: R2 workspace object-write token mint (AccountTokens.Create + Secret Manager custody) + id: r2_workspace_object_write_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_workspace_object_write + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'r2_workspace_object_write_mint' + timeout-minutes: 5 + - name: Upload R2 workspace object-write mint receipt (token id + custody version resource; no secret) + id: r2_workspace_object_write_mint_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: r2-workspace-object-write-mint-receipt + path: /tmp/r2-mint-receipts/${{ github.run_id }}-${{ github.run_attempt }}/r2-*-object-write-mint-receipt.txt + if-no-files-found: warn + retention-days: 30 + if: always() && github.event.inputs.mode == 'r2_workspace_object_write_mint' + timeout-minutes: 10 + - name: R2 cache-blobs object-read token mint (AccountTokens.Create + Secret Manager custody) + id: r2_cache_object_read_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_cache_object_read + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'r2_cache_object_read_mint' + timeout-minutes: 5 + - name: Upload R2 cache-blobs object-read mint receipt (token id + custody version resource; no secret) + id: r2_cache_object_read_mint_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: r2-cache-object-read-mint-receipt + path: /tmp/r2-mint-receipts/${{ github.run_id }}-${{ github.run_attempt }}/r2-*-object-read-mint-receipt.txt + if-no-files-found: warn + retention-days: 30 + if: always() && github.event.inputs.mode == 'r2_cache_object_read_mint' + timeout-minutes: 10 + - name: R2 cache-blobs object-write token mint (AccountTokens.Create + Secret Manager custody) + id: r2_cache_object_write_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_cache_object_write + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'r2_cache_object_write_mint' + timeout-minutes: 5 + - name: Upload R2 cache-blobs object-write mint receipt (token id + custody version resource; no secret) + id: r2_cache_object_write_mint_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: r2-cache-object-write-mint-receipt + path: /tmp/r2-mint-receipts/${{ github.run_id }}-${{ github.run_attempt }}/r2-*-object-write-mint-receipt.txt + if-no-files-found: warn + retention-days: 30 + if: always() && github.event.inputs.mode == 'r2_cache_object_write_mint' + timeout-minutes: 10 + - name: R2 If-Match race probe (two concurrent conditional PUTs per round on one entity tag) + id: r2_conditional_put_race_probe + 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_conditional_put_race_probe.dag --function run_r2_conditional_put_race_probe + cat "$ROOT/target/r2-conditional-put-race-probe-receipt.txt" + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'r2_conditional_put_race_probe' + timeout-minutes: 30 + - name: Upload R2 If-Match race probe receipt (per-round statuses and verdict; no secret) + id: r2_conditional_put_race_probe_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: r2-conditional-put-race-probe-receipt + path: target/r2-conditional-put-race-probe-receipt.txt + if-no-files-found: error + retention-days: 90 + if: always() && github.event.inputs.mode == 'r2_conditional_put_race_probe' + timeout-minutes: 10 + - name: "Workspace source pack: the operator workspace minus credentials and build output, into the workspace bucket by its SHA-256" + id: workspace_source_pack + 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/fabric/workspace_source.dag --function workspace_source_pack_wet + cat "$ROOT/target/workspace-source-pack-receipt.txt" + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'workspace_source_pack' + timeout-minutes: 120 + - name: Upload workspace source pack receipt + id: workspace_source_pack_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: workspace-source-pack + path: target/workspace-source-pack-receipt.txt + if-no-files-found: error + retention-days: 90 + compression-level: 0 + if: always() && github.event.inputs.mode == 'workspace_source_pack' + timeout-minutes: 10 + - name: "Workspace checkpoint stage 0: btrfs snapshot vs kopia scan vs chunk upload and gated commit, timed" + id: workspace_checkpoint_measure + 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/fabric/workspace_checkpoint_instrument.dag --function workspace_checkpoint_measure_wet + cat "$ROOT/target/workspace-checkpoint-measure-receipt.txt" + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + WORKSPACE_SOURCE_OBJECT: ${{ github.event.inputs.workspace_source_object }} + if: github.event.inputs.mode == 'workspace_checkpoint_measure' + timeout-minutes: 120 + - name: Upload workspace checkpoint stage-0 receipt + id: workspace_checkpoint_measure_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: workspace-checkpoint-measure + path: target/workspace-checkpoint-measure-receipt.txt + if-no-files-found: error + retention-days: 90 + compression-level: 0 + if: always() && github.event.inputs.mode == 'workspace_checkpoint_measure' + timeout-minutes: 10 + - name: "Workspace checkpoint stage 1: barrier, uncommitted chunk, destroy, restore from the head closure, byte-equality" + id: workspace_checkpoint_restore + 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/fabric/workspace_checkpoint_instrument.dag --function workspace_checkpoint_restore_wet + cat "$ROOT/target/workspace-checkpoint-restore-receipt.txt" + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'workspace_checkpoint_restore' + timeout-minutes: 120 + - name: Upload workspace checkpoint stage-1 receipt + id: workspace_checkpoint_restore_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: workspace-checkpoint-restore + path: target/workspace-checkpoint-restore-receipt.txt + if-no-files-found: error + retention-days: 90 + compression-level: 0 + if: always() && github.event.inputs.mode == 'workspace_checkpoint_restore' + timeout-minutes: 10 + - name: Materialize approval MAC keys on srv1 (SM pinned -> sha256 prefix -> /etc/gunbc-roadmap 0640) + id: approval_keyring_converge + run: |- + # 🟡 dissolve-on: gunbc_ci_approval_keyring_converge_invoke - orch-emitted foreign-executor (GitHub Actions run:) credential runner: WIF access token by env, two PINNED secret versions (capability and submission MAC keys) fetched over curl, 0600 files under RUNNER_TEMP with a trap armed BEFORE the credentials touch disk. The pipeline steps are modeled (gunbc_ci_approval_keyring_converge_prelude) but the runner transport itself remains hand-shell; DISSOLVES WHEN the runner is authored as v2.extdeps.languages.bash_build nodes (typed, grammar-quoted words; command substitution; assignment; pipe; if/test; redirect; || true) emitted by v2.workflow.bash_emit bash_emit_stmts -- a route AVAILABLE today (#12422 retired the sibling pack runner on it), so this migration is unauthored, not blocked on a missing capability; set -euo pipefail, umask, export and trap are ordinary commands on that route, and the trap body is itself built from nodes and passed as one grammar-quoted word, so no new orchestration intent is a prerequisite + set -euo pipefail + umask 077 + HDR_FILE="$RUNNER_TEMP/approval-keyring-auth-header" + RESP_FILE="$RUNNER_TEMP/approval-keyring-sm.json" + CAP_FILE="$RUNNER_TEMP/approval-capability-mac-key" + SUB_FILE="$RUNNER_TEMP/approval-submission-mac-key" + RCPT_FILE="$RUNNER_TEMP/approval-store-receipt-mac-key" + trap 'rm -f "$HDR_FILE" "$RESP_FILE" "$CAP_FILE" "$SUB_FILE" "$RCPT_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + DEST_FILE="$CAP_FILE"; curl -sSf --connect-timeout 10 --max-time 60 -o "$RESP_FILE" -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-capability-mac-key/versions/1:access" && python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$DEST_FILE" + chmod 600 "$CAP_FILE" + DEST_FILE="$SUB_FILE"; curl -sSf --connect-timeout 10 --max-time 60 -o "$RESP_FILE" -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key/versions/1:access" && python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$DEST_FILE" + chmod 600 "$SUB_FILE" + DEST_FILE="$RCPT_FILE"; curl -sSf --connect-timeout 10 --max-time 60 -o "$RESP_FILE" -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-store-receipt-mac-key/versions/1:access" && python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$DEST_FILE" + chmod 600 "$RCPT_FILE" + export GUNBC_APPROVAL_CAPABILITY_MAC_KEY_FILE="$CAP_FILE" + export GUNBC_APPROVAL_SUBMISSION_MAC_KEY_FILE="$SUB_FILE" + export GUNBC_APPROVAL_STORE_RECEIPT_MAC_KEY_FILE="$RCPT_FILE" + 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/auth/approval_keyring_converge.dag --function approval_keyring_converge_wet + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'approval_keyring_converge' + timeout-minutes: 5 + - name: Issue a device enrolment code on srv1 and deliver it to the operator channel (receipt only in this log) + id: approval_device_enrolment_code_issue + 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/auth/approval_device_enrolment_code_issue.dag --function approval_device_enrolment_code_issue_wet + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + RLM_EXPECTED_REVISION: ${{ github.event.inputs.expected_revision }} + if: github.event.inputs.mode == 'approval_device_enrolment_code_issue' + timeout-minutes: 15 + - name: "microVM runner group: read, file one approval if absent, create restricted to the shakedown workflow, read back" + id: microvm_runner_group_ensure + run: |- + # 🟡 dissolve-on: gunbc_ci_microvm_runner_group_ensure_invoke - orch-emitted foreign-executor (GitHub Actions run:) credential runner: the org-admin installation token prelude (gunbc_ci_org_admin_app_token_prelude) followed by ONE pinned submission-MAC secret version fetched over curl through the shared auth header and SM decode, a 0600 file under RUNNER_TEMP with the mint's cleanup extended in the EXIT trap. The pipeline steps are modeled (gunbc_ci_microvm_runner_group_ensure_prelude) but the runner transport itself remains hand-shell; DISSOLVES WHEN the runner is authored as v2.extdeps.languages.bash_build nodes (typed, grammar-quoted words; command substitution; assignment; pipe; if/test; redirect; || true) emitted by v2.workflow.bash_emit bash_emit_stmts -- a route AVAILABLE today (#12422 retired the sibling pack runner on it), so this migration is unauthored, not blocked on a missing capability; set -euo pipefail, umask, export and trap are ordinary commands on that route, and the trap body is itself built from nodes and passed as one grammar-quoted word, so no new orchestration intent is a prerequisite + set -euo pipefail + umask 077 + KEY_FILE="$RUNNER_TEMP/org-admin-app-key" + HDR_FILE="$RUNNER_TEMP/org-admin-auth-header" + JWT_FILE="$RUNNER_TEMP/org-admin-app-jwt" + TOKEN_HEADERS="$RUNNER_TEMP/org-admin-token-headers" + TOKEN_BODY="$RUNNER_TEMP/org-admin-token-body" + cleanup() { rm -f "$KEY_FILE" "$HDR_FILE" "$JWT_FILE" "$TOKEN_HEADERS" "$TOKEN_BODY" "$RUNNER_TEMP/org-admin-app-key-sm-body"; unset GUNBC_ORG_ADMIN_TOKEN GH_TOKEN; } + trap cleanup EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + SM_BODY="$RUNNER_TEMP/org-admin-app-key-sm-body" + SM_CODE="$(curl -sS -o "$SM_BODY" -w '%{http_code}' -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/ci-github-app-private-key/versions/latest:access")" + rm -f "$HDR_FILE" + if [ "$SM_CODE" != 200 ]; then rm -f "$SM_BODY"; echo "OrgAdminAppKeyUnreadable: Secret Manager answered HTTP $SM_CODE for projects/gunbai-secrets/secrets/ci-github-app-private-key/versions/latest to the fleet-cloud-convergence principal. 403 means the version exists and is not readable OR does not exist -- REMEDY: run org_admin_app_key_access_converge_with_supplied_token (gunbc.fleet.org_actions_converge) with a control-plane token to bind roles/secretmanager.secretAccessor, and confirm the version exists. Minting NO token." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$SM_BODY" > "$KEY_FILE" + rm -f "$SM_BODY" + chmod 600 "$KEY_FILE" + NOW=$(date +%s) + b64url() { base64 -w0 | tr '+/' '-_' | tr -d '='; } + JWT_HEADER=$(printf '%s' '{"alg":"RS256","typ":"JWT"}' | b64url) + JWT_PAYLOAD=$(printf '{"iat":%d,"exp":%d,"iss":"%s"}' "$((NOW-60))" "$((NOW+540))" "2653532" | b64url) + JWT_SIG=$(printf '%s.%s' "$JWT_HEADER" "$JWT_PAYLOAD" | openssl dgst -sha256 -sign "$KEY_FILE" | b64url) + rm -f "$KEY_FILE" + printf 'Authorization: Bearer %s.%s.%s\n' "$JWT_HEADER" "$JWT_PAYLOAD" "$JWT_SIG" > "$JWT_FILE" + curl -sS -X POST -H @"$JWT_FILE" -H 'Accept: application/vnd.github+json' -D "$TOKEN_HEADERS" -o "$TOKEN_BODY" "https://api.github.com/app/installations/104134109/access_tokens" + rm -f "$JWT_FILE" + if ! head -n1 "$TOKEN_HEADERS" | grep -q ' 201 '; then echo "OrgAdminInstallationTokenRefused: GitHub answered $(head -n1 "$TOKEN_HEADERS" | tr -d '\r') for the gunbai-ci installation; a revoked key, a removed installation, or a missing Self-hosted-runners permission each look like this -- inspect the App's installation on the organization" >&2; exit 1; fi + GUNBC_ORG_ADMIN_TOKEN="$(python3 -c 'import sys,json; print(json.load(open(sys.argv[1]))["token"])' "$TOKEN_BODY")" + rm -f "$TOKEN_HEADERS" "$TOKEN_BODY" + export GUNBC_ORG_ADMIN_TOKEN + export GH_TOKEN="$GUNBC_ORG_ADMIN_TOKEN" + echo "org-admin: installation token minted in-run for app gunbai-ci installation 104134109 (one-hour lifetime; key wiped; token held in this step's environment only)" + HDR_FILE="$RUNNER_TEMP/microvm-runner-group-auth-header" + RESP_FILE="$RUNNER_TEMP/microvm-runner-group-sm.json" + SUB_FILE="$RUNNER_TEMP/approval-submission-mac-key" + trap 'cleanup; rm -f "$HDR_FILE" "$RESP_FILE" "$SUB_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + DEST_FILE="$SUB_FILE"; curl -sSf --connect-timeout 10 --max-time 60 -o "$RESP_FILE" -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key/versions/1:access" && python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$DEST_FILE" || { echo 'ApprovalSubmissionMacKeyNotMaterialized: the pinned submission MAC key version could not be fetched and decoded; no request can be filed' >&2; exit 1; } + chmod 600 "$SUB_FILE" + rm -f "$HDR_FILE" "$RESP_FILE" + export GUNBC_APPROVAL_SUBMISSION_MAC_KEY_FILE="$SUB_FILE" + mkdir -p "$GITHUB_WORKSPACE/artifacts" + 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/runner/runner_group_restriction_ensure_run.dag --function microvm_runner_group_ensure_wet + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'microvm_runner_group_ensure' + timeout-minutes: 36 + - name: Upload the microVM runner group ensure receipt + id: microvm_runner_group_ensure_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: microvm-runner-group-ensure-receipts + path: artifacts/microvm-runner-group-ensure-* + if-no-files-found: warn + retention-days: 30 + if: always() && github.event.inputs.mode == 'microvm_runner_group_ensure' + timeout-minutes: 10 + - name: "Runner micro-VM guest image: observe base artifacts and their digests" + id: guest_image_observe + 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/runner/runner_guest_image.dag --function runner_guest_image_observe_wet + cat "$ROOT/target/runner-guest-image-standing.txt" + if: github.event.inputs.mode == 'guest_image_observe' + timeout-minutes: 30 + - name: "Runner micro-VM guest image: build the image and digest what was built" + id: guest_image_converge + 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/runner/runner_guest_image.dag --function runner_guest_image_converge_wet + cat "$ROOT/target/runner-guest-image-standing.txt" + if: github.event.inputs.mode == 'guest_image_converge' + timeout-minutes: 30 + - name: Upload runner micro-VM guest image standing receipt + id: guest_image_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: runner-guest-image-standing + path: target/runner-guest-image-standing.txt + if-no-files-found: error + retention-days: 30 + if: always() && (github.event.inputs.mode == 'guest_image_observe' || github.event.inputs.mode == 'guest_image_converge') + timeout-minutes: 10 + - name: "Runner micro-VM host: install the cited Firecracker release and re-observe" + id: microvm_host_converge + 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/runner/runner_microvm_host_ready.dag --function runner_microvm_host_converge_wet + cat "$ROOT/target/runner-microvm-host-standing.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'microvm_host_converge' + timeout-minutes: 30 + - name: Upload runner micro-VM host standing receipt + id: microvm_host_standing_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: runner-microvm-host-standing + path: target/runner-microvm-host-standing.txt + if-no-files-found: error + retention-days: 30 + if: always() && (github.event.inputs.mode == 'microvm_host_converge') + timeout-minutes: 10 + - name: "Runner micro-VM network: read the slot taps, ruleset and forwarding back into a receipt (installs nothing)" + id: microvm_network_observe + 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/runner/runner_microvm_network_observe.dag --function runner_microvm_network_observe_wet + cat "$ROOT/target/microvm-network-observe.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'microvm_network_observe' + timeout-minutes: 30 + - name: Upload runner micro-VM network converge receipt + id: microvm_network_observe_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: runner-microvm-network-observe-receipt + path: target/microvm-network-observe.txt + if-no-files-found: error + retention-days: 30 + if: always() && (github.event.inputs.mode == 'microvm_network_observe') + timeout-minutes: 10 + - name: "Runner micro-VM network: stage and install the slot and host network files as the host administrator, then read the ruleset back" + id: microvm_network_apply + 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/runner/runner_microvm_network_apply.dag --function runner_microvm_network_apply_ci_wet + cat "$ROOT/target/microvm-network-apply-receipt.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + RLM_EXPECTED_REVISION: ${{ github.event.inputs.expected_revision }} + if: github.event.inputs.mode == 'microvm_network_apply' + timeout-minutes: 30 + - name: Upload runner micro-VM network apply receipt + id: microvm_network_apply_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: runner-microvm-network-apply-receipt + path: target/microvm-network-apply-receipt.txt + if-no-files-found: error + retention-days: 30 + if: always() && (github.event.inputs.mode == 'microvm_network_apply') + timeout-minutes: 10 + - name: "Runner micro-VM: boot the guest image and read the serial console" + id: microvm_boot_probe + 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/runner/runner_microvm_boot_probe.dag --function runner_microvm_boot_probe_wet + cat "$ROOT/target/runner-microvm-boot-probe.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'microvm_boot_probe' + timeout-minutes: 30 + - name: Upload runner micro-VM boot probe console receipt + id: microvm_boot_probe_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: runner-microvm-boot-probe + path: target/runner-microvm-boot-probe.txt + if-no-files-found: error + retention-days: 30 + if: always() && (github.event.inputs.mode == 'microvm_boot_probe') + timeout-minutes: 10 + - name: "Runner micro-VM: reserve the shakedown cell through its broker unit and read the reservation" + id: microvm_slot_reserve + 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/runner/runner_microvm_slot_reserve.dag --function microvm_slot_reserve_ci_wet + cat "$ROOT/target/microvm-slot-reserve-receipt.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'microvm_slot_reserve' + timeout-minutes: 50 + - name: Upload the microVM slot reserve receipt + id: microvm_slot_reserve_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: microvm-slot-reserve + path: target/microvm-slot-reserve-receipt.txt + if-no-files-found: error + retention-days: 30 + if: always() && (github.event.inputs.mode == 'microvm_slot_reserve') + timeout-minutes: 10 + - name: "Runner micro-VM: start the shakedown slot's controller unit once and read its receipt" + id: microvm_slot_start + 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/runner/runner_microvm_slot_start.dag --function microvm_slot_start_ci_wet + cat "$ROOT/target/microvm-slot-start-receipt.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'microvm_slot_start' + timeout-minutes: 41 + - name: Upload the microVM slot start receipt + id: microvm_slot_start_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: microvm-slot-start + path: target/microvm-slot-start-receipt.txt + if-no-files-found: error + retention-days: 30 + if: always() && (github.event.inputs.mode == 'microvm_slot_start') + timeout-minutes: 10 + - name: Spark managed grant reconcile (materializes its own administrator credential in-run, installs only the grants measured missing at the named target, removes the credential within this same step) + id: spark_grants + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/managed_grant_install.dag --function spark_grant_install_ci_wet + cat "$ROOT/target/spark-grant-install-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'spark_grants' + timeout-minutes: 60 + - name: Spark managed-access bootstrap (creates gunbc-automation, installs the fleet key, proves key-only auth, lays the narrow grants; one target per run) + id: spark_bootstrap + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/managed_access_apply.dag --function spark_managed_access_bootstrap_ci_wet + cat "$ROOT/target/spark-managed-access-apply-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'spark_bootstrap' + timeout-minutes: 60 + - name: Spark pair serving apply (promoted fabric groups' vLLM units over the password session, workers before heads) + id: spark_serving_apply + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/pair_serving_apply.dag --function spark_pair_serving_apply_wet + cat "$ROOT/target/spark-pair-apply-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'spark_serving_apply' + timeout-minutes: 60 + - name: "Spark native serving apply (group B's native four-rank arm as one bounded transaction over the password session: all-host preflight that mutates nothing, the incumbent unit preserved, head before workers, a readback of the complete realization bound to each rank's own incarnation, then commit or roll the whole arm back)" + id: spark_native_serving_apply + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/native_serving_apply.dag --function spark_native_serving_apply_wet + cat "$ROOT/target/spark-native-apply-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'spark_native_serving_apply' + timeout-minutes: 60 + - name: Spark runtime image probe (pull the pinned image on the selected Spark and ask it, inside its own digest, what it registers and what it admits) + id: spark_runtime_image_probe + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/v41_runtime_image_probe.dag --function v41_published_image_probe_ci_wet + cat "$ROOT/target/v41-published-image-probe-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'spark_runtime_image_probe' + timeout-minutes: 60 + - name: Spark wireless link converge (Wi-Fi power save off at runtime and in the connection profile, with readback) + id: spark_wireless_link_converge + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/fleet/fleet_wireless_link_converge.dag --function spark_wireless_link_converge_ci_wet + cat "$ROOT/target/spark-wireless-link-converge-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'spark_wireless_link_converge' + timeout-minutes: 60 + - name: Spark grant observe (every procured Spark's sudo grant listing and sudoers drop-in shape, read-only) + id: spark_grants_observe + 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/fleet/fleet_multi_principal_probe.dag --function spark_exact_grant_probe_ci_wet + cat "$ROOT/target/spark-exact-grant-probe-receipt.txt" + if: github.event.inputs.mode == 'spark_grants_observe' + timeout-minutes: 60 + - name: Spark V4.1 checkpoint materialize (fetch the admitted published files onto the selected Group A Spark, verify each sha256 before publishing, read the storage-backed Engram spans) + id: spark_v41_checkpoint_materialize + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/v41_checkpoint_materialize.dag --function v41_checkpoint_materialize_ci_wet + cat "$ROOT/target/v41-checkpoint-materialize-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'spark_v41_checkpoint_materialize' + timeout-minutes: 60 + - name: Spark V4.1 index selection (verify the pinned index on the selected Group A Spark and read its .engram. selection with parse_json_document, in its own process) + id: spark_v41_index_selection + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/v41_checkpoint_materialize.dag --function v41_index_selection_ci_wet + cat "$ROOT/target/v41-index-selection-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: always() && github.event.inputs.mode == 'spark_v41_checkpoint_materialize' + timeout-minutes: 60 + - name: Spark V4.1 row store encode (encode the eight Engram row stores from the verified shards on the selected Group A Spark and read each store's sha256) + id: spark_v41_row_store_encode + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/v41_row_store_encode_run.dag --function v41_row_store_encode_ci_wet + cat "$ROOT/target/v41-row-store-encode-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'spark_v41_row_store_encode' + timeout-minutes: 60 + - name: Spark V4.1 row store readback (read the eight Engram row stores' header, first and last record and the published source rows at every rank seam on the selected Group A Spark, writing nothing) + id: spark_v41_row_store_readback + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/v41_row_store_readback_run.dag --function v41_row_store_readback_ci_wet + cat "$ROOT/target/v41-row-store-readback-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'spark_v41_row_store_readback' + timeout-minutes: 60 + - name: Spark V4.1 Engram differential (upstream lookup kernel vs the design-B file-backed lookup over sampled real rows of every row store, byte for byte, on the selected Group A Spark, read-only) + id: spark_v41_engram_differential + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/v41_engram_differential_run.dag --function v41_engram_differential_ci_wet + cat "$ROOT/target/v41-engram-differential-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'spark_v41_engram_differential' + timeout-minutes: 60 + - name: "Spark runtime image produce (the image named by the closed runtime_image input, gunbc.spark.runtime_image_produce_dispatch: built on the selected Group A Spark, read back by digest and probed inside that digest)" + id: spark_runtime_image_produce + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/runtime_image_produce_dispatch.dag --function runtime_image_produce_ci_wet + cat "$ROOT/target/runtime-image-produce-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + GUNBC_RUNTIME_IMAGE: ${{ github.event.inputs.runtime_image }} + if: github.event.inputs.mode == 'spark_runtime_image_produce' + timeout-minutes: 180 + - name: Spark runtime image distribute (the produced image named by the closed runtime_image input, by its configuration digest, from the host its production receipt names to the selected Spark of its group, read back there) + id: spark_runtime_image_distribute + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/runtime_image_produce_dispatch.dag --function runtime_image_distribute_ci_wet + cat "$ROOT/target/runtime-image-distribute-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + GUNBC_RUNTIME_IMAGE: ${{ github.event.inputs.runtime_image }} + if: github.event.inputs.mode == 'spark_runtime_image_distribute' + timeout-minutes: 120 + - name: Spark arm checkpoint materialize (the checkpoint named by the closed checkpoint input, adopted or fetched into the target Spark's Hub cache and verified) + id: spark_arm_checkpoint_materialize + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/arm_group_launch_dispatch.dag --function arm_checkpoint_materialize_ci + cat "$ROOT/target/arm-checkpoint-materialize-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + GUNBC_ARM_LAUNCH: ${{ github.event.inputs.arm }} + GUNBC_ARM_STAIRCASE_STEP: ${{ github.event.inputs.staircase_step }} + GUNBC_ARM_INCUMBENT: ${{ github.event.inputs.incumbent }} + GUNBC_ARM_CHECKPOINT: ${{ github.event.inputs.checkpoint }} + GUNBC_RUNTIME_IMAGE: ${{ github.event.inputs.runtime_image }} + if: github.event.inputs.mode == 'spark_arm_checkpoint_materialize' + timeout-minutes: 180 + - name: "Spark arm group OBSERVE (read-only: every host's running units, containers and GPU processes with their owning cgroups; nothing is changed)" + id: spark_arm_group_observe + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/arm_group_launch_dispatch.dag --function arm_group_observe_ci + cat "$ROOT/target/arm-group-observe-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + GUNBC_ARM_LAUNCH: ${{ github.event.inputs.arm }} + GUNBC_ARM_STAIRCASE_STEP: ${{ github.event.inputs.staircase_step }} + GUNBC_ARM_INCUMBENT: ${{ github.event.inputs.incumbent }} + if: github.event.inputs.mode == 'spark_arm_group_observe' + timeout-minutes: 60 + - name: Spark arm group launch PLAN (the chosen arm's plan, units, scripts, declared incumbent and admission verdict; no host is touched) + id: spark_arm_group_launch_plan + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/arm_group_launch_dispatch.dag --function arm_group_launch_plan_ci + cat "$ROOT/target/arm-group-launch-plan.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + GUNBC_ARM_LAUNCH: ${{ github.event.inputs.arm }} + GUNBC_ARM_STAIRCASE_STEP: ${{ github.event.inputs.staircase_step }} + GUNBC_ARM_INCUMBENT: ${{ github.event.inputs.incumbent }} + if: github.event.inputs.mode == 'spark_arm_group_launch_plan' + timeout-minutes: 60 + - name: Spark arm group launch (the chosen arm's reads and admission, its pre-apply, the declared incumbent stopped and the hosts re-read vacant, then the arm applied as one transaction; the incumbent restored on anything but a commit) + id: spark_arm_group_launch + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/arm_group_launch_dispatch.dag --function arm_group_launch_ci_wet + cat "$ROOT/target/arm-group-launch-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + GUNBC_ARM_LAUNCH: ${{ github.event.inputs.arm }} + GUNBC_ARM_STAIRCASE_STEP: ${{ github.event.inputs.staircase_step }} + GUNBC_ARM_INCUMBENT: ${{ github.event.inputs.incumbent }} + if: github.event.inputs.mode == 'spark_arm_group_launch' + timeout-minutes: 110 + - name: Spark arm group load (the staircase step named by the closed inputs, loaded at its cold floor and its ceiling from its head; stop rules judged; calibration receipt folded) + id: spark_arm_group_load + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/arm_group_launch_dispatch.dag --function arm_group_load_ci + cat "$ROOT/target/arm-group-load-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + GUNBC_ARM_LAUNCH: ${{ github.event.inputs.arm }} + GUNBC_ARM_STAIRCASE_STEP: ${{ github.event.inputs.staircase_step }} + GUNBC_ARM_INCUMBENT: ${{ github.event.inputs.incumbent }} + if: github.event.inputs.mode == 'spark_arm_group_load' + timeout-minutes: 60 + - name: "Spark V4.1 serving-load staircase (shared serving-load runner: per concurrency scrape, vllm bench serve, scrape, with rank pressure on srv5-8; the staircase's stop rules over the worst rank)" + id: spark_v41_serving_load + run: |- + set -euo pipefail + umask 077 + CRED_FILE="$RUNNER_TEMP/spark-administrator-credential" + RESP_FILE="$RUNNER_TEMP/spark-administrator-response.json" + HDR_FILE="$RUNNER_TEMP/spark-administrator-auth-header" + trap 'rm -f "$CRED_FILE" "$RESP_FILE" "$HDR_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + case "${SPARK_CONVERGE_TARGET:-}" in srv5) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv6) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv7) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv8) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv9) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv10) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv11) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; srv12) SM_URL="https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/spark-administrator-password/versions/1:access"; SM_RES="projects/gunbai-secrets/secrets/spark-administrator-password/versions/1" ;; *) echo "SparkAdministratorCredentialHostUnrostered: SPARK_CONVERGE_TARGET='${SPARK_CONVERGE_TARGET:-}' names no host in the administrator credential roster; contacting NO host" >&2; exit 1 ;; esac + HTTP_CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -H @"$HDR_FILE" "$SM_URL" || echo 000)" + if [ "$HTTP_CODE" = 403 ]; then echo "SparkAdministratorCredentialUnreadable: $SM_RES answered 403 to serviceAccount:fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com. Secret Manager returns 403 both when a version exists and is not readable AND when it does not exist, so this run cannot tell those apart -- REMEDY EITHER WAY: confirm the version exists and grant that principal roles/secretmanager.secretAccessor on it. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" = 404 ]; then echo "SparkAdministratorCredentialAbsent: $SM_RES answered 404; the modeled reference names a version Secret Manager does not hold. Contacting NO host." >&2; exit 1; fi + if [ "$HTTP_CODE" != 200 ]; then echo "SparkAdministratorCredentialFetchFailed: HTTP $HTTP_CODE from $SM_RES; contacting NO host." >&2; exit 1; fi + python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$CRED_FILE" + rm -f "$RESP_FILE" + chmod 600 "$CRED_FILE" + if [ ! -s "$CRED_FILE" ]; then echo "SparkAdministratorCredentialEmpty: $SM_RES returned 200 with an empty payload, which is an enrolled-but-blank secret and never a blank password; contacting NO host" >&2; exit 1; fi + export GUNBC_SPARK_BOOTSTRAP_CREDENTIAL_FILE="$CRED_FILE" + echo "spark-admin-credential: materialized from $SM_RES to a 0600 run-local file, consumed and removed within this one step" + 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/spark/v41_serving_load.dag --function v41_serving_load_ci_wet + cat "$ROOT/target/v41-serving-load-receipt.txt" + env: + SPARK_CONVERGE_TARGET: ${{ github.event.inputs.target }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'spark_v41_serving_load' + timeout-minutes: 60 + - name: "Runner host files: observe the teardown drop-in, the needrestart deferral and the loaded teardown (no writes)" + id: runner_host_file_observe + 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/runner/runner_host_file_converge.dag --function runner_host_file_observe_ci_wet + cat "$ROOT/target/runner-host-file-converge-receipt.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'runner_host_file_observe' + timeout-minutes: 15 + - name: "Runner host files: write what differs as the administrator, reload if the drop-in changed, read the loaded teardown back" + id: runner_host_file_converge + 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/runner/runner_host_file_converge.dag --function runner_host_file_converge_ci_wet + cat "$ROOT/target/runner-host-file-converge-receipt.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'runner_host_file_converge' + timeout-minutes: 15 + - name: "Password-session tools: ensure every tool password_session_host_cli_requirements enrolls is present on this runner, installing only what is measured absent" + id: runner_password_session_tool_converge + 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/runner/runner_password_session_tool_converge.dag --function runner_password_session_tool_converge_ci_wet + cat "$ROOT/target/runner-password-session-tool-converge-receipt.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'runner_password_session_tool_converge' + timeout-minutes: 15 + - name: Upload password-session tool converge receipt + id: runner_password_session_tool_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: runner-password-session-tool-converge-receipt + path: target/runner-password-session-tool-converge-receipt.txt + if-no-files-found: error + retention-days: 30 + if: always() && (github.event.inputs.mode == 'runner_password_session_tool_converge') + timeout-minutes: 10 + - name: "Browser toolchain: apt host libraries as the administrator, digest-pinned node/Playwright/Chromium archives into the job user's root, then read back digests, versions, libraries and a headless render" + id: runner_browser_toolchain_converge + 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/runner/runner_browser_toolchain.dag --function runner_browser_toolchain_converge_ci_wet + cat "$ROOT/target/runner-browser-toolchain-converge-receipt.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'runner_browser_toolchain_converge' + timeout-minutes: 15 + - name: Upload browser toolchain converge receipt + id: runner_browser_toolchain_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: runner-browser-toolchain-converge-receipt + path: target/runner-browser-toolchain-converge-receipt.txt + if-no-files-found: error + retention-days: 30 + if: always() && (github.event.inputs.mode == 'runner_browser_toolchain_converge') + timeout-minutes: 10 + - name: "Host credential custody: deliver the chosen rostered credential from Secret Manager at its row's owner, group and mode, read owner, mode, directory, bytes and staging back" + id: host_credential_custody_converge + 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/fleet/host_credential_custody_converge.dag --function host_credential_custody_converge_ci_wet + cat "$ROOT/target/host-credential-custody-converge-receipt.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + FLEET_CONVERGE_CUSTODY_CREDENTIAL: ${{ github.event.inputs.credential }} + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'host_credential_custody_converge' + timeout-minutes: 15 + - name: Upload host credential custody receipt + id: host_credential_custody_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: host-credential-custody-converge-receipt + path: target/host-credential-custody-converge-receipt.txt + if-no-files-found: error + retention-days: 30 + if: always() && (github.event.inputs.mode == 'host_credential_custody_converge') + timeout-minutes: 10 + - name: Upload runner host file converge receipt + id: runner_host_file_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: runner-host-file-converge-receipt + path: target/runner-host-file-converge-receipt.txt + if-no-files-found: error + retention-days: 30 + if: always() && (github.event.inputs.mode == 'runner_host_file_observe' || github.event.inputs.mode == 'runner_host_file_converge') + timeout-minutes: 10 + - name: "Site PXE edge: observe the chainloader digest, dnsmasq, the edge config and unit (no writes)" + id: site_pxe_edge_observe + 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/fleet/site_pxe_edge_converge.dag --function site_pxe_edge_observe_ci_wet + cat "$ROOT/target/site-pxe-edge-converge-receipt.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'site_pxe_edge_observe' + timeout-minutes: 15 + - name: "Site PXE edge: if the gate is open, write what differs, restart the unit, read its active state back" + id: site_pxe_edge_converge + 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/fleet/site_pxe_edge_converge.dag --function site_pxe_edge_converge_ci_wet + cat "$ROOT/target/site-pxe-edge-converge-receipt.txt" + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + if: github.event.inputs.mode == 'site_pxe_edge_converge' + timeout-minutes: 15 + - name: Upload site PXE edge converge receipt + id: site_pxe_edge_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: site-pxe-edge-converge-receipt + path: target/site-pxe-edge-converge-receipt.txt + if-no-files-found: error + retention-days: 30 + if: always() && (github.event.inputs.mode == 'site_pxe_edge_observe' || github.event.inputs.mode == 'site_pxe_edge_converge') + timeout-minutes: 10 + - name: Kill the in-run ssh-agent (key never outlives the job) + run: | + if [ -n "${SSH_AGENT_PID:-}" ]; then ssh-agent -k || true; fi + if: always() + timeout-minutes: 5 + mtcollins1-boot: + runs-on: [self-hosted, linux, arm64, srv1] + environment: mtcollins1-boot + timeout-minutes: 184 + if: github.event.inputs.mode == 'mtcollins1_boot' + permissions: + contents: read + actions: read + id-token: write + concurrency: + group: gunbc-host-mutation-srv1 + cancel-in-progress: false + steps: + - name: Checkout (the event sha only; no dispatch input selects these bytes) + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + fetch-depth: 0 + ref: ${{ github.sha }} + - name: Isolate toolchain dirs + run: |- + rm -rf "$RUNNER_TEMP/rustup" "$RUNNER_TEMP/cargo" + echo "HOME=$RUNNER_TEMP" >> "$GITHUB_ENV" + echo "CARGO_HOME=$RUNNER_TEMP/cargo" >> "$GITHUB_ENV" + echo "RUSTUP_HOME=$RUNNER_TEMP/rustup" >> "$GITHUB_ENV" + echo "MAKEFLAGS=" >> "$GITHUB_ENV" + echo "CARGO_BUILD_JOBS=6" >> "$GITHUB_ENV" + if sccache --show-stats >/dev/null 2>&1; then + echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" + echo "CARGO_INCREMENTAL=0" >> "$GITHUB_ENV" + printf '%s\n' "CiSccacheProviderReceipt: provider=bound catalog=sccache_local" + else + printf '%s\n' "CiSccacheProviderReceipt: provider=SKIPPED catalog=sccache_local host=$(hostname) cause=daemon-unavailable; the release build runs UNCACHED — a counted degradation, not a supported mode (host_build_cache_provision P1b should have converged this host)" + fi + - name: Setup Rust + uses: actions-rust-lang/setup-rust-toolchain@2b1f5e9b395427c92ee4e3331786ca3c37afe2d7 + with: + components: rustfmt + cache: false + rustflags: -D warnings + env: + HOME: ${{ runner.temp }} + - name: Derive native-cache root (rustc-version segment; after setup-rust-toolchain) + run: |- + # 🟡 dissolve-on: ci_native_cache_root_script — orch-emitted foreign-executor prelude step deriving GUNBC_NATIVE_CACHE_ROOT from rustc -V after setup-rust-toolchain; leaf rustc/tr/mkdir/echo strings remain until typed toolchain probe lands on host_effect_apply + if ! GUNBC_TOOLCHAIN_SEG_RAW=$(rustc -V 2>/dev/null); then echo "::error::rustc -V failed after setup-rust-toolchain: cannot derive native-cache toolchain segment"; exit 1; fi + GUNBC_TOOLCHAIN_SEG=$(echo "$GUNBC_TOOLCHAIN_SEG_RAW" | tr ' ' '-') + if [ -z "$GUNBC_TOOLCHAIN_SEG" ]; then echo "::error::rustc -V produced an empty native-cache toolchain segment after setup-rust-toolchain"; exit 1; fi + mkdir -p "$RUNNER_TOOL_CACHE/gunbc-native/$GUNBC_TOOLCHAIN_SEG" + echo "GUNBC_NATIVE_CACHE_ROOT=$RUNNER_TOOL_CACHE/gunbc-native/$GUNBC_TOOLCHAIN_SEG" >> "$GITHUB_ENV" + - name: Pin rustup default (isolated RUSTUP_HOME has no default toolchain) + run: |- + # dissolve-on: ci_pin_rustup_default_script -- orch-emitted foreign-executor step selecting a rustup default toolchain inside an isolated RUSTUP_HOME, which starts with none, and resolving the cargo binary that selection implies. The leaf rustup/command/echo strings remain until a typed TOOLCHAIN-SELECTION effect lands on host_effect_apply -- NOT the filesystem-and-environment effect ci_toolchain_home_isolation_script waits on, which is why this is a separate obligation: that effect landing alone would leave this carrier standing + rustup default "$(rustup show active-toolchain | awk '{print $1; exit}')" + if [ -x "$CARGO_HOME/bin/cargo" ]; then CARGO_BIN="$CARGO_HOME/bin/cargo"; else CARGO_BIN="$(command -v cargo || true)"; fi + if [ -z "$CARGO_BIN" ]; then echo "::error::no cargo binary: neither the isolated $CARGO_HOME/bin/cargo shim nor PATH carries one"; exit 1; fi + echo "CARGO_BIN=$CARGO_BIN" >> "$GITHUB_ENV" + - name: Build the compiler pair this boot runs (claim_executor + gunbc) + id: pair_build + run: | + cargo build --release -p v1-compiler --bin claim_executor --bin gunbc + timeout-minutes: 45 + - name: "Mt. Collins boot admission (credential-free): the dispatch names the unit hold store host" + id: mtcollins1_boot_admit + 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/machine_intake/mtcollins1_boot_admission.dag --function mtcollins1_boot_admit_executor + env: + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + timeout-minutes: 5 + - name: WIF auth (OIDC -> dedicated mtcollins1-boot pool -> mtcollins1-boot SA, access token only) + id: wif_auth + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 + with: + workload_identity_provider: projects/582015116396/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers/github-mtcollins1-boot-oidc + service_account: mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com + token_format: access_token + create_credentials_file: false + timeout-minutes: 5 + - name: Materialize fleet key in-run (SM versions/1 pinned -> RUNNER_TEMP 0600 -> ssh-agent -> wipe file) + run: |- + # 🟡 dissolve-on: gunbc_ci_fleet_key_agent_script - orch-emitted foreign-executor (GitHub Actions run:) credential runner: WIF access token by env, one PINNED secret version fetched over curl, a 0600 key file under RUNNER_TEMP with a trap armed BEFORE the credential touches disk, fingerprint verified against the modeled authority, ssh-agent load, file wipe. The pipeline steps are modeled (gunbc_ci_fleet_key_agent_prelude) but the runner transport itself remains hand-shell; DISSOLVES WHEN the runner is authored as v2.extdeps.languages.bash_build nodes (typed, grammar-quoted words; command substitution; assignment; pipe; if/test; redirect; || true) emitted by v2.workflow.bash_emit bash_emit_stmts -- a route AVAILABLE today (#12422 retired the sibling pack runner on it), so this migration is unauthored, not blocked on a missing capability; set -euo pipefail, umask, export and trap are ordinary commands on that route, and the trap body is itself built from nodes and passed as one grammar-quoted word, so no new orchestration intent is a prerequisite + set -euo pipefail + umask 077 + KEY_FILE="$RUNNER_TEMP/fleet-automation-key" + HDR_FILE="$RUNNER_TEMP/fleet-automation-auth-header" + AGENT_STARTED=0 + cleanup() { rm -f "$KEY_FILE" "$HDR_FILE"; if [ "$AGENT_STARTED" = 1 ] && [ -n "${SSH_AGENT_PID:-}" ]; then ssh-agent -k >/dev/null 2>&1 || true; fi; } + trap cleanup EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + curl -sSf -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key/versions/1:access" | python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(sys.stdin)["payload"]["data"]))' > "$KEY_FILE" + rm -f "$HDR_FILE" + chmod 600 "$KEY_FILE" + EXPECTED_FP="SHA256:mGT7qJsh36VsVb8OPh3m8br3oHedQHxRukRkW5P0CoQ" + OBSERVED_FP="$(ssh-keygen -y -f "$KEY_FILE" | ssh-keygen -lf - | awk '{print $2}')" + if [ "$OBSERVED_FP" != "$EXPECTED_FP" ]; then echo "ProbeCredentialIdentityMismatch: fetched secret versions/1 fingerprint $OBSERVED_FP != modeled $EXPECTED_FP; contacting NO host" >&2; exit 1; fi + eval "$(ssh-agent -s)" >/dev/null + AGENT_STARTED=1 + ssh-add "$KEY_FILE" 2>/dev/null + rm -f "$KEY_FILE" + trap - EXIT + echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> "$GITHUB_ENV" + echo "SSH_AGENT_PID=$SSH_AGENT_PID" >> "$GITHUB_ENV" + echo "fleet-key: agent loaded (identity fleet-automation@gunbc; secret versions/1 pinned; fingerprint verified against modeled authority; key file wiped)" + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + timeout-minutes: 5 + - name: Mt. Collins unit 1 diskless boot under the unit hold (hold, SOL, attach, CD handoff, release) + id: mtcollins1_boot + run: | + # 🟡 dissolve-on: gunbc.machine_intake.sol_hold ActivateHeld — a bash -c script held as a string: it starts `ipmitool sol activate` in the background (stdin from an unlinked read-write fifo on fd 3, stdout line-buffered and appended to the capture, stderr appended to the client file) inside a background supervisor subshell whose body -- start-time read, publication, stop on failure, wait, exit record -- is already bash_build nodes (sol_hold_supervisor_body); and the one line of the boot step (gunbc.ci_spec gunbc_ci_mtcollins1_boot_watcher_background_line) that starts the SOL notice watcher with `&` and captures its pid from $!, the step being otherwise bash_build nodes. Paths are positional data (`$1`…`$7`), not interpolated shell syntax. DISSOLVES WHEN [C5 background-hold] (no intent or bash grammar row for a background process with its pid captured via $!) and [C8 fd-redirection] (no bash grammar rows for opening a file read-write on a numbered fd, duplicating it onto stdin, closing it, or >>/2>> appends) land, or a typed process-hold realization provides the WHOLE lifecycle -- start a streaming session with a held-open silent input and redirected output, publish an instance-bound identity, and supervise and record the exit -- without a medium-as-string script; the release by identity is already bash_build nodes (mtcollins1_sol_hold_release_script) + set -e + 'set' '-uo' 'pipefail' + 'umask' '077' + HDR_FILE="$RUNNER_TEMP"'/mtcollins1-boot-auth-header' + RESP_FILE="$RUNNER_TEMP"'/mtcollins1-boot-sm.json' + DEST_FILE="$RUNNER_TEMP"'/mtcollins1-bmc-credential' + SOL_PID_FILE="$RUNNER_TEMP"'/mtcollins1-sol.pid' + SOL_OUT="$GITHUB_WORKSPACE"'/artifacts/mtcollins1-boot-sol.capture' + SOL_RELEASE='f="$1" + if ! '\''['\'' '\''-e'\'' "$f" '\'']'\''; then exit 0; fi + if ! '\''['\'' '\''-r'\'' "$f" '\'']'\''; then exit 2; fi + p=$('\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f1'\'' "$f") + s=$('\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f2'\'' "$f") + if ! ('\''['\'' '\''-n'\'' "$p" '\'']'\'' && '\''['\'' '\''-n'\'' "$s" '\'']'\''); then exit 2; fi + if '\''['\'' '\''-e'\'' '\''/proc/'\''"$p" '\'']'\'' && ost=$('\''sed'\'' '\''s/^.*) //'\'' '\''/proc/'\''"$p"'\''/stat'\'' 2>/dev/null | '\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f'\'' '\''20'\'') && '\''['\'' '\''-z'\'' "$ost" '\'']'\''; then exit 2; fi + if ! (ost=$('\''sed'\'' '\''s/^.*) //'\'' '\''/proc/'\''"$p"'\''/stat'\'' 2>/dev/null | '\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f'\'' '\''20'\'') && ostate=$('\''sed'\'' '\''s/^.*) //'\'' '\''/proc/'\''"$p"'\''/stat'\'' 2>/dev/null | '\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f'\'' '\''1'\'') && '\''['\'' "$ost" '\''='\'' "$s" '\'']'\'' && '\''['\'' "$ostate" '\''!='\'' '\''Z'\'' '\'']'\''); then exit 0; fi + '\''kill'\'' "$p" 2>/dev/null || '\''true'\'' + '\''timeout'\'' '\''2'\'' '\''tail'\'' '\''--pid='\''"$p" '\''-f'\'' '\''/dev/null'\'' || '\''true'\'' + if ! (ost=$('\''sed'\'' '\''s/^.*) //'\'' '\''/proc/'\''"$p"'\''/stat'\'' 2>/dev/null | '\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f'\'' '\''20'\'') && ostate=$('\''sed'\'' '\''s/^.*) //'\'' '\''/proc/'\''"$p"'\''/stat'\'' 2>/dev/null | '\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f'\'' '\''1'\'') && '\''['\'' "$ost" '\''='\'' "$s" '\'']'\'' && '\''['\'' "$ostate" '\''!='\'' '\''Z'\'' '\'']'\''); then exit 0; fi + '\''kill'\'' '\''-9'\'' "$p" 2>/dev/null || '\''true'\'' + '\''timeout'\'' '\''1'\'' '\''tail'\'' '\''--pid='\''"$p" '\''-f'\'' '\''/dev/null'\'' || '\''true'\'' + if ! (ost=$('\''sed'\'' '\''s/^.*) //'\'' '\''/proc/'\''"$p"'\''/stat'\'' 2>/dev/null | '\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f'\'' '\''20'\'') && ostate=$('\''sed'\'' '\''s/^.*) //'\'' '\''/proc/'\''"$p"'\''/stat'\'' 2>/dev/null | '\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f'\'' '\''1'\'') && '\''['\'' "$ost" '\''='\'' "$s" '\'']'\'' && '\''['\'' "$ostate" '\''!='\'' '\''Z'\'' '\'']'\''); then exit 0; fi + exit 1 + ' + SOL_OWNED='f="$1" + if ! '\''['\'' '\''-e'\'' "$f" '\'']'\''; then exit 1; fi + if ! '\''['\'' '\''-r'\'' "$f" '\'']'\''; then exit 2; fi + p=$('\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f1'\'' "$f") + s=$('\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f2'\'' "$f") + if ! ('\''['\'' '\''-n'\'' "$p" '\'']'\'' && '\''['\'' '\''-n'\'' "$s" '\'']'\''); then exit 2; fi + if ! '\''['\'' '\''-e'\'' '\''/proc/'\''"$p" '\'']'\''; then exit 1; fi + ost=$('\''sed'\'' '\''s/^.*) //'\'' '\''/proc/'\''"$p"'\''/stat'\'' 2>/dev/null | '\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f'\'' '\''20'\'') + if '\''['\'' '\''-z'\'' "$ost" '\'']'\''; then exit 2; fi + if ost=$('\''sed'\'' '\''s/^.*) //'\'' '\''/proc/'\''"$p"'\''/stat'\'' 2>/dev/null | '\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f'\'' '\''20'\'') && ostate=$('\''sed'\'' '\''s/^.*) //'\'' '\''/proc/'\''"$p"'\''/stat'\'' 2>/dev/null | '\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f'\'' '\''1'\'') && '\''['\'' "$ost" '\''='\'' "$s" '\'']'\'' && '\''['\'' "$ostate" '\''!='\'' '\''Z'\'' '\'']'\''; then exit 0; fi + exit 1 + ' + fail='0' + SOL_WORKFLOW='' + SOL_DELIVERY='' + SOL_NOTICE_WATCH='' + 'trap' 'rc="$?" + if '\''['\'' '\''-n'\'' "$SOL_WORKFLOW" '\'']'\''; then '\''echo'\'' '\''gunbc-workflow: collector release requested by the step exit trap at='\''$('\''date'\'' '\''-u'\'' '\''+%Y-%m-%dT%H:%M:%SZ'\'') | '\''dd'\'' '\''of='\''"$SOL_WORKFLOW" '\''oflag=append'\'' '\''conv=notrunc'\'' '\''status=none'\'' || '\''true'\''; fi + col='\''absent'\'' + if '\''['\'' '\''-e'\'' "$SOL_PID_FILE" '\'']'\''; then if crec=$('\''cat'\'' "$SOL_PID_FILE" 2>/dev/null) && crcpt=$('\''cut'\'' '\''-d'\'' '\'' '\'' '\''-f1,2'\'' "$SOL_PID_FILE"'\''.activated'\'' 2>/dev/null) && '\''['\'' '\''-n'\'' "$crec" '\'']'\'' && '\''['\'' "$crcpt" '\''='\'' "$crec" '\'']'\''; then '\''bash'\'' '\''-c'\'' "$SOL_RELEASE" '\''sol-release'\'' "$SOL_PID_FILE" || '\''true'\''; if '\''bash'\'' '\''-c'\'' "$SOL_OWNED" '\''sol-owned'\'' "$SOL_PID_FILE"; then co='\''0'\''; else co="$?"; fi; if '\''['\'' "$co" '\''='\'' '\''1'\'' '\'']'\''; then if '\''rm'\'' '\''-f'\'' "$SOL_PID_FILE" "$SOL_PID_FILE"'\''.activated'\''; then col='\''released'\''; else col='\''ended-but-record-not-retired'\''; fi; else col='\''unresolved-owned-check-'\''"$co"; fi; else col='\''foreign-record-kept'\''; fi; fi + if '\''bash'\'' '\''-c'\'' "$SOL_RELEASE" '\''kvm-release'\'' "$GITHUB_WORKSPACE"'\''/artifacts/mtcollins1-boot-kvm-'\''"$GITHUB_RUN_ID"'\''/observer.pid'\''; then kvm='\''released'\''; else kvm='\''unresolved-release-exit-'\''"$?"; fi + obs='\''unstarted'\'' + wclean='\''none'\'' + if '\''['\'' '\''-n'\'' "$SOL_NOTICE_WATCH" '\'']'\''; then '\'':'\'' > "$SOL_OUT"'\''.notice.stop'\'' || '\''true'\''; '\''timeout'\'' '\''10'\'' '\''tail'\'' '\''--pid='\''"$SOL_NOTICE_WATCH" '\''-f'\'' '\''/dev/null'\'' || '\''true'\''; if '\''bash'\'' '\''-c'\'' "$SOL_OWNED" '\''sol-owned'\'' "$SOL_OUT"'\''.notice.watcher'\''; then wo='\''0'\''; else wo="$?"; fi; if '\''jobs'\'' '\''-rp'\'' | '\''grep'\'' '\''-qx'\'' "$SOL_NOTICE_WATCH"; then obs='\''unfinished'\''; if '\''['\'' "$wo" '\''='\'' '\''0'\'' '\'']'\''; then if '\''bash'\'' '\''-c'\'' "$SOL_RELEASE" '\''sol-release'\'' "$SOL_OUT"'\''.notice.watcher'\''; then wclean='\''stopped'\''; else wclean='\''unresolved-survived-KILL'\''; fi; else wclean='\''unresolved-identity-not-confirmed-'\''"$wo"; fi; else if '\''wait'\'' "$SOL_NOTICE_WATCH"; then obs='\''0'\''; else obs="$?"; fi; fi; fi + if '\''rm'\'' '\''-f'\'' "$HDR_FILE" "$RESP_FILE" "$DEST_FILE"; then clean='\''ok'\''; else clean='\''failed'\''; fi + if '\''['\'' "$obs" '\''!='\'' '\''0'\'' '\'']'\'' && '\''['\'' "$obs" '\''!='\'' '\''unstarted'\'' '\'']'\''; then '\''echo'\'' '\''::error::mtcollins1 SOL notice watcher did not succeed '\'''\''(boot command exited '\''"$rc"'\''; watcher result '\''"$obs"'\''; watcher cleanup '\''"$wclean"'\''; collector release '\''"$col"'\''; KVM observer release '\''"$kvm"'\''; credential cleanup '\''"$clean"'\''; delivery ledger '\''"$SOL_DELIVERY"'\'')'\''; fail='\''1'\''; fi + if '\''['\'' "$col" '\''!='\'' '\''absent'\'' '\'']'\'' && '\''['\'' "$col" '\''!='\'' '\''released'\'' '\'']'\''; then '\''echo'\'' '\''::error::mtcollins1 SOL collector not released at step exit '\'''\''(boot command exited '\''"$rc"'\''; watcher result '\''"$obs"'\''; watcher cleanup '\''"$wclean"'\''; collector release '\''"$col"'\''; KVM observer release '\''"$kvm"'\''; credential cleanup '\''"$clean"'\''; delivery ledger '\''"$SOL_DELIVERY"'\'')'\''; fail='\''1'\''; fi + if '\''['\'' "$wclean" '\''!='\'' '\''none'\'' '\'']'\'' && '\''['\'' "$wclean" '\''!='\'' '\''stopped'\'' '\'']'\''; then '\''echo'\'' '\''::error::mtcollins1 SOL notice watcher cleanup unresolved '\'''\''(boot command exited '\''"$rc"'\''; watcher result '\''"$obs"'\''; watcher cleanup '\''"$wclean"'\''; collector release '\''"$col"'\''; KVM observer release '\''"$kvm"'\''; credential cleanup '\''"$clean"'\''; delivery ledger '\''"$SOL_DELIVERY"'\'')'\''; fail='\''1'\''; fi + if '\''['\'' "$kvm" '\''!='\'' '\''released'\'' '\'']'\''; then '\''echo'\'' '\''::error::mtcollins1 KVM observer release did not resolve at step exit (release exit 1 = survived KILL, 2 = unobservable) '\'''\''(boot command exited '\''"$rc"'\''; watcher result '\''"$obs"'\''; watcher cleanup '\''"$wclean"'\''; collector release '\''"$col"'\''; KVM observer release '\''"$kvm"'\''; credential cleanup '\''"$clean"'\''; delivery ledger '\''"$SOL_DELIVERY"'\'')'\''; fail='\''1'\''; fi + if '\''['\'' "$clean" '\''!='\'' '\''ok'\'' '\'']'\''; then '\''echo'\'' '\''::error::mtcollins1 boot credential cleanup failed '\'''\''(boot command exited '\''"$rc"'\''; watcher result '\''"$obs"'\''; watcher cleanup '\''"$wclean"'\''; collector release '\''"$col"'\''; KVM observer release '\''"$kvm"'\''; credential cleanup '\''"$clean"'\''; delivery ledger '\''"$SOL_DELIVERY"'\'')'\''; fail='\''1'\''; fi + if '\''['\'' "$rc" '\''='\'' '\''0'\'' '\'']'\'' && '\''['\'' "$fail" '\''='\'' '\''1'\'' '\'']'\''; then rc='\''1'\''; fi + '\''exit'\'' "$rc" + ' 'EXIT' + 'printf' 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + 'curl' '-sSf' '--connect-timeout' '10' '--max-time' '60' '-o' "$RESP_FILE" '-H' '@'"$HDR_FILE" 'https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc/versions/2:access' && 'python3' '-c' 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$DEST_FILE" + 'chmod' '600' "$DEST_FILE" + 'export' 'GUNBC_HOST_RESET_BMC_CREDENTIAL_FILE='"$DEST_FILE" + 'mkdir' '-p' "$GITHUB_WORKSPACE"'/artifacts' + ':' > "$SOL_OUT" + 'export' 'GUNBC_MTCOLLINS1_SOL_CAPTURE='"$SOL_OUT" + 'export' 'GUNBC_MTCOLLINS1_SOL_PID_FILE='"$SOL_PID_FILE" + SOL_LOSS="$SOL_OUT"'.loss' + SOL_CLIENT="$SOL_OUT"'.client' + SOL_WORKFLOW="$SOL_OUT"'.workflow' + SOL_DELIVERY="$SOL_OUT"'.delivery' + ':' > "$SOL_LOSS" + ':' > "$SOL_CLIENT" + ':' > "$SOL_WORKFLOW" + ':' > "$SOL_DELIVERY" + 'find' $('dirname' "$SOL_OUT") '-maxdepth' '1' '-name' $('basename' "$SOL_OUT")'.notice.*' '-delete' + SOL_NOTICE_TOKEN=$('od' '-An' '-N8' '-tx8' '/dev/urandom' | 'tr' '-d' ' \n') + '[' '-n' "$SOL_NOTICE_TOKEN" ']' + 'export' 'GUNBC_MTCOLLINS1_SOL_NOTICE_TOKEN='"$SOL_NOTICE_TOKEN" + 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/machine_intake/mtcollins1_sol_notice.dag --function mtcollins1_sol_notice_wet & SOL_NOTICE_WATCH=$! + if ! (WSTART=$('sed' 's/^.*) //' '/proc/'"$SOL_NOTICE_WATCH"'/stat' | 'cut' '-d' ' ' '-f' '20') && '[' '-n' "$WSTART" ']' && 'printf' '%s %s\n' "$SOL_NOTICE_WATCH" "$WSTART" > "$SOL_OUT"'.notice.watcher.tmp' && 'mv' '-T' "$SOL_OUT"'.notice.watcher.tmp' "$SOL_OUT"'.notice.watcher'); then 'echo' '::error::mtcollins1 SOL notice watcher could not be recorded; refusing the boot before any BMC contact'; exit 1; fi + + "$ROOT/target/release/gunbc" run --source-root "$ROOT/dag" --source-root "$ROOT/src/v2" --entry dag/gunbc/machine_intake/mtcollins1_boot_run.dag --function mtcollins1_boot_wet + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + FLEET_CONVERGE_EXPECTED_HOST: ${{ github.event.inputs.host }} + GUNBC_BOOT_ATTEMPT_RECEIPT: ${{ github.event.inputs.attempt_receipt }} + GUNBC_BOOT_ATTEMPT_NONCE: ${{ github.event.inputs.transaction_nonce }} + if: github.event.inputs.mode == 'mtcollins1_boot' + timeout-minutes: 134 + - name: Upload Mt. Collins unit 1 boot SOL capture and receipts + id: mtcollins1_boot_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: mtcollins1-boot-receipts + path: artifacts/mtcollins1-boot-* + if-no-files-found: warn + retention-days: 30 + if: always() && github.event.inputs.mode == 'mtcollins1_boot' + timeout-minutes: 10 + - name: Kill the in-run ssh-agent (key never outlives the job) + run: | + if [ -n "${SSH_AGENT_PID:-}" ]; then ssh-agent -k || true; fi + if: always() + timeout-minutes: 5 + host-reset-return: + runs-on: ${{ fromJSON(format('["self-hosted","linux","arm64","{0}"]', github.event.inputs.reset_observer)) }} + needs: [build] + timeout-minutes: 295 + if: github.event.inputs.mode == 'host_reset_return' + concurrency: + group: "gunbc-host-mutation-${{ fromJSON('{\"srv1\": \"mtcollins1\", \"srv2\": \"mtcollins1\", \"srv3\": \"mtcollins1\", \"srv4\": \"mtcollins1\"}')[github.event.inputs.reset_observer] }}" + cancel-in-progress: false + steps: + - name: Checkout + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + fetch-depth: 0 + ref: ${{ (github.event.inputs.mode == 'mtcollins1_boot' || github.event.inputs.mode == 'gcp_iam_converge' || github.event.inputs.mode == 'namecheap_observe') && github.sha || github.event.inputs.expected_revision || github.sha }} + - name: Download release-bins artifact + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: release-bins + path: ${{ runner.temp }}/release-bins-download + timeout-minutes: 10 + - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) + id: release_bins + run: | + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') + '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) + OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') + SOURCE_TREE=$('git' '-C' "$ROOT" 'rev-parse' 'HEAD^{tree}') + '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) + SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'printf' '%s' 'claim_executor + gunbc + discover_source_root_ingest + claim_batch + interp_recorded_fixture_witness + v1_src_dag_parse + auth_declared_but_unwired_witness + bootstrap_witness + dag_collect_fingerprint_witness + diagnostics_witness + effects_rest_transport_witness + infer_semantics_witness + parse_witness + cssl_assemble + namespace_structural_root_exposure_generated_witness + codex_app_server_stdio_session + ' > "$RUNNER_TEMP"'/release-bins.roster' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'printf' '%s' 'claim_executor:regular file:1 + gunbc:regular file:1 + discover_source_root_ingest:regular file:1 + claim_batch:regular file:1 + interp_recorded_fixture_witness:regular file:1 + v1_src_dag_parse:regular file:1 + auth_declared_but_unwired_witness:regular file:1 + bootstrap_witness:regular file:1 + dag_collect_fingerprint_witness:regular file:1 + diagnostics_witness:regular file:1 + effects_rest_transport_witness:regular file:1 + infer_semantics_witness:regular file:1 + parse_witness:regular file:1 + cssl_assemble:regular file:1 + namespace_structural_root_exposure_generated_witness:regular file:1 + codex_app_server_stdio_session:regular file:1 + ' > "$RUNNER_TEMP"'/member-types.expected' + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'stat' '-c' '%n:%F:%h' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') > "$RUNNER_TEMP"'/member-types.out' + 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' '-maxdepth' '0' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' + '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + 'mkdir' '-p' "$ROOT"'/target/release' + ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + "$ROOT"'/target/release/claim_executor' '--verify-build-artifacts' "$ROOT"'/target/release/claim_executor' "$ROOT"'/target/release/gunbc' "$ROOT"'/target/release/discover_source_root_ingest' "$ROOT"'/target/release/claim_batch' "$ROOT"'/target/release/interp_recorded_fixture_witness' "$ROOT"'/target/release/v1_src_dag_parse' "$ROOT"'/target/release/auth_declared_but_unwired_witness' "$ROOT"'/target/release/bootstrap_witness' "$ROOT"'/target/release/dag_collect_fingerprint_witness' "$ROOT"'/target/release/diagnostics_witness' "$ROOT"'/target/release/effects_rest_transport_witness' "$ROOT"'/target/release/infer_semantics_witness' "$ROOT"'/target/release/parse_witness' "$ROOT"'/target/release/cssl_assemble' "$ROOT"'/target/release/namespace_structural_root_exposure_generated_witness' "$ROOT"'/target/release/codex_app_server_stdio_session' + env: + RELEASE_BINS_KEY: ${{ needs.build.outputs.release_bins_key }} + timeout-minutes: 5 + - name: "Host reset-return: drive the subject through its controller and measure the return from a peer" + id: host_reset_return + 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/host/host_reset_return_run.dag --function host_reset_return_wet + cat "$ROOT/target/host-reset-return.txt" + env: + GUNBC_HOST_RESET_OBSERVER: ${{ github.event.inputs.reset_observer }} + if: github.event.inputs.mode == 'host_reset_return' + timeout-minutes: 30 + - name: Upload host reset-return attempt receipt + id: host_reset_return_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: host-reset-return + path: target/host-reset-return.txt + if-no-files-found: error + retention-days: 30 + if: always() && (github.event.inputs.mode == 'host_reset_return') + timeout-minutes: 10 + dashboard-deploy: + runs-on: ${{ fromJSON(format('["self-hosted","linux","arm64","{0}"]', github.event.inputs.host)) }} + needs: [build] + timeout-minutes: 50 + if: github.event.inputs.mode == 'dashboard_deploy' + concurrency: + group: gunbc-host-mutation-${{ github.event.inputs.host }} + cancel-in-progress: false + steps: + - name: Checkout + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + fetch-depth: 0 + ref: ${{ (github.event.inputs.mode == 'mtcollins1_boot' || github.event.inputs.mode == 'gcp_iam_converge' || github.event.inputs.mode == 'namecheap_observe') && github.sha || github.event.inputs.expected_revision || github.sha }} + - name: Download release-bins artifact + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: release-bins + path: ${{ runner.temp }}/release-bins-download + timeout-minutes: 10 + - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) + id: release_bins + run: | + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') + '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) + OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') + SOURCE_TREE=$('git' '-C' "$ROOT" 'rev-parse' 'HEAD^{tree}') + '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) + SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'printf' '%s' 'claim_executor + gunbc + discover_source_root_ingest + claim_batch + interp_recorded_fixture_witness + v1_src_dag_parse + auth_declared_but_unwired_witness + bootstrap_witness + dag_collect_fingerprint_witness + diagnostics_witness + effects_rest_transport_witness + infer_semantics_witness + parse_witness + cssl_assemble + namespace_structural_root_exposure_generated_witness + codex_app_server_stdio_session + ' > "$RUNNER_TEMP"'/release-bins.roster' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'printf' '%s' 'claim_executor:regular file:1 + gunbc:regular file:1 + discover_source_root_ingest:regular file:1 + claim_batch:regular file:1 + interp_recorded_fixture_witness:regular file:1 + v1_src_dag_parse:regular file:1 + auth_declared_but_unwired_witness:regular file:1 + bootstrap_witness:regular file:1 + dag_collect_fingerprint_witness:regular file:1 + diagnostics_witness:regular file:1 + effects_rest_transport_witness:regular file:1 + infer_semantics_witness:regular file:1 + parse_witness:regular file:1 + cssl_assemble:regular file:1 + namespace_structural_root_exposure_generated_witness:regular file:1 + codex_app_server_stdio_session:regular file:1 + ' > "$RUNNER_TEMP"'/member-types.expected' + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'stat' '-c' '%n:%F:%h' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') > "$RUNNER_TEMP"'/member-types.out' + 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' '-maxdepth' '0' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' + '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + 'mkdir' '-p' "$ROOT"'/target/release' + ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + "$ROOT"'/target/release/claim_executor' '--verify-build-artifacts' "$ROOT"'/target/release/claim_executor' "$ROOT"'/target/release/gunbc' "$ROOT"'/target/release/discover_source_root_ingest' "$ROOT"'/target/release/claim_batch' "$ROOT"'/target/release/interp_recorded_fixture_witness' "$ROOT"'/target/release/v1_src_dag_parse' "$ROOT"'/target/release/auth_declared_but_unwired_witness' "$ROOT"'/target/release/bootstrap_witness' "$ROOT"'/target/release/dag_collect_fingerprint_witness' "$ROOT"'/target/release/diagnostics_witness' "$ROOT"'/target/release/effects_rest_transport_witness' "$ROOT"'/target/release/infer_semantics_witness' "$ROOT"'/target/release/parse_witness' "$ROOT"'/target/release/cssl_assemble' "$ROOT"'/target/release/namespace_structural_root_exposure_generated_witness' "$ROOT"'/target/release/codex_app_server_stdio_session' + env: + RELEASE_BINS_KEY: ${{ needs.build.outputs.release_bins_key }} + timeout-minutes: 5 + - name: Download the plan artifact (carries the plan receipt) from the plan run + id: dashboard_plan_receipt_download + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: fleet-converge-plan + path: /tmp/fleet-converge-plan + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.inputs.plan_workflow_run_id }} + timeout-minutes: 10 + - name: Download the fleet apply receipt from the apply run + id: dashboard_apply_receipt_download + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: fleet-converge-apply-receipt + path: /tmp/fleet-converge-apply + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.inputs.apply_workflow_run_id }} + timeout-minutes: 10 + - name: Deploy dashboard to srv1 (live_deploy_apply_srv1_transaction_wet, receipted) + id: dashboard_deploy + 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/live_deploy/apply.dag --function live_deploy_apply_srv1_transaction_wet + cat "$ROOT/target/live-deploy-srv1-live-receipt/live_deploy_receipt.json" + env: + RLM_EXPECTED_REVISION: ${{ github.event.inputs.expected_revision }} + RLM_PLAN_RUN_ID: ${{ github.event.inputs.plan_workflow_run_id }} + RLM_APPLY_RUN_ID: ${{ github.event.inputs.apply_workflow_run_id }} + RLM_PLAN_ARTIFACT_HASH: ${{ github.event.inputs.plan_artifact_hash }} + timeout-minutes: 30 + - name: Upload live-deploy transaction receipt + id: dashboard_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: live-deploy-srv1-live-receipt + path: target/live-deploy-srv1-live-receipt + if-no-files-found: error + retention-days: 90 + compression-level: 0 + if: always() + timeout-minutes: 10 + approval-broker-dark-install: + runs-on: ${{ fromJSON(format('["self-hosted","linux","arm64","{0}"]', github.event.inputs.host)) }} + needs: [build] + timeout-minutes: 50 + if: github.event.inputs.mode == 'approval_broker_dark_install' + concurrency: + group: gunbc-host-mutation-${{ github.event.inputs.host }} + cancel-in-progress: false + steps: + - name: Checkout + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + fetch-depth: 0 + ref: ${{ (github.event.inputs.mode == 'mtcollins1_boot' || github.event.inputs.mode == 'gcp_iam_converge' || github.event.inputs.mode == 'namecheap_observe') && github.sha || github.event.inputs.expected_revision || github.sha }} + - name: Download release-bins artifact + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: release-bins + path: ${{ runner.temp }}/release-bins-download + timeout-minutes: 10 + - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) + id: release_bins + run: | + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') + '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) + OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') + SOURCE_TREE=$('git' '-C' "$ROOT" 'rev-parse' 'HEAD^{tree}') + '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) + SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'printf' '%s' 'claim_executor + gunbc + discover_source_root_ingest + claim_batch + interp_recorded_fixture_witness + v1_src_dag_parse + auth_declared_but_unwired_witness + bootstrap_witness + dag_collect_fingerprint_witness + diagnostics_witness + effects_rest_transport_witness + infer_semantics_witness + parse_witness + cssl_assemble + namespace_structural_root_exposure_generated_witness + codex_app_server_stdio_session + ' > "$RUNNER_TEMP"'/release-bins.roster' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'printf' '%s' 'claim_executor:regular file:1 + gunbc:regular file:1 + discover_source_root_ingest:regular file:1 + claim_batch:regular file:1 + interp_recorded_fixture_witness:regular file:1 + v1_src_dag_parse:regular file:1 + auth_declared_but_unwired_witness:regular file:1 + bootstrap_witness:regular file:1 + dag_collect_fingerprint_witness:regular file:1 + diagnostics_witness:regular file:1 + effects_rest_transport_witness:regular file:1 + infer_semantics_witness:regular file:1 + parse_witness:regular file:1 + cssl_assemble:regular file:1 + namespace_structural_root_exposure_generated_witness:regular file:1 + codex_app_server_stdio_session:regular file:1 + ' > "$RUNNER_TEMP"'/member-types.expected' + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'stat' '-c' '%n:%F:%h' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') > "$RUNNER_TEMP"'/member-types.out' + 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' '-maxdepth' '0' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' + '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + 'mkdir' '-p' "$ROOT"'/target/release' + ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + "$ROOT"'/target/release/claim_executor' '--verify-build-artifacts' "$ROOT"'/target/release/claim_executor' "$ROOT"'/target/release/gunbc' "$ROOT"'/target/release/discover_source_root_ingest' "$ROOT"'/target/release/claim_batch' "$ROOT"'/target/release/interp_recorded_fixture_witness' "$ROOT"'/target/release/v1_src_dag_parse' "$ROOT"'/target/release/auth_declared_but_unwired_witness' "$ROOT"'/target/release/bootstrap_witness' "$ROOT"'/target/release/dag_collect_fingerprint_witness' "$ROOT"'/target/release/diagnostics_witness' "$ROOT"'/target/release/effects_rest_transport_witness' "$ROOT"'/target/release/infer_semantics_witness' "$ROOT"'/target/release/parse_witness' "$ROOT"'/target/release/cssl_assemble' "$ROOT"'/target/release/namespace_structural_root_exposure_generated_witness' "$ROOT"'/target/release/codex_app_server_stdio_session' + env: + RELEASE_BINS_KEY: ${{ needs.build.outputs.release_bins_key }} + timeout-minutes: 5 + - name: Install the approval broker dark on srv1 (additive unit + slice; does not restart gunbc-roadmap.service) + id: approval_broker_dark_install + 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/live_deploy/apply.dag --function approval_broker_dark_install_srv1_wet + timeout-minutes: 30 + microvm-controller-install: + runs-on: ${{ fromJSON(format('["self-hosted","linux","arm64","{0}"]', github.event.inputs.host)) }} + needs: [build] + timeout-minutes: 50 + if: github.event.inputs.mode == 'microvm_controller_install' + concurrency: + group: gunbc-host-mutation-${{ github.event.inputs.host }} + cancel-in-progress: false + steps: + - name: Checkout + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + fetch-depth: 0 + ref: ${{ (github.event.inputs.mode == 'mtcollins1_boot' || github.event.inputs.mode == 'gcp_iam_converge' || github.event.inputs.mode == 'namecheap_observe') && github.sha || github.event.inputs.expected_revision || github.sha }} + - name: Download release-bins artifact + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: release-bins + path: ${{ runner.temp }}/release-bins-download + timeout-minutes: 10 + - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) + id: release_bins + run: | + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') + '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) + OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') + SOURCE_TREE=$('git' '-C' "$ROOT" 'rev-parse' 'HEAD^{tree}') + '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) + SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'printf' '%s' 'claim_executor + gunbc + discover_source_root_ingest + claim_batch + interp_recorded_fixture_witness + v1_src_dag_parse + auth_declared_but_unwired_witness + bootstrap_witness + dag_collect_fingerprint_witness + diagnostics_witness + effects_rest_transport_witness + infer_semantics_witness + parse_witness + cssl_assemble + namespace_structural_root_exposure_generated_witness + codex_app_server_stdio_session + ' > "$RUNNER_TEMP"'/release-bins.roster' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'printf' '%s' 'claim_executor:regular file:1 + gunbc:regular file:1 + discover_source_root_ingest:regular file:1 + claim_batch:regular file:1 + interp_recorded_fixture_witness:regular file:1 + v1_src_dag_parse:regular file:1 + auth_declared_but_unwired_witness:regular file:1 + bootstrap_witness:regular file:1 + dag_collect_fingerprint_witness:regular file:1 + diagnostics_witness:regular file:1 + effects_rest_transport_witness:regular file:1 + infer_semantics_witness:regular file:1 + parse_witness:regular file:1 + cssl_assemble:regular file:1 + namespace_structural_root_exposure_generated_witness:regular file:1 + codex_app_server_stdio_session:regular file:1 + ' > "$RUNNER_TEMP"'/member-types.expected' + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'stat' '-c' '%n:%F:%h' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') > "$RUNNER_TEMP"'/member-types.out' + 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' '-maxdepth' '0' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' + '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + 'mkdir' '-p' "$ROOT"'/target/release' + ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + "$ROOT"'/target/release/claim_executor' '--verify-build-artifacts' "$ROOT"'/target/release/claim_executor' "$ROOT"'/target/release/gunbc' "$ROOT"'/target/release/discover_source_root_ingest' "$ROOT"'/target/release/claim_batch' "$ROOT"'/target/release/interp_recorded_fixture_witness' "$ROOT"'/target/release/v1_src_dag_parse' "$ROOT"'/target/release/auth_declared_but_unwired_witness' "$ROOT"'/target/release/bootstrap_witness' "$ROOT"'/target/release/dag_collect_fingerprint_witness' "$ROOT"'/target/release/diagnostics_witness' "$ROOT"'/target/release/effects_rest_transport_witness' "$ROOT"'/target/release/infer_semantics_witness' "$ROOT"'/target/release/parse_witness' "$ROOT"'/target/release/cssl_assemble' "$ROOT"'/target/release/namespace_structural_root_exposure_generated_witness' "$ROOT"'/target/release/codex_app_server_stdio_session' + env: + RELEASE_BINS_KEY: ${{ needs.build.outputs.release_bins_key }} + timeout-minutes: 5 + - name: Install the microVM slot controller release locus, VMM + jailer and template unit on srv1 (additive; starts no instance) + id: microvm_controller_install + 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/live_deploy/apply.dag --function microvm_controller_install_srv1_wet + timeout-minutes: 30 + rlm-launch-deployment-receipt: + runs-on: ${{ fromJSON(format('["self-hosted","linux","arm64","{0}"]', github.event.inputs.host)) }} + needs: [build] + timeout-minutes: 80 + if: github.event.inputs.mode == 'rlm_launch_deployment_receipt' + permissions: + contents: read + actions: read + id-token: write + concurrency: + group: gunbc-host-mutation-${{ github.event.inputs.host }} + cancel-in-progress: false + steps: + - name: Checkout + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + fetch-depth: 0 + ref: ${{ (github.event.inputs.mode == 'mtcollins1_boot' || github.event.inputs.mode == 'gcp_iam_converge' || github.event.inputs.mode == 'namecheap_observe') && github.sha || github.event.inputs.expected_revision || github.sha }} + - name: Download release-bins artifact + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: release-bins + path: ${{ runner.temp }}/release-bins-download + timeout-minutes: 10 + - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) + id: release_bins + run: | + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') + '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) + OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') + SOURCE_TREE=$('git' '-C' "$ROOT" 'rev-parse' 'HEAD^{tree}') + '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) + SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'printf' '%s' 'claim_executor + gunbc + discover_source_root_ingest + claim_batch + interp_recorded_fixture_witness + v1_src_dag_parse + auth_declared_but_unwired_witness + bootstrap_witness + dag_collect_fingerprint_witness + diagnostics_witness + effects_rest_transport_witness + infer_semantics_witness + parse_witness + cssl_assemble + namespace_structural_root_exposure_generated_witness + codex_app_server_stdio_session + ' > "$RUNNER_TEMP"'/release-bins.roster' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'printf' '%s' 'claim_executor:regular file:1 + gunbc:regular file:1 + discover_source_root_ingest:regular file:1 + claim_batch:regular file:1 + interp_recorded_fixture_witness:regular file:1 + v1_src_dag_parse:regular file:1 + auth_declared_but_unwired_witness:regular file:1 + bootstrap_witness:regular file:1 + dag_collect_fingerprint_witness:regular file:1 + diagnostics_witness:regular file:1 + effects_rest_transport_witness:regular file:1 + infer_semantics_witness:regular file:1 + parse_witness:regular file:1 + cssl_assemble:regular file:1 + namespace_structural_root_exposure_generated_witness:regular file:1 + codex_app_server_stdio_session:regular file:1 + ' > "$RUNNER_TEMP"'/member-types.expected' + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'stat' '-c' '%n:%F:%h' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') > "$RUNNER_TEMP"'/member-types.out' + 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' '-maxdepth' '0' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' + '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + 'mkdir' '-p' "$ROOT"'/target/release' + ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + "$ROOT"'/target/release/claim_executor' '--verify-build-artifacts' "$ROOT"'/target/release/claim_executor' "$ROOT"'/target/release/gunbc' "$ROOT"'/target/release/discover_source_root_ingest' "$ROOT"'/target/release/claim_batch' "$ROOT"'/target/release/interp_recorded_fixture_witness' "$ROOT"'/target/release/v1_src_dag_parse' "$ROOT"'/target/release/auth_declared_but_unwired_witness' "$ROOT"'/target/release/bootstrap_witness' "$ROOT"'/target/release/dag_collect_fingerprint_witness' "$ROOT"'/target/release/diagnostics_witness' "$ROOT"'/target/release/effects_rest_transport_witness' "$ROOT"'/target/release/infer_semantics_witness' "$ROOT"'/target/release/parse_witness' "$ROOT"'/target/release/cssl_assemble' "$ROOT"'/target/release/namespace_structural_root_exposure_generated_witness' "$ROOT"'/target/release/codex_app_server_stdio_session' + env: + RELEASE_BINS_KEY: ${{ needs.build.outputs.release_bins_key }} + timeout-minutes: 5 + - name: WIF auth (OIDC -> fleet-cloud-convergence SA, access token only) + id: wif_auth + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 + with: + workload_identity_provider: projects/582015116396/locations/global/workloadIdentityPools/github-actions/providers/github-oidc + service_account: fleet-cloud-convergence@gunbai-secrets.iam.gserviceaccount.com + token_format: access_token + create_credentials_file: false + timeout-minutes: 5 + - name: Materialize fleet key in-run (SM versions/1 pinned -> RUNNER_TEMP 0600 -> ssh-agent -> wipe file) + run: |- + # 🟡 dissolve-on: gunbc_ci_fleet_key_agent_script - orch-emitted foreign-executor (GitHub Actions run:) credential runner: WIF access token by env, one PINNED secret version fetched over curl, a 0600 key file under RUNNER_TEMP with a trap armed BEFORE the credential touches disk, fingerprint verified against the modeled authority, ssh-agent load, file wipe. The pipeline steps are modeled (gunbc_ci_fleet_key_agent_prelude) but the runner transport itself remains hand-shell; DISSOLVES WHEN the runner is authored as v2.extdeps.languages.bash_build nodes (typed, grammar-quoted words; command substitution; assignment; pipe; if/test; redirect; || true) emitted by v2.workflow.bash_emit bash_emit_stmts -- a route AVAILABLE today (#12422 retired the sibling pack runner on it), so this migration is unauthored, not blocked on a missing capability; set -euo pipefail, umask, export and trap are ordinary commands on that route, and the trap body is itself built from nodes and passed as one grammar-quoted word, so no new orchestration intent is a prerequisite + set -euo pipefail + umask 077 + KEY_FILE="$RUNNER_TEMP/fleet-automation-key" + HDR_FILE="$RUNNER_TEMP/fleet-automation-auth-header" + AGENT_STARTED=0 + cleanup() { rm -f "$KEY_FILE" "$HDR_FILE"; if [ "$AGENT_STARTED" = 1 ] && [ -n "${SSH_AGENT_PID:-}" ]; then ssh-agent -k >/dev/null 2>&1 || true; fi; } + trap cleanup EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + curl -sSf -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key/versions/1:access" | python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(sys.stdin)["payload"]["data"]))' > "$KEY_FILE" + rm -f "$HDR_FILE" + chmod 600 "$KEY_FILE" + EXPECTED_FP="SHA256:mGT7qJsh36VsVb8OPh3m8br3oHedQHxRukRkW5P0CoQ" + OBSERVED_FP="$(ssh-keygen -y -f "$KEY_FILE" | ssh-keygen -lf - | awk '{print $2}')" + if [ "$OBSERVED_FP" != "$EXPECTED_FP" ]; then echo "ProbeCredentialIdentityMismatch: fetched secret versions/1 fingerprint $OBSERVED_FP != modeled $EXPECTED_FP; contacting NO host" >&2; exit 1; fi + eval "$(ssh-agent -s)" >/dev/null + AGENT_STARTED=1 + ssh-add "$KEY_FILE" 2>/dev/null + rm -f "$KEY_FILE" + trap - EXIT + echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> "$GITHUB_ENV" + echo "SSH_AGENT_PID=$SSH_AGENT_PID" >> "$GITHUB_ENV" + echo "fleet-key: agent loaded (identity fleet-automation@gunbc; secret versions/1 pinned; fingerprint verified against modeled authority; key file wiped)" + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + timeout-minutes: 5 + - name: Download fleet converge plan artifact (plan run) + id: rlm_plan_download + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: fleet-converge-plan + path: /tmp/fleet-converge-plan + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.inputs.plan_workflow_run_id }} + timeout-minutes: 10 + - name: Download fleet converge apply receipt (apply run) + id: rlm_apply_download + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: fleet-converge-apply-receipt + path: /tmp/fleet-converge-apply + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.inputs.apply_workflow_run_id }} + timeout-minutes: 10 + - name: Download live-deploy transaction receipt (dashboard run) + id: rlm_dashboard_download + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: live-deploy-srv1-live-receipt + path: target/live-deploy-srv1-live-receipt + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.inputs.dashboard_workflow_run_id }} + timeout-minutes: 10 + - name: Roadmap launch deployment receipt (rlm_launch_deployment_receipt_wet) + id: rlm_receipt + 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/roadmap/roadmap_launch_deployment_cli.dag --function rlm_launch_deployment_receipt_wet + cat "$ROOT/target/rlm-launch-deployment-receipt/receipt.json" + env: + RLM_EXPECTED_REVISION: ${{ github.event.inputs.expected_revision }} + RLM_PLAN_RUN_ID: ${{ github.event.inputs.plan_workflow_run_id }} + RLM_APPLY_RUN_ID: ${{ github.event.inputs.apply_workflow_run_id }} + RLM_DASHBOARD_RUN_ID: ${{ github.event.inputs.dashboard_workflow_run_id }} + timeout-minutes: 5 + - name: Upload roadmap launch deployment receipt + id: rlm_receipt_upload + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: rlm-launch-deployment-receipt + path: target/rlm-launch-deployment-receipt + if-no-files-found: error + retention-days: 90 + compression-level: 0 + if: always() + timeout-minutes: 10 + - name: Kill the in-run ssh-agent (key never outlives the job) + run: | + if [ -n "${SSH_AGENT_PID:-}" ]; then ssh-agent -k || true; fi + if: always() + timeout-minutes: 5 + gcp-iam-converge: + runs-on: [self-hosted, linux, arm64, srv1] + needs: [build] + environment: gcp-iam-converge + timeout-minutes: 60 + if: github.event.inputs.mode == 'gcp_iam_converge' + permissions: + contents: read + actions: read + id-token: write + concurrency: + group: gcp-iam-converge + cancel-in-progress: false + steps: + - name: Checkout (the event sha only; no dispatch input selects these bytes) + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + fetch-depth: 0 + ref: ${{ github.sha }} + - name: Download release-bins artifact + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: release-bins + path: ${{ runner.temp }}/release-bins-download + timeout-minutes: 10 + - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) + id: release_bins + run: | + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') + '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) + OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') + SOURCE_TREE=$('git' '-C' "$ROOT" 'rev-parse' 'HEAD^{tree}') + '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) + SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'printf' '%s' 'claim_executor + gunbc + discover_source_root_ingest + claim_batch + interp_recorded_fixture_witness + v1_src_dag_parse + auth_declared_but_unwired_witness + bootstrap_witness + dag_collect_fingerprint_witness + diagnostics_witness + effects_rest_transport_witness + infer_semantics_witness + parse_witness + cssl_assemble + namespace_structural_root_exposure_generated_witness + codex_app_server_stdio_session + ' > "$RUNNER_TEMP"'/release-bins.roster' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'printf' '%s' 'claim_executor:regular file:1 + gunbc:regular file:1 + discover_source_root_ingest:regular file:1 + claim_batch:regular file:1 + interp_recorded_fixture_witness:regular file:1 + v1_src_dag_parse:regular file:1 + auth_declared_but_unwired_witness:regular file:1 + bootstrap_witness:regular file:1 + dag_collect_fingerprint_witness:regular file:1 + diagnostics_witness:regular file:1 + effects_rest_transport_witness:regular file:1 + infer_semantics_witness:regular file:1 + parse_witness:regular file:1 + cssl_assemble:regular file:1 + namespace_structural_root_exposure_generated_witness:regular file:1 + codex_app_server_stdio_session:regular file:1 + ' > "$RUNNER_TEMP"'/member-types.expected' + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'stat' '-c' '%n:%F:%h' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') > "$RUNNER_TEMP"'/member-types.out' + 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' '-maxdepth' '0' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' + '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + 'mkdir' '-p' "$ROOT"'/target/release' + ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + "$ROOT"'/target/release/claim_executor' '--verify-build-artifacts' "$ROOT"'/target/release/claim_executor' "$ROOT"'/target/release/gunbc' "$ROOT"'/target/release/discover_source_root_ingest' "$ROOT"'/target/release/claim_batch' "$ROOT"'/target/release/interp_recorded_fixture_witness' "$ROOT"'/target/release/v1_src_dag_parse' "$ROOT"'/target/release/auth_declared_but_unwired_witness' "$ROOT"'/target/release/bootstrap_witness' "$ROOT"'/target/release/dag_collect_fingerprint_witness' "$ROOT"'/target/release/diagnostics_witness' "$ROOT"'/target/release/effects_rest_transport_witness' "$ROOT"'/target/release/infer_semantics_witness' "$ROOT"'/target/release/parse_witness' "$ROOT"'/target/release/cssl_assemble' "$ROOT"'/target/release/namespace_structural_root_exposure_generated_witness' "$ROOT"'/target/release/codex_app_server_stdio_session' + env: + RELEASE_BINS_KEY: ${{ needs.build.outputs.release_bins_key }} + timeout-minutes: 5 + - name: WIF auth (OIDC -> dedicated gcp-iam-converge pool -> iam-observe SA, access token only) + id: wif_auth + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 + with: + workload_identity_provider: projects/582015116396/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers/github-gcp-iam-converge-oidc + service_account: iam-observe@gunbai-secrets.iam.gserviceaccount.com + token_format: access_token + create_credentials_file: false + timeout-minutes: 5 + - name: "GCP IAM converge: plan as iam-observe, file one request, wait for the operator (no write credential exists yet)" + id: gcp_iam_request + run: |- + # 🟡 dissolve-on: gunbc_ci_gcp_iam_converge_request_invoke - orch-emitted foreign-executor (GitHub Actions run:) credential runner sharing gunbc_ci_submission_key_prelude with the D0 door: WIF access token (iam-observe) by env, ONE pinned submission-MAC secret version fetched over curl, a 0600 file under RUNNER_TEMP with a trap armed BEFORE the key touches disk; DISSOLVES WHEN the runner is authored as v2.extdeps.languages.bash_build nodes (typed, grammar-quoted words; command substitution; assignment; pipe; if/test; redirect; || true) emitted by v2.workflow.bash_emit bash_emit_stmts -- a route AVAILABLE today (#12422 retired the sibling pack runner on it), so this migration is unauthored, not blocked on a missing capability; set -euo pipefail, umask, export and trap are ordinary commands on that route, and the trap body is itself built from nodes and passed as one grammar-quoted word, so no new orchestration intent is a prerequisite + set -euo pipefail + umask 077 + HDR_FILE="$RUNNER_TEMP/gcp-iam-converge-auth-header" + RESP_FILE="$RUNNER_TEMP/gcp-iam-converge-sm.json" + SUB_FILE="$RUNNER_TEMP/approval-submission-mac-key" + trap 'rm -f "$HDR_FILE" "$RESP_FILE" "$SUB_FILE"' EXIT + printf 'Authorization: Bearer %s\n' "$WIF_ACCESS_TOKEN" > "$HDR_FILE" + if DEST_FILE="$SUB_FILE"; curl -sSf --connect-timeout 10 --max-time 60 -o "$RESP_FILE" -H @"$HDR_FILE" "https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key/versions/1:access" && python3 -c 'import sys,json,base64; sys.stdout.buffer.write(base64.b64decode(json.load(open(sys.argv[1]))["payload"]["data"]))' "$RESP_FILE" > "$DEST_FILE"; then chmod 600 "$SUB_FILE"; export GUNBC_APPROVAL_SUBMISSION_MAC_KEY_FILE="$SUB_FILE"; else rm -f "$SUB_FILE"; echo 'submission MAC key not materialized: a run that must file will refuse there; a held lifecycle resumes without it'; fi + 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/auth/gcp_iam_converge_run.dag --function gcp_iam_converge_request_wet + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + if: github.event.inputs.mode == 'gcp_iam_converge' + timeout-minutes: 40 + - name: WIF auth (OIDC -> dedicated gcp-iam-converge pool -> iam-converge SA; only after a live approval) + id: wif_auth_iam_converge + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 + with: + workload_identity_provider: projects/582015116396/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers/github-gcp-iam-converge-oidc + service_account: iam-converge@gunbai-secrets.iam.gserviceaccount.com + token_format: access_token + access_token_lifetime: 600s + create_credentials_file: false + if: github.event.inputs.mode == 'gcp_iam_converge' && steps.gcp_iam_request.outputs.approved == 'true' + timeout-minutes: 5 + - name: "GCP IAM converge: re-plan, re-admit the approval, apply exactly the approved effects as iam-converge, read back as iam-observe" + id: gcp_iam_apply + 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/auth/gcp_iam_converge_run.dag --function gcp_iam_converge_apply_wet + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + IAM_CONVERGE_ACCESS_TOKEN: ${{ steps.wif_auth_iam_converge.outputs.access_token }} + if: github.event.inputs.mode == 'gcp_iam_converge' && steps.gcp_iam_request.outputs.approved == 'true' + timeout-minutes: 15 + namecheap-observe: + runs-on: ${{ fromJSON(format('["self-hosted","linux","arm64","{0}"]', github.event.inputs.host)) }} + needs: [build] + environment: namecheap-dns + timeout-minutes: 25 + if: github.event.inputs.mode == 'namecheap_observe' + permissions: + contents: read + actions: read + id-token: write + steps: + - name: Checkout (the event sha only; no dispatch input selects these bytes) + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 + with: + fetch-depth: 0 + ref: ${{ github.sha }} + - name: Download release-bins artifact + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: release-bins + path: ${{ runner.temp }}/release-bins-download + timeout-minutes: 10 + - name: Verify the handed-off release bins against the build key, then promote and run --verify-build-artifacts (fail-closed) + id: release_bins + run: | + set -e + ROOT=$('git' 'rev-parse' '--show-toplevel') + '[' '-n' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerKeyMissing::the build job published no release-bins key'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.preimage'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.manifest'; exit 1) + '[' '-f' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' ']' || ('echo' '::error title=release-bins ConsumerFileMissing::the release-bins artifact is missing release-bins.tgz'; exit 1) + 'sha256sum' "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' > "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out' + DOWNLOADED_DIGEST=$('cut' '-d' ' ' '-f' '1' "$RUNNER_TEMP"'/DOWNLOADED_DIGEST.out') + '[' 'release-bins-'"$DOWNLOADED_DIGEST" '=' "$RELEASE_BINS_KEY" ']' || ('echo' '::error title=release-bins ConsumerPreimageKeyMismatch::the downloaded preimage does not hash to the build job'\''s key'; exit 1) + OBJECT_FORMAT=$('git' '-C' "$ROOT" 'rev-parse' '--show-object-format') + SOURCE_TREE=$('git' '-C' "$ROOT" 'rev-parse' 'HEAD^{tree}') + '[' '-n' "$OBJECT_FORMAT" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input OBJECT_FORMAT read back empty, so the key would not name the build; refusing to key'; exit 1) + '[' '-n' "$SOURCE_TREE" ']' || ('echo' '::error title=release-bins AxisValueUnread::the key input SOURCE_TREE read back empty, so the key would not name the build; refusing to key'; exit 1) + SOURCE_CLOSURE='tree:'"$OBJECT_FORMAT"':'"$SOURCE_TREE" + 'grep' '-qxF' 'source_closure='"$SOURCE_CLOSURE" "$RUNNER_TEMP"'/release-bins-download/release-bins.preimage' || ('echo' '::error title=release-bins ConsumerSourceTreeMismatch::the pack was keyed on a different source tree than this job'\''s checkout'; exit 1) + 'printf' '%s' 'claim_executor + gunbc + discover_source_root_ingest + claim_batch + interp_recorded_fixture_witness + v1_src_dag_parse + auth_declared_but_unwired_witness + bootstrap_witness + dag_collect_fingerprint_witness + diagnostics_witness + effects_rest_transport_witness + infer_semantics_witness + parse_witness + cssl_assemble + namespace_structural_root_exposure_generated_witness + codex_app_server_stdio_session + ' > "$RUNNER_TEMP"'/release-bins.roster' + 'tar' '-tzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' > "$RUNNER_TEMP"'/pack-listing.out' + 'cmp' '-s' "$RUNNER_TEMP"'/pack-listing.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins PackListingNotRoster::the downloaded pack'\''s archive listing is not exactly the roster (an extra, duplicate, directory or escaping entry)'; exit 1) + 'cut' '-c' '67-' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest' > "$RUNNER_TEMP"'/manifest-names.out' + 'cmp' '-s' "$RUNNER_TEMP"'/manifest-names.out' "$RUNNER_TEMP"'/release-bins.roster' || ('echo' '::error title=release-bins ManifestRosterMismatch::the downloaded manifest does not name exactly the gunbc.ci_release_bins roster in order'; exit 1) + if 'grep' '-qvE' '^[0-9a-f]{64} [A-Za-z0-9_]+$' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest'; then ('echo' '::error title=release-bins ManifestMalformed::the downloaded manifest has a line that is not one well-formed sha256 record for a roster member'; exit 1); fi + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + 'mkdir' '-p' "$RUNNER_TEMP"'/release-bins-check' + 'tar' '-xzf' "$RUNNER_TEMP"'/release-bins-download/release-bins.tgz' '-C' "$RUNNER_TEMP"'/release-bins-check' '--no-same-owner' + 'printf' '%s' 'claim_executor:regular file:1 + gunbc:regular file:1 + discover_source_root_ingest:regular file:1 + claim_batch:regular file:1 + interp_recorded_fixture_witness:regular file:1 + v1_src_dag_parse:regular file:1 + auth_declared_but_unwired_witness:regular file:1 + bootstrap_witness:regular file:1 + dag_collect_fingerprint_witness:regular file:1 + diagnostics_witness:regular file:1 + effects_rest_transport_witness:regular file:1 + infer_semantics_witness:regular file:1 + parse_witness:regular file:1 + cssl_assemble:regular file:1 + namespace_structural_root_exposure_generated_witness:regular file:1 + codex_app_server_stdio_session:regular file:1 + ' > "$RUNNER_TEMP"'/member-types.expected' + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'stat' '-c' '%n:%F:%h' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') > "$RUNNER_TEMP"'/member-types.out' + 'cmp' '-s' "$RUNNER_TEMP"'/member-types.out' "$RUNNER_TEMP"'/member-types.expected' || ('diff' "$RUNNER_TEMP"'/member-types.expected' "$RUNNER_TEMP"'/member-types.out' || ':' 'no-op'; ('echo' '::error title=release-bins MemberNotRegularFile::the downloaded pack has a member that is not a self-contained regular file (a link, special file or hard-link dependency; the report above names it)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'find' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' '-maxdepth' '0' '!' '-perm' '-u+x') > "$RUNNER_TEMP"'/member-nonexec.out' + '[' '!' '-s' "$RUNNER_TEMP"'/member-nonexec.out' ']' || ('cat' "$RUNNER_TEMP"'/member-nonexec.out'; ('echo' '::error title=release-bins MemberNotExecutable::the downloaded pack has a member that is not executable (listed above)'; exit 1)) + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'sha256sum' '--strict' '--quiet' '-c' "$RUNNER_TEMP"'/release-bins-download/release-bins.manifest') || ('echo' '::error title=release-bins MemberDigestMismatch::the downloaded member bytes do not match the manifest under strict checking; refusing before any member is promoted or run'; exit 1) + 'mkdir' '-p' "$ROOT"'/target/release' + ('cd' "$ROOT"'/target/release' && 'rm' '-f' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session') + ('cd' "$RUNNER_TEMP"'/release-bins-check' && 'cp' '-p' 'claim_executor' 'gunbc' 'discover_source_root_ingest' 'claim_batch' 'interp_recorded_fixture_witness' 'v1_src_dag_parse' 'auth_declared_but_unwired_witness' 'bootstrap_witness' 'dag_collect_fingerprint_witness' 'diagnostics_witness' 'effects_rest_transport_witness' 'infer_semantics_witness' 'parse_witness' 'cssl_assemble' 'namespace_structural_root_exposure_generated_witness' 'codex_app_server_stdio_session' "$ROOT"'/target/release/') + 'rm' '-rf' "$RUNNER_TEMP"'/release-bins-check' + "$ROOT"'/target/release/claim_executor' '--verify-build-artifacts' "$ROOT"'/target/release/claim_executor' "$ROOT"'/target/release/gunbc' "$ROOT"'/target/release/discover_source_root_ingest' "$ROOT"'/target/release/claim_batch' "$ROOT"'/target/release/interp_recorded_fixture_witness' "$ROOT"'/target/release/v1_src_dag_parse' "$ROOT"'/target/release/auth_declared_but_unwired_witness' "$ROOT"'/target/release/bootstrap_witness' "$ROOT"'/target/release/dag_collect_fingerprint_witness' "$ROOT"'/target/release/diagnostics_witness' "$ROOT"'/target/release/effects_rest_transport_witness' "$ROOT"'/target/release/infer_semantics_witness' "$ROOT"'/target/release/parse_witness' "$ROOT"'/target/release/cssl_assemble' "$ROOT"'/target/release/namespace_structural_root_exposure_generated_witness' "$ROOT"'/target/release/codex_app_server_stdio_session' + env: + RELEASE_BINS_KEY: ${{ needs.build.outputs.release_bins_key }} + timeout-minutes: 5 + - name: WIF auth (dedicated Namecheap reader) + id: wif_auth + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 + with: + workload_identity_provider: projects/582015116396/locations/global/workloadIdentityPools/github-namecheap-dns/providers/github-namecheap-dns-oidc + service_account: namecheap-dns@gunbai-secrets.iam.gserviceaccount.com + token_format: access_token + create_credentials_file: false + timeout-minutes: 5 + - name: Observe Namecheap getHosts with the Secret Manager credential + id: namecheap_observe + 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/namecheap/observe.dag --function namecheap_observe_ci_wet + cat "$ROOT/target/namecheap-observation.json" + env: + WIF_ACCESS_TOKEN: ${{ steps.wif_auth.outputs.access_token }} + timeout-minutes: 5 + - name: Upload Namecheap read-only DNS observation + id: namecheap_secret_receipt + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: namecheap-observation + path: target/namecheap-observation.json + if-no-files-found: error + retention-days: 90 + compression-level: 0 + if: success() + timeout-minutes: 10