@kungfu-tech/buildchain 3.0.9-alpha.1 → 3.0.9-alpha.11

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.
Files changed (105) hide show
  1. package/actions/promote-buildchain-ref/README.md +4 -0
  2. package/bin/buildchain.mjs +9 -1
  3. package/bin/internal/trust-release-release-handlers.mjs +1 -0
  4. package/contracts/dev-delivery-authority-v2.schema.json +253 -0
  5. package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
  6. package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
  7. package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
  8. package/contracts/next-development-request-v1.schema.json +66 -0
  9. package/contracts/next-development-transition-v1.schema.json +292 -0
  10. package/dist/site/agent-index.json +1 -0
  11. package/dist/site/artifact-schemas.json +2 -0
  12. package/dist/site/buildchain-contract.json +92 -27
  13. package/dist/site/buildchain-site.json +351 -38
  14. package/dist/site/capability-registry.json +7 -6
  15. package/dist/site/cli-registry.json +43 -3
  16. package/dist/site/controller-registry.json +6 -2
  17. package/dist/site/kfd-claims.json +223 -12
  18. package/dist/site/kfd-upstream-aggregate.json +9 -9
  19. package/dist/site/manual-registry.json +23 -7
  20. package/dist/site/node-api-registry.json +7926 -4533
  21. package/dist/site/page-registry.json +332 -27
  22. package/dist/site/public-surface-audit.json +359 -11
  23. package/dist/site/publication-registry.json +4 -4
  24. package/dist/site/release-passport-check-manifest.json +1 -0
  25. package/dist/site/release-provenance.json +14 -0
  26. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +442 -0
  27. package/dist/site/schemas/release-passport-v1.schema.json +3 -0
  28. package/dist/site/site-manifest.json +19 -11
  29. package/dist/site/workflow-registry.json +21 -10
  30. package/docs/MAP.md +5 -2
  31. package/docs/adopter-delivery-gate.md +106 -0
  32. package/docs/aws-us-elastic-runner-burst-plane.md +30 -3
  33. package/docs/cli-reference.md +202 -15
  34. package/docs/dev-alpha-candidate-patrol.md +8 -0
  35. package/docs/dev-delivery-qualification-landing-adr.md +137 -0
  36. package/docs/dev-delivery-warrant.md +149 -22
  37. package/docs/kfd-support.md +7 -2
  38. package/docs/next-development-transition.md +119 -0
  39. package/docs/node-api-reference.md +402 -204
  40. package/docs/release-governance.md +10 -7
  41. package/docs/release-passport.md +18 -2
  42. package/docs/versioning.md +4 -1
  43. package/package.json +23 -6
  44. package/packages/core/README.md +25 -0
  45. package/packages/core/adopter-delivery-gate.js +576 -0
  46. package/packages/core/adopter-delivery-json.js +66 -0
  47. package/packages/core/adopter-delivery-passport.js +259 -0
  48. package/packages/core/adopter-delivery-vectors.js +158 -0
  49. package/packages/core/adopter-delivery-vectors.json +77 -0
  50. package/packages/core/artifact-verification-envelope.js +5 -5
  51. package/packages/core/buildchain-agent-manuals.js +1 -0
  52. package/packages/core/buildchain-channel-identity.js +2 -3
  53. package/packages/core/buildchain-compatibility-proof.js +22 -0
  54. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  55. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  56. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  57. package/packages/core/channel-candidate.js +8 -0
  58. package/packages/core/channel-promotion-baseline.js +52 -0
  59. package/packages/core/dev-alpha-candidate-selection.js +10 -2
  60. package/packages/core/dev-delivery-authority.js +996 -0
  61. package/packages/core/dev-delivery-native-proof.js +418 -0
  62. package/packages/core/dev-delivery-warrant-settlement.js +67 -20
  63. package/packages/core/dev-delivery-warrant.js +129 -46
  64. package/packages/core/kfd-adopter-category-driver.js +167 -0
  65. package/packages/core/kfd-adopter-manifest.js +49 -50
  66. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  67. package/packages/core/next-development-candidate-reservation.js +186 -0
  68. package/packages/core/next-development-controller.js +726 -0
  69. package/packages/core/next-development-projection.js +288 -0
  70. package/packages/core/next-development-transition.js +738 -0
  71. package/packages/core/paper-agent-entry.js +7 -4
  72. package/packages/core/paper.js +14 -7
  73. package/packages/core/published-delivery-authority.js +247 -0
  74. package/packages/core/release-passport-contract.js +10 -6
  75. package/packages/core/release-passport.js +37 -13
  76. package/scripts/aws-macos-jit-controller-core.mjs +239 -4
  77. package/scripts/aws-macos-jit-controller.mjs +198 -2
  78. package/scripts/aws-macos-jit-instance-rehydrate.mjs +260 -0
  79. package/scripts/aws-macos-jit-source-rebind.mjs +235 -14
  80. package/scripts/aws-macos-jit.mjs +12 -0
  81. package/scripts/buildchain-cli-help.mjs +10 -2
  82. package/scripts/check-internal-architecture.mjs +1 -0
  83. package/scripts/check-inventory.mjs +12 -1
  84. package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
  85. package/scripts/dev-delivery-authority.mjs +368 -0
  86. package/scripts/dev-delivery-native-run.mjs +235 -0
  87. package/scripts/dev-delivery-proof.mjs +64 -2
  88. package/scripts/dev-delivery-two-phase-resume.mjs +90 -0
  89. package/scripts/dev-delivery-two-phase.mjs +577 -0
  90. package/scripts/dev-delivery-warrant.mjs +48 -6
  91. package/scripts/dev-pr-delivery-warrant.mjs +19 -14
  92. package/scripts/generate-buildchain-kfd-witnesses.mjs +2 -2
  93. package/scripts/generate-channel-build-workflow.mjs +4 -0
  94. package/scripts/generate-next-development-guidance.mjs +49 -0
  95. package/scripts/generate-site-bundle.mjs +8 -3
  96. package/scripts/init-repo.mjs +135 -62
  97. package/scripts/next-development-self-dogfood-harness.mjs +409 -0
  98. package/scripts/next-development-self-dogfood.mjs +532 -0
  99. package/scripts/next-development-transition.mjs +47 -0
  100. package/scripts/release-candidate-tail-reseal.mjs +426 -0
  101. package/scripts/seal-artifact-signing-requests.mjs +7 -4
  102. package/scripts/site-capability-metadata.mjs +25 -1
  103. package/scripts/stable-candidate-qualification.mjs +7 -7
  104. package/scripts/workflow-call-contract.mjs +1 -1
  105. package/templates/native-dev-delivery.yml +86 -0
