@kungfu-tech/buildchain 3.0.6-alpha.0 → 3.0.6-alpha.1
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 +5 -5
- package/dist/site/buildchain-site.json +19 -14
- package/dist/site/kfd-claims.json +4 -3
- package/dist/site/kfd-upstream-aggregate.json +9 -9
- package/dist/site/manual-registry.json +2 -2
- package/dist/site/page-registry.json +13 -8
- package/dist/site/public-surface-audit.json +4 -3
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +6 -6
- package/dist/site/workflow-registry.json +2 -1
- package/docs/MAP.md +1 -0
- package/docs/aws-us-elastic-runner-burst-plane.md +109 -79
- package/docs/release-governance.md +8 -1
- package/package.json +3 -2
- package/scripts/aws-windows-jit-campaign-core.mjs +7 -8
- package/scripts/aws-windows-jit-controller.mjs +1 -0
- package/scripts/aws-windows-jit-core.mjs +1 -1
- package/scripts/dev-pr-auto-merge.mjs +12 -12
- package/scripts/dispatch-artifact-signing-authority.mjs +2 -4
|
@@ -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-03T09:35:55.854Z",
|
|
5
|
+
"publishedAt": "2026-08-03T09:35:55.854Z",
|
|
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": "88a8b532ce69ee3def5926a42c9cd75e4809ae88",
|
|
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.6-alpha.
|
|
40
|
+
"version": "3.0.6-alpha.1",
|
|
41
41
|
"versionSource": "package.json#version"
|
|
42
42
|
},
|
|
43
43
|
"entrypoint": "buildchain-site.json",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"path": "docs/MAP.md",
|
|
54
54
|
"plane": "use",
|
|
55
55
|
"exists": true,
|
|
56
|
-
"digest": "sha256:
|
|
56
|
+
"digest": "sha256:0ea7f59030e5a087e3ff7e9eb591fec8d92c64399799e554aa1fa7ecb6a263ae"
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
"id": "getting-started",
|
|
@@ -293,7 +293,7 @@
|
|
|
293
293
|
"path": "docs/release-governance.md",
|
|
294
294
|
"plane": "why",
|
|
295
295
|
"exists": true,
|
|
296
|
-
"digest": "sha256:
|
|
296
|
+
"digest": "sha256:fc7f0add1afce775c327d50a1e269a7040865e65ca2441dbeb8d0373c5f8d5aa"
|
|
297
297
|
},
|
|
298
298
|
{
|
|
299
299
|
"id": "release-flow",
|
|
@@ -1681,13 +1681,14 @@
|
|
|
1681
1681
|
"landing-mode",
|
|
1682
1682
|
"max-merges",
|
|
1683
1683
|
"merge-method",
|
|
1684
|
+
"queue-admission-context",
|
|
1684
1685
|
"ready-label",
|
|
1685
1686
|
"require-approval",
|
|
1686
1687
|
"required-status-checks",
|
|
1687
1688
|
"same-repository-only",
|
|
1688
1689
|
"target-branch"
|
|
1689
1690
|
],
|
|
1690
|
-
"inputCount":
|
|
1691
|
+
"inputCount": 14,
|
|
1691
1692
|
"secrets": [
|
|
1692
1693
|
"github-token"
|
|
1693
1694
|
],
|
package/docs/MAP.md
CHANGED
|
@@ -152,6 +152,7 @@ replace them.
|
|
|
152
152
|
| How do I automatically qualify alpha candidates and publish the newest non-revoked qualified candidate at a fixed window? | [`stable-candidate-patrol.md`](stable-candidate-patrol.md) | use | preview |
|
|
153
153
|
| How do I deploy a site/app preview, staging, or production surface? | [`web-surface-deployments.md`](web-surface-deployments.md) | use | stable |
|
|
154
154
|
| How do I publish observed infrastructure contracts for downstream consumers? | [`infra-contract.md`](infra-contract.md) | use | preview |
|
|
155
|
+
| How do I operate a repeatable, disabled-by-default, budget-fail-closed AWS Windows JIT campaign? | [`aws-us-elastic-runner-burst-plane.md`](aws-us-elastic-runner-burst-plane.md#phase-2-operator-workflow) | use/verify | preview |
|
|
155
156
|
| How do I use the active actions directly? | [`../actions/validate-config/README.md`](../actions/validate-config/README.md), [`../actions/run-lifecycle/README.md`](../actions/run-lifecycle/README.md), [`../actions/promote-buildchain-ref/README.md`](../actions/promote-buildchain-ref/README.md), [`../actions/report-buildchain-issue/README.md`](../actions/report-buildchain-issue/README.md), [`../actions/github-artifact-attestation/README.md`](../actions/github-artifact-attestation/README.md), [`../actions/macos-credential-island/README.md`](../actions/macos-credential-island/README.md); `dist/site/workflow-registry.json#actions` is authoritative for the six-entry inventory. | use | stable |
|
|
156
157
|
| How can a consumer workflow report a Buildchain-owned failure back to Buildchain? | [`consumer-issue-reporting.md`](consumer-issue-reporting.md) + [`../actions/report-buildchain-issue/README.md`](../actions/report-buildchain-issue/README.md) | use | stable |
|
|
157
158
|
| What do the fixture repositories demonstrate? | [`../fixtures/libnode-shaped/README.md`](../fixtures/libnode-shaped/README.md), [`../fixtures/publish-transaction-shaped/README.md`](../fixtures/publish-transaction-shaped/README.md), [`../fixtures/web-surface-shaped/README.md`](../fixtures/web-surface-shaped/README.md), [`../fixtures/publication-artifact-shaped/README.md`](../fixtures/publication-artifact-shaped/README.md) | verify | stable |
|
|
@@ -8,11 +8,11 @@ confidence: high
|
|
|
8
8
|
sensitivity: public
|
|
9
9
|
evidence_grade: A
|
|
10
10
|
review_state: unreviewed
|
|
11
|
-
last_reviewed: 2026-08-
|
|
11
|
+
last_reviewed: 2026-08-03
|
|
12
12
|
ai_provenance:
|
|
13
13
|
model_family: GPT-5
|
|
14
14
|
product: Codex
|
|
15
|
-
generated_at: 2026-08-
|
|
15
|
+
generated_at: 2026-08-03
|
|
16
16
|
invisible_information: No hidden model checkpoint, parameters, or private training data were available.
|
|
17
17
|
---
|
|
18
18
|
|
|
@@ -189,7 +189,7 @@ and atomically refuses a sixth accepted instance. Five accepted instances
|
|
|
189
189
|
therefore reserve at most USD 21.75. The campaign also persists the
|
|
190
190
|
operator-observed spend from earlier Windows work, and refuses to arm unless
|
|
191
191
|
that baseline, all five reservations, and one USD 4.35 fail-closed race
|
|
192
|
-
allowance remain below the USD
|
|
192
|
+
allowance remain below the USD 110 phase cap.
|
|
193
193
|
|
|
194
194
|
The campaign starts unarmed and expires within 24 hours. Its `CONTROL` record
|
|
195
195
|
can be created only once: a killed or expired campaign cannot be re-armed by
|
|
@@ -199,16 +199,34 @@ paid launch. Reservations are never refunded: a controller crash, ambiguous
|
|
|
199
199
|
launch, or successful launch all remain charged to the campaign, favoring a
|
|
200
200
|
false stop over an accidental budget overrun.
|
|
201
201
|
|
|
202
|
+
The 2026-08-03 timeout-only campaign decision narrows the campaign to two
|
|
203
|
+
accepted instances with one active instance at a time. The second reservation
|
|
204
|
+
is an operator-gated repair retry: it may be used only after the first attempt
|
|
205
|
+
is classified as non-counting and runner, EC2, EBS, SSM, and workflow residue
|
|
206
|
+
have returned to zero. The two-slot ledger is a maximum spend boundary, not an
|
|
207
|
+
authorization to consume both reservations.
|
|
208
|
+
|
|
202
209
|
Each stack owns a stack-scoped reaper log group, so an independent retained
|
|
203
210
|
one-shot campaign stack can be created without colliding with another
|
|
204
211
|
campaign's audit log resource.
|
|
205
212
|
|
|
206
213
|
The tag-filtered AWS Budget is defense in depth, not the authoritative launch
|
|
207
|
-
gate. It is
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
214
|
+
gate. It is owned by the singleton
|
|
215
|
+
`kungfu-buildchain-windows-jit-budget-guard` stack rather than any retained
|
|
216
|
+
campaign stack. This prevents Budget-name collisions and prevents a stale
|
|
217
|
+
campaign reaper from becoming the provider-wide cost authority. The Budget
|
|
218
|
+
filters exactly `user:kungfu:provider$windows-ec2-jit`; its 80% and 95% actual
|
|
219
|
+
notifications persist the provider kill sentinel, terminate every tagged
|
|
220
|
+
Windows JIT instance, and delete scoped JIT parameters. Every launch controller
|
|
221
|
+
refuses to proceed when the sentinel exists or when the Budget identity or tag
|
|
222
|
+
filter does not match.
|
|
223
|
+
|
|
224
|
+
Budget installation fails closed until the AWS Organizations management
|
|
225
|
+
account activates `kungfu:provider` and Cost Explorer returns
|
|
226
|
+
`windows-ec2-jit` for that key. A linked account cannot activate the tag. Do not
|
|
227
|
+
create an unfiltered fallback Budget or treat a tag-filtered zero as evidence.
|
|
228
|
+
The DynamoDB campaign reservation remains the atomic launch authority because
|
|
229
|
+
Cost Explorer and AWS Budgets can lag provider activity.
|
|
212
230
|
|
|
213
231
|
Qualification requires one runner-profile smoke and three trusted exact-source
|
|
214
232
|
full Windows jobs all bound to the same campaign, independent cancellation and
|
|
@@ -216,82 +234,94 @@ timeout cleanup exercises, and zero repository runner, EC2 instance,
|
|
|
216
234
|
disposable volume, min capacity, and desired capacity within 15 minutes of the
|
|
217
235
|
final job.
|
|
218
236
|
|
|
219
|
-
### Phase 2
|
|
220
|
-
|
|
221
|
-
`
|
|
222
|
-
|
|
223
|
-
campaign
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
237
|
+
### Phase 2 operator workflow
|
|
238
|
+
|
|
239
|
+
`pnpm operator:windows-jit` is the reusable lifecycle entrypoint. Its default
|
|
240
|
+
mode is `plan`, which performs no AWS or GitHub call. A plan binds the account,
|
|
241
|
+
region, unique campaign and stack names, source SHA/ref, Cost Explorer window,
|
|
242
|
+
workflow id, network, OIDC provider, expiry, slot ceiling, singleton Budget
|
|
243
|
+
identity, and exact confirmation digest.
|
|
244
|
+
|
|
245
|
+
The modes are deliberately separated:
|
|
246
|
+
|
|
247
|
+
- `plan` emits the deterministic mutation boundary and digest.
|
|
248
|
+
- `audit` reads AWS and GitHub only. It verifies the account, disabled workflow,
|
|
249
|
+
singleton guard stack, exact Budget filter, SNS thresholds/subscribers,
|
|
250
|
+
provider kill sentinel, campaign stack, and zero EC2/EBS/SSM/JIT/runner
|
|
251
|
+
residue.
|
|
252
|
+
- `install-budget --execute` deploys or updates only the singleton Budget guard.
|
|
253
|
+
It refuses to mutate unless the provider tag value is visible, the Windows
|
|
254
|
+
workflow is disabled, and the account, campaign, source, Budget, and plan
|
|
255
|
+
digest confirmations match.
|
|
256
|
+
- `prepare --execute` requires the installed Budget guard, absent kill
|
|
257
|
+
sentinel, fresh Cost Explorer readback filtered by both
|
|
258
|
+
`kungfu:provider=windows-ec2-jit` and `BoxUsage:c7i.4xlarge`, zero residue, a
|
|
259
|
+
never-used campaign stack name, and the disabled workflow. The receipt binds
|
|
260
|
+
the query timestamp and exact filter identity. Preparation deploys the
|
|
261
|
+
campaign stack and atomically arms the ledger with that provider-spend
|
|
262
|
+
baseline. It never enables or dispatches the workflow and never creates EC2
|
|
263
|
+
capacity.
|
|
264
|
+
- `close --execute` disables the workflow first, persists `KILLED`, publishes
|
|
265
|
+
the campaign kill switch, and reports terminal success only after EC2, EBS,
|
|
266
|
+
SSM, JIT parameter, and GitHub runner residue is zero. It is safe to rerun
|
|
267
|
+
while the reaper settles.
|
|
268
|
+
|
|
269
|
+
All mutating modes require `--execute`, `--confirm-plan-digest`,
|
|
270
|
+
`--confirm-account-id`, `--confirm-campaign-id`, and
|
|
271
|
+
`--confirm-source-sha`. Budget installation and preparation additionally
|
|
272
|
+
require `--confirm-budget-name`. A future paid workload still requires a
|
|
273
|
+
separate exact workflow/run authorization and uses
|
|
274
|
+
`scripts/aws-windows-jit-controller.mjs`; preparation is not paid-launch
|
|
275
|
+
authority.
|
|
276
|
+
|
|
277
|
+
Start by recording one reproducible plan:
|
|
242
278
|
|
|
243
279
|
```bash
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
--
|
|
253
|
-
--
|
|
254
|
-
--
|
|
255
|
-
--
|
|
256
|
-
--
|
|
257
|
-
--
|
|
258
|
-
|
|
259
|
-
node scripts/aws-windows-jit-campaign.mjs arm-campaign \
|
|
260
|
-
--campaign-id "$windows_campaign" \
|
|
261
|
-
--confirm-campaign-id "$windows_campaign" \
|
|
262
|
-
--source-sha "$windows_source" \
|
|
263
|
-
--confirm-source-sha "$windows_source" \
|
|
264
|
-
--state-table "$windows_state_table" \
|
|
265
|
-
--confirm-state-table "$windows_state_table" \
|
|
266
|
-
--expires-at "$windows_expires_at" \
|
|
267
|
-
--phase-spend-baseline-usd "$windows_phase_spend_baseline_usd" \
|
|
268
|
-
--confirm-phase-spend-baseline-usd "$windows_phase_spend_baseline_usd" \
|
|
269
|
-
--max-accepted-instances "$windows_max_accepted_instances" \
|
|
270
|
-
--confirm-max-accepted-instances "$windows_max_accepted_instances"
|
|
280
|
+
pnpm operator:windows-jit plan \
|
|
281
|
+
--aws-profile us \
|
|
282
|
+
--account-id 727884401362 \
|
|
283
|
+
--campaign-id win-REPLACE \
|
|
284
|
+
--source-sha REPLACE_WITH_EXACT_40_CHARACTER_SHA \
|
|
285
|
+
--source-ref refs/heads/dev/v4/v4.0 \
|
|
286
|
+
--observed-at REPLACE_WITH_ISO_TIMESTAMP \
|
|
287
|
+
--expires-at REPLACE_WITH_ISO_TIMESTAMP_WITHIN_24_HOURS \
|
|
288
|
+
--cost-start REPLACE_WITH_PHASE_START_DATE \
|
|
289
|
+
--cost-end REPLACE_WITH_EXCLUSIVE_END_DATE \
|
|
290
|
+
--max-accepted-instances 1 \
|
|
291
|
+
--workflow-id 322620360 \
|
|
292
|
+
--vpc-id REPLACE_WITH_VPC_ID \
|
|
293
|
+
--subnet-id REPLACE_WITH_SUBNET_ID \
|
|
294
|
+
--oidc-provider-arn REPLACE_WITH_GITHUB_OIDC_PROVIDER_ARN
|
|
271
295
|
```
|
|
272
296
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
terminates active card-owned instances and removes their JIT parameters. The
|
|
278
|
-
command is idempotent, but the operator must read back DynamoDB, EC2, SSM, and
|
|
279
|
-
GitHub runners before treating cleanup as complete:
|
|
297
|
+
Reuse those exact arguments for `audit`, `install-budget`, `prepare`, or
|
|
298
|
+
`close`; never regenerate `--observed-at` between the plan and its confirmed
|
|
299
|
+
mutation. Capture stdout as the operator receipt. Do not put credentials,
|
|
300
|
+
tokens, JIT configuration, or signed URLs in arguments or receipts.
|
|
280
301
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
302
|
+
### Lower-level campaign and launch controllers
|
|
303
|
+
|
|
304
|
+
`scripts/aws-windows-jit-campaign-core.mjs` owns the pure one-shot ledger
|
|
305
|
+
contract used by the operator and launch controller. Arming creates `CONTROL`
|
|
306
|
+
and `CAMPAIGN#<id>` with `attribute_not_exists` conditions, so DynamoDB refuses
|
|
307
|
+
a second campaign in the same retained state table. There is deliberately no
|
|
308
|
+
clear or re-arm operation.
|
|
309
|
+
|
|
310
|
+
Every `scripts/aws-windows-jit-controller.mjs --execute` call must provide the
|
|
311
|
+
same `--account-id`, `--campaign-id`, `--confirm-campaign-id`, `--state-table`,
|
|
312
|
+
and `--confirm-state-table`. Before GitHub JIT material is created, the
|
|
313
|
+
controller verifies the exact provider Budget/tag filter and proves the global
|
|
314
|
+
Budget kill sentinel absent. After the GitHub, AMI, active-instance, SSM, and
|
|
315
|
+
EC2 DryRun checks pass, the controller
|
|
316
|
+
atomically reserves one run. Duplicate run-attempt-qualification identities,
|
|
317
|
+
source mismatch, expiry, `KILLED`, the sixth accepted instance, or a
|
|
318
|
+
reservation that would exceed the USD 110 ceiling after combining the persisted
|
|
319
|
+
fresh Cost Explorer baseline with all in-flight campaign reservations all fail
|
|
320
|
+
closed in one DynamoDB transaction before `RunInstances`. AWS Budget alarms are
|
|
321
|
+
defense in depth for delayed billing telemetry; the atomic ledger is the
|
|
322
|
+
authoritative launch-time guard. The operator is the only supported mutation
|
|
323
|
+
surface for campaign preparation and closeout; direct imports of the core are
|
|
324
|
+
not operator authority.
|
|
295
325
|
|
|
296
326
|
## Phase 3 contract
|
|
297
327
|
|
|
@@ -648,10 +648,11 @@ jobs:
|
|
|
648
648
|
contents: write
|
|
649
649
|
pull-requests: write
|
|
650
650
|
checks: read
|
|
651
|
-
statuses:
|
|
651
|
+
statuses: write
|
|
652
652
|
with:
|
|
653
653
|
target-branch: dev/v3/v3.0
|
|
654
654
|
required-status-checks: check / check
|
|
655
|
+
queue-admission-context: Queue admission lease
|
|
655
656
|
ready-label: ready
|
|
656
657
|
block-labels: blocked,do-not-merge
|
|
657
658
|
max-merges: 1
|
|
@@ -659,6 +660,12 @@ jobs:
|
|
|
659
660
|
dry-run: ${{ inputs.dry-run || false }}
|
|
660
661
|
```
|
|
661
662
|
|
|
663
|
+
When the protected branch requires a merge-group-only queue lease, the wrapper
|
|
664
|
+
posts that configured context as a temporary success status on the exact PR
|
|
665
|
+
head only after the ready, review, and required-check gates pass. It then
|
|
666
|
+
enqueues with `expectedHeadOid`; a rejected enqueue rewrites the temporary
|
|
667
|
+
status to failure, while the merge group must still produce its own final check.
|
|
668
|
+
|
|
662
669
|
## Buildchain Patrol
|
|
663
670
|
|
|
664
671
|
`dev-pr-auto-merge.yml` remains the focused merge primitive. For repositories
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "3.0.6-alpha.
|
|
3
|
+
"version": "3.0.6-alpha.1",
|
|
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",
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
},
|
|
109
109
|
"packageManager": "pnpm@11.7.0",
|
|
110
110
|
"scripts": {
|
|
111
|
+
"operator:windows-jit": "bash scripts/aws-windows-jit-operator.sh",
|
|
111
112
|
"check": "pnpm run check:static && node scripts/check-maintainability.mjs && node scripts/check-internal-architecture.mjs && node scripts/check-inventory.mjs && pnpm run check:site && pnpm run check:workflows && pnpm run test:unit && pnpm run check:golden-path && node scripts/check-action-bundles.mjs",
|
|
112
113
|
"check:static": "node scripts/check-javascript-syntax.mjs && prettier --check architecture/*.json bin/internal/command-registry.mjs scripts/authorize-promotion-runtime-override.cjs scripts/check-javascript-syntax.mjs scripts/check-maintainability.mjs scripts/maintainability-metrics.mjs scripts/public-reference.mjs scripts/site-reference-registry.mjs scripts/generate-public-reference.mjs scripts/verify-golden-path.mjs tests/authorize-promotion-runtime-override.test.mjs tests/command-registry.test.mjs tests/maintainability.test.mjs tests/promotion-responsibilities.test.mjs tests/public-reference.test.mjs",
|
|
113
114
|
"check:workflows": "bash scripts/check-workflows.sh",
|
|
@@ -128,7 +129,7 @@
|
|
|
128
129
|
"typescript": "^5.9.0"
|
|
129
130
|
},
|
|
130
131
|
"dependencies": {
|
|
131
|
-
"@kungfu-tech/kfd": "1.0.0-alpha.
|
|
132
|
+
"@kungfu-tech/kfd": "1.0.0-alpha.53",
|
|
132
133
|
"smol-toml": "1.7.0"
|
|
133
134
|
}
|
|
134
135
|
}
|
|
@@ -35,18 +35,15 @@ function tableName(value) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function number(value) {
|
|
38
|
-
return { N: String(value) };
|
|
38
|
+
return { N: String(Math.round(value * 100_000_000) / 100_000_000) };
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function money(value, label) {
|
|
42
42
|
const normalized = String(value ?? "").trim();
|
|
43
|
-
if (!normalized) {
|
|
44
|
-
throw new Error(`${label} is required`);
|
|
45
|
-
}
|
|
43
|
+
if (!normalized) throw new Error(`${label} is required`);
|
|
46
44
|
const parsed = Number(normalized);
|
|
47
|
-
if (!Number.isFinite(parsed) || parsed < 0)
|
|
45
|
+
if (!Number.isFinite(parsed) || parsed < 0)
|
|
48
46
|
throw new Error(`${label} must be a non-negative finite number`);
|
|
49
|
-
}
|
|
50
47
|
return Math.round(parsed * 100_000_000) / 100_000_000;
|
|
51
48
|
}
|
|
52
49
|
|
|
@@ -90,9 +87,11 @@ export function createWindowsJitCampaignArmPlan(values = {}) {
|
|
|
90
87
|
);
|
|
91
88
|
const maxAcceptedInstances = acceptedInstances(values.maxAcceptedInstances);
|
|
92
89
|
const campaignReservationCeilingUsd = reservationUsd * maxAcceptedInstances;
|
|
93
|
-
const campaignSafetyCeilingUsd =
|
|
90
|
+
const campaignSafetyCeilingUsd = money(
|
|
94
91
|
campaignReservationCeilingUsd +
|
|
95
|
-
|
|
92
|
+
reservationUsd * WINDOWS_EC2_JIT.maxConcurrentInstances,
|
|
93
|
+
"campaignSafetyCeilingUsd",
|
|
94
|
+
);
|
|
96
95
|
const remainingPhaseBudgetUsd =
|
|
97
96
|
WINDOWS_EC2_JIT.budgetLimitUsd - phaseSpendBaselineUsd;
|
|
98
97
|
if (campaignSafetyCeilingUsd >= remainingPhaseBudgetUsd) {
|
|
@@ -164,6 +164,7 @@ function assertLivePreflight(plan, profile) {
|
|
|
164
164
|
throw new Error("Windows AMI identity or availability mismatch");
|
|
165
165
|
}
|
|
166
166
|
return {
|
|
167
|
+
budgetGuard: jsonResult(commandResult("/bin/bash", ["scripts/aws-windows-jit-operator.sh", "launch-gate", "--region", plan.aws.region, ...(profile ? ["--aws-profile", profile] : [])]), "provider Budget launch gate"),
|
|
167
168
|
runStatus: run.status,
|
|
168
169
|
jobStatus: job.status,
|
|
169
170
|
activeInstances: activeInstances.length,
|
|
@@ -12,7 +12,7 @@ export const WINDOWS_EC2_JIT = Object.freeze({
|
|
|
12
12
|
maximumInstanceLifetimeMinutes: 180,
|
|
13
13
|
maxConcurrentInstances: 1,
|
|
14
14
|
maxAcceptedInstances: 5,
|
|
15
|
-
budgetLimitUsd:
|
|
15
|
+
budgetLimitUsd: 110,
|
|
16
16
|
minimumSmokeJobs: 1,
|
|
17
17
|
minimumFullJobs: 3,
|
|
18
18
|
maximumCleanupLatencySeconds: 900,
|
|
@@ -8,13 +8,7 @@ const DEFAULT_REQUIRED_CHECKS = ["check"];
|
|
|
8
8
|
const SUCCESS_STATES = new Set(["success"]);
|
|
9
9
|
const SUCCESS_CONCLUSIONS = new Set(["success", "neutral", "skipped"]);
|
|
10
10
|
const VALID_LANDING_MODES = new Set(["auto", "direct", "queue"]);
|
|
11
|
-
const STATIC_SKIP_REASONS = new Set([
|
|
12
|
-
"draft",
|
|
13
|
-
"fork-or-cross-repository-head",
|
|
14
|
-
"head-prefix-not-allowed",
|
|
15
|
-
"missing-ready-label",
|
|
16
|
-
"blocked-label",
|
|
17
|
-
]);
|
|
11
|
+
const STATIC_SKIP_REASONS = new Set(["draft", "fork-or-cross-repository-head", "head-prefix-not-allowed", "missing-ready-label", "blocked-label"]);
|
|
18
12
|
const ADMISSION_CONTRACT = "kungfu-buildchain-dev-merge-queue-admission";
|
|
19
13
|
|
|
20
14
|
function splitList(value, fallback = []) {
|
|
@@ -62,6 +56,7 @@ function normalizeOptions(options = {}) {
|
|
|
62
56
|
blockLabels: splitList(options.blockLabels, DEFAULT_BLOCK_LABELS).map((label) => label.toLowerCase()),
|
|
63
57
|
allowedHeadPrefixes: splitList(options.allowedHeadPrefixes, DEFAULT_ALLOWED_HEAD_PREFIXES),
|
|
64
58
|
requiredChecks: splitList(options.requiredChecks, DEFAULT_REQUIRED_CHECKS),
|
|
59
|
+
queueAdmissionContext: String(options.queueAdmissionContext || "").trim(),
|
|
65
60
|
requireApproval: boolOption(options.requireApproval, true),
|
|
66
61
|
sameRepositoryOnly: boolOption(options.sameRepositoryOnly, true),
|
|
67
62
|
maxMerges: intOption(options.maxMerges, 1),
|
|
@@ -155,8 +150,12 @@ function summarizeChecks({ statuses = [], checkRuns = [] } = {}, requiredChecks
|
|
|
155
150
|
function mergeableAccepted(pr, landingMode = "direct") {
|
|
156
151
|
if (pr.mergeable === false) return false;
|
|
157
152
|
const state = String(pr.mergeable_state || pr.mergeStateStatus || "").toLowerCase();
|
|
158
|
-
|
|
159
|
-
|
|
153
|
+
return state ? ["clean", "has_hooks", "unstable", "unknown", ...(landingMode === "queue" && pr.mergeable === true ? ["blocked"] : [])].includes(state) : pr.mergeable === true;
|
|
154
|
+
}
|
|
155
|
+
async function setQueueAdmissionStatus(client, repository, sha, context, state) {
|
|
156
|
+
if (!context) return null;
|
|
157
|
+
await client.request("POST", `/repos/${repository.owner}/${repository.repo}/statuses/${sha}`, { body: { state, context, description: state === "success" ? "Buildchain admitted this exact PR head to the merge queue" : "Buildchain rejected merge queue admission for this exact PR head" } });
|
|
158
|
+
return { context, state, sha };
|
|
160
159
|
}
|
|
161
160
|
|
|
162
161
|
function skip(reason, details = {}) {
|
|
@@ -521,9 +520,7 @@ export async function runDevPrAutoMerge(optionsInput = {}, clientInput) {
|
|
|
521
520
|
client.getMergeQueueState(options.targetBranch),
|
|
522
521
|
]);
|
|
523
522
|
const landingMode = initialQueueState.enabled ? "queue" : options.landingMode === "queue" ? "queue" : "direct";
|
|
524
|
-
const orderedPullRequests = landingMode === "queue"
|
|
525
|
-
? [...pullRequests].sort((left, right) => Number(left.number) - Number(right.number))
|
|
526
|
-
: pullRequests;
|
|
523
|
+
const orderedPullRequests = landingMode === "queue" ? [...pullRequests].sort((left, right) => Number(left.number) - Number(right.number)) : pullRequests;
|
|
527
524
|
const result = {
|
|
528
525
|
schemaVersion: 1,
|
|
529
526
|
contract: "kungfu-buildchain-dev-pr-auto-merge",
|
|
@@ -662,6 +659,7 @@ export async function runDevPrAutoMerge(optionsInput = {}, clientInput) {
|
|
|
662
659
|
result.skipped.push(entry);
|
|
663
660
|
} else {
|
|
664
661
|
try {
|
|
662
|
+
entry.queueAdmissionStatus = await setQueueAdmissionStatus(client, options.repository, expectedHeadSha, options.queueAdmissionContext, "success");
|
|
665
663
|
const queueEntry = await client.enqueuePullRequest({
|
|
666
664
|
pullRequestId: decision.pullRequestId,
|
|
667
665
|
expectedHeadOid: expectedHeadSha,
|
|
@@ -673,6 +671,7 @@ export async function runDevPrAutoMerge(optionsInput = {}, clientInput) {
|
|
|
673
671
|
result.actions.push(entry);
|
|
674
672
|
result.enqueued.push(entry);
|
|
675
673
|
} catch (error) {
|
|
674
|
+
entry.queueAdmissionStatus = await setQueueAdmissionStatus(client, options.repository, expectedHeadSha, options.queueAdmissionContext, "failure");
|
|
676
675
|
entry.action = "skip";
|
|
677
676
|
entry.reason = "enqueue-rejected";
|
|
678
677
|
entry.enqueueError = {
|
|
@@ -744,6 +743,7 @@ async function main() {
|
|
|
744
743
|
blockLabels: process.env.BUILDCHAIN_DEV_PR_BLOCK_LABELS,
|
|
745
744
|
allowedHeadPrefixes: process.env.BUILDCHAIN_DEV_PR_ALLOWED_HEAD_PREFIXES,
|
|
746
745
|
requiredChecks: process.env.BUILDCHAIN_DEV_PR_REQUIRED_CHECKS,
|
|
746
|
+
queueAdmissionContext: process.env.BUILDCHAIN_DEV_PR_QUEUE_ADMISSION_CONTEXT,
|
|
747
747
|
requireApproval: process.env.BUILDCHAIN_DEV_PR_REQUIRE_APPROVAL,
|
|
748
748
|
sameRepositoryOnly: process.env.BUILDCHAIN_DEV_PR_SAME_REPOSITORY_ONLY,
|
|
749
749
|
maxMerges: process.env.BUILDCHAIN_DEV_PR_MAX_MERGES,
|
|
@@ -86,10 +86,8 @@ export function validateArtifactSigningAuthorityRun(
|
|
|
86
86
|
) {
|
|
87
87
|
throw new Error("Buildchain signing authority repository mismatch");
|
|
88
88
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
!String(run.path).startsWith(`.github/workflows/${AUTHORITY_WORKFLOW}@`)
|
|
92
|
-
) {
|
|
89
|
+
const runPath = String(run.path || "").split("@", 1)[0];
|
|
90
|
+
if (run.path && runPath !== `.github/workflows/${AUTHORITY_WORKFLOW}`) {
|
|
93
91
|
throw new Error("Buildchain signing authority workflow path mismatch");
|
|
94
92
|
}
|
|
95
93
|
return run;
|