@kungfu-tech/buildchain 3.0.9-alpha.2 → 3.0.9-alpha.3

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 (48) hide show
  1. package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
  2. package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
  3. package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
  4. package/contracts/next-development-request-v1.schema.json +66 -0
  5. package/contracts/next-development-transition-v1.schema.json +292 -0
  6. package/dist/site/buildchain-contract.json +4 -4
  7. package/dist/site/buildchain-site.json +89 -18
  8. package/dist/site/capability-registry.json +4 -3
  9. package/dist/site/kfd-claims.json +65 -5
  10. package/dist/site/kfd-upstream-aggregate.json +1 -1
  11. package/dist/site/manual-registry.json +19 -3
  12. package/dist/site/node-api-registry.json +1016 -36
  13. package/dist/site/page-registry.json +74 -11
  14. package/dist/site/public-surface-audit.json +7 -3
  15. package/dist/site/publication-registry.json +4 -4
  16. package/dist/site/release-provenance.json +3 -0
  17. package/dist/site/site-manifest.json +15 -7
  18. package/dist/site/workflow-registry.json +4 -4
  19. package/docs/MAP.md +2 -1
  20. package/docs/aws-us-elastic-runner-burst-plane.md +11 -3
  21. package/docs/dev-alpha-candidate-patrol.md +8 -0
  22. package/docs/next-development-transition.md +119 -0
  23. package/docs/node-api-reference.md +126 -73
  24. package/docs/versioning.md +1 -1
  25. package/package.json +8 -3
  26. package/packages/core/buildchain-agent-manuals.js +1 -0
  27. package/packages/core/channel-candidate.js +8 -0
  28. package/packages/core/channel-promotion-baseline.js +52 -0
  29. package/packages/core/dev-alpha-candidate-selection.js +10 -2
  30. package/packages/core/next-development-candidate-reservation.js +186 -0
  31. package/packages/core/next-development-controller.js +726 -0
  32. package/packages/core/next-development-projection.js +288 -0
  33. package/packages/core/next-development-transition.js +738 -0
  34. package/packages/core/paper-agent-entry.js +7 -4
  35. package/packages/core/paper.js +14 -7
  36. package/scripts/aws-macos-jit-controller-core.mjs +83 -2
  37. package/scripts/aws-macos-jit-controller.mjs +32 -1
  38. package/scripts/aws-macos-jit-instance-rehydrate.mjs +260 -0
  39. package/scripts/check-inventory.mjs +11 -0
  40. package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
  41. package/scripts/generate-next-development-guidance.mjs +49 -0
  42. package/scripts/generate-site-bundle.mjs +2 -0
  43. package/scripts/init-repo.mjs +119 -62
  44. package/scripts/next-development-self-dogfood-harness.mjs +409 -0
  45. package/scripts/next-development-self-dogfood.mjs +532 -0
  46. package/scripts/next-development-transition.mjs +47 -0
  47. package/scripts/site-capability-metadata.mjs +3 -0
  48. package/scripts/stable-candidate-qualification.mjs +7 -7
@@ -13,6 +13,7 @@ import {
13
13
  workCheck,
14
14
  } from "./paper-repository.js";
15
15
  import { mergePublicationRehearsalAgentInstructions } from "./publication-rehearsal-projection.js";
16
+ import { mergeNextDevelopmentAgentInstructions } from "./next-development-projection.js";
16
17
 
17
18
  export const PAPER_AGENT_ENTRY_CONTRACT = "kungfu-buildchain-paper-agent-entry";
18
19
  export const PAPER_AGENT_ENTRY_SCHEMA_VERSION = 1;