@@ -0,0 +1,260 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+
3
+ import {
4
+ AWS_MACOS_JIT_CONTROLLER_CONTRACT,
5
+ macosRunInstancesArgs,
6
+ } from "./aws-macos-jit-controller-core.mjs";
7
+ import { MACOS_EC2_JIT_REGIONS } from "./aws-macos-jit-core.mjs";
8
+ import {
9
+ assertDryRun,
10
+ assertMacosBudgetLaunchGate,
11
+ assertOwnership,
12
+ awsArgs,
13
+ awsJson,
14
+ commandResult,
15
+ ghJson,
16
+ requireSuccess,
17
+ } from "./aws-macos-jit-controller-runtime.mjs";
18
+
19
+ function assertInstanceRehydratePreflight(plan, profile) {
20
+ const launchGate = assertMacosBudgetLaunchGate(plan, profile);
21
+ const commit = ghJson(
22
+ ["api", `repos/${plan.repository}/commits/${plan.source.sha}`],
23
+ undefined,
24
+ "GitHub exact-source preflight",
25
+ );
26
+ if (String(commit.sha || "").toLowerCase() !== plan.source.sha) {
27
+ throw new Error("GitHub did not resolve the exact campaign source SHA");
28
+ }
29
+ const host = awsJson(
30
+ plan,
31
+ profile,
32
+ [
33
+ "ec2",
34
+ "describe-hosts",
35
+ "--host-ids",
36
+ plan.aws.hostId,
37
+ "--output",
38
+ "json",
39
+ ],
40
+ "macOS rehydration host preflight",
41
+ ).Hosts?.[0];
42
+ if (
43
+ !host ||
44
+ host.State !== "available" ||
45
+ host.AvailabilityZone !== plan.aws.availabilityZone ||
46
+ host.HostProperties?.InstanceType !== plan.aws.instanceType ||
47
+ (host.Instances || []).length !== 0 ||
48
+ new Date(host.AllocationTime).toISOString() !== plan.aws.hostAllocatedAt
49
+ ) {
50
+ throw new Error("macOS rehydration host identity or capacity mismatch");
51
+ }
52
+ assertOwnership(host.Tags, plan);
53
+
54
+ const regionalPlans = Object.keys(MACOS_EC2_JIT_REGIONS).map((region) => ({
55
+ ...plan,
56
+ aws: { ...plan.aws, region },
57
+ }));
58
+ const otherHosts = regionalPlans.flatMap((regionalPlan) =>
59
+ (
60
+ awsJson(
61
+ regionalPlan,
62
+ profile,
63
+ [
64
+ "ec2",
65
+ "describe-hosts",
66
+ "--filter",
67
+ "Name=tag:kungfu:plane,Values=aws-us-elastic-runner-burst",
68
+ "Name=tag:kungfu:provider,Values=macos-ec2-jit",
69
+ "Name=state,Values=available,pending,under-assessment",
70
+ "--output",
71
+ "json",
72
+ ],
73
+ `macOS rehydration host ceiling in ${regionalPlan.aws.region}`,
74
+ ).Hosts || []
75
+ ).filter((candidate) => candidate.HostId !== plan.aws.hostId),
76
+ );
77
+ if (otherHosts.length !== 0) {
78
+ throw new Error("macOS rehydration found another active Dedicated Host");
79
+ }
80
+ const activeInstances = regionalPlans.flatMap((regionalPlan) => {
81
+ const active = awsJson(
82
+ regionalPlan,
83
+ profile,
84
+ [
85
+ "ec2",
86
+ "describe-instances",
87
+ "--filters",
88
+ "Name=tag:kungfu:plane,Values=aws-us-elastic-runner-burst",
89
+ "Name=tag:kungfu:provider,Values=macos-ec2-jit",
90
+ "Name=instance-state-name,Values=pending,running,stopping,stopped,shutting-down",
91
+ "--output",
92
+ "json",
93
+ ],
94
+ `active macOS JIT instance preflight in ${regionalPlan.aws.region}`,
95
+ );
96
+ return (active.Reservations || []).flatMap(
97
+ (reservation) => reservation.Instances || [],
98
+ );
99
+ });
100
+ if (activeInstances.length !== 0) {
101
+ throw new Error("macOS rehydration requires zero active JIT instances");
102
+ }
103
+ const runners = ghJson(
104
+ ["api", `repos/${plan.repository}/actions/runners?per_page=100`],
105
+ undefined,
106
+ "macOS rehydration runner preflight",
107
+ ).runners;
108
+ if (
109
+ (runners || []).some((runner) =>
110
+ (runner.labels || []).some((label) =>
111
+ String(label.name || label).startsWith("aws-us-ec2-macos-jit-"),
112
+ ),
113
+ )
114
+ ) {
115
+ throw new Error("macOS rehydration requires zero JIT runner residue");
116
+ }
117
+ const image = awsJson(
118
+ plan,
119
+ profile,
120
+ [
121
+ "ec2",
122
+ "describe-images",
123
+ "--image-ids",
124
+ plan.aws.amiId,
125
+ "--output",
126
+ "json",
127
+ ],
128
+ "macOS rehydration AMI preflight",
129
+ ).Images?.[0];
130
+ if (
131
+ !image ||
132
+ image.Name !== plan.aws.amiName ||
133
+ image.Architecture !== "arm64_mac" ||
134
+ image.State !== "available"
135
+ ) {
136
+ throw new Error("macOS AMI identity or availability mismatch");
137
+ }
138
+ const subnet = awsJson(
139
+ plan,
140
+ profile,
141
+ [
142
+ "ec2",
143
+ "describe-subnets",
144
+ "--subnet-ids",
145
+ plan.aws.subnetId,
146
+ "--output",
147
+ "json",
148
+ ],
149
+ "macOS rehydration subnet preflight",
150
+ ).Subnets?.[0];
151
+ if (!subnet || subnet.AvailabilityZone !== plan.aws.availabilityZone) {
152
+ throw new Error(
153
+ "macOS subnet and Dedicated Host availability zone mismatch",
154
+ );
155
+ }
156
+ return {
157
+ ...launchGate,
158
+ exactCommit: plan.source.sha,
159
+ hostId: host.HostId,
160
+ hostState: host.State,
161
+ activeInstances: 0,
162
+ otherActiveHosts: 0,
163
+ amiOwnerId: image.OwnerId,
164
+ subnetAvailabilityZone: subnet.AvailabilityZone,
165
+ };
166
+ }
167
+
168
+ export function executeMacosJitInstanceRehydrate(plan, { profile = "" } = {}) {
169
+ if (
170
+ plan?.contract !== AWS_MACOS_JIT_CONTROLLER_CONTRACT ||
171
+ plan.kind !== "instance-rehydrate-plan"
172
+ ) {
173
+ throw new Error("macOS JIT instance rehydrate plan contract is invalid");
174
+ }
175
+ const preflight = assertInstanceRehydratePreflight(plan, profile);
176
+ assertDryRun(
177
+ commandResult(
178
+ "aws",
179
+ awsArgs(
180
+ plan,
181
+ profile,
182
+ macosRunInstancesArgs(plan, { hostId: plan.aws.hostId, dryRun: true }),
183
+ ),
184
+ ),
185
+ "EC2 same-host RunInstances DryRun",
186
+ );
187
+ const launched = awsJson(
188
+ plan,
189
+ profile,
190
+ macosRunInstancesArgs(plan, { hostId: plan.aws.hostId }),
191
+ "EC2 same-host RunInstances",
192
+ );
193
+ const instanceId = launched.Instances?.[0]?.InstanceId;
194
+ if (!/^i-[0-9a-f]+$/.test(String(instanceId || ""))) {
195
+ throw new Error("EC2 same-host RunInstances returned no instance identity");
196
+ }
197
+ requireSuccess(
198
+ commandResult(
199
+ "aws",
200
+ awsArgs(plan, profile, [
201
+ "ec2",
202
+ "wait",
203
+ "instance-running",
204
+ "--instance-ids",
205
+ instanceId,
206
+ ]),
207
+ ),
208
+ "macOS rehydrated instance-running waiter",
209
+ );
210
+ const instance = awsJson(
211
+ plan,
212
+ profile,
213
+ [
214
+ "ec2",
215
+ "describe-instances",
216
+ "--instance-ids",
217
+ instanceId,
218
+ "--output",
219
+ "json",
220
+ ],
221
+ "macOS rehydrated instance readback",
222
+ ).Reservations?.[0]?.Instances?.[0];
223
+ if (
224
+ !instance ||
225
+ instance.State?.Name !== "running" ||
226
+ instance.Placement?.HostId !== plan.aws.hostId ||
227
+ instance.ImageId !== plan.aws.amiId ||
228
+ instance.InstanceType !== plan.aws.instanceType
229
+ ) {
230
+ throw new Error("macOS rehydrated instance identity or placement mismatch");
231
+ }
232
+ assertOwnership(instance.Tags, plan);
233
+ return {
234
+ schemaVersion: 1,
235
+ contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
236
+ kind: "instance-rehydrate-result",
237
+ status: "rehydrated",
238
+ repository: plan.repository,
239
+ campaign: plan.campaign,
240
+ source: plan.source,
241
+ replacement: {
242
+ rehydrationId: plan.aws.rehydrationId,
243
+ replacesInstanceId: plan.aws.replacesInstanceId,
244
+ },
245
+ aws: {
246
+ region: plan.aws.region,
247
+ availabilityZone: plan.aws.availabilityZone,
248
+ hostId: plan.aws.hostId,
249
+ hostAllocatedAt: plan.aws.hostAllocatedAt,
250
+ instanceId,
251
+ instanceType: instance.InstanceType,
252
+ imageId: instance.ImageId,
253
+ launchTime: new Date(instance.LaunchTime).toISOString(),
254
+ },
255
+ preflight,
256
+ dryRun: "RunInstances:DryRunOperation",
257
+ hostAllocated: false,
258
+ planDigest: plan.digest,
259
+ };
260
+ }
@@ -28,7 +28,169 @@ function priorOwnershipPlan(plan) {
28
28
  };
