@kungfu-tech/buildchain 3.0.9-alpha.3 → 3.0.9-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/site/buildchain-contract.json +3 -3
- package/dist/site/buildchain-site.json +6 -6
- package/dist/site/kfd-claims.json +2 -2
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/node-api-registry.json +1 -1
- package/dist/site/page-registry.json +2 -2
- package/dist/site/public-surface-audit.json +2 -2
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +4 -4
- package/dist/site/workflow-registry.json +4 -4
- package/docs/aws-us-elastic-runner-burst-plane.md +14 -0
- package/package.json +1 -1
- package/packages/core/release-passport-contract.js +1 -1
- package/scripts/aws-macos-jit-controller-core.mjs +26 -2
- package/scripts/aws-macos-jit-controller.mjs +39 -1
- package/scripts/aws-macos-jit-source-rebind.mjs +71 -14
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"product": {
|
|
5
5
|
"name": "Buildchain",
|
|
6
6
|
"package": "@kungfu-tech/buildchain",
|
|
7
|
-
"version": "3.0.9-alpha.
|
|
7
|
+
"version": "3.0.9-alpha.4",
|
|
8
8
|
"repository": "https://github.com/kungfu-systems/buildchain"
|
|
9
9
|
},
|
|
10
10
|
"majorLine": "v3",
|
|
@@ -1093,7 +1093,7 @@
|
|
|
1093
1093
|
"agents can discover supported Node APIs without importing internal file paths"
|
|
1094
1094
|
],
|
|
1095
1095
|
"breakingDigest": "sha256:48f925608d3e2131d90936b07dc2a30341204cae3e6e785c0f77d61ad755c945",
|
|
1096
|
-
"auditDigest": "sha256:
|
|
1096
|
+
"auditDigest": "sha256:9e16a32cc8b9607a50f7a2316c5ab5e6a52cc96629f79c9ff8f6b34ddd9ff42a"
|
|
1097
1097
|
},
|
|
1098
1098
|
{
|
|
1099
1099
|
"contractVersion": 1,
|
|
@@ -4024,5 +4024,5 @@
|
|
|
4024
4024
|
],
|
|
4025
4025
|
"compatibilityProofRegistryRoot": "sha256:f11b62ce69cfb0ff25b66861474b99a75148192c0474c9d1cc2067e347b05abd",
|
|
4026
4026
|
"compatibilityDigest": "sha256:80f65cc191b1f771475184edf8a1bf9c10dd69bb58060aa09313d0e1e57f508a",
|
|
4027
|
-
"contractDigest": "sha256:
|
|
4027
|
+
"contractDigest": "sha256:fd841a3328a74499295012af11881b65de30ea7add2c03e45e6e46ac79b472b3"
|
|
4028
4028
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-site-bundle",
|
|
4
|
-
"generatedAt": "2026-08-
|
|
5
|
-
"publishedAt": "2026-08-
|
|
4
|
+
"generatedAt": "2026-08-11T15:41:25.000Z",
|
|
5
|
+
"publishedAt": "2026-08-11T15:41:25.000Z",
|
|
6
6
|
"reproducible": true,
|
|
7
7
|
"timestampPolicy": "ci-injected",
|
|
8
8
|
"deterministicInputs": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"declared Buildchain surface manifest contract"
|
|
20
20
|
],
|
|
21
21
|
"sourceDateEpoch": "0",
|
|
22
|
-
"sourceRevision": "
|
|
22
|
+
"sourceRevision": "d6b36d5cf30d80a0b8406740660675f8b40cd7a0",
|
|
23
23
|
"timestampPolicyDetails": {
|
|
24
24
|
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
25
|
"timestampFields": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"package": {
|
|
39
39
|
"name": "@kungfu-tech/buildchain",
|
|
40
|
-
"version": "3.0.9-alpha.
|
|
40
|
+
"version": "3.0.9-alpha.4",
|
|
41
41
|
"versionSource": "package.json#version"
|
|
42
42
|
},
|
|
43
43
|
"source": {
|
|
@@ -478,7 +478,7 @@
|
|
|
478
478
|
],
|
|
479
479
|
"maturity": "stable",
|
|
480
480
|
"sourcePath": "docs/aws-us-elastic-runner-burst-plane.md",
|
|
481
|
-
"digest": "sha256:
|
|
481
|
+
"digest": "sha256:7ffe2235f33cf4309232ea720040397e915e71528ed7bb6a45d1bd604ca1e335",
|
|
482
482
|
"headings": [
|
|
483
483
|
{
|
|
484
484
|
"level": 1,
|
|
@@ -541,7 +541,7 @@
|
|
|
541
541
|
"anchor": "source-boundaries"
|
|
542
542
|
}
|
|
543
543
|
],
|
|
544
|
-
"markdown": "---\nstatus: draft\nperiod: 2026-07-28\ntheme: aws-us-elastic-runner-burst-plane\ndoc_type: design\nsource_level: local-files-and-provider-docs\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-08-03\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-08-03\n invisible_information: No hidden model checkpoint, parameters, or private training data were available.\n---\n\n# AWS US elastic runner burst plane\n\nThe local runner fleet remains the normal Kungfu build plane. This AWS US plane\nis an explicit, temporary overflow mechanism with sequential qualification:\n\n1. Linux CodeBuild proof of concept under USD 50.\n2. Windows EC2 one-job JIT runners.\n3. One bounded 24-hour EC2 Mac campaign.\n\nNo later phase can start from design intent alone. The preceding phase must\nproduce a qualifying source-bound receipt, actual cost, and zero-resource\ncleanup proof.\n\n## Phase 1 contract\n\n`aws-us-codebuild-linux` is a Linux-only runner preset. It requires the exact\nCodeBuild project name and resolves the runner label at workflow evaluation\ntime:\n\n```text\ncodebuild-<project>-<github.run_id>-<github.run_attempt>\n```\n\nThe GitHub-hosted `trust-gate` remains ahead of the matrix job. A fork pull\nrequest therefore fails or skips before the CodeBuild `runs-on` label exists as\na queued job. The dedicated consumer workflow is manual-only and does not add\nthe preset to dev, alpha, release, signing, notarization, deployment, or\npublication workflows.\n\nThe CodeBuild project is:\n\n- repository-scoped through an AWS CodeConnections GitHub App;\n- one ephemeral runner and one GitHub job per CodeBuild build;\n- outside a VPC, with no idle VM, NAT gateway, public ingress, SSH, or persistent\n workspace;\n- limited to two concurrent builds, 15 queued minutes, and 40 execution\n minutes;\n- allowed to write only its dedicated CloudWatch log group and request a token\n from its dedicated GitHub App connection;\n- forbidden from receiving signing, notarization, package publication, release,\n deploy, static AWS, long-lived GitHub, or SSH credentials.\n\nThe AWS-managed Ubuntu 24.04 standard image is the immutable base. Before a\nnative lifecycle starts, Buildchain installs the distribution's `gcc-14` and\n`g++-14` packages, exposes only per-job `gcc`/`g++` aliases, and downloads the\npinned Kitware CMake 3.31.6 archive after verifying its reviewed SHA256. The\nresolved package manager, versions, and CMake source digest are retained as\n`aws-native-toolchain.json`; no toolchain state survives the ephemeral\nCodeBuild execution. The toolchain adapter also retains the reviewed Amazon\nLinux 2023 `gcc14` path for compatible projects.\n\n## Cost and kill-switch envelope\n\nThe 2026-07-28 AWS Price List entry for\n`BUILD_GENERAL1_XLARGE` Linux in `us-east-1` is USD 0.0798 per build minute.\nThe contract rounds that rate up to USD 0.08. Twelve fully timed-out accepted\nbuilds reserve at most USD 38.40. At project concurrency two, the fail-closed\ncontroller can see at most two over-cap builds. The envelope conservatively\ncharges both race builds for their complete 40-minute timeout rather than\nassuming fast EventBridge delivery. The bounded CodeBuild maximum is therefore\nUSD 44.80, below the dedicated USD 49 budget and leaving USD 4.20 for the small\ncontroller, state, notification, and log charges.\n\nThe controller stores an idempotent build-id ledger, an atomic accepted-build\ncounter, and worst-case reservation in DynamoDB. Duplicate EventBridge delivery\ndoes not consume the bounded build allowance. It deletes the CodeBuild webhook\nand stops the triggering build when:\n\n- the accepted-build or reserved-cost cap is reached;\n- actual-cost telemetry is missing or more than six hours old;\n- actual CodeBuild spend reaches the budget;\n- AWS Budgets sends the 80% or 95% actual-spend notification;\n- the kill switch was already set.\n\nThe stack starts fail closed: it has no cost telemetry item and CloudFormation\ndoes not create the webhook. Before arming the webhook, the operator must write\na current Cost Explorer observation to the `COST` item, clear only the dedicated\ncontroller's killed state, and create the exact workflow-filtered webhook.\nRe-arming after any kill is a separate provider mutation and requires a new\nexplicit approval.\n\n## Qualification evidence\n\nEach successful job uploads `aws-runner-burst.json`, binding:\n\n- consumer repository, exact source SHA and ref;\n- GitHub run id, attempt and job;\n- CodeBuild project, build id, build ARN and initiator;\n- observation timestamp and canonical digest.\n\nLinux qualification requires:\n\n- at least 10 trusted exact-source successful jobs;\n- observed concurrency of at least two;\n- p95 queue-to-start of at most five minutes;\n- actual incremental AWS spend below USD 49;\n- no idle build and no active cloud residue.\n\n`node scripts/aws-runner-burst.mjs verify-linux --input <snapshot.json>` fails\nclosed when cost telemetry is missing/stale or any acceptance predicate is\nfalse.\n\n### Phase 1 recorded outcome\n\nThe Linux phase passed on 2026-07-29. Ten trusted exact-source Kungfu jobs\ncompleted successfully, including four overlapping two-job waves. The observed\nCodeBuild queue-to-start p95 was 0.696 seconds. All 16 paid executions,\nincluding six diagnostic runs, produced a conservative incremental compute\nupper bound of USD 25.798 by rounding every execution up to a whole minute at\nthe live AWS Price List rate.\n\nThe global webhook kill switch was exercised after the tenth qualifying job.\nThe project then reported no webhook or in-progress build, and the card-owned\nEC2 inventory was empty. AWS Billing and Cost Explorer still reported an\nestimated zero during their provider ingestion delay; the retained\nexecution-derived upper bound is therefore the immediate cost proof and must be\nreconciled with the eventual AWS line item in the final campaign report.\n\nThe source-bound evidence and deterministic phase receipt are:\n\n- `evidence/aws-us-elastic-runner-burst-plane/linux-codebuild-qualification-input.json`\n- `evidence/aws-us-elastic-runner-burst-plane/linux-codebuild-qualification-receipt.json`\n\n## Phase 2 contract\n\nThe Windows phase uses the explicit `aws-us-ec2-windows-jit` runner preset.\nIts caller supplies one bounded label under\n`aws-us-ec2-windows-jit-<campaign-id>-<qualification-id>`, and Buildchain\nresolves exactly one Windows x64 native lane. The reusable trust gate still\nruns on a GitHub-hosted runner before the JIT label can select EC2.\n\nThe provider creates repository-level GitHub JIT configuration for\n`kungfu-systems/kungfu`. Its `labels` request must contain all four scheduling\nlabels: `self-hosted`, `Windows`, `X64`, and the card-scoped\n`aws-us-ec2-windows-jit-<campaign-id>-<qualification-id>` label. The workflow\ndisplay title also carries both identities, allowing the launch controller to\nverify the queued run against its campaign plan. GitHub's JIT endpoint does\nnot infer the default OS and architecture labels when they are omitted. The\nencoded configuration is never placed in EC2 user data, a tag, a command log,\nor an artifact. The operator writes it to a card-scoped SSM SecureString under\n`/kungfu/burst/windows/`; the instance role can read and delete only that\nprefix. Bootstrap reads the value once, deletes the parameter immediately, and\npasses it only to the pinned runner process.\n\nEach runner uses:\n\n- Amazon's current Windows Server 2025 Full Base AMI, resolved through the\n public SSM AMI parameter and retained by exact AMI id and name;\n- `c7i.4xlarge`, one instance and one JIT runner per job;\n- GitHub Actions Runner 2.336.0 with the official Windows x64 SHA256;\n- PowerShell 7.6.4 with the official Windows x64 MSI SHA256 and Microsoft\n Authenticode verification;\n- pinned PortableGit 2.55.0.3 with its GitHub release SHA256, exposing only its\n `cmd` directory so POSIX compatibility tools cannot shadow Windows tools;\n- a Microsoft Authenticode-verified Visual Studio 2022 Build Tools bootstrap;\n- IMDSv2, an encrypted root volume with delete-on-termination, no inbound\n security-group rule, no key pair, and no warm Auto Scaling capacity.\n\nRunner diagnostics and a redacted lifecycle record are uploaded to the\nprovider's encrypted, private evidence bucket under the exact campaign, run,\nattempt, and instance identity. The runner process exits after one job, Windows\nshuts down, and EC2's instance-initiated shutdown behavior is set to\n`terminate`. A five-minute reaper terminates card-owned stopped or\nthree-hour-old instances and deletes only their dedicated JIT parameter.\n\nAt the 2026-07-29 AWS Price List rate of USD 1.45 per Windows\n`c7i.4xlarge` hour, each accepted instance reserves its complete three-hour\nUSD 4.35 fail-closed lifetime before `RunInstances`. A DynamoDB transaction\nbinds the exact campaign and source, creates an idempotent run ledger entry,\nand atomically refuses a sixth accepted instance. Five accepted instances\ntherefore reserve at most USD 21.75. The campaign also persists the\noperator-observed spend from earlier Windows work, and refuses to arm unless\nthat baseline, all five reservations, and one USD 4.35 fail-closed race\nallowance remain below the USD 110 phase cap.\n\nThe campaign starts unarmed and expires within 24 hours. Its `CONTROL` record\ncan be created only once: a killed or expired campaign cannot be re-armed by\nthe campaign tool. A budget notification or any instance lifetime violation\npersists `KILLED` before cleanup, so later workflow dispatches fail before a\npaid launch. Reservations are never refunded: a controller crash, ambiguous\nlaunch, or successful launch all remain charged to the campaign, favoring a\nfalse stop over an accidental budget overrun.\n\nThe 2026-08-03 timeout-only campaign decision narrows the campaign to two\naccepted instances with one active instance at a time. The second reservation\nis an operator-gated repair retry: it may be used only after the first attempt\nis classified as non-counting and runner, EC2, EBS, SSM, and workflow residue\nhave returned to zero. The two-slot ledger is a maximum spend boundary, not an\nauthorization to consume both reservations.\n\nEach stack owns a stack-scoped reaper log group, so an independent retained\none-shot campaign stack can be created without colliding with another\ncampaign's audit log resource.\n\nThe account-native AWS Budget is defense in depth, not the authoritative launch\ngate. It is owned by the singleton\n`kungfu-buildchain-windows-jit-budget-guard` stack rather than any retained\ncampaign stack. This prevents Budget-name collisions and prevents a stale\ncampaign reaper from becoming the provider-wide cost authority. The Budget\nfilters exactly `USAGE_TYPE=BoxUsage:c7i.4xlarge`,\n`OPERATION=RunInstances:0002` (Windows), and `REGION=us-east-1`; its 80% and\n95% actual notifications persist the provider kill sentinel, terminate every\ntagged Windows JIT instance, and delete scoped JIT parameters. Every launch\ncontroller refuses to proceed when the sentinel exists or when the Budget\nidentity or dimension filter does not match.\n\nBudget installation is intentionally deployable by the workload account without\nAWS Organizations management-account access. It fails closed unless Cost\nExplorer exposes all three AWS-owned billing dimensions in the requested phase\nwindow. The `kungfu:provider=windows-ec2-jit` resource tag remains mandatory for\nownership, cleanup, and IAM scoping, but it is not a billing filter. Do not\ncreate an unfiltered fallback Budget or treat an incomplete dimension readback\nas evidence.\nThe DynamoDB campaign reservation remains the atomic launch authority because\nCost Explorer and AWS Budgets can lag provider activity.\n\nQualification requires one runner-profile smoke and three trusted exact-source\nfull Windows jobs all bound to the same campaign, independent cancellation and\ntimeout cleanup exercises, and zero repository runner, EC2 instance,\ndisposable volume, min capacity, and desired capacity within 15 minutes of the\nfinal job.\n\n### Phase 2 operator workflow\n\n`pnpm operator:windows-jit` is the reusable lifecycle entrypoint. Its default\nmode is `plan`, which performs no AWS or GitHub call. A plan binds the account,\nregion, unique campaign and stack names, source SHA/ref, Cost Explorer window,\nworkflow id, network, OIDC provider, expiry, slot ceiling, singleton Budget\nidentity, and exact confirmation digest.\n\nThe modes are deliberately separated:\n\n- `plan` emits the deterministic mutation boundary and digest.\n- `audit` reads AWS and GitHub only. It verifies the account, disabled workflow,\n singleton guard stack, exact Budget filter, SNS thresholds/subscribers,\n provider kill sentinel, campaign stack, and zero EC2/EBS/SSM/JIT/runner\n residue.\n- `install-budget --execute` deploys or updates only the singleton Budget guard.\n It refuses to mutate unless all exact Windows billing dimensions are visible,\n the Windows workflow is disabled, and the account, campaign, source, Budget,\n and plan digest confirmations match.\n- `prepare --execute` requires the installed Budget guard, absent kill\n sentinel, fresh Cost Explorer readback filtered by `BoxUsage:c7i.4xlarge`,\n `RunInstances:0002`, and `us-east-1`, zero residue, a\n never-used campaign stack name, and the disabled workflow. The receipt binds\n the query timestamp and exact filter identity. Preparation deploys the\n campaign stack and atomically arms the ledger with that provider-spend\n baseline. It never enables or dispatches the workflow and never creates EC2\n capacity.\n- `close --execute` disables the workflow first, persists `KILLED`, publishes\n the campaign kill switch, and reports terminal success only after EC2, EBS,\n SSM, JIT parameter, and GitHub runner residue is zero. It is safe to rerun\n while the reaper settles.\n\nAll mutating modes require `--execute`, `--confirm-plan-digest`,\n`--confirm-account-id`, `--confirm-campaign-id`, and\n`--confirm-source-sha`. Budget installation and preparation additionally\nrequire `--confirm-budget-name`. A future paid workload still requires a\nseparate exact workflow/run authorization and uses\n`scripts/aws-windows-jit-controller.mjs`; preparation is not paid-launch\nauthority.\n\nStart by recording one reproducible plan:\n\n```bash\npnpm operator:windows-jit plan \\\n --aws-profile us \\\n --account-id 727884401362 \\\n --campaign-id win-REPLACE \\\n --source-sha REPLACE_WITH_EXACT_40_CHARACTER_SHA \\\n --source-ref refs/heads/dev/v4/v4.0 \\\n --observed-at REPLACE_WITH_ISO_TIMESTAMP \\\n --expires-at REPLACE_WITH_ISO_TIMESTAMP_WITHIN_24_HOURS \\\n --cost-start REPLACE_WITH_PHASE_START_DATE \\\n --cost-end REPLACE_WITH_EXCLUSIVE_END_DATE \\\n --max-accepted-instances 1 \\\n --workflow-id 322620360 \\\n --vpc-id REPLACE_WITH_VPC_ID \\\n --subnet-id REPLACE_WITH_SUBNET_ID \\\n --oidc-provider-arn REPLACE_WITH_GITHUB_OIDC_PROVIDER_ARN\n```\n\nReuse those exact arguments for `audit`, `install-budget`, `prepare`, or\n`close`; never regenerate `--observed-at` between the plan and its confirmed\nmutation. Capture stdout as the operator receipt. Do not put credentials,\ntokens, JIT configuration, or signed URLs in arguments or receipts.\n\n### Lower-level campaign and launch controllers\n\n`scripts/aws-windows-jit-campaign-core.mjs` owns the pure one-shot ledger\ncontract used by the operator and launch controller. Arming creates `CONTROL`\nand `CAMPAIGN#<id>` with `attribute_not_exists` conditions, so DynamoDB refuses\na second campaign in the same retained state table. There is deliberately no\nclear or re-arm operation.\n\nEvery `scripts/aws-windows-jit-controller.mjs --execute` call must provide the\nsame `--account-id`, `--campaign-id`, `--confirm-campaign-id`, `--state-table`,\nand `--confirm-state-table`. Before GitHub JIT material is created, the\ncontroller verifies the exact provider Budget/dimension filter and proves the\nglobal Budget kill sentinel absent. After the GitHub, AMI, active-instance,\nSSM, and EC2 DryRun checks pass, the controller\natomically reserves one run. Duplicate run-attempt-qualification identities,\nsource mismatch, expiry, `KILLED`, the sixth accepted instance, or a\nreservation that would exceed the USD 110 ceiling after combining the persisted\nfresh Cost Explorer baseline with all in-flight campaign reservations all fail\nclosed in one DynamoDB transaction before `RunInstances`. AWS Budget alarms are\ndefense in depth for delayed billing telemetry; the atomic ledger is the\nauthoritative launch-time guard. The operator is the only supported mutation\nsurface for campaign preparation and closeout; direct imports of the core are\nnot operator authority.\n\n## Phase 3 contract\n\nThe macOS phase uses the explicit `aws-us-ec2-macos-jit` runner preset. Its\ncaller supplies one unique label under\n`aws-us-ec2-macos-jit-<qualification-id>`, and Buildchain resolves exactly one\nnative macOS ARM64 lane with `self-hosted`, `macOS`, `ARM64`, and the unique\ncampaign label. The reusable trust gate remains ahead of the JIT runner.\n\nUnlike Windows, the Mac campaign deliberately reuses one instance on one\n`mac2.metal` Dedicated Host. The operator allocates exactly one tagged host,\nlaunches exactly one tagged instance, and sends three sequential SSM bootstrap\ncommands. Each command consumes and immediately deletes a distinct repository\nJIT SecureString under `/kungfu/burst/macos/`, then runs GitHub Actions Runner\n2.336.0 for exactly one job. The runner archive is pinned to the official\nmacOS ARM64 SHA256. No GitHub, signing, notarization, publication, SSH, or\nstatic AWS credential is admitted to the instance.\n\nThe instance uses the exact retained Amazon EC2 macOS AMI, IMDSv2, an encrypted\ndelete-on-termination root volume, no inbound security-group rule, and the\nAMI's preinstalled SSM Agent and AWS CLI v2. The three accepted jobs must bind\nto the same host id, instance id, AMI id, source SHA, and campaign. At least one\njob must exercise the full native lifecycle.\n\nAWS imposes a 24-hour minimum Dedicated Host allocation. The contract therefore\nkeeps the one host for at least 24 hours even if all three jobs finish earlier.\nAt the recorded USD 0.65 hourly rate, the minimum commitment is USD 15.60. A\n30-hour fail-closed ceiling is USD 19.50, below the dedicated\nUSD 25 budget. A ten-minute reaper terminates an expired campaign instance and\nretries host release after the minimum allocation and Apple scrub constraints\nallow it. Budget notifications at 80% and 95% invoke the same card-scoped kill\nswitch.\n\nThe launch controller defaults to `us-east-1` and admits only `us-east-2` as a\ncapacity fallback. The regions use mutually exclusive control-plane stacks and\none shared USD 25 Budget covering Virginia `HostUsage:mac2` and the AWS catalog\nidentity `USE2-HostUsage:mac2` for Ohio. A requested region, availability zone,\nstack, and Budget must agree before allocation, and the controller checks both\nregions against one global Host and instance ceiling; no other region is\naccepted.\n\nQualification requires three trusted exact-source one-job JIT runs on the one\nhost, including at least one full run, plus proof that:\n\n- the instance terminated and the encrypted disposable volume disappeared;\n- Apple host scrub completed;\n- the Dedicated Host was released between 24 and 30 hours after allocation;\n- the repository has no registered campaign runner;\n- AWS has no active campaign instance or allocated campaign host;\n- actual incremental spend remained below USD 25.\n\n### Phase 3 lifecycle controller\n\n`scripts/aws-macos-jit-controller.mjs` is the operator boundary for the paid\ncampaign. It has five explicit mutation modes:\n\n- `launch-campaign` binds the exact repository source, AMI, availability zone,\n tagged Dedicated Host, and reusable instance. Before paid allocation it\n verifies the exact AWS account, disabled GitHub workflow, complete control\n plane stack, AWS-dimension-filtered USD 25 Budget, 80% and 95% SNS\n subscribers, and zero pre-existing Mac capacity. Because AWS does not expose\n a DryRun parameter for `AllocateHosts`, it requires an allowed IAM policy\n simulation for the exact tagged allocation before the real call, then a\n successful `RunInstances` DryRun before launching the instance.\n- `rehydrate-instance` creates a replacement instance on the one already-paid,\n empty campaign host after an interrupted or failed instance lifecycle. It\n rechecks the exact account, disabled workflow, Budget, source, host identity\n and allocation time, zero runner residue, zero active JIT instances, and no\n other active Mac host in either admitted region. It then requires a\n successful same-host `RunInstances` DryRun and never calls `AllocateHosts`.\n- `run-job` binds one queued exact-source GitHub job to the existing campaign\n host and instance. It writes the repository JIT configuration through a\n mode-0600 temporary file into a distinct SSM SecureString, sends only the\n credential-free bootstrap through SSM, and removes the parameter plus runner\n registration if command delivery fails.\n- `rebind-campaign` repairs the source of an allocated but unused campaign\n without buying another host. It requires the workflow to remain manually\n disabled, the replacement source to be a strict descendant on the same\n campaign ref, and every prior matching run to contain zero jobs and zero\n artifacts. It also requires no registered JIT runner, SSM parameter, or\n bootstrap evidence and verifies the one exact host, instance, and encrypted\n volume. The operation updates only those resources' source tags and the\n existing GitHub ref, emits a zero-allocation receipt, and compensates back to\n the prior source if the ref update or readback fails. It never calls\n `AllocateHosts`, `RunInstances`, or workflow dispatch.\n- `close-campaign` refuses execution before the provider's 24-hour minimum,\n verifies the encrypted delete-on-termination root volume, removes scoped JIT\n residue, terminates the exact instance, and requires a `ReleaseHosts` DryRun\n before release. If Apple host scrubbing is still in progress, it reports\n `release-pending`; the ten-minute card-scoped reaper remains the bounded\n retry path.\n\nEvery execute mode requires the exact source SHA and campaign id to be repeated\nthrough `--confirm-source-sha` and `--confirm-campaign-id`. `run-job` also\nrequires `--confirm-run-id`; `launch-campaign` additionally requires the\nexpected workload account through `--account-id`. `rebind-campaign`\nadditionally repeats the prior source, host, and instance identities and\nrequires `--confirm-zero-allocation`. `rehydrate-instance` additionally repeats\nthe existing host and replaced instance identities and requires\n`--confirm-no-host-allocation`. Omitting `--execute` emits a deterministic plan\nwithout changing AWS or GitHub state.\n\n## Provider lifecycle\n\nThe three infrastructure templates live under\n`infra/aws-us-elastic-runner-burst-plane/`. Creating a change set is the review\nboundary. Executing it, completing the GitHub App connection, creating or\nre-arming a webhook, allocating or releasing a Dedicated Host, writing cost\ntelemetry, dispatching paid jobs, operating a kill switch, and deleting a stack\nare all explicit provider mutations.\n\nThe reviewed Phase 1 provider sequence is below. It deliberately separates\nconnection creation, change-set inspection, stack execution, cost observation,\nand webhook arming:\n\n```bash\nburst_profile=us\nburst_region=us-east-1\nburst_stack=kungfu-buildchain-linux-burst-poc\nburst_project=kungfu-buildchain-linux-burst-poc\nburst_connection_name=kungfu-linux-burst-poc\nburst_change_set=phase1-linux-codebuild-poc\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections create-connection \\\n --provider-type GitHub \\\n --connection-name \"$burst_connection_name\" \\\n --tags Key=kungfu:owner,Value=buildchain \\\n Key=kungfu:plane,Value=aws-us-elastic-runner-burst\n```\n\nThe returned connection is `PENDING` until an operator completes the GitHub App\nhandshake in AWS. Read back `ConnectionStatus=AVAILABLE` before creating the\nchange set. Do not put an OAuth token or GitHub token in the shell:\n\nAWS CodeConnections connection names are limited to 32 characters, so keep the\nshorter connection name even when the stack and project use the longer\nBuildchain-specific name.\n\n```bash\nburst_connection_arn=REPLACE_WITH_AVAILABLE_CONNECTION_ARN\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections get-connection \\\n --connection-arn \"$burst_connection_arn\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation create-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\" \\\n --change-set-type CREATE \\\n --template-body \\\n file://infra/aws-us-elastic-runner-burst-plane/codebuild-poc.template.yml \\\n --capabilities CAPABILITY_IAM \\\n --parameters \\\n ParameterKey=GitHubConnectionArn,ParameterValue=\"$burst_connection_arn\" \\\n ParameterKey=ProjectName,ParameterValue=\"$burst_project\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait change-set-create-complete \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation describe-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n```\n\nOnly after the change-set resource list and IAM diff are accepted:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation execute-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait stack-create-complete \\\n --stack-name \"$burst_stack\"\n```\n\nArming requires a fresh, operator-observed CodeBuild cost value. `COST` is the\nonly mutable telemetry item and `CONTROL` is the only state cleared:\n\n```bash\nburst_table=$(\n aws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation describe-stacks \\\n --stack-name \"$burst_stack\" \\\n --query \"Stacks[0].Outputs[?OutputKey=='StateTable'].OutputValue\" \\\n --output text\n)\nburst_observed_at=$(date -u +%s)\nburst_actual_usd=REPLACE_WITH_CURRENT_CODEBUILD_ACTUAL_USD\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n dynamodb put-item \\\n --table-name \"$burst_table\" \\\n --item \"{\\\"pk\\\":{\\\"S\\\":\\\"COST\\\"},\\\"actual_usd\\\":{\\\"N\\\":\\\"$burst_actual_usd\\\"},\\\"observed_at\\\":{\\\"N\\\":\\\"$burst_observed_at\\\"}}\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n dynamodb delete-item \\\n --table-name \"$burst_table\" \\\n --key '{\"pk\":{\"S\":\"CONTROL\"}}'\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codebuild create-webhook \\\n --project-name \"$burst_project\" \\\n --filter-groups \\\n '[[{\"type\":\"EVENT\",\"pattern\":\"WORKFLOW_JOB_QUEUED\"},{\"type\":\"WORKFLOW_NAME\",\"pattern\":\"^AWS US Linux Burst Qualification$\"}]]'\n```\n\nThe immediate global kill is idempotent and targets only the dedicated project:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codebuild delete-webhook \\\n --project-name \"$burst_project\"\n```\n\nAfter preserving the qualification evidence and proving no build is in\nprogress, rollback removes only the card-owned stack and connection:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation delete-stack \\\n --stack-name \"$burst_stack\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait stack-delete-complete \\\n --stack-name \"$burst_stack\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections delete-connection \\\n --connection-arn \"$burst_connection_arn\"\n```\n\nPhase cleanup evidence must include:\n\n- CodeBuild batch/list results showing no in-progress build;\n- controller state and accepted-build ledger;\n- CodeBuild actual cost observation and its timestamp;\n- no EC2 instance, volume, launch template, Auto Scaling group, or dedicated\n host created by this phase;\n- the CodeBuild webhook deleted or the whole stack deleted.\n\n## Source boundaries\n\nThe design follows the current AWS CodeBuild GitHub Actions runner contract:\n`WORKFLOW_JOB_QUEUED` starts an ephemeral runner, the run id maps cancellation,\nand the build terminates after one job. It uses the current GitHub guidance to\nprefer ephemeral autoscaled self-hosted runners and to retain runner logs\nexternally. Provider documentation and the live AWS Price List query are the\nauthoritative external sources; this document is an auditable cache."
|
|
544
|
+
"markdown": "---\nstatus: draft\nperiod: 2026-07-28\ntheme: aws-us-elastic-runner-burst-plane\ndoc_type: design\nsource_level: local-files-and-provider-docs\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-08-03\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-08-03\n invisible_information: No hidden model checkpoint, parameters, or private training data were available.\n---\n\n# AWS US elastic runner burst plane\n\nThe local runner fleet remains the normal Kungfu build plane. This AWS US plane\nis an explicit, temporary overflow mechanism with sequential qualification:\n\n1. Linux CodeBuild proof of concept under USD 50.\n2. Windows EC2 one-job JIT runners.\n3. One bounded 24-hour EC2 Mac campaign.\n\nNo later phase can start from design intent alone. The preceding phase must\nproduce a qualifying source-bound receipt, actual cost, and zero-resource\ncleanup proof.\n\n## Phase 1 contract\n\n`aws-us-codebuild-linux` is a Linux-only runner preset. It requires the exact\nCodeBuild project name and resolves the runner label at workflow evaluation\ntime:\n\n```text\ncodebuild-<project>-<github.run_id>-<github.run_attempt>\n```\n\nThe GitHub-hosted `trust-gate` remains ahead of the matrix job. A fork pull\nrequest therefore fails or skips before the CodeBuild `runs-on` label exists as\na queued job. The dedicated consumer workflow is manual-only and does not add\nthe preset to dev, alpha, release, signing, notarization, deployment, or\npublication workflows.\n\nThe CodeBuild project is:\n\n- repository-scoped through an AWS CodeConnections GitHub App;\n- one ephemeral runner and one GitHub job per CodeBuild build;\n- outside a VPC, with no idle VM, NAT gateway, public ingress, SSH, or persistent\n workspace;\n- limited to two concurrent builds, 15 queued minutes, and 40 execution\n minutes;\n- allowed to write only its dedicated CloudWatch log group and request a token\n from its dedicated GitHub App connection;\n- forbidden from receiving signing, notarization, package publication, release,\n deploy, static AWS, long-lived GitHub, or SSH credentials.\n\nThe AWS-managed Ubuntu 24.04 standard image is the immutable base. Before a\nnative lifecycle starts, Buildchain installs the distribution's `gcc-14` and\n`g++-14` packages, exposes only per-job `gcc`/`g++` aliases, and downloads the\npinned Kitware CMake 3.31.6 archive after verifying its reviewed SHA256. The\nresolved package manager, versions, and CMake source digest are retained as\n`aws-native-toolchain.json`; no toolchain state survives the ephemeral\nCodeBuild execution. The toolchain adapter also retains the reviewed Amazon\nLinux 2023 `gcc14` path for compatible projects.\n\n## Cost and kill-switch envelope\n\nThe 2026-07-28 AWS Price List entry for\n`BUILD_GENERAL1_XLARGE` Linux in `us-east-1` is USD 0.0798 per build minute.\nThe contract rounds that rate up to USD 0.08. Twelve fully timed-out accepted\nbuilds reserve at most USD 38.40. At project concurrency two, the fail-closed\ncontroller can see at most two over-cap builds. The envelope conservatively\ncharges both race builds for their complete 40-minute timeout rather than\nassuming fast EventBridge delivery. The bounded CodeBuild maximum is therefore\nUSD 44.80, below the dedicated USD 49 budget and leaving USD 4.20 for the small\ncontroller, state, notification, and log charges.\n\nThe controller stores an idempotent build-id ledger, an atomic accepted-build\ncounter, and worst-case reservation in DynamoDB. Duplicate EventBridge delivery\ndoes not consume the bounded build allowance. It deletes the CodeBuild webhook\nand stops the triggering build when:\n\n- the accepted-build or reserved-cost cap is reached;\n- actual-cost telemetry is missing or more than six hours old;\n- actual CodeBuild spend reaches the budget;\n- AWS Budgets sends the 80% or 95% actual-spend notification;\n- the kill switch was already set.\n\nThe stack starts fail closed: it has no cost telemetry item and CloudFormation\ndoes not create the webhook. Before arming the webhook, the operator must write\na current Cost Explorer observation to the `COST` item, clear only the dedicated\ncontroller's killed state, and create the exact workflow-filtered webhook.\nRe-arming after any kill is a separate provider mutation and requires a new\nexplicit approval.\n\n## Qualification evidence\n\nEach successful job uploads `aws-runner-burst.json`, binding:\n\n- consumer repository, exact source SHA and ref;\n- GitHub run id, attempt and job;\n- CodeBuild project, build id, build ARN and initiator;\n- observation timestamp and canonical digest.\n\nLinux qualification requires:\n\n- at least 10 trusted exact-source successful jobs;\n- observed concurrency of at least two;\n- p95 queue-to-start of at most five minutes;\n- actual incremental AWS spend below USD 49;\n- no idle build and no active cloud residue.\n\n`node scripts/aws-runner-burst.mjs verify-linux --input <snapshot.json>` fails\nclosed when cost telemetry is missing/stale or any acceptance predicate is\nfalse.\n\n### Phase 1 recorded outcome\n\nThe Linux phase passed on 2026-07-29. Ten trusted exact-source Kungfu jobs\ncompleted successfully, including four overlapping two-job waves. The observed\nCodeBuild queue-to-start p95 was 0.696 seconds. All 16 paid executions,\nincluding six diagnostic runs, produced a conservative incremental compute\nupper bound of USD 25.798 by rounding every execution up to a whole minute at\nthe live AWS Price List rate.\n\nThe global webhook kill switch was exercised after the tenth qualifying job.\nThe project then reported no webhook or in-progress build, and the card-owned\nEC2 inventory was empty. AWS Billing and Cost Explorer still reported an\nestimated zero during their provider ingestion delay; the retained\nexecution-derived upper bound is therefore the immediate cost proof and must be\nreconciled with the eventual AWS line item in the final campaign report.\n\nThe source-bound evidence and deterministic phase receipt are:\n\n- `evidence/aws-us-elastic-runner-burst-plane/linux-codebuild-qualification-input.json`\n- `evidence/aws-us-elastic-runner-burst-plane/linux-codebuild-qualification-receipt.json`\n\n## Phase 2 contract\n\nThe Windows phase uses the explicit `aws-us-ec2-windows-jit` runner preset.\nIts caller supplies one bounded label under\n`aws-us-ec2-windows-jit-<campaign-id>-<qualification-id>`, and Buildchain\nresolves exactly one Windows x64 native lane. The reusable trust gate still\nruns on a GitHub-hosted runner before the JIT label can select EC2.\n\nThe provider creates repository-level GitHub JIT configuration for\n`kungfu-systems/kungfu`. Its `labels` request must contain all four scheduling\nlabels: `self-hosted`, `Windows`, `X64`, and the card-scoped\n`aws-us-ec2-windows-jit-<campaign-id>-<qualification-id>` label. The workflow\ndisplay title also carries both identities, allowing the launch controller to\nverify the queued run against its campaign plan. GitHub's JIT endpoint does\nnot infer the default OS and architecture labels when they are omitted. The\nencoded configuration is never placed in EC2 user data, a tag, a command log,\nor an artifact. The operator writes it to a card-scoped SSM SecureString under\n`/kungfu/burst/windows/`; the instance role can read and delete only that\nprefix. Bootstrap reads the value once, deletes the parameter immediately, and\npasses it only to the pinned runner process.\n\nEach runner uses:\n\n- Amazon's current Windows Server 2025 Full Base AMI, resolved through the\n public SSM AMI parameter and retained by exact AMI id and name;\n- `c7i.4xlarge`, one instance and one JIT runner per job;\n- GitHub Actions Runner 2.336.0 with the official Windows x64 SHA256;\n- PowerShell 7.6.4 with the official Windows x64 MSI SHA256 and Microsoft\n Authenticode verification;\n- pinned PortableGit 2.55.0.3 with its GitHub release SHA256, exposing only its\n `cmd` directory so POSIX compatibility tools cannot shadow Windows tools;\n- a Microsoft Authenticode-verified Visual Studio 2022 Build Tools bootstrap;\n- IMDSv2, an encrypted root volume with delete-on-termination, no inbound\n security-group rule, no key pair, and no warm Auto Scaling capacity.\n\nRunner diagnostics and a redacted lifecycle record are uploaded to the\nprovider's encrypted, private evidence bucket under the exact campaign, run,\nattempt, and instance identity. The runner process exits after one job, Windows\nshuts down, and EC2's instance-initiated shutdown behavior is set to\n`terminate`. A five-minute reaper terminates card-owned stopped or\nthree-hour-old instances and deletes only their dedicated JIT parameter.\n\nAt the 2026-07-29 AWS Price List rate of USD 1.45 per Windows\n`c7i.4xlarge` hour, each accepted instance reserves its complete three-hour\nUSD 4.35 fail-closed lifetime before `RunInstances`. A DynamoDB transaction\nbinds the exact campaign and source, creates an idempotent run ledger entry,\nand atomically refuses a sixth accepted instance. Five accepted instances\ntherefore reserve at most USD 21.75. The campaign also persists the\noperator-observed spend from earlier Windows work, and refuses to arm unless\nthat baseline, all five reservations, and one USD 4.35 fail-closed race\nallowance remain below the USD 110 phase cap.\n\nThe campaign starts unarmed and expires within 24 hours. Its `CONTROL` record\ncan be created only once: a killed or expired campaign cannot be re-armed by\nthe campaign tool. A budget notification or any instance lifetime violation\npersists `KILLED` before cleanup, so later workflow dispatches fail before a\npaid launch. Reservations are never refunded: a controller crash, ambiguous\nlaunch, or successful launch all remain charged to the campaign, favoring a\nfalse stop over an accidental budget overrun.\n\nThe 2026-08-03 timeout-only campaign decision narrows the campaign to two\naccepted instances with one active instance at a time. The second reservation\nis an operator-gated repair retry: it may be used only after the first attempt\nis classified as non-counting and runner, EC2, EBS, SSM, and workflow residue\nhave returned to zero. The two-slot ledger is a maximum spend boundary, not an\nauthorization to consume both reservations.\n\nEach stack owns a stack-scoped reaper log group, so an independent retained\none-shot campaign stack can be created without colliding with another\ncampaign's audit log resource.\n\nThe account-native AWS Budget is defense in depth, not the authoritative launch\ngate. It is owned by the singleton\n`kungfu-buildchain-windows-jit-budget-guard` stack rather than any retained\ncampaign stack. This prevents Budget-name collisions and prevents a stale\ncampaign reaper from becoming the provider-wide cost authority. The Budget\nfilters exactly `USAGE_TYPE=BoxUsage:c7i.4xlarge`,\n`OPERATION=RunInstances:0002` (Windows), and `REGION=us-east-1`; its 80% and\n95% actual notifications persist the provider kill sentinel, terminate every\ntagged Windows JIT instance, and delete scoped JIT parameters. Every launch\ncontroller refuses to proceed when the sentinel exists or when the Budget\nidentity or dimension filter does not match.\n\nBudget installation is intentionally deployable by the workload account without\nAWS Organizations management-account access. It fails closed unless Cost\nExplorer exposes all three AWS-owned billing dimensions in the requested phase\nwindow. The `kungfu:provider=windows-ec2-jit` resource tag remains mandatory for\nownership, cleanup, and IAM scoping, but it is not a billing filter. Do not\ncreate an unfiltered fallback Budget or treat an incomplete dimension readback\nas evidence.\nThe DynamoDB campaign reservation remains the atomic launch authority because\nCost Explorer and AWS Budgets can lag provider activity.\n\nQualification requires one runner-profile smoke and three trusted exact-source\nfull Windows jobs all bound to the same campaign, independent cancellation and\ntimeout cleanup exercises, and zero repository runner, EC2 instance,\ndisposable volume, min capacity, and desired capacity within 15 minutes of the\nfinal job.\n\n### Phase 2 operator workflow\n\n`pnpm operator:windows-jit` is the reusable lifecycle entrypoint. Its default\nmode is `plan`, which performs no AWS or GitHub call. A plan binds the account,\nregion, unique campaign and stack names, source SHA/ref, Cost Explorer window,\nworkflow id, network, OIDC provider, expiry, slot ceiling, singleton Budget\nidentity, and exact confirmation digest.\n\nThe modes are deliberately separated:\n\n- `plan` emits the deterministic mutation boundary and digest.\n- `audit` reads AWS and GitHub only. It verifies the account, disabled workflow,\n singleton guard stack, exact Budget filter, SNS thresholds/subscribers,\n provider kill sentinel, campaign stack, and zero EC2/EBS/SSM/JIT/runner\n residue.\n- `install-budget --execute` deploys or updates only the singleton Budget guard.\n It refuses to mutate unless all exact Windows billing dimensions are visible,\n the Windows workflow is disabled, and the account, campaign, source, Budget,\n and plan digest confirmations match.\n- `prepare --execute` requires the installed Budget guard, absent kill\n sentinel, fresh Cost Explorer readback filtered by `BoxUsage:c7i.4xlarge`,\n `RunInstances:0002`, and `us-east-1`, zero residue, a\n never-used campaign stack name, and the disabled workflow. The receipt binds\n the query timestamp and exact filter identity. Preparation deploys the\n campaign stack and atomically arms the ledger with that provider-spend\n baseline. It never enables or dispatches the workflow and never creates EC2\n capacity.\n- `close --execute` disables the workflow first, persists `KILLED`, publishes\n the campaign kill switch, and reports terminal success only after EC2, EBS,\n SSM, JIT parameter, and GitHub runner residue is zero. It is safe to rerun\n while the reaper settles.\n\nAll mutating modes require `--execute`, `--confirm-plan-digest`,\n`--confirm-account-id`, `--confirm-campaign-id`, and\n`--confirm-source-sha`. Budget installation and preparation additionally\nrequire `--confirm-budget-name`. A future paid workload still requires a\nseparate exact workflow/run authorization and uses\n`scripts/aws-windows-jit-controller.mjs`; preparation is not paid-launch\nauthority.\n\nStart by recording one reproducible plan:\n\n```bash\npnpm operator:windows-jit plan \\\n --aws-profile us \\\n --account-id 727884401362 \\\n --campaign-id win-REPLACE \\\n --source-sha REPLACE_WITH_EXACT_40_CHARACTER_SHA \\\n --source-ref refs/heads/dev/v4/v4.0 \\\n --observed-at REPLACE_WITH_ISO_TIMESTAMP \\\n --expires-at REPLACE_WITH_ISO_TIMESTAMP_WITHIN_24_HOURS \\\n --cost-start REPLACE_WITH_PHASE_START_DATE \\\n --cost-end REPLACE_WITH_EXCLUSIVE_END_DATE \\\n --max-accepted-instances 1 \\\n --workflow-id 322620360 \\\n --vpc-id REPLACE_WITH_VPC_ID \\\n --subnet-id REPLACE_WITH_SUBNET_ID \\\n --oidc-provider-arn REPLACE_WITH_GITHUB_OIDC_PROVIDER_ARN\n```\n\nReuse those exact arguments for `audit`, `install-budget`, `prepare`, or\n`close`; never regenerate `--observed-at` between the plan and its confirmed\nmutation. Capture stdout as the operator receipt. Do not put credentials,\ntokens, JIT configuration, or signed URLs in arguments or receipts.\n\n### Lower-level campaign and launch controllers\n\n`scripts/aws-windows-jit-campaign-core.mjs` owns the pure one-shot ledger\ncontract used by the operator and launch controller. Arming creates `CONTROL`\nand `CAMPAIGN#<id>` with `attribute_not_exists` conditions, so DynamoDB refuses\na second campaign in the same retained state table. There is deliberately no\nclear or re-arm operation.\n\nEvery `scripts/aws-windows-jit-controller.mjs --execute` call must provide the\nsame `--account-id`, `--campaign-id`, `--confirm-campaign-id`, `--state-table`,\nand `--confirm-state-table`. Before GitHub JIT material is created, the\ncontroller verifies the exact provider Budget/dimension filter and proves the\nglobal Budget kill sentinel absent. After the GitHub, AMI, active-instance,\nSSM, and EC2 DryRun checks pass, the controller\natomically reserves one run. Duplicate run-attempt-qualification identities,\nsource mismatch, expiry, `KILLED`, the sixth accepted instance, or a\nreservation that would exceed the USD 110 ceiling after combining the persisted\nfresh Cost Explorer baseline with all in-flight campaign reservations all fail\nclosed in one DynamoDB transaction before `RunInstances`. AWS Budget alarms are\ndefense in depth for delayed billing telemetry; the atomic ledger is the\nauthoritative launch-time guard. The operator is the only supported mutation\nsurface for campaign preparation and closeout; direct imports of the core are\nnot operator authority.\n\n## Phase 3 contract\n\nThe macOS phase uses the explicit `aws-us-ec2-macos-jit` runner preset. Its\ncaller supplies one unique label under\n`aws-us-ec2-macos-jit-<qualification-id>`, and Buildchain resolves exactly one\nnative macOS ARM64 lane with `self-hosted`, `macOS`, `ARM64`, and the unique\ncampaign label. The reusable trust gate remains ahead of the JIT runner.\n\nUnlike Windows, the Mac campaign deliberately reuses one instance on one\n`mac2.metal` Dedicated Host. The operator allocates exactly one tagged host,\nlaunches exactly one tagged instance, and sends three sequential SSM bootstrap\ncommands. Each command consumes and immediately deletes a distinct repository\nJIT SecureString under `/kungfu/burst/macos/`, then runs GitHub Actions Runner\n2.336.0 for exactly one job. The runner archive is pinned to the official\nmacOS ARM64 SHA256. No GitHub, signing, notarization, publication, SSH, or\nstatic AWS credential is admitted to the instance.\n\nThe instance uses the exact retained Amazon EC2 macOS AMI, IMDSv2, an encrypted\ndelete-on-termination root volume, no inbound security-group rule, and the\nAMI's preinstalled SSM Agent and AWS CLI v2. The three accepted jobs must bind\nto the same host id, instance id, AMI id, source SHA, and campaign. At least one\njob must exercise the full native lifecycle.\n\nAWS imposes a 24-hour minimum Dedicated Host allocation. The contract therefore\nkeeps the one host for at least 24 hours even if all three jobs finish earlier.\nAt the recorded USD 0.65 hourly rate, the minimum commitment is USD 15.60. A\n30-hour fail-closed ceiling is USD 19.50, below the dedicated\nUSD 25 budget. A ten-minute reaper terminates an expired campaign instance and\nretries host release after the minimum allocation and Apple scrub constraints\nallow it. Budget notifications at 80% and 95% invoke the same card-scoped kill\nswitch.\n\nThe launch controller defaults to `us-east-1` and admits only `us-east-2` as a\ncapacity fallback. The regions use mutually exclusive control-plane stacks and\none shared USD 25 Budget covering Virginia `HostUsage:mac2` and the AWS catalog\nidentity `USE2-HostUsage:mac2` for Ohio. A requested region, availability zone,\nstack, and Budget must agree before allocation, and the controller checks both\nregions against one global Host and instance ceiling; no other region is\naccepted.\n\nQualification requires three trusted exact-source one-job JIT runs on the one\nhost, including at least one full run, plus proof that:\n\n- the instance terminated and the encrypted disposable volume disappeared;\n- Apple host scrub completed;\n- the Dedicated Host was released between 24 and 30 hours after allocation;\n- the repository has no registered campaign runner;\n- AWS has no active campaign instance or allocated campaign host;\n- actual incremental spend remained below USD 25.\n\n### Phase 3 lifecycle controller\n\n`scripts/aws-macos-jit-controller.mjs` is the operator boundary for the paid\ncampaign. It has five explicit mutation modes:\n\n- `launch-campaign` binds the exact repository source, AMI, availability zone,\n tagged Dedicated Host, and reusable instance. Before paid allocation it\n verifies the exact AWS account, disabled GitHub workflow, complete control\n plane stack, AWS-dimension-filtered USD 25 Budget, 80% and 95% SNS\n subscribers, and zero pre-existing Mac capacity. Because AWS does not expose\n a DryRun parameter for `AllocateHosts`, it requires an allowed IAM policy\n simulation for the exact tagged allocation before the real call, then a\n successful `RunInstances` DryRun before launching the instance.\n- `rehydrate-instance` creates a replacement instance on the one already-paid,\n empty campaign host after an interrupted or failed instance lifecycle. It\n rechecks the exact account, disabled workflow, Budget, source, host identity\n and allocation time, zero runner residue, zero active JIT instances, and no\n other active Mac host in either admitted region. It then requires a\n successful same-host `RunInstances` DryRun and never calls `AllocateHosts`.\n- `run-job` binds one queued exact-source GitHub job to the existing campaign\n host and instance. It writes the repository JIT configuration through a\n mode-0600 temporary file into a distinct SSM SecureString, sends only the\n credential-free bootstrap through SSM, and removes the parameter plus runner\n registration if command delivery fails.\n- `rebind-campaign` repairs the source of an allocated but unused campaign\n without buying another host. It requires the workflow to remain manually\n disabled, the replacement source to be a strict descendant on the same\n campaign ref, and every prior matching run to contain zero jobs and zero\n artifacts. It also requires no registered JIT runner, SSM parameter, or\n bootstrap evidence and verifies the one exact host, instance, and encrypted\n volume. The operation updates only those resources' source tags and the\n existing GitHub ref, emits a zero-allocation receipt, and compensates back to\n the prior source if the ref update or readback fails. It never calls\n `AllocateHosts`, `RunInstances`, or workflow dispatch.\n- `rebind-campaign-after-failure` advances that same host and instance only\n after the operator confirms the complete prior run-id inventory. Every named\n run must be terminal with conclusion `failure`, all of its jobs must be\n complete, and both GitHub artifacts and S3 bootstrap evidence must still be\n present. The replacement source must remain a strict descendant on the same\n ref, the workflow must be disabled, the next-source evidence prefix must be\n empty, and runner plus SSM residue must be zero. The old evidence is retained\n and included in the rebind receipt; the operation changes only the existing\n source ref and resource tags, with the same compensated rollback and zero\n allocation boundary as an unused-campaign rebind.\n- `close-campaign` refuses execution before the provider's 24-hour minimum,\n verifies the encrypted delete-on-termination root volume, removes scoped JIT\n residue, terminates the exact instance, and requires a `ReleaseHosts` DryRun\n before release. If Apple host scrubbing is still in progress, it reports\n `release-pending`; the ten-minute card-scoped reaper remains the bounded\n retry path.\n\nEvery execute mode requires the exact source SHA and campaign id to be repeated\nthrough `--confirm-source-sha` and `--confirm-campaign-id`. `run-job` also\nrequires `--confirm-run-id`; `launch-campaign` additionally requires the\nexpected workload account through `--account-id`. `rebind-campaign`\nadditionally repeats the prior source, host, and instance identities and\nrequires `--confirm-zero-allocation`. `rehydrate-instance` additionally repeats\nthe existing host and replaced instance identities and requires\n`--confirm-no-host-allocation`. Omitting `--execute` emits a deterministic plan\nwithout changing AWS or GitHub state.\n`rebind-campaign-after-failure` additionally requires matching\n`--terminal-failure-run-ids-json` and\n`--confirm-terminal-failure-run-ids-json` arrays so an unobserved or newly\ncreated run fails closed before any tag or ref mutation.\n\n## Provider lifecycle\n\nThe three infrastructure templates live under\n`infra/aws-us-elastic-runner-burst-plane/`. Creating a change set is the review\nboundary. Executing it, completing the GitHub App connection, creating or\nre-arming a webhook, allocating or releasing a Dedicated Host, writing cost\ntelemetry, dispatching paid jobs, operating a kill switch, and deleting a stack\nare all explicit provider mutations.\n\nThe reviewed Phase 1 provider sequence is below. It deliberately separates\nconnection creation, change-set inspection, stack execution, cost observation,\nand webhook arming:\n\n```bash\nburst_profile=us\nburst_region=us-east-1\nburst_stack=kungfu-buildchain-linux-burst-poc\nburst_project=kungfu-buildchain-linux-burst-poc\nburst_connection_name=kungfu-linux-burst-poc\nburst_change_set=phase1-linux-codebuild-poc\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections create-connection \\\n --provider-type GitHub \\\n --connection-name \"$burst_connection_name\" \\\n --tags Key=kungfu:owner,Value=buildchain \\\n Key=kungfu:plane,Value=aws-us-elastic-runner-burst\n```\n\nThe returned connection is `PENDING` until an operator completes the GitHub App\nhandshake in AWS. Read back `ConnectionStatus=AVAILABLE` before creating the\nchange set. Do not put an OAuth token or GitHub token in the shell:\n\nAWS CodeConnections connection names are limited to 32 characters, so keep the\nshorter connection name even when the stack and project use the longer\nBuildchain-specific name.\n\n```bash\nburst_connection_arn=REPLACE_WITH_AVAILABLE_CONNECTION_ARN\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections get-connection \\\n --connection-arn \"$burst_connection_arn\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation create-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\" \\\n --change-set-type CREATE \\\n --template-body \\\n file://infra/aws-us-elastic-runner-burst-plane/codebuild-poc.template.yml \\\n --capabilities CAPABILITY_IAM \\\n --parameters \\\n ParameterKey=GitHubConnectionArn,ParameterValue=\"$burst_connection_arn\" \\\n ParameterKey=ProjectName,ParameterValue=\"$burst_project\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait change-set-create-complete \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation describe-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n```\n\nOnly after the change-set resource list and IAM diff are accepted:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation execute-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait stack-create-complete \\\n --stack-name \"$burst_stack\"\n```\n\nArming requires a fresh, operator-observed CodeBuild cost value. `COST` is the\nonly mutable telemetry item and `CONTROL` is the only state cleared:\n\n```bash\nburst_table=$(\n aws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation describe-stacks \\\n --stack-name \"$burst_stack\" \\\n --query \"Stacks[0].Outputs[?OutputKey=='StateTable'].OutputValue\" \\\n --output text\n)\nburst_observed_at=$(date -u +%s)\nburst_actual_usd=REPLACE_WITH_CURRENT_CODEBUILD_ACTUAL_USD\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n dynamodb put-item \\\n --table-name \"$burst_table\" \\\n --item \"{\\\"pk\\\":{\\\"S\\\":\\\"COST\\\"},\\\"actual_usd\\\":{\\\"N\\\":\\\"$burst_actual_usd\\\"},\\\"observed_at\\\":{\\\"N\\\":\\\"$burst_observed_at\\\"}}\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n dynamodb delete-item \\\n --table-name \"$burst_table\" \\\n --key '{\"pk\":{\"S\":\"CONTROL\"}}'\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codebuild create-webhook \\\n --project-name \"$burst_project\" \\\n --filter-groups \\\n '[[{\"type\":\"EVENT\",\"pattern\":\"WORKFLOW_JOB_QUEUED\"},{\"type\":\"WORKFLOW_NAME\",\"pattern\":\"^AWS US Linux Burst Qualification$\"}]]'\n```\n\nThe immediate global kill is idempotent and targets only the dedicated project:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codebuild delete-webhook \\\n --project-name \"$burst_project\"\n```\n\nAfter preserving the qualification evidence and proving no build is in\nprogress, rollback removes only the card-owned stack and connection:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation delete-stack \\\n --stack-name \"$burst_stack\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait stack-delete-complete \\\n --stack-name \"$burst_stack\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections delete-connection \\\n --connection-arn \"$burst_connection_arn\"\n```\n\nPhase cleanup evidence must include:\n\n- CodeBuild batch/list results showing no in-progress build;\n- controller state and accepted-build ledger;\n- CodeBuild actual cost observation and its timestamp;\n- no EC2 instance, volume, launch template, Auto Scaling group, or dedicated\n host created by this phase;\n- the CodeBuild webhook deleted or the whole stack deleted.\n\n## Source boundaries\n\nThe design follows the current AWS CodeBuild GitHub Actions runner contract:\n`WORKFLOW_JOB_QUEUED` starts an ephemeral runner, the run id maps cancellation,\nand the build terminates after one job. It uses the current GitHub guidance to\nprefer ephemeral autoscaled self-hosted runners and to retain runner logs\nexternally. Provider documentation and the live AWS Price List query are the\nauthoritative external sources; this document is an auditable cache."
|
|
545
545
|
},
|
|
546
546
|
{
|
|
547
547
|
"id": "manual:binary-distribution",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"contract": "kungfu-buildchain-public-surface-reverse-audit",
|
|
22
22
|
"path": "dist/site/public-surface-audit.json",
|
|
23
23
|
"status": "passed",
|
|
24
|
-
"sha256": "
|
|
24
|
+
"sha256": "07817e269917640fdcfeb57b5040f92432f4c05fbfc5ec68b22e41af0c06cb10",
|
|
25
25
|
"summary": {
|
|
26
26
|
"cliCommandCount": 114,
|
|
27
27
|
"workflowCount": 69,
|
|
@@ -246,7 +246,7 @@
|
|
|
246
246
|
"contract": "kungfu-buildchain-public-surface-reverse-audit",
|
|
247
247
|
"path": "dist/site/public-surface-audit.json",
|
|
248
248
|
"status": "passed",
|
|
249
|
-
"sha256": "
|
|
249
|
+
"sha256": "07817e269917640fdcfeb57b5040f92432f4c05fbfc5ec68b22e41af0c06cb10",
|
|
250
250
|
"summary": {
|
|
251
251
|
"cliCommandCount": 114,
|
|
252
252
|
"workflowCount": 69,
|
|
@@ -24936,7 +24936,7 @@
|
|
|
24936
24936
|
"specifier": "@kungfu-tech/buildchain/release-passport-contract",
|
|
24937
24937
|
"export": "./release-passport-contract",
|
|
24938
24938
|
"target": "./packages/core/release-passport-contract.js",
|
|
24939
|
-
"digest": "sha256:
|
|
24939
|
+
"digest": "sha256:028d755bda8e510b0e443eb0c899e4ebfa1d124b4a6d38234dcdf63b6ab4b59b",
|
|
24940
24940
|
"summary": "Standalone release passport JSON Schema, ownership/check manifest, and structural validation APIs.",
|
|
24941
24941
|
"capabilityGroup": "release-passport-trust",
|
|
24942
24942
|
"audience": [
|
|
@@ -412,7 +412,7 @@
|
|
|
412
412
|
],
|
|
413
413
|
"maturity": "stable",
|
|
414
414
|
"sourcePath": "docs/aws-us-elastic-runner-burst-plane.md",
|
|
415
|
-
"digest": "sha256:
|
|
415
|
+
"digest": "sha256:7ffe2235f33cf4309232ea720040397e915e71528ed7bb6a45d1bd604ca1e335",
|
|
416
416
|
"headings": [
|
|
417
417
|
{
|
|
418
418
|
"level": 1,
|
|
@@ -475,7 +475,7 @@
|
|
|
475
475
|
"anchor": "source-boundaries"
|
|
476
476
|
}
|
|
477
477
|
],
|
|
478
|
-
"markdown": "---\nstatus: draft\nperiod: 2026-07-28\ntheme: aws-us-elastic-runner-burst-plane\ndoc_type: design\nsource_level: local-files-and-provider-docs\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-08-03\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-08-03\n invisible_information: No hidden model checkpoint, parameters, or private training data were available.\n---\n\n# AWS US elastic runner burst plane\n\nThe local runner fleet remains the normal Kungfu build plane. This AWS US plane\nis an explicit, temporary overflow mechanism with sequential qualification:\n\n1. Linux CodeBuild proof of concept under USD 50.\n2. Windows EC2 one-job JIT runners.\n3. One bounded 24-hour EC2 Mac campaign.\n\nNo later phase can start from design intent alone. The preceding phase must\nproduce a qualifying source-bound receipt, actual cost, and zero-resource\ncleanup proof.\n\n## Phase 1 contract\n\n`aws-us-codebuild-linux` is a Linux-only runner preset. It requires the exact\nCodeBuild project name and resolves the runner label at workflow evaluation\ntime:\n\n```text\ncodebuild-<project>-<github.run_id>-<github.run_attempt>\n```\n\nThe GitHub-hosted `trust-gate` remains ahead of the matrix job. A fork pull\nrequest therefore fails or skips before the CodeBuild `runs-on` label exists as\na queued job. The dedicated consumer workflow is manual-only and does not add\nthe preset to dev, alpha, release, signing, notarization, deployment, or\npublication workflows.\n\nThe CodeBuild project is:\n\n- repository-scoped through an AWS CodeConnections GitHub App;\n- one ephemeral runner and one GitHub job per CodeBuild build;\n- outside a VPC, with no idle VM, NAT gateway, public ingress, SSH, or persistent\n workspace;\n- limited to two concurrent builds, 15 queued minutes, and 40 execution\n minutes;\n- allowed to write only its dedicated CloudWatch log group and request a token\n from its dedicated GitHub App connection;\n- forbidden from receiving signing, notarization, package publication, release,\n deploy, static AWS, long-lived GitHub, or SSH credentials.\n\nThe AWS-managed Ubuntu 24.04 standard image is the immutable base. Before a\nnative lifecycle starts, Buildchain installs the distribution's `gcc-14` and\n`g++-14` packages, exposes only per-job `gcc`/`g++` aliases, and downloads the\npinned Kitware CMake 3.31.6 archive after verifying its reviewed SHA256. The\nresolved package manager, versions, and CMake source digest are retained as\n`aws-native-toolchain.json`; no toolchain state survives the ephemeral\nCodeBuild execution. The toolchain adapter also retains the reviewed Amazon\nLinux 2023 `gcc14` path for compatible projects.\n\n## Cost and kill-switch envelope\n\nThe 2026-07-28 AWS Price List entry for\n`BUILD_GENERAL1_XLARGE` Linux in `us-east-1` is USD 0.0798 per build minute.\nThe contract rounds that rate up to USD 0.08. Twelve fully timed-out accepted\nbuilds reserve at most USD 38.40. At project concurrency two, the fail-closed\ncontroller can see at most two over-cap builds. The envelope conservatively\ncharges both race builds for their complete 40-minute timeout rather than\nassuming fast EventBridge delivery. The bounded CodeBuild maximum is therefore\nUSD 44.80, below the dedicated USD 49 budget and leaving USD 4.20 for the small\ncontroller, state, notification, and log charges.\n\nThe controller stores an idempotent build-id ledger, an atomic accepted-build\ncounter, and worst-case reservation in DynamoDB. Duplicate EventBridge delivery\ndoes not consume the bounded build allowance. It deletes the CodeBuild webhook\nand stops the triggering build when:\n\n- the accepted-build or reserved-cost cap is reached;\n- actual-cost telemetry is missing or more than six hours old;\n- actual CodeBuild spend reaches the budget;\n- AWS Budgets sends the 80% or 95% actual-spend notification;\n- the kill switch was already set.\n\nThe stack starts fail closed: it has no cost telemetry item and CloudFormation\ndoes not create the webhook. Before arming the webhook, the operator must write\na current Cost Explorer observation to the `COST` item, clear only the dedicated\ncontroller's killed state, and create the exact workflow-filtered webhook.\nRe-arming after any kill is a separate provider mutation and requires a new\nexplicit approval.\n\n## Qualification evidence\n\nEach successful job uploads `aws-runner-burst.json`, binding:\n\n- consumer repository, exact source SHA and ref;\n- GitHub run id, attempt and job;\n- CodeBuild project, build id, build ARN and initiator;\n- observation timestamp and canonical digest.\n\nLinux qualification requires:\n\n- at least 10 trusted exact-source successful jobs;\n- observed concurrency of at least two;\n- p95 queue-to-start of at most five minutes;\n- actual incremental AWS spend below USD 49;\n- no idle build and no active cloud residue.\n\n`node scripts/aws-runner-burst.mjs verify-linux --input <snapshot.json>` fails\nclosed when cost telemetry is missing/stale or any acceptance predicate is\nfalse.\n\n### Phase 1 recorded outcome\n\nThe Linux phase passed on 2026-07-29. Ten trusted exact-source Kungfu jobs\ncompleted successfully, including four overlapping two-job waves. The observed\nCodeBuild queue-to-start p95 was 0.696 seconds. All 16 paid executions,\nincluding six diagnostic runs, produced a conservative incremental compute\nupper bound of USD 25.798 by rounding every execution up to a whole minute at\nthe live AWS Price List rate.\n\nThe global webhook kill switch was exercised after the tenth qualifying job.\nThe project then reported no webhook or in-progress build, and the card-owned\nEC2 inventory was empty. AWS Billing and Cost Explorer still reported an\nestimated zero during their provider ingestion delay; the retained\nexecution-derived upper bound is therefore the immediate cost proof and must be\nreconciled with the eventual AWS line item in the final campaign report.\n\nThe source-bound evidence and deterministic phase receipt are:\n\n- `evidence/aws-us-elastic-runner-burst-plane/linux-codebuild-qualification-input.json`\n- `evidence/aws-us-elastic-runner-burst-plane/linux-codebuild-qualification-receipt.json`\n\n## Phase 2 contract\n\nThe Windows phase uses the explicit `aws-us-ec2-windows-jit` runner preset.\nIts caller supplies one bounded label under\n`aws-us-ec2-windows-jit-<campaign-id>-<qualification-id>`, and Buildchain\nresolves exactly one Windows x64 native lane. The reusable trust gate still\nruns on a GitHub-hosted runner before the JIT label can select EC2.\n\nThe provider creates repository-level GitHub JIT configuration for\n`kungfu-systems/kungfu`. Its `labels` request must contain all four scheduling\nlabels: `self-hosted`, `Windows`, `X64`, and the card-scoped\n`aws-us-ec2-windows-jit-<campaign-id>-<qualification-id>` label. The workflow\ndisplay title also carries both identities, allowing the launch controller to\nverify the queued run against its campaign plan. GitHub's JIT endpoint does\nnot infer the default OS and architecture labels when they are omitted. The\nencoded configuration is never placed in EC2 user data, a tag, a command log,\nor an artifact. The operator writes it to a card-scoped SSM SecureString under\n`/kungfu/burst/windows/`; the instance role can read and delete only that\nprefix. Bootstrap reads the value once, deletes the parameter immediately, and\npasses it only to the pinned runner process.\n\nEach runner uses:\n\n- Amazon's current Windows Server 2025 Full Base AMI, resolved through the\n public SSM AMI parameter and retained by exact AMI id and name;\n- `c7i.4xlarge`, one instance and one JIT runner per job;\n- GitHub Actions Runner 2.336.0 with the official Windows x64 SHA256;\n- PowerShell 7.6.4 with the official Windows x64 MSI SHA256 and Microsoft\n Authenticode verification;\n- pinned PortableGit 2.55.0.3 with its GitHub release SHA256, exposing only its\n `cmd` directory so POSIX compatibility tools cannot shadow Windows tools;\n- a Microsoft Authenticode-verified Visual Studio 2022 Build Tools bootstrap;\n- IMDSv2, an encrypted root volume with delete-on-termination, no inbound\n security-group rule, no key pair, and no warm Auto Scaling capacity.\n\nRunner diagnostics and a redacted lifecycle record are uploaded to the\nprovider's encrypted, private evidence bucket under the exact campaign, run,\nattempt, and instance identity. The runner process exits after one job, Windows\nshuts down, and EC2's instance-initiated shutdown behavior is set to\n`terminate`. A five-minute reaper terminates card-owned stopped or\nthree-hour-old instances and deletes only their dedicated JIT parameter.\n\nAt the 2026-07-29 AWS Price List rate of USD 1.45 per Windows\n`c7i.4xlarge` hour, each accepted instance reserves its complete three-hour\nUSD 4.35 fail-closed lifetime before `RunInstances`. A DynamoDB transaction\nbinds the exact campaign and source, creates an idempotent run ledger entry,\nand atomically refuses a sixth accepted instance. Five accepted instances\ntherefore reserve at most USD 21.75. The campaign also persists the\noperator-observed spend from earlier Windows work, and refuses to arm unless\nthat baseline, all five reservations, and one USD 4.35 fail-closed race\nallowance remain below the USD 110 phase cap.\n\nThe campaign starts unarmed and expires within 24 hours. Its `CONTROL` record\ncan be created only once: a killed or expired campaign cannot be re-armed by\nthe campaign tool. A budget notification or any instance lifetime violation\npersists `KILLED` before cleanup, so later workflow dispatches fail before a\npaid launch. Reservations are never refunded: a controller crash, ambiguous\nlaunch, or successful launch all remain charged to the campaign, favoring a\nfalse stop over an accidental budget overrun.\n\nThe 2026-08-03 timeout-only campaign decision narrows the campaign to two\naccepted instances with one active instance at a time. The second reservation\nis an operator-gated repair retry: it may be used only after the first attempt\nis classified as non-counting and runner, EC2, EBS, SSM, and workflow residue\nhave returned to zero. The two-slot ledger is a maximum spend boundary, not an\nauthorization to consume both reservations.\n\nEach stack owns a stack-scoped reaper log group, so an independent retained\none-shot campaign stack can be created without colliding with another\ncampaign's audit log resource.\n\nThe account-native AWS Budget is defense in depth, not the authoritative launch\ngate. It is owned by the singleton\n`kungfu-buildchain-windows-jit-budget-guard` stack rather than any retained\ncampaign stack. This prevents Budget-name collisions and prevents a stale\ncampaign reaper from becoming the provider-wide cost authority. The Budget\nfilters exactly `USAGE_TYPE=BoxUsage:c7i.4xlarge`,\n`OPERATION=RunInstances:0002` (Windows), and `REGION=us-east-1`; its 80% and\n95% actual notifications persist the provider kill sentinel, terminate every\ntagged Windows JIT instance, and delete scoped JIT parameters. Every launch\ncontroller refuses to proceed when the sentinel exists or when the Budget\nidentity or dimension filter does not match.\n\nBudget installation is intentionally deployable by the workload account without\nAWS Organizations management-account access. It fails closed unless Cost\nExplorer exposes all three AWS-owned billing dimensions in the requested phase\nwindow. The `kungfu:provider=windows-ec2-jit` resource tag remains mandatory for\nownership, cleanup, and IAM scoping, but it is not a billing filter. Do not\ncreate an unfiltered fallback Budget or treat an incomplete dimension readback\nas evidence.\nThe DynamoDB campaign reservation remains the atomic launch authority because\nCost Explorer and AWS Budgets can lag provider activity.\n\nQualification requires one runner-profile smoke and three trusted exact-source\nfull Windows jobs all bound to the same campaign, independent cancellation and\ntimeout cleanup exercises, and zero repository runner, EC2 instance,\ndisposable volume, min capacity, and desired capacity within 15 minutes of the\nfinal job.\n\n### Phase 2 operator workflow\n\n`pnpm operator:windows-jit` is the reusable lifecycle entrypoint. Its default\nmode is `plan`, which performs no AWS or GitHub call. A plan binds the account,\nregion, unique campaign and stack names, source SHA/ref, Cost Explorer window,\nworkflow id, network, OIDC provider, expiry, slot ceiling, singleton Budget\nidentity, and exact confirmation digest.\n\nThe modes are deliberately separated:\n\n- `plan` emits the deterministic mutation boundary and digest.\n- `audit` reads AWS and GitHub only. It verifies the account, disabled workflow,\n singleton guard stack, exact Budget filter, SNS thresholds/subscribers,\n provider kill sentinel, campaign stack, and zero EC2/EBS/SSM/JIT/runner\n residue.\n- `install-budget --execute` deploys or updates only the singleton Budget guard.\n It refuses to mutate unless all exact Windows billing dimensions are visible,\n the Windows workflow is disabled, and the account, campaign, source, Budget,\n and plan digest confirmations match.\n- `prepare --execute` requires the installed Budget guard, absent kill\n sentinel, fresh Cost Explorer readback filtered by `BoxUsage:c7i.4xlarge`,\n `RunInstances:0002`, and `us-east-1`, zero residue, a\n never-used campaign stack name, and the disabled workflow. The receipt binds\n the query timestamp and exact filter identity. Preparation deploys the\n campaign stack and atomically arms the ledger with that provider-spend\n baseline. It never enables or dispatches the workflow and never creates EC2\n capacity.\n- `close --execute` disables the workflow first, persists `KILLED`, publishes\n the campaign kill switch, and reports terminal success only after EC2, EBS,\n SSM, JIT parameter, and GitHub runner residue is zero. It is safe to rerun\n while the reaper settles.\n\nAll mutating modes require `--execute`, `--confirm-plan-digest`,\n`--confirm-account-id`, `--confirm-campaign-id`, and\n`--confirm-source-sha`. Budget installation and preparation additionally\nrequire `--confirm-budget-name`. A future paid workload still requires a\nseparate exact workflow/run authorization and uses\n`scripts/aws-windows-jit-controller.mjs`; preparation is not paid-launch\nauthority.\n\nStart by recording one reproducible plan:\n\n```bash\npnpm operator:windows-jit plan \\\n --aws-profile us \\\n --account-id 727884401362 \\\n --campaign-id win-REPLACE \\\n --source-sha REPLACE_WITH_EXACT_40_CHARACTER_SHA \\\n --source-ref refs/heads/dev/v4/v4.0 \\\n --observed-at REPLACE_WITH_ISO_TIMESTAMP \\\n --expires-at REPLACE_WITH_ISO_TIMESTAMP_WITHIN_24_HOURS \\\n --cost-start REPLACE_WITH_PHASE_START_DATE \\\n --cost-end REPLACE_WITH_EXCLUSIVE_END_DATE \\\n --max-accepted-instances 1 \\\n --workflow-id 322620360 \\\n --vpc-id REPLACE_WITH_VPC_ID \\\n --subnet-id REPLACE_WITH_SUBNET_ID \\\n --oidc-provider-arn REPLACE_WITH_GITHUB_OIDC_PROVIDER_ARN\n```\n\nReuse those exact arguments for `audit`, `install-budget`, `prepare`, or\n`close`; never regenerate `--observed-at` between the plan and its confirmed\nmutation. Capture stdout as the operator receipt. Do not put credentials,\ntokens, JIT configuration, or signed URLs in arguments or receipts.\n\n### Lower-level campaign and launch controllers\n\n`scripts/aws-windows-jit-campaign-core.mjs` owns the pure one-shot ledger\ncontract used by the operator and launch controller. Arming creates `CONTROL`\nand `CAMPAIGN#<id>` with `attribute_not_exists` conditions, so DynamoDB refuses\na second campaign in the same retained state table. There is deliberately no\nclear or re-arm operation.\n\nEvery `scripts/aws-windows-jit-controller.mjs --execute` call must provide the\nsame `--account-id`, `--campaign-id`, `--confirm-campaign-id`, `--state-table`,\nand `--confirm-state-table`. Before GitHub JIT material is created, the\ncontroller verifies the exact provider Budget/dimension filter and proves the\nglobal Budget kill sentinel absent. After the GitHub, AMI, active-instance,\nSSM, and EC2 DryRun checks pass, the controller\natomically reserves one run. Duplicate run-attempt-qualification identities,\nsource mismatch, expiry, `KILLED`, the sixth accepted instance, or a\nreservation that would exceed the USD 110 ceiling after combining the persisted\nfresh Cost Explorer baseline with all in-flight campaign reservations all fail\nclosed in one DynamoDB transaction before `RunInstances`. AWS Budget alarms are\ndefense in depth for delayed billing telemetry; the atomic ledger is the\nauthoritative launch-time guard. The operator is the only supported mutation\nsurface for campaign preparation and closeout; direct imports of the core are\nnot operator authority.\n\n## Phase 3 contract\n\nThe macOS phase uses the explicit `aws-us-ec2-macos-jit` runner preset. Its\ncaller supplies one unique label under\n`aws-us-ec2-macos-jit-<qualification-id>`, and Buildchain resolves exactly one\nnative macOS ARM64 lane with `self-hosted`, `macOS`, `ARM64`, and the unique\ncampaign label. The reusable trust gate remains ahead of the JIT runner.\n\nUnlike Windows, the Mac campaign deliberately reuses one instance on one\n`mac2.metal` Dedicated Host. The operator allocates exactly one tagged host,\nlaunches exactly one tagged instance, and sends three sequential SSM bootstrap\ncommands. Each command consumes and immediately deletes a distinct repository\nJIT SecureString under `/kungfu/burst/macos/`, then runs GitHub Actions Runner\n2.336.0 for exactly one job. The runner archive is pinned to the official\nmacOS ARM64 SHA256. No GitHub, signing, notarization, publication, SSH, or\nstatic AWS credential is admitted to the instance.\n\nThe instance uses the exact retained Amazon EC2 macOS AMI, IMDSv2, an encrypted\ndelete-on-termination root volume, no inbound security-group rule, and the\nAMI's preinstalled SSM Agent and AWS CLI v2. The three accepted jobs must bind\nto the same host id, instance id, AMI id, source SHA, and campaign. At least one\njob must exercise the full native lifecycle.\n\nAWS imposes a 24-hour minimum Dedicated Host allocation. The contract therefore\nkeeps the one host for at least 24 hours even if all three jobs finish earlier.\nAt the recorded USD 0.65 hourly rate, the minimum commitment is USD 15.60. A\n30-hour fail-closed ceiling is USD 19.50, below the dedicated\nUSD 25 budget. A ten-minute reaper terminates an expired campaign instance and\nretries host release after the minimum allocation and Apple scrub constraints\nallow it. Budget notifications at 80% and 95% invoke the same card-scoped kill\nswitch.\n\nThe launch controller defaults to `us-east-1` and admits only `us-east-2` as a\ncapacity fallback. The regions use mutually exclusive control-plane stacks and\none shared USD 25 Budget covering Virginia `HostUsage:mac2` and the AWS catalog\nidentity `USE2-HostUsage:mac2` for Ohio. A requested region, availability zone,\nstack, and Budget must agree before allocation, and the controller checks both\nregions against one global Host and instance ceiling; no other region is\naccepted.\n\nQualification requires three trusted exact-source one-job JIT runs on the one\nhost, including at least one full run, plus proof that:\n\n- the instance terminated and the encrypted disposable volume disappeared;\n- Apple host scrub completed;\n- the Dedicated Host was released between 24 and 30 hours after allocation;\n- the repository has no registered campaign runner;\n- AWS has no active campaign instance or allocated campaign host;\n- actual incremental spend remained below USD 25.\n\n### Phase 3 lifecycle controller\n\n`scripts/aws-macos-jit-controller.mjs` is the operator boundary for the paid\ncampaign. It has five explicit mutation modes:\n\n- `launch-campaign` binds the exact repository source, AMI, availability zone,\n tagged Dedicated Host, and reusable instance. Before paid allocation it\n verifies the exact AWS account, disabled GitHub workflow, complete control\n plane stack, AWS-dimension-filtered USD 25 Budget, 80% and 95% SNS\n subscribers, and zero pre-existing Mac capacity. Because AWS does not expose\n a DryRun parameter for `AllocateHosts`, it requires an allowed IAM policy\n simulation for the exact tagged allocation before the real call, then a\n successful `RunInstances` DryRun before launching the instance.\n- `rehydrate-instance` creates a replacement instance on the one already-paid,\n empty campaign host after an interrupted or failed instance lifecycle. It\n rechecks the exact account, disabled workflow, Budget, source, host identity\n and allocation time, zero runner residue, zero active JIT instances, and no\n other active Mac host in either admitted region. It then requires a\n successful same-host `RunInstances` DryRun and never calls `AllocateHosts`.\n- `run-job` binds one queued exact-source GitHub job to the existing campaign\n host and instance. It writes the repository JIT configuration through a\n mode-0600 temporary file into a distinct SSM SecureString, sends only the\n credential-free bootstrap through SSM, and removes the parameter plus runner\n registration if command delivery fails.\n- `rebind-campaign` repairs the source of an allocated but unused campaign\n without buying another host. It requires the workflow to remain manually\n disabled, the replacement source to be a strict descendant on the same\n campaign ref, and every prior matching run to contain zero jobs and zero\n artifacts. It also requires no registered JIT runner, SSM parameter, or\n bootstrap evidence and verifies the one exact host, instance, and encrypted\n volume. The operation updates only those resources' source tags and the\n existing GitHub ref, emits a zero-allocation receipt, and compensates back to\n the prior source if the ref update or readback fails. It never calls\n `AllocateHosts`, `RunInstances`, or workflow dispatch.\n- `close-campaign` refuses execution before the provider's 24-hour minimum,\n verifies the encrypted delete-on-termination root volume, removes scoped JIT\n residue, terminates the exact instance, and requires a `ReleaseHosts` DryRun\n before release. If Apple host scrubbing is still in progress, it reports\n `release-pending`; the ten-minute card-scoped reaper remains the bounded\n retry path.\n\nEvery execute mode requires the exact source SHA and campaign id to be repeated\nthrough `--confirm-source-sha` and `--confirm-campaign-id`. `run-job` also\nrequires `--confirm-run-id`; `launch-campaign` additionally requires the\nexpected workload account through `--account-id`. `rebind-campaign`\nadditionally repeats the prior source, host, and instance identities and\nrequires `--confirm-zero-allocation`. `rehydrate-instance` additionally repeats\nthe existing host and replaced instance identities and requires\n`--confirm-no-host-allocation`. Omitting `--execute` emits a deterministic plan\nwithout changing AWS or GitHub state.\n\n## Provider lifecycle\n\nThe three infrastructure templates live under\n`infra/aws-us-elastic-runner-burst-plane/`. Creating a change set is the review\nboundary. Executing it, completing the GitHub App connection, creating or\nre-arming a webhook, allocating or releasing a Dedicated Host, writing cost\ntelemetry, dispatching paid jobs, operating a kill switch, and deleting a stack\nare all explicit provider mutations.\n\nThe reviewed Phase 1 provider sequence is below. It deliberately separates\nconnection creation, change-set inspection, stack execution, cost observation,\nand webhook arming:\n\n```bash\nburst_profile=us\nburst_region=us-east-1\nburst_stack=kungfu-buildchain-linux-burst-poc\nburst_project=kungfu-buildchain-linux-burst-poc\nburst_connection_name=kungfu-linux-burst-poc\nburst_change_set=phase1-linux-codebuild-poc\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections create-connection \\\n --provider-type GitHub \\\n --connection-name \"$burst_connection_name\" \\\n --tags Key=kungfu:owner,Value=buildchain \\\n Key=kungfu:plane,Value=aws-us-elastic-runner-burst\n```\n\nThe returned connection is `PENDING` until an operator completes the GitHub App\nhandshake in AWS. Read back `ConnectionStatus=AVAILABLE` before creating the\nchange set. Do not put an OAuth token or GitHub token in the shell:\n\nAWS CodeConnections connection names are limited to 32 characters, so keep the\nshorter connection name even when the stack and project use the longer\nBuildchain-specific name.\n\n```bash\nburst_connection_arn=REPLACE_WITH_AVAILABLE_CONNECTION_ARN\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections get-connection \\\n --connection-arn \"$burst_connection_arn\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation create-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\" \\\n --change-set-type CREATE \\\n --template-body \\\n file://infra/aws-us-elastic-runner-burst-plane/codebuild-poc.template.yml \\\n --capabilities CAPABILITY_IAM \\\n --parameters \\\n ParameterKey=GitHubConnectionArn,ParameterValue=\"$burst_connection_arn\" \\\n ParameterKey=ProjectName,ParameterValue=\"$burst_project\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait change-set-create-complete \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation describe-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n```\n\nOnly after the change-set resource list and IAM diff are accepted:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation execute-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait stack-create-complete \\\n --stack-name \"$burst_stack\"\n```\n\nArming requires a fresh, operator-observed CodeBuild cost value. `COST` is the\nonly mutable telemetry item and `CONTROL` is the only state cleared:\n\n```bash\nburst_table=$(\n aws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation describe-stacks \\\n --stack-name \"$burst_stack\" \\\n --query \"Stacks[0].Outputs[?OutputKey=='StateTable'].OutputValue\" \\\n --output text\n)\nburst_observed_at=$(date -u +%s)\nburst_actual_usd=REPLACE_WITH_CURRENT_CODEBUILD_ACTUAL_USD\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n dynamodb put-item \\\n --table-name \"$burst_table\" \\\n --item \"{\\\"pk\\\":{\\\"S\\\":\\\"COST\\\"},\\\"actual_usd\\\":{\\\"N\\\":\\\"$burst_actual_usd\\\"},\\\"observed_at\\\":{\\\"N\\\":\\\"$burst_observed_at\\\"}}\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n dynamodb delete-item \\\n --table-name \"$burst_table\" \\\n --key '{\"pk\":{\"S\":\"CONTROL\"}}'\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codebuild create-webhook \\\n --project-name \"$burst_project\" \\\n --filter-groups \\\n '[[{\"type\":\"EVENT\",\"pattern\":\"WORKFLOW_JOB_QUEUED\"},{\"type\":\"WORKFLOW_NAME\",\"pattern\":\"^AWS US Linux Burst Qualification$\"}]]'\n```\n\nThe immediate global kill is idempotent and targets only the dedicated project:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codebuild delete-webhook \\\n --project-name \"$burst_project\"\n```\n\nAfter preserving the qualification evidence and proving no build is in\nprogress, rollback removes only the card-owned stack and connection:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation delete-stack \\\n --stack-name \"$burst_stack\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait stack-delete-complete \\\n --stack-name \"$burst_stack\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections delete-connection \\\n --connection-arn \"$burst_connection_arn\"\n```\n\nPhase cleanup evidence must include:\n\n- CodeBuild batch/list results showing no in-progress build;\n- controller state and accepted-build ledger;\n- CodeBuild actual cost observation and its timestamp;\n- no EC2 instance, volume, launch template, Auto Scaling group, or dedicated\n host created by this phase;\n- the CodeBuild webhook deleted or the whole stack deleted.\n\n## Source boundaries\n\nThe design follows the current AWS CodeBuild GitHub Actions runner contract:\n`WORKFLOW_JOB_QUEUED` starts an ephemeral runner, the run id maps cancellation,\nand the build terminates after one job. It uses the current GitHub guidance to\nprefer ephemeral autoscaled self-hosted runners and to retain runner logs\nexternally. Provider documentation and the live AWS Price List query are the\nauthoritative external sources; this document is an auditable cache."
|
|
478
|
+
"markdown": "---\nstatus: draft\nperiod: 2026-07-28\ntheme: aws-us-elastic-runner-burst-plane\ndoc_type: design\nsource_level: local-files-and-provider-docs\nconfidence: high\nsensitivity: public\nevidence_grade: A\nreview_state: unreviewed\nlast_reviewed: 2026-08-03\nai_provenance:\n model_family: GPT-5\n product: Codex\n generated_at: 2026-08-03\n invisible_information: No hidden model checkpoint, parameters, or private training data were available.\n---\n\n# AWS US elastic runner burst plane\n\nThe local runner fleet remains the normal Kungfu build plane. This AWS US plane\nis an explicit, temporary overflow mechanism with sequential qualification:\n\n1. Linux CodeBuild proof of concept under USD 50.\n2. Windows EC2 one-job JIT runners.\n3. One bounded 24-hour EC2 Mac campaign.\n\nNo later phase can start from design intent alone. The preceding phase must\nproduce a qualifying source-bound receipt, actual cost, and zero-resource\ncleanup proof.\n\n## Phase 1 contract\n\n`aws-us-codebuild-linux` is a Linux-only runner preset. It requires the exact\nCodeBuild project name and resolves the runner label at workflow evaluation\ntime:\n\n```text\ncodebuild-<project>-<github.run_id>-<github.run_attempt>\n```\n\nThe GitHub-hosted `trust-gate` remains ahead of the matrix job. A fork pull\nrequest therefore fails or skips before the CodeBuild `runs-on` label exists as\na queued job. The dedicated consumer workflow is manual-only and does not add\nthe preset to dev, alpha, release, signing, notarization, deployment, or\npublication workflows.\n\nThe CodeBuild project is:\n\n- repository-scoped through an AWS CodeConnections GitHub App;\n- one ephemeral runner and one GitHub job per CodeBuild build;\n- outside a VPC, with no idle VM, NAT gateway, public ingress, SSH, or persistent\n workspace;\n- limited to two concurrent builds, 15 queued minutes, and 40 execution\n minutes;\n- allowed to write only its dedicated CloudWatch log group and request a token\n from its dedicated GitHub App connection;\n- forbidden from receiving signing, notarization, package publication, release,\n deploy, static AWS, long-lived GitHub, or SSH credentials.\n\nThe AWS-managed Ubuntu 24.04 standard image is the immutable base. Before a\nnative lifecycle starts, Buildchain installs the distribution's `gcc-14` and\n`g++-14` packages, exposes only per-job `gcc`/`g++` aliases, and downloads the\npinned Kitware CMake 3.31.6 archive after verifying its reviewed SHA256. The\nresolved package manager, versions, and CMake source digest are retained as\n`aws-native-toolchain.json`; no toolchain state survives the ephemeral\nCodeBuild execution. The toolchain adapter also retains the reviewed Amazon\nLinux 2023 `gcc14` path for compatible projects.\n\n## Cost and kill-switch envelope\n\nThe 2026-07-28 AWS Price List entry for\n`BUILD_GENERAL1_XLARGE` Linux in `us-east-1` is USD 0.0798 per build minute.\nThe contract rounds that rate up to USD 0.08. Twelve fully timed-out accepted\nbuilds reserve at most USD 38.40. At project concurrency two, the fail-closed\ncontroller can see at most two over-cap builds. The envelope conservatively\ncharges both race builds for their complete 40-minute timeout rather than\nassuming fast EventBridge delivery. The bounded CodeBuild maximum is therefore\nUSD 44.80, below the dedicated USD 49 budget and leaving USD 4.20 for the small\ncontroller, state, notification, and log charges.\n\nThe controller stores an idempotent build-id ledger, an atomic accepted-build\ncounter, and worst-case reservation in DynamoDB. Duplicate EventBridge delivery\ndoes not consume the bounded build allowance. It deletes the CodeBuild webhook\nand stops the triggering build when:\n\n- the accepted-build or reserved-cost cap is reached;\n- actual-cost telemetry is missing or more than six hours old;\n- actual CodeBuild spend reaches the budget;\n- AWS Budgets sends the 80% or 95% actual-spend notification;\n- the kill switch was already set.\n\nThe stack starts fail closed: it has no cost telemetry item and CloudFormation\ndoes not create the webhook. Before arming the webhook, the operator must write\na current Cost Explorer observation to the `COST` item, clear only the dedicated\ncontroller's killed state, and create the exact workflow-filtered webhook.\nRe-arming after any kill is a separate provider mutation and requires a new\nexplicit approval.\n\n## Qualification evidence\n\nEach successful job uploads `aws-runner-burst.json`, binding:\n\n- consumer repository, exact source SHA and ref;\n- GitHub run id, attempt and job;\n- CodeBuild project, build id, build ARN and initiator;\n- observation timestamp and canonical digest.\n\nLinux qualification requires:\n\n- at least 10 trusted exact-source successful jobs;\n- observed concurrency of at least two;\n- p95 queue-to-start of at most five minutes;\n- actual incremental AWS spend below USD 49;\n- no idle build and no active cloud residue.\n\n`node scripts/aws-runner-burst.mjs verify-linux --input <snapshot.json>` fails\nclosed when cost telemetry is missing/stale or any acceptance predicate is\nfalse.\n\n### Phase 1 recorded outcome\n\nThe Linux phase passed on 2026-07-29. Ten trusted exact-source Kungfu jobs\ncompleted successfully, including four overlapping two-job waves. The observed\nCodeBuild queue-to-start p95 was 0.696 seconds. All 16 paid executions,\nincluding six diagnostic runs, produced a conservative incremental compute\nupper bound of USD 25.798 by rounding every execution up to a whole minute at\nthe live AWS Price List rate.\n\nThe global webhook kill switch was exercised after the tenth qualifying job.\nThe project then reported no webhook or in-progress build, and the card-owned\nEC2 inventory was empty. AWS Billing and Cost Explorer still reported an\nestimated zero during their provider ingestion delay; the retained\nexecution-derived upper bound is therefore the immediate cost proof and must be\nreconciled with the eventual AWS line item in the final campaign report.\n\nThe source-bound evidence and deterministic phase receipt are:\n\n- `evidence/aws-us-elastic-runner-burst-plane/linux-codebuild-qualification-input.json`\n- `evidence/aws-us-elastic-runner-burst-plane/linux-codebuild-qualification-receipt.json`\n\n## Phase 2 contract\n\nThe Windows phase uses the explicit `aws-us-ec2-windows-jit` runner preset.\nIts caller supplies one bounded label under\n`aws-us-ec2-windows-jit-<campaign-id>-<qualification-id>`, and Buildchain\nresolves exactly one Windows x64 native lane. The reusable trust gate still\nruns on a GitHub-hosted runner before the JIT label can select EC2.\n\nThe provider creates repository-level GitHub JIT configuration for\n`kungfu-systems/kungfu`. Its `labels` request must contain all four scheduling\nlabels: `self-hosted`, `Windows`, `X64`, and the card-scoped\n`aws-us-ec2-windows-jit-<campaign-id>-<qualification-id>` label. The workflow\ndisplay title also carries both identities, allowing the launch controller to\nverify the queued run against its campaign plan. GitHub's JIT endpoint does\nnot infer the default OS and architecture labels when they are omitted. The\nencoded configuration is never placed in EC2 user data, a tag, a command log,\nor an artifact. The operator writes it to a card-scoped SSM SecureString under\n`/kungfu/burst/windows/`; the instance role can read and delete only that\nprefix. Bootstrap reads the value once, deletes the parameter immediately, and\npasses it only to the pinned runner process.\n\nEach runner uses:\n\n- Amazon's current Windows Server 2025 Full Base AMI, resolved through the\n public SSM AMI parameter and retained by exact AMI id and name;\n- `c7i.4xlarge`, one instance and one JIT runner per job;\n- GitHub Actions Runner 2.336.0 with the official Windows x64 SHA256;\n- PowerShell 7.6.4 with the official Windows x64 MSI SHA256 and Microsoft\n Authenticode verification;\n- pinned PortableGit 2.55.0.3 with its GitHub release SHA256, exposing only its\n `cmd` directory so POSIX compatibility tools cannot shadow Windows tools;\n- a Microsoft Authenticode-verified Visual Studio 2022 Build Tools bootstrap;\n- IMDSv2, an encrypted root volume with delete-on-termination, no inbound\n security-group rule, no key pair, and no warm Auto Scaling capacity.\n\nRunner diagnostics and a redacted lifecycle record are uploaded to the\nprovider's encrypted, private evidence bucket under the exact campaign, run,\nattempt, and instance identity. The runner process exits after one job, Windows\nshuts down, and EC2's instance-initiated shutdown behavior is set to\n`terminate`. A five-minute reaper terminates card-owned stopped or\nthree-hour-old instances and deletes only their dedicated JIT parameter.\n\nAt the 2026-07-29 AWS Price List rate of USD 1.45 per Windows\n`c7i.4xlarge` hour, each accepted instance reserves its complete three-hour\nUSD 4.35 fail-closed lifetime before `RunInstances`. A DynamoDB transaction\nbinds the exact campaign and source, creates an idempotent run ledger entry,\nand atomically refuses a sixth accepted instance. Five accepted instances\ntherefore reserve at most USD 21.75. The campaign also persists the\noperator-observed spend from earlier Windows work, and refuses to arm unless\nthat baseline, all five reservations, and one USD 4.35 fail-closed race\nallowance remain below the USD 110 phase cap.\n\nThe campaign starts unarmed and expires within 24 hours. Its `CONTROL` record\ncan be created only once: a killed or expired campaign cannot be re-armed by\nthe campaign tool. A budget notification or any instance lifetime violation\npersists `KILLED` before cleanup, so later workflow dispatches fail before a\npaid launch. Reservations are never refunded: a controller crash, ambiguous\nlaunch, or successful launch all remain charged to the campaign, favoring a\nfalse stop over an accidental budget overrun.\n\nThe 2026-08-03 timeout-only campaign decision narrows the campaign to two\naccepted instances with one active instance at a time. The second reservation\nis an operator-gated repair retry: it may be used only after the first attempt\nis classified as non-counting and runner, EC2, EBS, SSM, and workflow residue\nhave returned to zero. The two-slot ledger is a maximum spend boundary, not an\nauthorization to consume both reservations.\n\nEach stack owns a stack-scoped reaper log group, so an independent retained\none-shot campaign stack can be created without colliding with another\ncampaign's audit log resource.\n\nThe account-native AWS Budget is defense in depth, not the authoritative launch\ngate. It is owned by the singleton\n`kungfu-buildchain-windows-jit-budget-guard` stack rather than any retained\ncampaign stack. This prevents Budget-name collisions and prevents a stale\ncampaign reaper from becoming the provider-wide cost authority. The Budget\nfilters exactly `USAGE_TYPE=BoxUsage:c7i.4xlarge`,\n`OPERATION=RunInstances:0002` (Windows), and `REGION=us-east-1`; its 80% and\n95% actual notifications persist the provider kill sentinel, terminate every\ntagged Windows JIT instance, and delete scoped JIT parameters. Every launch\ncontroller refuses to proceed when the sentinel exists or when the Budget\nidentity or dimension filter does not match.\n\nBudget installation is intentionally deployable by the workload account without\nAWS Organizations management-account access. It fails closed unless Cost\nExplorer exposes all three AWS-owned billing dimensions in the requested phase\nwindow. The `kungfu:provider=windows-ec2-jit` resource tag remains mandatory for\nownership, cleanup, and IAM scoping, but it is not a billing filter. Do not\ncreate an unfiltered fallback Budget or treat an incomplete dimension readback\nas evidence.\nThe DynamoDB campaign reservation remains the atomic launch authority because\nCost Explorer and AWS Budgets can lag provider activity.\n\nQualification requires one runner-profile smoke and three trusted exact-source\nfull Windows jobs all bound to the same campaign, independent cancellation and\ntimeout cleanup exercises, and zero repository runner, EC2 instance,\ndisposable volume, min capacity, and desired capacity within 15 minutes of the\nfinal job.\n\n### Phase 2 operator workflow\n\n`pnpm operator:windows-jit` is the reusable lifecycle entrypoint. Its default\nmode is `plan`, which performs no AWS or GitHub call. A plan binds the account,\nregion, unique campaign and stack names, source SHA/ref, Cost Explorer window,\nworkflow id, network, OIDC provider, expiry, slot ceiling, singleton Budget\nidentity, and exact confirmation digest.\n\nThe modes are deliberately separated:\n\n- `plan` emits the deterministic mutation boundary and digest.\n- `audit` reads AWS and GitHub only. It verifies the account, disabled workflow,\n singleton guard stack, exact Budget filter, SNS thresholds/subscribers,\n provider kill sentinel, campaign stack, and zero EC2/EBS/SSM/JIT/runner\n residue.\n- `install-budget --execute` deploys or updates only the singleton Budget guard.\n It refuses to mutate unless all exact Windows billing dimensions are visible,\n the Windows workflow is disabled, and the account, campaign, source, Budget,\n and plan digest confirmations match.\n- `prepare --execute` requires the installed Budget guard, absent kill\n sentinel, fresh Cost Explorer readback filtered by `BoxUsage:c7i.4xlarge`,\n `RunInstances:0002`, and `us-east-1`, zero residue, a\n never-used campaign stack name, and the disabled workflow. The receipt binds\n the query timestamp and exact filter identity. Preparation deploys the\n campaign stack and atomically arms the ledger with that provider-spend\n baseline. It never enables or dispatches the workflow and never creates EC2\n capacity.\n- `close --execute` disables the workflow first, persists `KILLED`, publishes\n the campaign kill switch, and reports terminal success only after EC2, EBS,\n SSM, JIT parameter, and GitHub runner residue is zero. It is safe to rerun\n while the reaper settles.\n\nAll mutating modes require `--execute`, `--confirm-plan-digest`,\n`--confirm-account-id`, `--confirm-campaign-id`, and\n`--confirm-source-sha`. Budget installation and preparation additionally\nrequire `--confirm-budget-name`. A future paid workload still requires a\nseparate exact workflow/run authorization and uses\n`scripts/aws-windows-jit-controller.mjs`; preparation is not paid-launch\nauthority.\n\nStart by recording one reproducible plan:\n\n```bash\npnpm operator:windows-jit plan \\\n --aws-profile us \\\n --account-id 727884401362 \\\n --campaign-id win-REPLACE \\\n --source-sha REPLACE_WITH_EXACT_40_CHARACTER_SHA \\\n --source-ref refs/heads/dev/v4/v4.0 \\\n --observed-at REPLACE_WITH_ISO_TIMESTAMP \\\n --expires-at REPLACE_WITH_ISO_TIMESTAMP_WITHIN_24_HOURS \\\n --cost-start REPLACE_WITH_PHASE_START_DATE \\\n --cost-end REPLACE_WITH_EXCLUSIVE_END_DATE \\\n --max-accepted-instances 1 \\\n --workflow-id 322620360 \\\n --vpc-id REPLACE_WITH_VPC_ID \\\n --subnet-id REPLACE_WITH_SUBNET_ID \\\n --oidc-provider-arn REPLACE_WITH_GITHUB_OIDC_PROVIDER_ARN\n```\n\nReuse those exact arguments for `audit`, `install-budget`, `prepare`, or\n`close`; never regenerate `--observed-at` between the plan and its confirmed\nmutation. Capture stdout as the operator receipt. Do not put credentials,\ntokens, JIT configuration, or signed URLs in arguments or receipts.\n\n### Lower-level campaign and launch controllers\n\n`scripts/aws-windows-jit-campaign-core.mjs` owns the pure one-shot ledger\ncontract used by the operator and launch controller. Arming creates `CONTROL`\nand `CAMPAIGN#<id>` with `attribute_not_exists` conditions, so DynamoDB refuses\na second campaign in the same retained state table. There is deliberately no\nclear or re-arm operation.\n\nEvery `scripts/aws-windows-jit-controller.mjs --execute` call must provide the\nsame `--account-id`, `--campaign-id`, `--confirm-campaign-id`, `--state-table`,\nand `--confirm-state-table`. Before GitHub JIT material is created, the\ncontroller verifies the exact provider Budget/dimension filter and proves the\nglobal Budget kill sentinel absent. After the GitHub, AMI, active-instance,\nSSM, and EC2 DryRun checks pass, the controller\natomically reserves one run. Duplicate run-attempt-qualification identities,\nsource mismatch, expiry, `KILLED`, the sixth accepted instance, or a\nreservation that would exceed the USD 110 ceiling after combining the persisted\nfresh Cost Explorer baseline with all in-flight campaign reservations all fail\nclosed in one DynamoDB transaction before `RunInstances`. AWS Budget alarms are\ndefense in depth for delayed billing telemetry; the atomic ledger is the\nauthoritative launch-time guard. The operator is the only supported mutation\nsurface for campaign preparation and closeout; direct imports of the core are\nnot operator authority.\n\n## Phase 3 contract\n\nThe macOS phase uses the explicit `aws-us-ec2-macos-jit` runner preset. Its\ncaller supplies one unique label under\n`aws-us-ec2-macos-jit-<qualification-id>`, and Buildchain resolves exactly one\nnative macOS ARM64 lane with `self-hosted`, `macOS`, `ARM64`, and the unique\ncampaign label. The reusable trust gate remains ahead of the JIT runner.\n\nUnlike Windows, the Mac campaign deliberately reuses one instance on one\n`mac2.metal` Dedicated Host. The operator allocates exactly one tagged host,\nlaunches exactly one tagged instance, and sends three sequential SSM bootstrap\ncommands. Each command consumes and immediately deletes a distinct repository\nJIT SecureString under `/kungfu/burst/macos/`, then runs GitHub Actions Runner\n2.336.0 for exactly one job. The runner archive is pinned to the official\nmacOS ARM64 SHA256. No GitHub, signing, notarization, publication, SSH, or\nstatic AWS credential is admitted to the instance.\n\nThe instance uses the exact retained Amazon EC2 macOS AMI, IMDSv2, an encrypted\ndelete-on-termination root volume, no inbound security-group rule, and the\nAMI's preinstalled SSM Agent and AWS CLI v2. The three accepted jobs must bind\nto the same host id, instance id, AMI id, source SHA, and campaign. At least one\njob must exercise the full native lifecycle.\n\nAWS imposes a 24-hour minimum Dedicated Host allocation. The contract therefore\nkeeps the one host for at least 24 hours even if all three jobs finish earlier.\nAt the recorded USD 0.65 hourly rate, the minimum commitment is USD 15.60. A\n30-hour fail-closed ceiling is USD 19.50, below the dedicated\nUSD 25 budget. A ten-minute reaper terminates an expired campaign instance and\nretries host release after the minimum allocation and Apple scrub constraints\nallow it. Budget notifications at 80% and 95% invoke the same card-scoped kill\nswitch.\n\nThe launch controller defaults to `us-east-1` and admits only `us-east-2` as a\ncapacity fallback. The regions use mutually exclusive control-plane stacks and\none shared USD 25 Budget covering Virginia `HostUsage:mac2` and the AWS catalog\nidentity `USE2-HostUsage:mac2` for Ohio. A requested region, availability zone,\nstack, and Budget must agree before allocation, and the controller checks both\nregions against one global Host and instance ceiling; no other region is\naccepted.\n\nQualification requires three trusted exact-source one-job JIT runs on the one\nhost, including at least one full run, plus proof that:\n\n- the instance terminated and the encrypted disposable volume disappeared;\n- Apple host scrub completed;\n- the Dedicated Host was released between 24 and 30 hours after allocation;\n- the repository has no registered campaign runner;\n- AWS has no active campaign instance or allocated campaign host;\n- actual incremental spend remained below USD 25.\n\n### Phase 3 lifecycle controller\n\n`scripts/aws-macos-jit-controller.mjs` is the operator boundary for the paid\ncampaign. It has five explicit mutation modes:\n\n- `launch-campaign` binds the exact repository source, AMI, availability zone,\n tagged Dedicated Host, and reusable instance. Before paid allocation it\n verifies the exact AWS account, disabled GitHub workflow, complete control\n plane stack, AWS-dimension-filtered USD 25 Budget, 80% and 95% SNS\n subscribers, and zero pre-existing Mac capacity. Because AWS does not expose\n a DryRun parameter for `AllocateHosts`, it requires an allowed IAM policy\n simulation for the exact tagged allocation before the real call, then a\n successful `RunInstances` DryRun before launching the instance.\n- `rehydrate-instance` creates a replacement instance on the one already-paid,\n empty campaign host after an interrupted or failed instance lifecycle. It\n rechecks the exact account, disabled workflow, Budget, source, host identity\n and allocation time, zero runner residue, zero active JIT instances, and no\n other active Mac host in either admitted region. It then requires a\n successful same-host `RunInstances` DryRun and never calls `AllocateHosts`.\n- `run-job` binds one queued exact-source GitHub job to the existing campaign\n host and instance. It writes the repository JIT configuration through a\n mode-0600 temporary file into a distinct SSM SecureString, sends only the\n credential-free bootstrap through SSM, and removes the parameter plus runner\n registration if command delivery fails.\n- `rebind-campaign` repairs the source of an allocated but unused campaign\n without buying another host. It requires the workflow to remain manually\n disabled, the replacement source to be a strict descendant on the same\n campaign ref, and every prior matching run to contain zero jobs and zero\n artifacts. It also requires no registered JIT runner, SSM parameter, or\n bootstrap evidence and verifies the one exact host, instance, and encrypted\n volume. The operation updates only those resources' source tags and the\n existing GitHub ref, emits a zero-allocation receipt, and compensates back to\n the prior source if the ref update or readback fails. It never calls\n `AllocateHosts`, `RunInstances`, or workflow dispatch.\n- `rebind-campaign-after-failure` advances that same host and instance only\n after the operator confirms the complete prior run-id inventory. Every named\n run must be terminal with conclusion `failure`, all of its jobs must be\n complete, and both GitHub artifacts and S3 bootstrap evidence must still be\n present. The replacement source must remain a strict descendant on the same\n ref, the workflow must be disabled, the next-source evidence prefix must be\n empty, and runner plus SSM residue must be zero. The old evidence is retained\n and included in the rebind receipt; the operation changes only the existing\n source ref and resource tags, with the same compensated rollback and zero\n allocation boundary as an unused-campaign rebind.\n- `close-campaign` refuses execution before the provider's 24-hour minimum,\n verifies the encrypted delete-on-termination root volume, removes scoped JIT\n residue, terminates the exact instance, and requires a `ReleaseHosts` DryRun\n before release. If Apple host scrubbing is still in progress, it reports\n `release-pending`; the ten-minute card-scoped reaper remains the bounded\n retry path.\n\nEvery execute mode requires the exact source SHA and campaign id to be repeated\nthrough `--confirm-source-sha` and `--confirm-campaign-id`. `run-job` also\nrequires `--confirm-run-id`; `launch-campaign` additionally requires the\nexpected workload account through `--account-id`. `rebind-campaign`\nadditionally repeats the prior source, host, and instance identities and\nrequires `--confirm-zero-allocation`. `rehydrate-instance` additionally repeats\nthe existing host and replaced instance identities and requires\n`--confirm-no-host-allocation`. Omitting `--execute` emits a deterministic plan\nwithout changing AWS or GitHub state.\n`rebind-campaign-after-failure` additionally requires matching\n`--terminal-failure-run-ids-json` and\n`--confirm-terminal-failure-run-ids-json` arrays so an unobserved or newly\ncreated run fails closed before any tag or ref mutation.\n\n## Provider lifecycle\n\nThe three infrastructure templates live under\n`infra/aws-us-elastic-runner-burst-plane/`. Creating a change set is the review\nboundary. Executing it, completing the GitHub App connection, creating or\nre-arming a webhook, allocating or releasing a Dedicated Host, writing cost\ntelemetry, dispatching paid jobs, operating a kill switch, and deleting a stack\nare all explicit provider mutations.\n\nThe reviewed Phase 1 provider sequence is below. It deliberately separates\nconnection creation, change-set inspection, stack execution, cost observation,\nand webhook arming:\n\n```bash\nburst_profile=us\nburst_region=us-east-1\nburst_stack=kungfu-buildchain-linux-burst-poc\nburst_project=kungfu-buildchain-linux-burst-poc\nburst_connection_name=kungfu-linux-burst-poc\nburst_change_set=phase1-linux-codebuild-poc\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections create-connection \\\n --provider-type GitHub \\\n --connection-name \"$burst_connection_name\" \\\n --tags Key=kungfu:owner,Value=buildchain \\\n Key=kungfu:plane,Value=aws-us-elastic-runner-burst\n```\n\nThe returned connection is `PENDING` until an operator completes the GitHub App\nhandshake in AWS. Read back `ConnectionStatus=AVAILABLE` before creating the\nchange set. Do not put an OAuth token or GitHub token in the shell:\n\nAWS CodeConnections connection names are limited to 32 characters, so keep the\nshorter connection name even when the stack and project use the longer\nBuildchain-specific name.\n\n```bash\nburst_connection_arn=REPLACE_WITH_AVAILABLE_CONNECTION_ARN\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections get-connection \\\n --connection-arn \"$burst_connection_arn\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation create-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\" \\\n --change-set-type CREATE \\\n --template-body \\\n file://infra/aws-us-elastic-runner-burst-plane/codebuild-poc.template.yml \\\n --capabilities CAPABILITY_IAM \\\n --parameters \\\n ParameterKey=GitHubConnectionArn,ParameterValue=\"$burst_connection_arn\" \\\n ParameterKey=ProjectName,ParameterValue=\"$burst_project\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait change-set-create-complete \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation describe-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n```\n\nOnly after the change-set resource list and IAM diff are accepted:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation execute-change-set \\\n --stack-name \"$burst_stack\" \\\n --change-set-name \"$burst_change_set\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait stack-create-complete \\\n --stack-name \"$burst_stack\"\n```\n\nArming requires a fresh, operator-observed CodeBuild cost value. `COST` is the\nonly mutable telemetry item and `CONTROL` is the only state cleared:\n\n```bash\nburst_table=$(\n aws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation describe-stacks \\\n --stack-name \"$burst_stack\" \\\n --query \"Stacks[0].Outputs[?OutputKey=='StateTable'].OutputValue\" \\\n --output text\n)\nburst_observed_at=$(date -u +%s)\nburst_actual_usd=REPLACE_WITH_CURRENT_CODEBUILD_ACTUAL_USD\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n dynamodb put-item \\\n --table-name \"$burst_table\" \\\n --item \"{\\\"pk\\\":{\\\"S\\\":\\\"COST\\\"},\\\"actual_usd\\\":{\\\"N\\\":\\\"$burst_actual_usd\\\"},\\\"observed_at\\\":{\\\"N\\\":\\\"$burst_observed_at\\\"}}\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n dynamodb delete-item \\\n --table-name \"$burst_table\" \\\n --key '{\"pk\":{\"S\":\"CONTROL\"}}'\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codebuild create-webhook \\\n --project-name \"$burst_project\" \\\n --filter-groups \\\n '[[{\"type\":\"EVENT\",\"pattern\":\"WORKFLOW_JOB_QUEUED\"},{\"type\":\"WORKFLOW_NAME\",\"pattern\":\"^AWS US Linux Burst Qualification$\"}]]'\n```\n\nThe immediate global kill is idempotent and targets only the dedicated project:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codebuild delete-webhook \\\n --project-name \"$burst_project\"\n```\n\nAfter preserving the qualification evidence and proving no build is in\nprogress, rollback removes only the card-owned stack and connection:\n\n```bash\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation delete-stack \\\n --stack-name \"$burst_stack\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n cloudformation wait stack-delete-complete \\\n --stack-name \"$burst_stack\"\n\naws --profile \"$burst_profile\" --region \"$burst_region\" \\\n codeconnections delete-connection \\\n --connection-arn \"$burst_connection_arn\"\n```\n\nPhase cleanup evidence must include:\n\n- CodeBuild batch/list results showing no in-progress build;\n- controller state and accepted-build ledger;\n- CodeBuild actual cost observation and its timestamp;\n- no EC2 instance, volume, launch template, Auto Scaling group, or dedicated\n host created by this phase;\n- the CodeBuild webhook deleted or the whole stack deleted.\n\n## Source boundaries\n\nThe design follows the current AWS CodeBuild GitHub Actions runner contract:\n`WORKFLOW_JOB_QUEUED` starts an ephemeral runner, the run id maps cancellation,\nand the build terminates after one job. It uses the current GitHub guidance to\nprefer ephemeral autoscaled self-hosted runners and to retain runner logs\nexternally. Provider documentation and the live AWS Price List query are the\nauthoritative external sources; this document is an auditable cache."
|
|
479
479
|
},
|
|
480
480
|
{
|
|
481
481
|
"id": "manual:binary-distribution",
|
|
@@ -8007,8 +8007,8 @@
|
|
|
8007
8007
|
"workflowRegistryPath": "dist/site/workflow-registry.json",
|
|
8008
8008
|
"pageRegistryPath": "dist/site/page-registry.json",
|
|
8009
8009
|
"cliRegistryDigest": "099c0db9c5346942aee236af2cb7b6a7a91fbb2c54ebb17b7a2544a339c00d40",
|
|
8010
|
-
"workflowRegistryDigest": "
|
|
8011
|
-
"pageRegistryDigest": "
|
|
8010
|
+
"workflowRegistryDigest": "cc4c72a8bbafd7b788a7124a42bfa94a82347e29af5eeb1268f9b3d28e5a4685",
|
|
8011
|
+
"pageRegistryDigest": "18cec917571f67e74c06ba3024631887e73cf917e0d5c88b993c227995ffbcca"
|
|
8012
8012
|
},
|
|
8013
8013
|
"comparison": {
|
|
8014
8014
|
"missingCliRegistry": [],
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-publication-release-registry",
|
|
4
|
-
"generatedAt": "2026-08-
|
|
5
|
-
"publishedAt": "2026-08-
|
|
4
|
+
"generatedAt": "2026-08-11T15:41:25.000Z",
|
|
5
|
+
"publishedAt": "2026-08-11T15:41:25.000Z",
|
|
6
6
|
"reproducible": true,
|
|
7
7
|
"timestampPolicy": "ci-injected",
|
|
8
8
|
"deterministicInputs": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"declared Buildchain surface manifest contract"
|
|
20
20
|
],
|
|
21
21
|
"sourceDateEpoch": "0",
|
|
22
|
-
"sourceRevision": "
|
|
22
|
+
"sourceRevision": "d6b36d5cf30d80a0b8406740660675f8b40cd7a0",
|
|
23
23
|
"timestampPolicyDetails": {
|
|
24
24
|
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
25
|
"timestampFields": [
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"package": {
|
|
34
34
|
"name": "@kungfu-tech/buildchain",
|
|
35
|
-
"version": "3.0.9-alpha.
|
|
35
|
+
"version": "3.0.9-alpha.4",
|
|
36
36
|
"versionSource": "package.json#version"
|
|
37
37
|
},
|
|
38
38
|
"sourceKind": "package-site-bundle",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-site-manifest",
|
|
4
|
-
"generatedAt": "2026-08-
|
|
5
|
-
"publishedAt": "2026-08-
|
|
4
|
+
"generatedAt": "2026-08-11T15:41:25.000Z",
|
|
5
|
+
"publishedAt": "2026-08-11T15:41:25.000Z",
|
|
6
6
|
"reproducible": true,
|
|
7
7
|
"timestampPolicy": "ci-injected",
|
|
8
8
|
"deterministicInputs": [
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"declared Buildchain surface manifest contract"
|
|
20
20
|
],
|
|
21
21
|
"sourceDateEpoch": "0",
|
|
22
|
-
"sourceRevision": "
|
|
22
|
+
"sourceRevision": "d6b36d5cf30d80a0b8406740660675f8b40cd7a0",
|
|
23
23
|
"timestampPolicyDetails": {
|
|
24
24
|
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
25
|
"timestampFields": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"package": {
|
|
39
39
|
"name": "@kungfu-tech/buildchain",
|
|
40
|
-
"version": "3.0.9-alpha.
|
|
40
|
+
"version": "3.0.9-alpha.4",
|
|
41
41
|
"versionSource": "package.json#version"
|
|
42
42
|
},
|
|
43
43
|
"entrypoint": "buildchain-site.json",
|
|
@@ -1961,7 +1961,7 @@
|
|
|
1961
1961
|
"status": "preview",
|
|
1962
1962
|
"owner": "buildchain-workflows",
|
|
1963
1963
|
"maturity": "preview",
|
|
1964
|
-
"introducedVersion": "3.0.9-alpha.
|
|
1964
|
+
"introducedVersion": "3.0.9-alpha.4",
|
|
1965
1965
|
"compatibilityPromise": "preserved-through-the-v3-major-line",
|
|
1966
1966
|
"deprecationReplacement": "",
|
|
1967
1967
|
"sunsetCondition": "explicit-breaking-change-review-in-a-future-major-line",
|
|
@@ -1982,7 +1982,7 @@
|
|
|
1982
1982
|
"status": "preview",
|
|
1983
1983
|
"owner": "buildchain-workflows",
|
|
1984
1984
|
"maturity": "preview",
|
|
1985
|
-
"introducedVersion": "3.0.9-alpha.
|
|
1985
|
+
"introducedVersion": "3.0.9-alpha.4",
|
|
1986
1986
|
"compatibilityPromise": "preserved-through-the-v3-major-line",
|
|
1987
1987
|
"deprecationReplacement": "",
|
|
1988
1988
|
"sunsetCondition": "explicit-breaking-change-review-in-a-future-major-line",
|
|
@@ -2003,7 +2003,7 @@
|
|
|
2003
2003
|
"status": "preview",
|
|
2004
2004
|
"owner": "buildchain-workflows",
|
|
2005
2005
|
"maturity": "preview",
|
|
2006
|
-
"introducedVersion": "3.0.9-alpha.
|
|
2006
|
+
"introducedVersion": "3.0.9-alpha.4",
|
|
2007
2007
|
"compatibilityPromise": "preserved-through-the-v3-major-line",
|
|
2008
2008
|
"deprecationReplacement": "",
|
|
2009
2009
|
"sunsetCondition": "explicit-breaking-change-review-in-a-future-major-line",
|
|
@@ -2053,7 +2053,7 @@
|
|
|
2053
2053
|
"status": "preview",
|
|
2054
2054
|
"owner": "buildchain-workflows",
|
|
2055
2055
|
"maturity": "preview",
|
|
2056
|
-
"introducedVersion": "3.0.9-alpha.
|
|
2056
|
+
"introducedVersion": "3.0.9-alpha.4",
|
|
2057
2057
|
"compatibilityPromise": "preserved-through-the-v3-major-line",
|
|
2058
2058
|
"deprecationReplacement": "",
|
|
2059
2059
|
"sunsetCondition": "explicit-breaking-change-review-in-a-future-major-line",
|
|
@@ -411,6 +411,16 @@ campaign. It has five explicit mutation modes:
|
|
|
411
411
|
existing GitHub ref, emits a zero-allocation receipt, and compensates back to
|
|
412
412
|
the prior source if the ref update or readback fails. It never calls
|
|
413
413
|
`AllocateHosts`, `RunInstances`, or workflow dispatch.
|
|
414
|
+
- `rebind-campaign-after-failure` advances that same host and instance only
|
|
415
|
+
after the operator confirms the complete prior run-id inventory. Every named
|
|
416
|
+
run must be terminal with conclusion `failure`, all of its jobs must be
|
|
417
|
+
complete, and both GitHub artifacts and S3 bootstrap evidence must still be
|
|
418
|
+
present. The replacement source must remain a strict descendant on the same
|
|
419
|
+
ref, the workflow must be disabled, the next-source evidence prefix must be
|
|
420
|
+
empty, and runner plus SSM residue must be zero. The old evidence is retained
|
|
421
|
+
and included in the rebind receipt; the operation changes only the existing
|
|
422
|
+
source ref and resource tags, with the same compensated rollback and zero
|
|
423
|
+
allocation boundary as an unused-campaign rebind.
|
|
414
424
|
- `close-campaign` refuses execution before the provider's 24-hour minimum,
|
|
415
425
|
verifies the encrypted delete-on-termination root volume, removes scoped JIT
|
|
416
426
|
residue, terminates the exact instance, and requires a `ReleaseHosts` DryRun
|
|
@@ -427,6 +437,10 @@ requires `--confirm-zero-allocation`. `rehydrate-instance` additionally repeats
|
|
|
427
437
|
the existing host and replaced instance identities and requires
|
|
428
438
|
`--confirm-no-host-allocation`. Omitting `--execute` emits a deterministic plan
|
|
429
439
|
without changing AWS or GitHub state.
|
|
440
|
+
`rebind-campaign-after-failure` additionally requires matching
|
|
441
|
+
`--terminal-failure-run-ids-json` and
|
|
442
|
+
`--confirm-terminal-failure-run-ids-json` arrays so an unobserved or newly
|
|
443
|
+
created run fails closed before any tag or ref mutation.
|
|
430
444
|
|
|
431
445
|
## Provider lifecycle
|
|
432
446
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "3.0.9-alpha.
|
|
3
|
+
"version": "3.0.9-alpha.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
|
|
6
6
|
"repository": "https://github.com/kungfu-systems/buildchain",
|
|
@@ -414,7 +414,7 @@ export function collectKfdAdopterReleaseEvidence({ manifest, gateResults = [], c
|
|
|
414
414
|
}
|
|
415
415
|
const manifestGate = createKfdAdopterManifestGate({
|
|
416
416
|
manifest, packageArtifactRoot: installedKfdPackageArtifactRoot(), gateResults,
|
|
417
|
-
expectedSourceSha: sourceSha, checkedAt,
|
|
417
|
+
authorityPath: "kfd-adopter-manifest.json", expectedSourceSha: sourceSha, checkedAt,
|
|
418
418
|
});
|
|
419
419
|
const legacyProjection = createKfdLegacySupportMatrixProjection({ manifest, manifestGate });
|
|
420
420
|
if (comparisonMatrix) {
|
|
@@ -75,6 +75,19 @@ function qualificationId(value) {
|
|
|
75
75
|
return exact(value, /^mac-(?:smoke-0[12]|full-01)$/, "qualificationId");
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
function terminalFailureRunIds(value) {
|
|
79
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
80
|
+
throw new Error("terminalFailureRunIds must be a non-empty array");
|
|
81
|
+
}
|
|
82
|
+
const normalized = value.map((entry) =>
|
|
83
|
+
exact(entry, /^[1-9]\d*$/, "terminalFailureRunId"),
|
|
84
|
+
);
|
|
85
|
+
if (new Set(normalized).size !== normalized.length) {
|
|
86
|
+
throw new Error("terminalFailureRunIds must be unique");
|
|
87
|
+
}
|
|
88
|
+
return [...normalized].sort((left, right) => Number(left) - Number(right));
|
|
89
|
+
}
|
|
90
|
+
|
|
78
91
|
function commonAws(values) {
|
|
79
92
|
const region = exact(
|
|
80
93
|
values.region || MACOS_EC2_JIT.region,
|
|
@@ -300,6 +313,14 @@ export function createMacosJitSourceRebindPlan(values = {}) {
|
|
|
300
313
|
if (workflowId !== MACOS_EC2_JIT.workflowId) {
|
|
301
314
|
throw new Error(`workflowId must be ${MACOS_EC2_JIT.workflowId}`);
|
|
302
315
|
}
|
|
316
|
+
const priorRunPolicy = values.priorRunPolicy || "unused";
|
|
317
|
+
if (!["unused", "terminal-failure"].includes(priorRunPolicy)) {
|
|
318
|
+
throw new Error("priorRunPolicy must be unused or terminal-failure");
|
|
319
|
+
}
|
|
320
|
+
const terminalRunIds =
|
|
321
|
+
priorRunPolicy === "terminal-failure"
|
|
322
|
+
? terminalFailureRunIds(values.terminalFailureRunIds)
|
|
323
|
+
: [];
|
|
303
324
|
const plan = {
|
|
304
325
|
schemaVersion: 1,
|
|
305
326
|
contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
|
|
@@ -314,6 +335,8 @@ export function createMacosJitSourceRebindPlan(values = {}) {
|
|
|
314
335
|
github: {
|
|
315
336
|
workflowId,
|
|
316
337
|
requiredState: "disabled_manually",
|
|
338
|
+
priorRunPolicy,
|
|
339
|
+
terminalFailureRunIds: terminalRunIds,
|
|
317
340
|
},
|
|
318
341
|
aws: {
|
|
319
342
|
...aws,
|
|
@@ -327,8 +350,9 @@ export function createMacosJitSourceRebindPlan(values = {}) {
|
|
|
327
350
|
forwardOnlySource: true,
|
|
328
351
|
sameSourceRefRequired: true,
|
|
329
352
|
workflowDisabledRequired: true,
|
|
330
|
-
zeroPriorJobsRequired:
|
|
331
|
-
zeroPriorArtifactsRequired:
|
|
353
|
+
zeroPriorJobsRequired: priorRunPolicy === "unused",
|
|
354
|
+
zeroPriorArtifactsRequired: priorRunPolicy === "unused",
|
|
355
|
+
terminalFailureEvidencePreserved: priorRunPolicy === "terminal-failure",
|
|
332
356
|
zeroJitResidueRequired: true,
|
|
333
357
|
zeroEvidenceRequired: true,
|
|
334
358
|
exactCampaignResourcesRequired: true,
|
|
@@ -38,6 +38,19 @@ function flag(name) {
|
|
|
38
38
|
return process.argv.includes(`--${name}`);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
function jsonArrayArg(name) {
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(arg(name));
|
|
45
|
+
} catch {
|
|
46
|
+
throw new Error(`--${name} must be valid JSON`);
|
|
47
|
+
}
|
|
48
|
+
if (!Array.isArray(parsed)) {
|
|
49
|
+
throw new Error(`--${name} must be a JSON array`);
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
|
|
41
54
|
function assertCampaignLaunchPreflight(plan, profile) {
|
|
42
55
|
const launchGate = assertMacosBudgetLaunchGate(plan, profile);
|
|
43
56
|
const commit = ghJson(
|
|
@@ -517,6 +530,19 @@ function confirm(plan) {
|
|
|
517
530
|
if (!flag("confirm-zero-allocation")) {
|
|
518
531
|
throw new Error("--confirm-zero-allocation is required");
|
|
519
532
|
}
|
|
533
|
+
if (plan.github.priorRunPolicy === "terminal-failure") {
|
|
534
|
+
const confirmed = jsonArrayArg(
|
|
535
|
+
"confirm-terminal-failure-run-ids-json",
|
|
536
|
+
).map(String);
|
|
537
|
+
if (
|
|
538
|
+
JSON.stringify(confirmed) !==
|
|
539
|
+
JSON.stringify(plan.github.terminalFailureRunIds)
|
|
540
|
+
) {
|
|
541
|
+
throw new Error(
|
|
542
|
+
"--confirm-terminal-failure-run-ids-json must equal the exact failed run inventory",
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
520
546
|
}
|
|
521
547
|
if (plan.kind === "instance-rehydrate-plan") {
|
|
522
548
|
if (arg("confirm-host-id") !== plan.aws.hostId) {
|
|
@@ -570,7 +596,15 @@ export function main() {
|
|
|
570
596
|
}),
|
|
571
597
|
);
|
|
572
598
|
}
|
|
573
|
-
if (
|
|
599
|
+
if (
|
|
600
|
+
[
|
|
601
|
+
"plan-rebind",
|
|
602
|
+
"rebind-campaign",
|
|
603
|
+
"plan-rebind-after-failure",
|
|
604
|
+
"rebind-campaign-after-failure",
|
|
605
|
+
].includes(mode)
|
|
606
|
+
) {
|
|
607
|
+
const afterFailure = mode.endsWith("after-failure");
|
|
574
608
|
const plan = createMacosJitSourceRebindPlan({
|
|
575
609
|
...commonValues(execute),
|
|
576
610
|
previousSourceSha: arg("previous-source-sha"),
|
|
@@ -578,6 +612,10 @@ export function main() {
|
|
|
578
612
|
workflowId: arg("workflow-id"),
|
|
579
613
|
hostId: arg("host-id"),
|
|
580
614
|
instanceId: arg("instance-id"),
|
|
615
|
+
priorRunPolicy: afterFailure ? "terminal-failure" : "unused",
|
|
616
|
+
terminalFailureRunIds: afterFailure
|
|
617
|
+
? jsonArrayArg("terminal-failure-run-ids-json")
|
|
618
|
+
: [],
|
|
581
619
|
});
|
|
582
620
|
return emit(plan, mode, execute, () =>
|
|
583
621
|
executeMacosJitSourceRebind(plan, { profile: arg("aws-profile") }),
|
|
@@ -28,7 +28,29 @@ function priorOwnershipPlan(plan) {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function
|
|
31
|
+
function assertRunMatchesPolicy(plan, run, jobs, jobCount, artifactCount) {
|
|
32
|
+
if (plan.github.priorRunPolicy === "unused") {
|
|
33
|
+
if (jobCount !== 0 || artifactCount !== 0) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`macOS campaign run ${run.id} already has jobs or artifacts`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (
|
|
41
|
+
run.status !== "completed" ||
|
|
42
|
+
run.conclusion !== "failure" ||
|
|
43
|
+
jobCount === 0 ||
|
|
44
|
+
artifactCount === 0 ||
|
|
45
|
+
(jobs.jobs || []).some((job) => job.status !== "completed")
|
|
46
|
+
) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`macOS campaign run ${run.id} is not a terminal failed run with retained evidence`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function campaignRunReceipts(plan) {
|
|
32
54
|
const branch = sourceRefName(plan.source.ref);
|
|
33
55
|
const response = ghJson(
|
|
34
56
|
[
|
|
@@ -68,19 +90,35 @@ function campaignRunsWithNoWork(plan) {
|
|
|
68
90
|
const artifactCount = Number(
|
|
69
91
|
artifacts.total_count || (artifacts.artifacts || []).length || 0,
|
|
70
92
|
);
|
|
71
|
-
|
|
72
|
-
throw new Error(
|
|
73
|
-
`macOS campaign run ${run.id} already has jobs or artifacts`,
|
|
74
|
-
);
|
|
75
|
-
}
|
|
93
|
+
assertRunMatchesPolicy(plan, run, jobs, jobCount, artifactCount);
|
|
76
94
|
receipts.push({
|
|
77
95
|
runId: String(run.id),
|
|
78
96
|
status: String(run.status || "unknown"),
|
|
79
97
|
conclusion: String(run.conclusion || ""),
|
|
80
98
|
jobCount,
|
|
81
99
|
artifactCount,
|
|
100
|
+
artifacts: (artifacts.artifacts || []).map((artifact) => ({
|
|
101
|
+
id: String(artifact.id),
|
|
102
|
+
name: String(artifact.name || ""),
|
|
103
|
+
sizeInBytes: Number(artifact.size_in_bytes || 0),
|
|
104
|
+
digest: String(artifact.digest || ""),
|
|
105
|
+
expired: artifact.expired === true,
|
|
106
|
+
})),
|
|
82
107
|
});
|
|
83
108
|
}
|
|
109
|
+
if (plan.github.priorRunPolicy === "terminal-failure") {
|
|
110
|
+
const observed = receipts
|
|
111
|
+
.map((receipt) => receipt.runId)
|
|
112
|
+
.sort((left, right) => Number(left) - Number(right));
|
|
113
|
+
if (
|
|
114
|
+
JSON.stringify(observed) !==
|
|
115
|
+
JSON.stringify(plan.github.terminalFailureRunIds)
|
|
116
|
+
) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
"macOS campaign terminal failed run inventory does not match the confirmed run ids",
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
84
122
|
return receipts;
|
|
85
123
|
}
|
|
86
124
|
|
|
@@ -286,7 +324,7 @@ function assertZeroCampaignResidue(plan, profile) {
|
|
|
286
324
|
if (matchingRunners.length !== 0) {
|
|
287
325
|
throw new Error("macOS campaign already has a registered JIT runner");
|
|
288
326
|
}
|
|
289
|
-
const
|
|
327
|
+
const evidenceObjects = {};
|
|
290
328
|
for (const sourceSha of [plan.previousSource.sha, plan.source.sha]) {
|
|
291
329
|
const evidence = awsJson(
|
|
292
330
|
plan,
|
|
@@ -299,27 +337,43 @@ function assertZeroCampaignResidue(plan, profile) {
|
|
|
299
337
|
"--prefix",
|
|
300
338
|
`macos/${sourceSha}/`,
|
|
301
339
|
"--max-keys",
|
|
302
|
-
"
|
|
340
|
+
"1000",
|
|
303
341
|
"--output",
|
|
304
342
|
"json",
|
|
305
343
|
],
|
|
306
344
|
`macOS campaign evidence inventory for ${sourceSha}`,
|
|
307
345
|
);
|
|
346
|
+
if (evidence.IsTruncated === true) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
`macOS campaign source ${sourceSha} evidence inventory is incomplete`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
const objects = (evidence.Contents || []).map((object) => ({
|
|
352
|
+
key: String(object.Key || ""),
|
|
353
|
+
size: Number(object.Size || 0),
|
|
354
|
+
etag: String(object.ETag || ""),
|
|
355
|
+
}));
|
|
308
356
|
const count = Number(
|
|
309
357
|
evidence.KeyCount || (evidence.Contents || []).length || 0,
|
|
310
358
|
);
|
|
311
|
-
|
|
359
|
+
const previousTerminalEvidence =
|
|
360
|
+
sourceSha === plan.previousSource.sha &&
|
|
361
|
+
plan.github.priorRunPolicy === "terminal-failure";
|
|
362
|
+
if (
|
|
363
|
+
(!previousTerminalEvidence && count !== 0) ||
|
|
364
|
+
(previousTerminalEvidence && count === 0)
|
|
365
|
+
) {
|
|
312
366
|
throw new Error(
|
|
313
|
-
`macOS campaign source ${sourceSha}
|
|
367
|
+
`macOS campaign source ${sourceSha} bootstrap evidence does not match the rebind policy`,
|
|
314
368
|
);
|
|
315
369
|
}
|
|
316
|
-
|
|
370
|
+
evidenceObjects[sourceSha] = objects;
|
|
317
371
|
}
|
|
318
372
|
return {
|
|
319
|
-
priorRuns:
|
|
373
|
+
priorRuns: campaignRunReceipts(plan),
|
|
320
374
|
jitParameterCount: parameters.length,
|
|
321
375
|
runnerCount: matchingRunners.length,
|
|
322
|
-
|
|
376
|
+
evidenceObjects,
|
|
323
377
|
};
|
|
324
378
|
}
|
|
325
379
|
|
|
@@ -480,7 +534,10 @@ export function executeMacosJitSourceRebind(plan, { profile = "" } = {}) {
|
|
|
480
534
|
schemaVersion: 1,
|
|
481
535
|
contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
|
|
482
536
|
kind: "campaign-source-rebind-result",
|
|
483
|
-
status:
|
|
537
|
+
status:
|
|
538
|
+
plan.github.priorRunPolicy === "terminal-failure"
|
|
539
|
+
? "rebound-after-terminal-failure-zero-allocation"
|
|
540
|
+
: "rebound-zero-allocation",
|
|
484
541
|
repository: plan.repository,
|
|
485
542
|
campaign: plan.campaign,
|
|
486
543
|
previousSource: plan.previousSource,
|