@@ -168,10 +169,12 @@ export function paperAgentEntryFiles({
168
169
  [PAPER_PATHS.agentEntry, jsonText(entry)],
169
170
  [
170
171
  PAPER_PATHS.agentInstructions,
171
- mergePublicationRehearsalAgentInstructions(
172
- mergePaperAgentEntryInstructions(currentAgents, {
173
- developmentRef: resolvedDevelopmentRef,
174
- }),
172
+ mergeNextDevelopmentAgentInstructions(
173
+ mergePublicationRehearsalAgentInstructions(
174
+ mergePaperAgentEntryInstructions(currentAgents, {
175
+ developmentRef: resolvedDevelopmentRef,
176
+ }),
177
+ ),
175
178
  ),
176
179
  ],
177
180
  ]);
@@ -51,6 +51,11 @@ import {
51
51
  projectPublicationRehearsalToml,
52
52
  publicationRehearsalWorkflow,
53
53
  } from "./publication-rehearsal-projection.js";
54
+ import {
55
+ appendNextDevelopmentToml,
56
+ nextDevelopmentWorkflowHeader,
57
+ projectNextDevelopmentToml,
58
+ } from "./next-development-projection.js";
54
59
 
55
60
  export { PAPER_PATHS, resolvePaperRepository } from "./paper-repository.js";
56
61
  export {
@@ -356,7 +361,7 @@ immutable_base_url = ${tomlString(joinUrl(siteBaseUrl, "archive").replace(/\/$/,
356
361
  registry_path = ".buildchain/publication/publication-registry.json"
357
362
  `
358
363
  : "";
359
- return appendPublicationRehearsalToml(`schema = 1
364
+ return appendNextDevelopmentToml(appendPublicationRehearsalToml(`schema = 1
360
365
 
361
366
  [project]
362
367
  type = "publication-artifact"
@@ -390,14 +395,14 @@ command = "make pdf"
390
395
 
391
396
  [lifecycle.verify]
392
397
  command = "make check"
393
- `);
398
+ `));
394
399
  }
395
400
 
396
401
  function scaffoldBuildWorkflow(
397
402
  buildchainSha,
398
403
  { artifactName = "paper-publication" } = {},
399
404
  ) {
400
- return `name: Build
405
+ return `${nextDevelopmentWorkflowHeader()}name: Build
401
406
 
402
407
  on:
403
408
  workflow_dispatch:
@@ -425,7 +430,7 @@ jobs:
425
430
  }
426
431
 
427
432
  function scaffoldVerifyWorkflow(buildchainSha) {
428
- return `name: Verify
433
+ return `${nextDevelopmentWorkflowHeader()}name: Verify
429
434
 
430
435
  on:
431
436
  pull_request:
@@ -456,7 +461,7 @@ function scaffoldReleaseWorkflow(
456
461
  const passportInput = releasePassportProductName
457
462
  ? ` release-passport-product-name: ${JSON.stringify(releasePassportProductName)}\n`
458
463
  : "";
459
- return `name: Paper Release
464
+ return `${nextDevelopmentWorkflowHeader()}name: Paper Release
460
465
 
461
466
  on:
462
467
  workflow_dispatch:
@@ -1017,8 +1022,10 @@ function migrationFiles({
1017
1022
  const files = new Map([
1018
1023
  [
1019
1024
  PAPER_PATHS.config,
1020
- projectPublicationRehearsalToml(
1021
- fs.readFileSync(path.resolve(cwd, PAPER_PATHS.config), "utf8"),
1025
+ projectNextDevelopmentToml(
1026
+ projectPublicationRehearsalToml(
1027
+ fs.readFileSync(path.resolve(cwd, PAPER_PATHS.config), "utf8"),
1028
+ ),
1022
1029
  ),
1023
1030
  ],
1024
1031
  [PAPER_PATHS.contractLock, contractLockText],
@@ -203,6 +203,87 @@ export function createMacosJitCampaignPlan(values = {}) {
203
203
  return { ...plan, digest: digest(plan) };
204
204
  }
205
205
 
206
+ export function createMacosJitInstanceRehydratePlan(values = {}) {
207
+ const id = campaignId(values.campaignId);
208
+ const boundSource = source(values);
209
+ const aws = commonAws(values);
210
+ const regionConfig = macosJitRegionConfig(aws.region);
211
+ const tags = ownershipTags({ id, sourceSha: boundSource.sha });
212
+ const rehydrationId = exact(
213
+ values.rehydrationId,
214
+ /^r[1-9]\d{0,2}$/,
215
+ "rehydrationId",
216
+ );
217
+ const instanceClientToken = `kungfu-mac-rehydrate-${id}-${rehydrationId}`;
218
+ if (instanceClientToken.length > 64) {
219
+ throw new Error("rehydration client token exceeds the EC2 limit");
220
+ }
221
+ const plan = {
222
+ schemaVersion: 1,
223
+ contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
224
+ kind: "instance-rehydrate-plan",
225
+ repository: repository(values.repository),
226
+ account: {
227
+ id: exact(values.accountId, /^\d{12}$/, "accountId"),
228
+ },
229
+ github: {
230
+ workflowId: MACOS_EC2_JIT.workflowId,
231
+ requiredState: "disabled_manually",
232
+ },
233
+ campaign: { id },
234
+ source: boundSource,
235
+ aws: {
236
+ ...aws,
237
+ hostId: exact(values.hostId, /^h-[0-9a-f]+$/, "hostId"),
238
+ hostAllocatedAt: iso(values.hostAllocatedAt, "hostAllocatedAt"),
239
+ replacesInstanceId: exact(
240
+ values.replacesInstanceId,
241
+ /^i-[0-9a-f]+$/,
242
+ "replacesInstanceId",
243
+ ),
244
+ rehydrationId,
245
+ instanceTags: tags,
246
+ volumeTags: tags,
247
+ instanceClientToken,
248
+ rootVolume: {
249
+ deviceName: "/dev/sda1",
250
+ deleteOnTermination: true,
251
+ encrypted: true,
252
+ volumeSizeGiB: 200,
253
+ volumeType: "gp3",
254
+ },
255
+ metadata: {
256
+ httpEndpoint: "enabled",
257
+ httpTokens: "required",
258
+ httpPutResponseHopLimit: 1,
259
+ instanceMetadataTags: "disabled",
260
+ },
261
+ },
262
+ safety: {
263
+ applyMode: values.execute === true ? "execute" : "dry-run",
264
+ exactSourceRequired: true,
265
+ existingCampaignHostRequired: true,
266
+ noHostAllocation: true,
267
+ zeroActiveInstanceRequired: true,
268
+ zeroRunnerResidueRequired: true,
269
+ awsDryRunRequiredBeforeLaunch: true,
270
+ cleanupOwner: "scheduled-card-scoped-reaper",
271
+ budget: {
272
+ name: regionConfig.budgetName,
273
+ limitUsd: MACOS_EC2_JIT.budgetLimitUsd,
274
+ metrics: ["UnblendedCost"],
275
+ dimensionFilter: {
276
+ usageTypes: regionConfig.budgetUsageTypes,
277
+ operation: MACOS_EC2_JIT.budgetOperation,
278
+ regions: regionConfig.budgetRegions,
279
+ },
280
+ requiredActualThresholds: [80, 95],
281
+ },
282
+ },
283
+ };
284
+ return { ...plan, digest: digest(plan) };
285
+ }
286
+
206
287
  export function createMacosJitSourceRebindPlan(values = {}) {
207
288
  const id = campaignId(values.campaignId);
208
289
  const boundSource = source(values);
@@ -301,9 +382,9 @@ export function macosAllocateHostsArgs(plan) {
301
382
  export function macosRunInstancesArgs(plan, { hostId, dryRun = false } = {}) {
302
383
  if (
303
384
  plan?.contract !== AWS_MACOS_JIT_CONTROLLER_CONTRACT ||
304
- plan.kind !== "campaign-launch-plan"
385
+ !["campaign-launch-plan", "instance-rehydrate-plan"].includes(plan.kind)
305
386
  ) {
306
- throw new Error("macOS JIT campaign launch plan contract is invalid");
387
+ throw new Error("macOS JIT instance launch plan contract is invalid");
307
388
  }
308
389
  const resolvedHostId = exact(hostId, /^h-[0-9a-f]+$/, "hostId");
309
390
  const args = [
@@ -6,6 +6,7 @@ import {
6
6
  AWS_MACOS_JIT_CONTROLLER_CONTRACT,
7
7
  createMacosJitCampaignPlan,
8
8
  createMacosJitClosePlan,
9
+ createMacosJitInstanceRehydratePlan,
9
10
  createMacosJitJobPlan,
10
11
  createMacosJitSourceRebindPlan,
11
12
  macosAllocateHostsArgs,
@@ -13,6 +14,7 @@ import {
13
14
  macosRunInstancesArgs,
14
15
  } from "./aws-macos-jit-controller-core.mjs";
15
16
  import { MACOS_EC2_JIT_REGIONS } from "./aws-macos-jit-core.mjs";
17
+ import { executeMacosJitInstanceRehydrate } from "./aws-macos-jit-instance-rehydrate.mjs";
16
18
  import { executeMacosJitJob } from "./aws-macos-jit-job-controller.mjs";
17
19
  import { executeMacosJitSourceRebind } from "./aws-macos-jit-source-rebind.mjs";
18
20
  import {
@@ -516,6 +518,19 @@ function confirm(plan) {
516
518
  throw new Error("--confirm-zero-allocation is required");
517
519
  }
518
520
  }
521
+ if (plan.kind === "instance-rehydrate-plan") {
522
+ if (arg("confirm-host-id") !== plan.aws.hostId) {
523
+ throw new Error("--confirm-host-id must equal the exact host id");
524
+ }
525
+ if (arg("confirm-replaces-instance-id") !== plan.aws.replacesInstanceId) {
526
+ throw new Error(
527
+ "--confirm-replaces-instance-id must equal replacesInstanceId",
528
+ );
529
+ }
530
+ if (!flag("confirm-no-host-allocation")) {
531
+ throw new Error("--confirm-no-host-allocation is required");
532
+ }
533
+ }
519
534
  }
520
535
 
521
536
  function emit(plan, mode, execute, operation) {
@@ -541,6 +556,20 @@ export function main() {
541
556
  executeMacosJitCampaignLaunch(plan, { profile: arg("aws-profile") }),
542
557
  );
543
558
  }
559
+ if (["plan-rehydrate", "rehydrate-instance"].includes(mode)) {
560
+ const plan = createMacosJitInstanceRehydratePlan({
561
+ ...commonValues(execute),
562
+ hostId: arg("host-id"),
563
+ hostAllocatedAt: arg("host-allocated-at"),
564
+ replacesInstanceId: arg("replaces-instance-id"),
565
+ rehydrationId: arg("rehydration-id"),
566
+ });
567
+ return emit(plan, mode, execute, () =>
568
+ executeMacosJitInstanceRehydrate(plan, {
569
+ profile: arg("aws-profile"),
570
+ }),
571
+ );
572
+ }
544
573
  if (["plan-rebind", "rebind-campaign"].includes(mode)) {
545
574
  const plan = createMacosJitSourceRebindPlan({
546
575
  ...commonValues(execute),
@@ -550,7 +579,9 @@ export function main() {
550
579
  hostId: arg("host-id"),
551
580
  instanceId: arg("instance-id"),
552
581
  });
553
- return emit(plan, mode, execute, () => executeMacosJitSourceRebind(plan, { profile: arg("aws-profile") }));
582
+ return emit(plan, mode, execute, () =>
583
+ executeMacosJitSourceRebind(plan, { profile: arg("aws-profile") }),
584
+ );
554
585
  }
555
586
  if (["plan-job", "run-job"].includes(mode)) {
556
587
  const plan = createMacosJitJobPlan({
@@ -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
+ }
@@ -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",
@@ -20,6 +20,7 @@ import {
20
20
  candidateFromDecision,
21
21
  selectLatestQualifiedSource,
22
22
  } from "../packages/core/dev-alpha-candidate-selection.js";
23
+ import { readGitHubNextDevelopmentVersionReservation } from "../packages/core/next-development-candidate-reservation.js";
23
24
 
24
25
  export { reconcileActiveReleaseTrain, selectLatestQualifiedSource };
25
26
 
@@ -617,6 +618,7 @@ export async function runDevAlphaCandidatePatrol(
617
618
  comparison,
618
619
  workflowEvidence,
619
620
  skippedNewerCommitCount,
621
+ versionReservation,
620
622
  qualificationError,
621
623
  } = await resolvePatrolSourceInputs({
622
624
  client,
@@ -646,6 +648,7 @@ export async function runDevAlphaCandidatePatrol(
646
648
  observedSourceHeadSha,
647
649
  skippedNewerCommitCount,
648
650
  ...(targetBaseline ? { targetBaseline } : {}),
651
+ ...(versionReservation ? { versionReservation } : {}),
649
652
  },
650
653
  workflowEvidence,
651
654
  requiredWorkflowPaths,
@@ -992,6 +995,20 @@ export function createGitHubChannelCandidateClient({
992
995
  targetBranch,
993
996
  parseCandidate: managedCandidateFromPullRequest,
994
997
  }),
998
+ async readNextDevelopmentVersionReservation({
999
+ reservationSha,
1000
+ candidateSha,
1001
+ targetVersion,
1002
+ }) {
1003
+ return readGitHubNextDevelopmentVersionReservation({
1004
+ api,
1005
+ owner,
1006
+ repo,
1007
+ reservationSha,
1008
+ candidateSha,
1009
+ targetVersion,
1010
+ });
1011
+ },
995
1012
  async listCompletedWorkflowRuns(workflowPathValue, sourceBranch) {
996
1013
  const runs = [];
997
1014
  for (let page = 1; page <= 10; page += 1) {
@@ -1015,7 +1032,11 @@ export function createGitHubChannelCandidateClient({
1015
1032
  for (const commit of rows) {
1016
1033
  const commitSha = text(commit.sha);
1017
1034
  if (commitSha === targetSha) return commits;
1018
- commits.push(commitSha);
1035
+ commits.push({
1036
+ sha: commitSha,
1037
+ message: text(commit.commit?.message),
1038
+ parents: (commit.parents || []).map((parent) => text(parent.sha)),
1039
+ });
1019
1040
  }
1020
1041
  if (rows.length < 100) return commits;
1021
1042
  }
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ import { nextDevelopmentManual } from "../packages/core/next-development-projection.js";
8
+
9
+ export const NEXT_DEVELOPMENT_MANUAL_PATH =
10
+ "docs/next-development-transition.md";
11
+
12
+ export function generateNextDevelopmentGuidance({
13
+ cwd = process.cwd(),
14
+ check = false,
15
+ } = {}) {
16
+ const target = path.resolve(cwd, NEXT_DEVELOPMENT_MANUAL_PATH);
17
+ const expected = nextDevelopmentManual();
18
+ const current = fs.existsSync(target) ? fs.readFileSync(target, "utf8") : "";
19
+ if (check) {
20
+ if (current !== expected) {
21
+ throw new Error(
22
+ `${NEXT_DEVELOPMENT_MANUAL_PATH} drifted from its contract source`,
23
+ );
24
+ }
25
+ return { ok: true, changed: false, path: NEXT_DEVELOPMENT_MANUAL_PATH };
26
+ }
27
+ fs.mkdirSync(path.dirname(target), { recursive: true });
28
+ if (current !== expected) fs.writeFileSync(target, expected);
29
+ return {
30
+ ok: true,
31
+ changed: current !== expected,
32
+ path: NEXT_DEVELOPMENT_MANUAL_PATH,
33
+ };
34
+ }
35
+
36
+ if (
37
+ process.argv[1] &&
38
+ import.meta.url === pathToFileURL(process.argv[1]).href
39
+ ) {
40
+ try {
41
+ const result = generateNextDevelopmentGuidance({
42
+ check: process.argv.includes("--check"),
43
+ });
44
+ process.stdout.write(`${JSON.stringify(result)}\n`);
45
+ } catch (error) {
46
+ process.stderr.write(`next-development guidance: ${error.message}\n`);
47
+ process.exitCode = 1;
48
+ }
49
+ }
@@ -368,6 +368,7 @@ const manualMetaById = new Map(Object.entries({
368
368
  "release-activation-transaction": { capabilityGroup: "release-passport-trust", audience: ["release-operator", "agent"], maturity: "preview", order: 125 },
369
369
  "release-candidate": { capabilityGroup: "reusable-build", audience: ["release-operator", "consumer"], maturity: "stable", order: 130 },
370
370
  "release-train": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 132 },
371
+ "next-development-transition": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 133 },
371
372
  "stable-candidate-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer"], maturity: "preview", order: 135 },
372
373
  "dev-qualification-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 136 },
373
374
  "dev-alpha-candidate-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 137 },
@@ -733,6 +734,7 @@ function buildSiteBundle() {
733
734
  "docs/reusable-build-surface.md",
734
735
  "docs/release-candidate.md",
735
736
  "docs/release-train.md",
737
+ "docs/next-development-transition.md",
736
738
  "docs/stable-candidate-patrol.md",
737
739
  "docs/dev-qualification-patrol.md",
738
740
  "docs/dev-alpha-candidate-patrol.md",