29
29
  }
30
30
 
31
- function campaignRunsWithNoWork(plan) {
31
+ function assertRunMatchesPolicy(policy, run, jobs, jobCount, artifactCount) {
32
+ if (policy === "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 (policy === "preflight-failure") {
41
+ const jobList = jobs.jobs || [];
42
+ const failedJobs = jobList.filter((job) => job.conclusion === "failure");
43
+ const nonFailedJobs = jobList.filter((job) => job.conclusion !== "failure");
44
+ const hasJitLabel = jobList.some((job) =>
45
+ (job.labels || []).some((label) =>
46
+ String(label.name || label).startsWith(MACOS_JIT_RUNNER_LABEL_PREFIX),
47
+ ),
48
+ );
49
+ if (
50
+ run.status !== "completed" ||
51
+ run.conclusion !== "failure" ||
52
+ jobCount === 0 ||
53
+ artifactCount === 0 ||
54
+ jobList.some((job) => job.status !== "completed") ||
55
+ failedJobs.length !== 1 ||
56
+ !String(failedJobs[0].name || "").endsWith(" / Trust gate") ||
57
+ nonFailedJobs.some((job) => job.conclusion !== "skipped") ||
58
+ hasJitLabel
59
+ ) {
60
+ throw new Error(
61
+ `macOS campaign run ${run.id} is not an exact preflight Trust gate failure`,
62
+ );
63
+ }
64
+ return;
65
+ }
66
+ if (
67
+ run.status !== "completed" ||
68
+ run.conclusion !== "failure" ||
69
+ jobCount === 0 ||
70
+ artifactCount === 0 ||
71
+ (jobs.jobs || []).some((job) => job.status !== "completed")
72
+ ) {
73
+ throw new Error(
74
+ `macOS campaign run ${run.id} is not a terminal failed run with retained evidence`,
75
+ );
76
+ }
77
+ }
78
+
79
+ function assertExactStartupFailure(run, jobCount, artifactCount) {
80
+ if (
81
+ run.status !== "completed" ||
82
+ run.conclusion !== "startup_failure" ||
83
+ jobCount !== 0 ||
84
+ artifactCount !== 0
85
+ ) {
86
+ throw new Error(
87
+ `macOS campaign run ${run.id} is not an exact startup failure`,
88
+ );
89
+ }
90
+ }
91
+
92
+ function assertExactSuccessfulRun(run, jobs, jobCount, artifactCount) {
93
+ if (
94
+ run.status !== "completed" ||
95
+ run.conclusion !== "success" ||
96
+ jobCount === 0 ||
97
+ artifactCount === 0 ||
98
+ (jobs.jobs || []).some((job) => job.status !== "completed")
99
+ ) {
100
+ throw new Error(
101
+ `macOS campaign run ${run.id} is not a terminal successful run with retained evidence`,
102
+ );
103
+ }
104
+ }
105
+
106
+ function classifyCampaignRun(plan, run) {
107
+ const startupFailure = plan.github.startupFailureRuns.find(
108
+ ({ runId }) => runId === String(run.id),
109
+ );
110
+ const historicalTerminalFailure =
111
+ plan.github.historicalTerminalFailureRuns.find(
112
+ ({ runId }) => runId === String(run.id),
113
+ );
114
+ const historicalPreflightFailure =
115
+ plan.github.historicalPreflightFailureRuns.find(
116
+ ({ runId }) => runId === String(run.id),
117
+ );
118
+ const preflightFailure = plan.github.preflightFailureRunIds.includes(
119
+ String(run.id),
120
+ );
121
+ const successfulRun = plan.github.successfulRunIds.includes(String(run.id));
122
+ return {
123
+ startupFailure,
124
+ historicalTerminalFailure,
125
+ historicalPreflightFailure,
126
+ successfulRun,
127
+ expectedSourceSha:
128
+ startupFailure?.sourceSha ||
129
+ historicalTerminalFailure?.sourceSha ||
130
+ historicalPreflightFailure?.sourceSha ||
131
+ plan.previousSource.sha,
132
+ classification: startupFailure
133
+ ? "startup-failure"
134
+ : historicalTerminalFailure
135
+ ? "historical-terminal-failure"
136
+ : historicalPreflightFailure
137
+ ? "historical-preflight-failure"
138
+ : successfulRun
139
+ ? "successful-run"
140
+ : preflightFailure
141
+ ? "preflight-failure"
142
+ : plan.github.priorRunPolicy,
143
+ };
144
+ }
145
+
146
+ function exactReceiptInventory(receipts, classification, withSource = false) {
147
+ return receipts
148
+ .filter((receipt) => receipt.classification === classification)
149
+ .map((receipt) =>
150
+ withSource
151
+ ? { runId: receipt.runId, sourceSha: receipt.sourceSha }
152
+ : receipt.runId,
153
+ )
154
+ .sort(
155
+ (left, right) =>
156
+ Number(withSource ? left.runId : left) -
157
+ Number(withSource ? right.runId : right),
158
+ );
159
+ }
160
+
161
+ function assertExactReceiptInventories(plan, receipts) {
162
+ if (plan.github.priorRunPolicy === "unused") return;
163
+ const inventories = [
164
+ ["terminal-failure", plan.github.terminalFailureRunIds, false],
165
+ ["preflight-failure", plan.github.preflightFailureRunIds, false],
166
+ ["successful-run", plan.github.successfulRunIds, false],
167
+ ["startup-failure", plan.github.startupFailureRuns, true],
168
+ [
169
+ "historical-terminal-failure",
170
+ plan.github.historicalTerminalFailureRuns,
171
+ true,
172
+ ],
173
+ [
174
+ "historical-preflight-failure",
175
+ plan.github.historicalPreflightFailureRuns,
176
+ true,
177
+ ],
178
+ ];
179
+ for (const [classification, expected, withSource] of inventories) {
180
+ const observed = exactReceiptInventory(
181
+ receipts,
182
+ classification,
183
+ withSource,
184
+ );
185
+ if (JSON.stringify(observed) !== JSON.stringify(expected)) {
186
+ throw new Error(
187
+ `macOS campaign ${classification} inventory does not match the confirmed runs`,
188
+ );
189
+ }
190
+ }
191
+ }
192
+
193
+ function campaignRunReceipts(plan) {
32
194
  const branch = sourceRefName(plan.source.ref);
33
195
  const response = ghJson(
34
196
  [
@@ -46,7 +208,8 @@ function campaignRunsWithNoWork(plan) {
46
208
  }
47
209
  const receipts = [];
48
210
  for (const run of matching) {
49
- if (run.head_sha !== plan.previousSource.sha) {
211
+ const classification = classifyCampaignRun(plan, run);
212
+ if (run.head_sha !== classification.expectedSourceSha) {
50
213
  throw new Error(
51
214
  `macOS campaign run ${run.id} is bound to an unexpected source`,
52
215
  );
@@ -68,19 +231,41 @@ function campaignRunsWithNoWork(plan) {
68
231
  const artifactCount = Number(
69
232
  artifacts.total_count || (artifacts.artifacts || []).length || 0,
70
233
  );
71
- if (jobCount !== 0 || artifactCount !== 0) {
72
- throw new Error(
73
- `macOS campaign run ${run.id} already has jobs or artifacts`,
234
+ if (classification.startupFailure) {
235
+ assertExactStartupFailure(run, jobCount, artifactCount);
236
+ } else if (classification.successfulRun) {
237
+ assertExactSuccessfulRun(run, jobs, jobCount, artifactCount);
238
+ } else {
239
+ assertRunMatchesPolicy(
240
+ classification.historicalTerminalFailure
241
+ ? "terminal-failure"
242
+ : classification.historicalPreflightFailure
243
+ ? "preflight-failure"
244
+ : plan.github.priorRunPolicy,
245
+ run,
246
+ jobs,
247
+ jobCount,
248
+ artifactCount,
74
249
  );
75
250
  }
76
251
  receipts.push({
77
252
  runId: String(run.id),
253
+ sourceSha: String(run.head_sha || ""),
254
+ classification: classification.classification,
78
255
  status: String(run.status || "unknown"),
79
256
  conclusion: String(run.conclusion || ""),
80
257
  jobCount,
81
258
  artifactCount,
259
+ artifacts: (artifacts.artifacts || []).map((artifact) => ({
260
+ id: String(artifact.id),
261
+ name: String(artifact.name || ""),
262
+ sizeInBytes: Number(artifact.size_in_bytes || 0),
263
+ digest: String(artifact.digest || ""),
264
+ expired: artifact.expired === true,
265
+ })),
82
266
  });
83
267
  }
268
+ assertExactReceiptInventories(plan, receipts);
84
269
  return receipts;
85
270
  }
86
271
 
@@ -286,8 +471,22 @@ function assertZeroCampaignResidue(plan, profile) {
286
471
  if (matchingRunners.length !== 0) {
287
472
  throw new Error("macOS campaign already has a registered JIT runner");
288
473
  }
289
- const evidenceObjectCounts = {};
290
- for (const sourceSha of [plan.previousSource.sha, plan.source.sha]) {
474
+ const evidenceObjects = {};
475
+ const historicalTerminalSources =
476
+ plan.github.historicalTerminalFailureRuns.map(({ sourceSha }) => sourceSha);
477
+ const historicalPreflightSources =
478
+ plan.github.historicalPreflightFailureRuns.map(
479
+ ({ sourceSha }) => sourceSha,
480
+ );
481
+ const evidenceSources = [
482
+ ...new Set([
483
+ plan.previousSource.sha,
484
+ ...historicalTerminalSources,
485
+ ...historicalPreflightSources,
486
+ plan.source.sha,
487
+ ]),
488
+ ];
489
+ for (const sourceSha of evidenceSources) {
291
490
  const evidence = awsJson(
292
491
  plan,
293
492
  profile,
@@ -299,27 +498,44 @@ function assertZeroCampaignResidue(plan, profile) {
299
498
  "--prefix",
300
499
  `macos/${sourceSha}/`,
301
500
  "--max-keys",
302
- "1",
501
+ "1000",
303
502
  "--output",
304
503
  "json",
305
504
  ],
306
505
  `macOS campaign evidence inventory for ${sourceSha}`,
307
506
  );
507
+ if (evidence.IsTruncated === true) {
508
+ throw new Error(
509
+ `macOS campaign source ${sourceSha} evidence inventory is incomplete`,
510
+ );
511
+ }
512
+ const objects = (evidence.Contents || []).map((object) => ({
513
+ key: String(object.Key || ""),
514
+ size: Number(object.Size || 0),
515
+ etag: String(object.ETag || ""),
516
+ }));
308
517
  const count = Number(
309
518
  evidence.KeyCount || (evidence.Contents || []).length || 0,
310
519
  );
311
- if (count !== 0) {
520
+ const terminalEvidenceSource =
521
+ (sourceSha === plan.previousSource.sha &&
522
+ plan.github.priorRunPolicy === "terminal-failure") ||
523
+ historicalTerminalSources.includes(sourceSha);
524
+ if (
525
+ (!terminalEvidenceSource && count !== 0) ||
526
+ (terminalEvidenceSource && count === 0)
527
+ ) {
312
528
  throw new Error(
313
- `macOS campaign source ${sourceSha} already has bootstrap evidence`,
529
+ `macOS campaign source ${sourceSha} bootstrap evidence does not match the rebind policy`,
314
530
  );
315
531
  }
316
- evidenceObjectCounts[sourceSha] = count;
532
+ evidenceObjects[sourceSha] = objects;
317
533
  }
318
534
  return {
319
- priorRuns: campaignRunsWithNoWork(plan),
535
+ priorRuns: campaignRunReceipts(plan),
320
536
  jitParameterCount: parameters.length,
321
537
  runnerCount: matchingRunners.length,
322
- evidenceObjectCounts,
538
+ evidenceObjects,
323
539
  };
324
540
  }
325
541
 
@@ -480,7 +696,12 @@ export function executeMacosJitSourceRebind(plan, { profile = "" } = {}) {
480
696
  schemaVersion: 1,
481
697
  contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
482
698
  kind: "campaign-source-rebind-result",
483
- status: "rebound-zero-allocation",
699
+ status:
700
+ plan.github.priorRunPolicy === "terminal-failure"
701
+ ? "rebound-after-terminal-failure-zero-allocation"
702
+ : plan.github.priorRunPolicy === "preflight-failure"
703
+ ? "rebound-after-preflight-failure-zero-allocation"
704
+ : "rebound-zero-allocation",
484
705
  repository: plan.repository,
485
706
  campaign: plan.campaign,
486
707
  previousSource: plan.previousSource,
@@ -25,6 +25,17 @@ function writeJson(file, value) {
25
25
  return resolved;
26
26
  }
27
27
 
28
+ export function writeMacosJitQualificationEnvironment(
29
+ githubEnv = process.env.GITHUB_ENV,
30
+ ) {
31
+ if (!githubEnv) return false;
32
+ fs.appendFileSync(
33
+ path.resolve(githubEnv),
34
+ "BUILDCHAIN_SIGNING_REQUESTS_ENABLED=false\n",
35
+ );
36
+ return true;
37
+ }
38
+
28
39
  export function main() {
29
40
  const mode = process.argv[2] || "plan";
30
41
  if (mode === "plan") {
@@ -64,6 +75,7 @@ export function main() {
64
75
  ".buildchain/aws-macos-jit.json",
65
76
  result,
66
77
  );
78
+ writeMacosJitQualificationEnvironment();
67
79
  process.stdout.write(`${output}\n`);
68
80
  return result;
69
81
  }
@@ -67,6 +67,7 @@ export const BUILDCHAIN_USAGE = `Usage:
67
67
  [--kfd-3-prebuild-witness-json <json-or-path>]...
68
68
  [--kfd-3-artifact-witness-json <json-or-path>]...
69
69
  [--kfd-3-artifact-verify-cmd <command>]
70
+ [--adopter-delivery-json <json-or-path>]
70
71
  [--kfd-adopter-manifest-json <json-or-path>]
71
72
  [--kfd-support-matrix-json <json-or-path>]
72
73
  [--kfd-product-gate-json <json-or-path>]...
@@ -129,11 +130,18 @@ export const BUILDCHAIN_USAGE = `Usage:
129
130
  [--from-config | --workflow <required-workflow.yml>...] [--cwd <dir>]
130
131
  [--check-response-timeout-minutes <n>]
131
132
  [--max-entries-to-build <n>] [--apply]
132
- buildchain dev warrant <submit|select|heartbeat|recover|close|cancel-queued|observe>
133
+ buildchain dev warrant <submit|select|heartbeat|qualify|recover|close|cancel-queued|observe>
133
134
  --repository <owner/repo> --branch <dev/vN/vN.M>
134
135
  [--execute] [--output <file>] [--json]
135
- buildchain dev proof <source|verify-source|classify|replay|integration|verify-integration>
136
+ buildchain dev authority <migrate|submit|lease-qualification|heartbeat-qualification|complete-qualification|lease-landing|heartbeat-landing|recover|admit-merge-group|settle|observe>
137
+ --repository <owner/repo> --branch <dev/vN/vN.M>
138
+ [--execute] [--output <file>] [--json]
139
+ buildchain dev proof <source|verify-source|classify|native|verify-native|classify-native|verify-native-reuse|replay|integration|verify-integration>
136
140
  [--output <file>] [--json]
141
+ buildchain dev two-phase --repository <owner/repo> --branch <dev/vN/vN.M>
142
+ --pull-request <n> --expected-head <sha>
143
+ --warrant-result <file> [--native-proof <file>]
144
+ [--native-command <command>]
137
145
  buildchain log <info|warn|error> --event <name> [--phase <phase>]
138
146
  [--component <name>] [--source <name>] [--attribute key=value]...
139
147
  [--path <jsonl>] [--json]
@@ -131,6 +131,7 @@ function repositoryJavaScriptFiles(root) {
131
131
  .filter((file) => [".js", ".mjs", ".cjs"].includes(path.extname(file)))
132
132
  .filter((file) => !file.startsWith("tests/"))
133
133
  .filter((file) => !/(?:^|\/)tests?\//u.test(file))
134
+ .filter((file) => !file.startsWith(".buildchain/"))
134
135
  .filter((file) => !/^actions\/[^/]+\/dist\//u.test(file))
135
136
  .sort();
136
137
  }
@@ -53,6 +53,10 @@ const requiredPaths = [
53
53
  "packages/core/homebrew.js",
54
54
  "packages/core/artifact-verification-envelope.js",
55
55
  "packages/core/anchored-version-material.js",
56
+ "packages/core/next-development-transition.js",
57
+ "packages/core/next-development-controller.js",
58
+ "packages/core/next-development-candidate-reservation.js",
59
+ "packages/core/next-development-projection.js",
56
60
  "packages/core/build-facts.js",
57
61
  "packages/core/publication-package.js",
58
62
  "packages/core/publication-reproducibility.js",
@@ -75,6 +79,8 @@ const requiredPaths = [
75
79
  "docs/auditable-demo.md",
76
80
  "contracts/auditable-demo-scenario-v1.schema.json",
77
81
  "contracts/release-candidate-recovery-v1.schema.json",
82
+ "contracts/next-development-request-v1.schema.json",
83
+ "contracts/next-development-transition-v1.schema.json",
78
84
  "contracts/auditable-demo-media-profiles-v1.json",
79
85
  "contracts/evidence/auditable-demo-web-delivery-v1.json",
80
86
  "contracts/evidence/auditable-demo-responsive-web-delivery-v1.json",
@@ -86,10 +92,15 @@ const requiredPaths = [
86
92
  "contracts/fixtures/auditable-demo-responsive-web-delivery-v1/public-projection.json",
87
93
  "contracts/fixtures/auditable-demo-responsive-web-delivery-v1/scene.json",
88
94
  "docs/release-propagation.md",
95
+ "docs/next-development-transition.md",
89
96
  "docs/site-bundle-contract.md",
90
97
  "docs/toolkit-observability.md",
91
98
  "docs/versioning.md",
92
99
  "scripts/release-line-dry-run.mjs",
100
+ "scripts/next-development-transition.mjs",
101
+ "scripts/next-development-self-dogfood.mjs",
102
+ "scripts/next-development-self-dogfood-harness.mjs",
103
+ "scripts/generate-next-development-guidance.mjs",
93
104
  "scripts/reconcile-release-governance.mjs",
94
105
  "scripts/buildchain-channel-router.mjs",
95
106
  "scripts/generate-channel-build-workflow.mjs",
@@ -992,7 +1003,7 @@ for (const forbiddenSnippet of [
992
1003
  for (const requiredSnippet of [
993
1004
  "id-token: write",
994
1005
  "actions: write",
995
- "uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@769b221bad7a6b9104afad4c2628d9dca396ab0f\n",
1006
+ "uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@15db8a9d7c39f7a649a0e3971af68490356dd9ac\n",
996
1007
  "github.event.workflow_run.event == 'push'",
997
1008
  "!startsWith(github.event.workflow_run.display_title, 'chore(release): prepare v')",
998
1009
  "!startsWith(github.event.workflow_run.display_title, 'chore(release): release v')",