@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
@@ -3,21 +3,35 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { BUILDCHAIN_CONFIG_PATH } from "../packages/core/buildchain-layout.js";
6
- import { detectPackageManager, assertPackageManager } from "../packages/core/package-manager.js";
6
+ import {
7
+ detectPackageManager,
8
+ assertPackageManager,
9
+ } from "../packages/core/package-manager.js";
7
10
  import {
8
11
  PUBLICATION_REHEARSAL_WORKFLOW_PATH,
9
12
  appendPublicationRehearsalToml,
10
13
  mergePublicationRehearsalAgentInstructions,
11
14
  publicationRehearsalWorkflow,
12
15
  } from "../packages/core/publication-rehearsal-projection.js";
13
-
14
- const BUILDCHAIN_WORKFLOW_REF = "kungfu-systems/buildchain/.github/workflows/.build.yml@v3";
15
- const DEFAULT_PUBLICATION_LATEX_IMAGE = "ghcr.io/kungfu-systems/build-images/latex-pdf-builder";
16
- const DEFAULT_PUBLICATION_LATEX_DIGEST = "sha256:c20f3809e96836c1c78e97c76939d12f1de3fed0ea9b7c40c43332ec2ea480f8";
17
- const DEFAULT_PUBLICATION_LATEX_COMMAND = "latexmk -pdf -outdir=_build paper/main.tex";
16
+ import {
17
+ appendNextDevelopmentToml,
18
+ mergeNextDevelopmentAgentInstructions,
19
+ nextDevelopmentWorkflowHeader,
20
+ } from "../packages/core/next-development-projection.js";
21
+
22
+ const BUILDCHAIN_WORKFLOW_REF =
23
+ "kungfu-systems/buildchain/.github/workflows/.build.yml@v3";
24
+ const DEFAULT_PUBLICATION_LATEX_IMAGE =
25
+ "ghcr.io/kungfu-systems/build-images/latex-pdf-builder";
26
+ const DEFAULT_PUBLICATION_LATEX_DIGEST =
27
+ "sha256:c20f3809e96836c1c78e97c76939d12f1de3fed0ea9b7c40c43332ec2ea480f8";
28
+ const DEFAULT_PUBLICATION_LATEX_COMMAND =
29
+ "latexmk -pdf -outdir=_build paper/main.tex";
18
30
 
19
31
  function posixPath(value) {
20
- return String(value || "").split(path.sep).join("/");
32
+ return String(value || "")
33
+ .split(path.sep)
34
+ .join("/");
21
35
  }
22
36
 
23
37
  function repoName(cwd) {
@@ -282,34 +296,42 @@ command = "make check"
282
296
  }
283
297
 
284
298
  function infraContractDesiredJson(cwd) {
285
- return `${JSON.stringify({
286
- service: repoName(cwd),
287
- environment: "staging",
288
- resources: [
289
- {
290
- kind: "static-site",
291
- name: repoName(cwd),
292
- desiredState: {
293
- hostname: "staging.example.com",
294
- accessControl: "managed-network",
295
- owner: "platform",
299
+ return `${JSON.stringify(
300
+ {
301
+ service: repoName(cwd),
302
+ environment: "staging",
303
+ resources: [
304
+ {
305
+ kind: "static-site",
306
+ name: repoName(cwd),
307
+ desiredState: {
308
+ hostname: "staging.example.com",
309
+ accessControl: "managed-network",
310
+ owner: "platform",
311
+ },
296
312
  },
297
- },
298
- ],
299
- }, null, 2)}\n`;
313
+ ],
314
+ },
315
+ null,
316
+ 2,
317
+ )}\n`;
300
318
  }
301
319
 
302
320
  function infraContractOutputsJson(cwd) {
303
- return `${JSON.stringify({
304
- service: repoName(cwd),
305
- environment: "staging",
306
- observedMode: "manual-observed",
307
- outputs: {
308
- hostname: "staging.example.com",
309
- distributionId: "observed-distribution-id",
310
- bucket: "observed-bucket-name",
321
+ return `${JSON.stringify(
322
+ {
323
+ service: repoName(cwd),
324
+ environment: "staging",
325
+ observedMode: "manual-observed",
326
+ outputs: {
327
+ hostname: "staging.example.com",
328
+ distributionId: "observed-distribution-id",
329
+ bucket: "observed-bucket-name",
330
+ },
311
331
  },
312
- }, null, 2)}\n`;
332
+ null,
333
+ 2,
334
+ )}\n`;
313
335
  }
314
336
 
315
337
  function workflowArtifactPaths(type) {
@@ -333,7 +355,7 @@ function workflowArtifactPaths(type) {
333
355
  }
334
356
 
335
357
  function publicationWorkflowYaml() {
336
- return `name: Build
358
+ return `${nextDevelopmentWorkflowHeader()}name: Build
337
359
 
338
360
  on:
339
361
  workflow_dispatch:
@@ -365,7 +387,7 @@ jobs:
365
387
  }
366
388
 
367
389
  function workflowYaml({ type, runnerPreset, artifactName }) {
368
- return `name: Build
390
+ return `${nextDevelopmentWorkflowHeader()}name: Build
369
391
 
370
392
  on:
371
393
  workflow_dispatch:
@@ -400,7 +422,9 @@ jobs:
400
422
 
401
423
  function writeIfAllowed(filePath, content, { force }) {
402
424
  if (fs.existsSync(filePath) && !force) {
403
- throw new Error(`${posixPath(filePath)} already exists; pass --force to overwrite`);
425
+ throw new Error(
426
+ `${posixPath(filePath)} already exists; pass --force to overwrite`,
427
+ );
404
428
  }
405
429
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
406
430
  fs.writeFileSync(filePath, content);
@@ -408,7 +432,9 @@ function writeIfAllowed(filePath, content, { force }) {
408
432
  }
409
433
 
410
434
  function writeManagedAgentEntry(filePath, current) {
411
- const content = mergePublicationRehearsalAgentInstructions(current);
435
+ const content = mergeNextDevelopmentAgentInstructions(
436
+ mergePublicationRehearsalAgentInstructions(current),
437
+ );
412
438
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
413
439
  fs.writeFileSync(filePath, content);
414
440
  return filePath;
@@ -424,27 +450,33 @@ export function initBuildchainRepo({
424
450
  } = {}) {
425
451
  const resolvedCwd = path.resolve(cwd);
426
452
  const manager = detectOrDefaultPackageManager(resolvedCwd, packageManager);
427
- const toml = appendPublicationRehearsalToml((() => {
428
- if (type === "package") {
429
- return packageToml(resolvedCwd, manager);
430
- }
431
- if (type === "native") {
432
- return nativeToml(resolvedCwd);
433
- }
434
- if (type === "web-surface") {
435
- return webSurfaceToml(resolvedCwd);
436
- }
437
- if (type === "infra-contract") {
438
- return infraContractToml(resolvedCwd);
439
- }
440
- if (type === "publication-artifact") {
441
- return publicationArtifactToml(resolvedCwd);
442
- }
443
- if (type === "anchored-package") {
444
- return anchoredPackageToml(resolvedCwd, manager);
445
- }
446
- throw new Error("init --type must be one of package, native, web-surface, infra-contract, publication-artifact, or anchored-package");
447
- })());
453
+ const toml = appendNextDevelopmentToml(
454
+ appendPublicationRehearsalToml(
455
+ (() => {
456
+ if (type === "package") {
457
+ return packageToml(resolvedCwd, manager);
458
+ }
459
+ if (type === "native") {
460
+ return nativeToml(resolvedCwd);
461
+ }
462
+ if (type === "web-surface") {
463
+ return webSurfaceToml(resolvedCwd);
464
+ }
465
+ if (type === "infra-contract") {
466
+ return infraContractToml(resolvedCwd);
467
+ }
468
+ if (type === "publication-artifact") {
469
+ return publicationArtifactToml(resolvedCwd);
470
+ }
471
+ if (type === "anchored-package") {
472
+ return anchoredPackageToml(resolvedCwd, manager);
473
+ }
474
+ throw new Error(
475
+ "init --type must be one of package, native, web-surface, infra-contract, publication-artifact, or anchored-package",
476
+ );
477
+ })(),
478
+ ),
479
+ );
448
480
 
449
481
  const agentsPath = path.join(resolvedCwd, "AGENTS.md");
450
482
  const currentAgents = fs.existsSync(agentsPath)
@@ -452,7 +484,9 @@ export function initBuildchainRepo({
452
484
  : "";
453
485
 
454
486
  const written = [
455
- writeIfAllowed(path.join(resolvedCwd, BUILDCHAIN_CONFIG_PATH), toml, { force }),
487
+ writeIfAllowed(path.join(resolvedCwd, BUILDCHAIN_CONFIG_PATH), toml, {
488
+ force,
489
+ }),
456
490
  writeIfAllowed(
457
491
  path.join(resolvedCwd, ".github", "workflows", "build.yml"),
458
492
  type === "publication-artifact"
@@ -472,14 +506,31 @@ export function initBuildchainRepo({
472
506
  writeManagedAgentEntry(agentsPath, currentAgents),
473
507
  ];
474
508
 
475
- if (type === "anchored-package" && !fs.existsSync(path.join(resolvedCwd, "release.json"))) {
476
- written.push(writeIfAllowed(path.join(resolvedCwd, "release.json"), "{\n \"upstream\": \"\",\n \"version\": \"0.0.0\"\n}\n", { force }));
509
+ if (
510
+ type === "anchored-package" &&
511
+ !fs.existsSync(path.join(resolvedCwd, "release.json"))
512
+ ) {
513
+ written.push(
514
+ writeIfAllowed(
515
+ path.join(resolvedCwd, "release.json"),
516
+ '{\n "upstream": "",\n "version": "0.0.0"\n}\n',
517
+ { force },
518
+ ),
519
+ );
477
520
  }
478
521
 
479
522
  if (type === "infra-contract") {
480
523
  written.push(
481
- writeIfAllowed(path.join(resolvedCwd, "infra", "desired.json"), infraContractDesiredJson(resolvedCwd), { force }),
482
- writeIfAllowed(path.join(resolvedCwd, "infra", "outputs.json"), infraContractOutputsJson(resolvedCwd), { force }),
524
+ writeIfAllowed(
525
+ path.join(resolvedCwd, "infra", "desired.json"),
526
+ infraContractDesiredJson(resolvedCwd),
527
+ { force },
528
+ ),
529
+ writeIfAllowed(
530
+ path.join(resolvedCwd, "infra", "outputs.json"),
531
+ infraContractOutputsJson(resolvedCwd),
532
+ { force },
533
+ ),
483
534
  );
484
535
  }
485
536
 
@@ -489,7 +540,9 @@ export function initBuildchainRepo({
489
540
  cwd: resolvedCwd,
490
541
  packageManager: manager,
491
542
  workflowRef: BUILDCHAIN_WORKFLOW_REF,
492
- written: written.map((filePath) => posixPath(path.relative(resolvedCwd, filePath))),
543
+ written: written.map((filePath) =>
544
+ posixPath(path.relative(resolvedCwd, filePath)),
545
+ ),
493
546
  };
494
547
  }
495
548
 
@@ -501,7 +554,11 @@ function readArg(name, fallback = "") {
501
554
  return process.argv[index + 1] || "";
502
555
  }
503
556
 
504
- if (!process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT && process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
557
+ if (
558
+ !process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT &&
559
+ process.argv[1] &&
560
+ import.meta.url === pathToFileURL(process.argv[1]).href
561
+ ) {
505
562
  try {
506
563
  const result = initBuildchainRepo({
507
564
  cwd: readArg("cwd", process.cwd()),
@@ -0,0 +1,409 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+
3
+ import crypto from "node:crypto";
4
+ import fs from "node:fs";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+
8
+ import { validateNextDevelopmentController } from "../packages/core/next-development-controller.js";
9
+ import {
10
+ NEXT_DEVELOPMENT_REQUEST_CONTRACT,
11
+ NEXT_DEVELOPMENT_TRANSITION_CONTRACT,
12
+ materializeNextDevelopmentTransition,
13
+ nextDevelopmentRoot,
14
+ } from "../packages/core/next-development-transition.js";
15
+
16
+ const ROOT = /^sha256:[0-9a-f]{64}$/u;
17
+ const SHA = /^[0-9a-f]{40}$/u;
18
+ const REPOSITORY = /^[^/\s]+\/[^/\s]+$/u;
19
+ const ANCHOR_BYTES = '{"upstream":"v22.1.0"}\n';
20
+
21
+ export const RECORDED_AT = "2026-08-11T02:00:00.000Z";
22
+ export const PROTECTED_DEV_BRANCH = "dev/v3/v3.0";
23
+ export const HOSTED_READBACK_PATHS = [
24
+ ".buildchain/release-impact.json",
25
+ "dist/site/buildchain-contract.json",
26
+ "package.json",
27
+ ];
28
+
29
+ export function required(value, label) {
30
+ const normalized = String(value || "").trim();
31
+ if (!normalized) throw new Error(`${label} is required`);
32
+ return normalized;
33
+ }
34
+
35
+ export function exactRoot(value, label) {
36
+ const normalized = required(value, label).toLowerCase();
37
+ if (!ROOT.test(normalized)) throw new Error(`${label} must be a sha256 root`);
38
+ return normalized;
39
+ }
40
+
41
+ export function exactSha(value, label) {
42
+ const normalized = required(value, label).toLowerCase();
43
+ if (!SHA.test(normalized)) {
44
+ throw new Error(`${label} must be an exact 40-character Git SHA`);
45
+ }
46
+ return normalized;
47
+ }
48
+
49
+ export function repository(value, label = "repository") {
50
+ const normalized = required(value, label);
51
+ if (!REPOSITORY.test(normalized)) throw new Error(`${label} must be owner/repo`);
52
+ return normalized;
53
+ }
54
+
55
+ export function fixtureSha(label) {
56
+ return nextDevelopmentRoot({ label }).slice("sha256:".length, 40 + 7);
57
+ }
58
+
59
+ function completedAlpha({ runtimeSha, contractRoot, model }) {
60
+ const version = model === "anchored" ? "22.0.0-alpha.1" : "3.0.9-alpha.1";
61
+ return {
62
+ outcome: "succeeded",
63
+ version,
64
+ exactTag: `v${version}`,
65
+ releaseSha: runtimeSha,
66
+ treeSha: fixtureSha(`completed-alpha-tree:${model}`),
67
+ publicationRoot: nextDevelopmentRoot({ runtimeSha, contractRoot, model }),
68
+ completedAt: "2026-08-11T01:00:00.000Z",
69
+ };
70
+ }
71
+
72
+ function adapterCheckout(model, version) {
73
+ const cwd = fs.mkdtempSync(
74
+ path.join(os.tmpdir(), `buildchain-next-development-self-dogfood-${model}-`),
75
+ );
76
+ fs.mkdirSync(path.join(cwd, ".buildchain"), { recursive: true });
77
+ fs.writeFileSync(
78
+ path.join(cwd, ".buildchain", "buildchain.toml"),
79
+ `schema = 1
80
+
81
+ [version]
82
+ required = true
83
+ strategy = "${model === "anchored" ? "anchored" : "semver"}"
84
+ next = "${model === "anchored" ? "manual" : "auto"}"
85
+ ${model === "anchored" ? 'manifest = "release.json"\n' : ""}
86
+ [[version.files]]
87
+ type = "json"
88
+ path = "package.json"
89
+ key = "version"
90
+ `,
91
+ );
92
+ fs.writeFileSync(
93
+ path.join(cwd, "package.json"),
94
+ `${JSON.stringify({ name: "self-dogfood-fixture", version }, null, 2)}\n`,
95
+ );
96
+ if (model === "anchored") fs.writeFileSync(path.join(cwd, "release.json"), ANCHOR_BYTES);
97
+ return cwd;
98
+ }
99
+
100
+ export function anchorInput() {
101
+ return {
102
+ targetVersion: "22.1.0",
103
+ anchor: {
104
+ manifestPath: "release.json",
105
+ manifestRoot: `sha256:${crypto.createHash("sha256").update(ANCHOR_BYTES).digest("hex")}`,
106
+ },
107
+ };
108
+ }
109
+
110
+ function adapterRequest(route, model, reviewedInput) {
111
+ return {
112
+ contract: NEXT_DEVELOPMENT_REQUEST_CONTRACT,
113
+ repository: route.repository,
114
+ completedAlpha: completedAlpha({ ...route, model }),
115
+ recordedAt: RECORDED_AT,
116
+ ...(reviewedInput || {}),
117
+ };
118
+ }
119
+
120
+ export function adapterTransition(route, model) {
121
+ const alpha = completedAlpha({ ...route, model });
122
+ const cwd = adapterCheckout(model, alpha.version);
123
+ try {
124
+ return materializeNextDevelopmentTransition({
125
+ cwd,
126
+ request: adapterRequest(route, model),
127
+ });
128
+ } finally {
129
+ fs.rmSync(cwd, { recursive: true, force: true });
130
+ }
131
+ }
132
+
133
+ function operationBody(record) {
134
+ const body = structuredClone(record);
135
+ delete body.operationRoot;
136
+ return body;
137
+ }
138
+
139
+ export function validateAdapterOperation(record) {
140
+ exactRoot(record?.operationKey, "adapter operation key");
141
+ exactRoot(record?.idempotencyKey, "adapter idempotency key");
142
+ exactRoot(record?.materializationRoot, "adapter materialization root");
143
+ if (
144
+ record?.adapterContract !== NEXT_DEVELOPMENT_TRANSITION_CONTRACT ||
145
+ !["semver", "anchored"].includes(record?.model)
146
+ ) {
147
+ throw new Error("real version-state adapter operation contract mismatch");
148
+ }
149
+ if (record.operationRoot !== nextDevelopmentRoot(operationBody(record))) {
150
+ throw new Error("real version-state adapter operation root drifted");
151
+ }
152
+ return structuredClone(record);
153
+ }
154
+
155
+ export function validateSelfDogfoodRoute(route) {
156
+ exactSha(route?.runtimeSha, "route.runtimeSha");
157
+ const targetRepository = repository(route?.repository, "route.repository");
158
+ if (
159
+ targetRepository !== "kungfu-systems/buildchain" ||
160
+ route.runtimeRef !== "v3-alpha" ||
161
+ route.publicWorkflow !==
162
+ `${targetRepository}/.github/workflows/build.yml@v3-alpha` ||
163
+ route.protectedDevBranch !== PROTECTED_DEV_BRANCH ||
164
+ JSON.stringify(route.hostedReadbackPaths) !==
165
+ JSON.stringify(HOSTED_READBACK_PATHS)
166
+ ) {
167
+ throw new Error("next-development evidence public route drifted");
168
+ }
169
+ return targetRepository;
170
+ }
171
+
172
+ export function validateRecoveryEvidence(recovery) {
173
+ const sameRunner =
174
+ required(recovery?.faultRunnerId, "faultRunnerId") ===
175
+ required(recovery?.resumeRunnerId, "resumeRunnerId");
176
+ const unchangedDev =
177
+ exactSha(recovery?.protectedDevMovement?.initialSha, "protected Dev initial SHA") ===
178
+ exactSha(recovery?.protectedDevMovement?.movedSha, "protected Dev moved SHA");
179
+ if (
180
+ recovery?.freshRunner !== true ||
181
+ sameRunner ||
182
+ recovery?.transientDurableStateFailures !== 1 ||
183
+ recovery?.recoveredAdapterOperations !== 1 ||
184
+ recovery?.alphaCandidateRebuilds !== 0 ||
185
+ recovery?.devMaterializationsBefore !== 1 ||
186
+ recovery?.devMaterializationsAfter !== 2 ||
187
+ recovery?.protectedDevMovement?.supersededAttempts !== 1 ||
188
+ unchangedDev ||
189
+ recovery?.protectedPrDelay?.status !== "pr-pending" ||
190
+ recovery?.protectedPrDelay?.delayedStatus !== "pr-pending" ||
191
+ recovery?.protectedPrDelay?.unchangedControllerRoot !== true
192
+ ) {
193
+ throw new Error("fresh-runner failure, Dev movement, or protected-PR delay proof is incomplete");
194
+ }
195
+ }
196
+
197
+ export function validateAdapterEvidence(adapter, semver, anchored) {
198
+ const operations = adapter?.operations || [];
199
+ operations.forEach(validateAdapterOperation);
200
+ const expectedKeys = [...semver.attempts, ...anchored.attempts]
201
+ .map((entry) => entry.operationKey)
202
+ .sort();
203
+ const observedKeys = operations.map((entry) => entry.operationKey).sort();
204
+ if (
205
+ adapter?.boundary !== "materializeNextDevelopmentTransition" ||
206
+ adapter?.contract !== NEXT_DEVELOPMENT_TRANSITION_CONTRACT ||
207
+ operations.length !== 3 ||
208
+ new Set(operations.map((entry) => entry.model)).size !== 2 ||
209
+ JSON.stringify(observedKeys) !== JSON.stringify(expectedKeys)
210
+ ) {
211
+ throw new Error("real version-state adapter boundary proof is incomplete");
212
+ }
213
+ }
214
+
215
+ export class DurableStore {
216
+ constructor(records = [], { transientFailures = 0 } = {}) {
217
+ this.records = new Map(
218
+ records.map((record) => {
219
+ const current = validateNextDevelopmentController(record);
220
+ return [current.childKey, current];
221
+ }),
222
+ );
223
+ this.transientFailuresRemaining = transientFailures;
224
+ this.transientFailures = 0;
225
+ }
226
+
227
+ async readChild(key) {
228
+ return structuredClone(this.records.get(key) || null);
229
+ }
230
+
231
+ async createChild(key, record) {
232
+ if (!this.records.has(key)) this.records.set(key, structuredClone(record));
233
+ return { record: structuredClone(this.records.get(key)) };
234
+ }
235
+
236
+ async compareAndSwapChild(key, expectedRoot, record) {
237
+ if (this.transientFailuresRemaining > 0) {
238
+ this.transientFailuresRemaining -= 1;
239
+ this.transientFailures += 1;
240
+ throw new Error("injected transient durable-state write failure");
241
+ }
242
+ const current = this.records.get(key);
243
+ if (!current || current.controllerRoot !== expectedRoot) {
244
+ throw new Error("self-dogfood durable store compare-and-swap failed");
245
+ }
246
+ this.records.set(key, structuredClone(record));
247
+ return { record: structuredClone(record) };
248
+ }
249
+
250
+ only() {
251
+ if (this.records.size !== 1) {
252
+ throw new Error("self-dogfood durable store expected exactly one child");
253
+ }
254
+ return structuredClone([...this.records.values()][0]);
255
+ }
256
+ }
257
+
258
+ export class VersionStateAdapterHarness {
259
+ constructor({
260
+ route,
261
+ operations = [],
262
+ devSha = fixtureSha("protected-dev:initial"),
263
+ movedDevSha = "",
264
+ moveAfterRecoveredOperation = false,
265
+ pullRequestReadsBeforeMerge = 0,
266
+ } = {}) {
267
+ this.operations = new Map(
268
+ operations.map((record) => {
269
+ const current = validateAdapterOperation(record);
270
+ return [current.operationKey, current];
271
+ }),
272
+ );
273
+ this.route = route ? structuredClone(route) : null;
274
+ this.devSha = devSha;
275
+ this.movedDevSha = movedDevSha;
276
+ this.moveAfterRecoveredOperation = moveAfterRecoveredOperation;
277
+ this.pullRequestReadsBeforeMerge = pullRequestReadsBeforeMerge;
278
+ this.materializations = operations.length;
279
+ this.recoveredOperations = 0;
280
+ this.pullRequestReads = 0;
281
+ this.pullRequest = null;
282
+ }
283
+
284
+ async readProtectedDev() {
285
+ return { sha: this.devSha };
286
+ }
287
+
288
+ async materialize({ operationKey, repository: targetRepository, baseDevSha, targetVersion, anchor }) {
289
+ const existing = this.operations.get(operationKey);
290
+ if (existing) {
291
+ this.recoveredOperations += 1;
292
+ if (this.moveAfterRecoveredOperation && this.movedDevSha) {
293
+ this.devSha = this.movedDevSha;
294
+ this.moveAfterRecoveredOperation = false;
295
+ }
296
+ return structuredClone(existing.result);
297
+ }
298
+ const model = anchor ? "anchored" : "semver";
299
+ const route = this.route || {
300
+ repository: targetRepository,
301
+ runtimeSha: model === "anchored" ? fixtureSha("anchored-runtime") : fixtureSha("semver-runtime"),
302
+ contractRoot: nextDevelopmentRoot({ model, targetRepository }),
303
+ };
304
+ const alpha = completedAlpha({ ...route, model });
305
+ const cwd = adapterCheckout(model, alpha.version);
306
+ let materialized;
307
+ try {
308
+ materialized = materializeNextDevelopmentTransition({
309
+ cwd,
310
+ request: adapterRequest(
311
+ route,
312
+ model,
313
+ model === "anchored" ? { targetVersion, anchor } : undefined,
314
+ ),
315
+ write: true,
316
+ });
317
+ } finally {
318
+ fs.rmSync(cwd, { recursive: true, force: true });
319
+ }
320
+ if (materialized.state.status !== "materialized" || materialized.target.version !== targetVersion) {
321
+ throw new Error("real version-state adapter did not materialize the target");
322
+ }
323
+ const result = {
324
+ baseDevSha,
325
+ commitSha: fixtureSha(`prepared:${operationKey}:${materialized.materialization.materializationRoot}`),
326
+ treeSha: fixtureSha(`prepared-tree:${operationKey}:${materialized.materialization.materializationRoot}`),
327
+ sourceRoots: materialized.materialization.paths.map((entry) => ({
328
+ path: entry.path,
329
+ beforeRoot: entry.beforeRoot,
330
+ afterRoot: entry.afterRoot,
331
+ version: targetVersion,
332
+ })),
333
+ derivedRoots: [],
334
+ changedPaths: materialized.materialization.paths
335
+ .filter((entry) => entry.changed)
336
+ .map((entry) => entry.path)
337
+ .sort(),
338
+ lifecycleEvidenceRoot: materialized.materialization.materializationRoot,
339
+ };
340
+ const record = {
341
+ operationKey,
342
+ model,
343
+ adapterContract: materialized.contract,
344
+ idempotencyKey: materialized.idempotencyKey,
345
+ materializationRoot: materialized.materialization.materializationRoot,
346
+ result,
347
+ };
348
+ record.operationRoot = nextDevelopmentRoot(record);
349
+ this.operations.set(operationKey, validateAdapterOperation(record));
350
+ this.materializations += 1;
351
+ return structuredClone(result);
352
+ }
353
+
354
+ async ensurePullRequest({ baseBranch, headSha }) {
355
+ this.pullRequest = {
356
+ number: 91,
357
+ url: "https://github.com/kungfu-systems/buildchain/pull/91",
358
+ baseBranch,
359
+ headSha,
360
+ status: "open",
361
+ evidenceRoot: nextDevelopmentRoot({ baseBranch, headSha, pullRequest: 91 }),
362
+ };
363
+ return structuredClone(this.pullRequest);
364
+ }
365
+
366
+ async readPullRequest({ number }) {
367
+ if (!this.pullRequest) throw new Error(`self-dogfood pull request ${number} was not restored`);
368
+ this.pullRequestReads += 1;
369
+ if (this.pullRequestReads > this.pullRequestReadsBeforeMerge) {
370
+ this.pullRequest.status = "merged";
371
+ this.devSha = fixtureSha(`protected-merge:${this.pullRequest.headSha}`);
372
+ }
373
+ return structuredClone(this.pullRequest);
374
+ }
375
+
376
+ async readProtectedVersionState({ sourcePaths, derivedPaths, targetVersion, preparedCommitSha }) {
377
+ const operation = [...this.operations.values()].find(
378
+ (entry) => entry.result.commitSha === preparedCommitSha,
379
+ );
380
+ if (!operation) throw new Error("protected readback has no rooted adapter operation");
381
+ const sourceRoots = Object.fromEntries(
382
+ operation.result.sourceRoots.map((entry) => [entry.path, entry.afterRoot]),
383
+ );
384
+ const derivedRoots = Object.fromEntries(
385
+ operation.result.derivedRoots.map((entry) => [entry.path, entry.root]),
386
+ );
387
+ const body = {
388
+ branch: PROTECTED_DEV_BRANCH,
389
+ devSha: this.devSha,
390
+ preparedCommitSha,
391
+ targetVersion,
392
+ versionRoots: sourcePaths.map((filePath) => ({ path: filePath, root: sourceRoots[filePath] })),
393
+ derivedRoots: derivedPaths.map((filePath) => ({ path: filePath, root: derivedRoots[filePath] })),
394
+ };
395
+ return {
396
+ devSha: this.devSha,
397
+ treeSha: fixtureSha(`protected-readback-tree:${preparedCommitSha}`),
398
+ version: targetVersion,
399
+ containsPreparedCommit: true,
400
+ versionRoots: body.versionRoots,
401
+ derivedRoots: body.derivedRoots,
402
+ evidenceRoot: nextDevelopmentRoot(body),
403
+ };
404
+ }
405
+
406
+ operationRecords() {
407
+ return [...this.operations.values()].map((entry) => structuredClone(entry));
408
+ }
409
+ }