@kungfu-tech/buildchain 4.0.1-alpha.5 → 4.0.1-alpha.6

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 (79) hide show
  1. package/actions/release-tail/README.md +8 -0
  2. package/architecture/internal-capabilities.json +65 -5
  3. package/architecture/maintainability-policy.json +22 -16
  4. package/architecture/release-tail-contract-inventory.json +5 -2
  5. package/architecture/v4-adopter-delivery-parity.json +32 -5
  6. package/architecture/v4-publication-rehearsal-parity.json +84 -0
  7. package/bin/buildchain.mjs +7 -1
  8. package/bin/internal/adopter-delivery-cli.mjs +70 -0
  9. package/bin/internal/command-registry.mjs +1 -0
  10. package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
  11. package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
  12. package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
  13. package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
  14. package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
  15. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
  16. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
  17. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
  18. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
  19. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
  20. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
  21. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
  22. package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
  23. package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
  24. package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
  25. package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  26. package/dist/site/agent-index.json +3 -0
  27. package/dist/site/artifact-schemas.json +6 -0
  28. package/dist/site/buildchain-contract.json +14 -14
  29. package/dist/site/buildchain-site.json +219 -22
  30. package/dist/site/capability-registry.json +10 -9
  31. package/dist/site/cli-registry.json +53 -1
  32. package/dist/site/kfd-claims.json +253 -14
  33. package/dist/site/kfd-upstream-aggregate.json +1 -1
  34. package/dist/site/manual-registry.json +20 -5
  35. package/dist/site/node-api-registry.json +11501 -9312
  36. package/dist/site/page-registry.json +202 -13
  37. package/dist/site/public-surface-audit.json +195 -11
  38. package/dist/site/publication-authority-registry.json +17 -4
  39. package/dist/site/publication-registry.json +4 -4
  40. package/dist/site/release-provenance.json +14 -0
  41. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
  42. package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
  43. package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  44. package/dist/site/site-manifest.json +17 -9
  45. package/dist/site/workflow-registry.json +89 -8
  46. package/docs/MAP.md +3 -1
  47. package/docs/cli-reference.md +68 -0
  48. package/docs/cli.md +8 -0
  49. package/docs/node-api-reference.md +198 -51
  50. package/docs/release-tail-provider-plane.md +30 -0
  51. package/docs/v4-adopter-delivery.md +133 -0
  52. package/docs/v4-publication-rehearsal.md +130 -0
  53. package/package.json +17 -3
  54. package/packages/core/adopter-delivery-passport.js +259 -0
  55. package/packages/core/adopter-delivery-vectors.js +158 -0
  56. package/packages/core/adopter-delivery-vectors.json +77 -0
  57. package/packages/core/buildchain-agent-manuals.js +1 -0
  58. package/packages/core/buildchain-config.js +84 -0
  59. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  60. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  61. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  62. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  63. package/packages/core/published-delivery-authority.js +266 -0
  64. package/packages/core/release-passport.js +26 -8
  65. package/packages/core/v4-adopter-delivery-parity.js +3 -3
  66. package/packages/core/v4-adopter-delivery.js +359 -0
  67. package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
  68. package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
  69. package/packages/core/v4-publication-rehearsal.js +481 -0
  70. package/scripts/buildchain-cli-help.mjs +10 -0
  71. package/scripts/check-inventory.mjs +3 -3
  72. package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
  73. package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
  74. package/scripts/generate-release-candidate-passport.mjs +152 -0
  75. package/scripts/generate-site-bundle.mjs +30 -0
  76. package/scripts/release-tail.mjs +51 -3
  77. package/scripts/resume-from-candidate-run.mjs +545 -15
  78. package/scripts/site-capability-metadata.mjs +16 -0
  79. package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
@@ -3,6 +3,7 @@ import crypto from "node:crypto";
3
3
  import fs from "node:fs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
+ import { execFileSync } from "node:child_process";
6
7
  import { pathToFileURL } from "node:url";
7
8
 
8
9
  import { writeGitHubOutputs } from "./build-contract-core.mjs";
@@ -27,6 +28,15 @@ import {
27
28
  } from "../packages/core/publication-artifact-candidate.js";
28
29
  import { createPublicationSealedBundle } from "../packages/core/publication-sealed-bundle.js";
29
30
  import { releaseTransactionStateRef } from "../packages/core/publish-transaction.js";
31
+ import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
32
+ import { validateV4StageCapsule } from "../packages/core/v4-stage-capsule.js";
33
+ import {
34
+ authorizeV4RuntimeSelection,
35
+ createV4RuntimeResumeLineage,
36
+ scanV4RuntimeSelectorPersistence,
37
+ v4RuntimeResumeDocumentRoot,
38
+ verifyV4RuntimeAuthorizationReceipt,
39
+ } from "../packages/core/v4-runtime-ref-resume-authority.js";
30
40
 
31
41
  function env(name, fallback = "") {
32
42
  return process.env[name] || fallback;
@@ -99,8 +109,373 @@ function readOnlyJson(files, label) {
99
109
  return JSON.parse(fs.readFileSync(files[0].absolutePath, "utf8"));
100
110
  }
101
111
 
102
- async function readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, version }) {
103
- const stateRef = releaseTransactionStateRef(version);
112
+ function trackedRuntimePersistenceScan() {
113
+ const paths = execFileSync(
114
+ "git",
115
+ ["ls-files", ".github/workflows", ".github/actions", ".buildchain"],
116
+ { encoding: "utf8" },
117
+ )
118
+ .split(/\r?\n/)
119
+ .filter((entry) => /\.(?:json|toml|ya?ml)$/u.test(entry));
120
+ return scanV4RuntimeSelectorPersistence({ root: process.cwd(), paths });
121
+ }
122
+
123
+ export function verifyReleaseCandidateStageCapsules({ sidecar, passport, downloads }) {
124
+ if (
125
+ !sidecar ||
126
+ sidecar.contract !==
127
+ "kungfu-buildchain-v4-release-candidate-stage-capsules/v1" ||
128
+ sidecar.status !== "sealed"
129
+ ) {
130
+ throw new Error(
131
+ "cross-runtime recovery requires original sealed release-candidate Stage Capsules",
132
+ );
133
+ }
134
+ const payload = { ...sidecar };
135
+ delete payload.root;
136
+ const expectedAttempt =
137
+ `github-run:${passport.workflow.runId}:attempt:${passport.workflow.runAttempt}`;
138
+ if (
139
+ sidecar.root !== v4RuntimeResumeDocumentRoot(payload) ||
140
+ sidecar.repository !== passport.repository ||
141
+ sidecar.source?.sha !== passport.source.headSha ||
142
+ sidecar.source?.treeSha !== passport.source.treeHash ||
143
+ sidecar.buildAttempt?.id !== expectedAttempt ||
144
+ sidecar.buildAttempt?.runtimeSha !== passport.buildchain.sha ||
145
+ sidecar.consumerPolicyReceiptRoot !== passport.consumerPolicy?.receiptRoot
146
+ ) {
147
+ throw new Error("release-candidate Stage Capsule sidecar identity mismatch");
148
+ }
149
+ const artifactByName = new Map(downloads.map((entry) => [entry.artifact.name, entry]));
150
+ const required = [...passport.platformMatrix.map((entry) => entry.platformId)].sort();
151
+ const entries = [...(sidecar.capsules || [])].sort((left, right) =>
152
+ left.platform.localeCompare(right.platform),
153
+ );
154
+ if (
155
+ entries.length !== required.length ||
156
+ entries.some((entry, index) => entry.platform !== required[index])
157
+ ) {
158
+ throw new Error("release-candidate Stage Capsule platform set is incomplete");
159
+ }
160
+ return entries.map((entry) => {
161
+ validateV4StageCapsule(entry.capsule);
162
+ const download = artifactByName.get(entry.artifactName);
163
+ const artifact = entry.artifact;
164
+ if (
165
+ !download ||
166
+ artifact?.platformId !== entry.platform ||
167
+ artifact?.id !== String(download.artifact.id) ||
168
+ artifact?.name !== download.artifact.name ||
169
+ artifact?.digest !== entry.artifactDigest ||
170
+ artifact?.digest !== download.artifact.digest ||
171
+ artifact?.digest !== download.record.digest ||
172
+ artifact?.digest !== download.record.downloadedDigest ||
173
+ artifact?.expiresAt !==
174
+ new Date(download.artifact.expires_at).toISOString() ||
175
+ entry.capsule.identity.policyRoot !== sidecar.consumerPolicyReceiptRoot ||
176
+ entry.capsule.identity.sourceRoot !==
177
+ v4ContentRoot("candidate-identity", passport.source) ||
178
+ entry.capsule.identity.runtimeRoot !==
179
+ v4ContentRoot("candidate-identity", { sha: passport.buildchain.sha })
180
+ ) {
181
+ throw new Error(
182
+ `Stage Capsule ${entry.platform} does not bind the verified provider artifact`,
183
+ );
184
+ }
185
+ return {
186
+ platform: entry.platform,
187
+ capsuleRoot: entry.capsule.capsuleRoot,
188
+ identityRoot: entry.capsule.identityRoot,
189
+ artifactDigest: entry.artifactDigest,
190
+ sourceSha: sidecar.source.sha,
191
+ sourceTreeSha: sidecar.source.treeSha,
192
+ policyRoot: sidecar.consumerPolicyReceiptRoot,
193
+ buildRuntimeSha: sidecar.buildAttempt.runtimeSha,
194
+ sealed: true,
195
+ };
196
+ });
197
+ }
198
+
199
+ export function validateV4RuntimeResumePublicReadback({
200
+ targetRef,
201
+ targetSha,
202
+ alphaSha,
203
+ exactTagSha,
204
+ tagLineage,
205
+ runtimeSha,
206
+ version,
207
+ transaction,
208
+ main,
209
+ npm,
210
+ }) {
211
+ if (
212
+ transaction?.target_ref !== targetRef ||
213
+ transaction?.version !== version ||
214
+ exactTagSha !== transaction?.source_sha ||
215
+ !["ahead", "identical"].includes(tagLineage?.status) ||
216
+ alphaSha !== runtimeSha ||
217
+ !targetSha ||
218
+ npm["dist-tags"]?.alpha !== version ||
219
+ npm.versions?.[version]?.dist?.integrity !== main.digest
220
+ ) {
221
+ throw new Error(
222
+ "cross-runtime final public readback does not match durable publication bytes and runtime",
223
+ );
224
+ }
225
+ }
226
+
227
+ export async function readPublicResumeState({
228
+ repoInfo,
229
+ targetRef,
230
+ runtimeSha,
231
+ version,
232
+ transaction,
233
+ token,
234
+ apiUrl,
235
+ fetchImpl,
236
+ }) {
237
+ const readRef = async (ref) =>
238
+ (await githubJson({
239
+ apiUrl,
240
+ token,
241
+ fetchImpl,
242
+ path: `/repos/${repoInfo.owner}/${repoInfo.repo}/git/ref/${ref}`,
243
+ })).object?.sha || "";
244
+ const main = transaction?.artifacts?.find((entry) => entry.kind === "npm" && entry.required !== false);
245
+ if (!main || main.ref !== version || !main.digest) {
246
+ throw new Error(
247
+ "cross-runtime recovery requires exact durable npm publication evidence",
248
+ );
249
+ }
250
+ const packageUrl =
251
+ `https://registry.npmjs.org/${encodeURIComponent(main.name)}`;
252
+ const exactTag = transaction.exact_tag || `v${version}`;
253
+ const [targetSha, alphaSha, exactTagSha, npmResponse] = await Promise.all([
254
+ readRef(`heads/${targetRef}`),
255
+ readRef("tags/v4-alpha"),
256
+ readRef(`tags/${exactTag}`),
257
+ fetchImpl(packageUrl),
258
+ ]);
259
+ if (!npmResponse.ok) {
260
+ throw new Error(`npm public readback failed with HTTP ${npmResponse.status}`);
261
+ }
262
+ const npm = await npmResponse.json();
263
+ const tagLineage = await githubJson({
264
+ apiUrl,
265
+ token,
266
+ fetchImpl,
267
+ path: `/repos/${repoInfo.owner}/${repoInfo.repo}/compare/${exactTagSha}...${targetSha}`,
268
+ });
269
+ validateV4RuntimeResumePublicReadback({
270
+ targetRef,
271
+ targetSha,
272
+ alphaSha,
273
+ exactTagSha,
274
+ tagLineage,
275
+ runtimeSha,
276
+ version,
277
+ transaction,
278
+ main,
279
+ npm,
280
+ });
281
+ const body = {
282
+ schemaVersion: 1,
283
+ contract: "kungfu-buildchain-v4-runtime-resume-public-readback/v1",
284
+ observedAt: new Date().toISOString(),
285
+ repository: repoInfo.fullName,
286
+ version,
287
+ refs: {
288
+ target: { ref: targetRef, sha: targetSha },
289
+ floating: { ref: "v4-alpha", sha: alphaSha },
290
+ exactTag: { ref: exactTag, sha: exactTagSha },
291
+ },
292
+ npm: {
293
+ package: main.name,
294
+ version,
295
+ distTag: "alpha",
296
+ integrity: npm.versions[version].dist.integrity,
297
+ },
298
+ };
299
+ return { ...body, root: v4RuntimeResumeDocumentRoot(body) };
300
+ }
301
+
302
+ function prepareRuntimeResumeEvidence({
303
+ repoInfo,
304
+ targetRef,
305
+ runtimeSha,
306
+ version,
307
+ passport,
308
+ sidecar,
309
+ stageCapsules,
310
+ recovery,
311
+ outputDir,
312
+ }) {
313
+ const authorizationPath = path.resolve(
314
+ ".buildchain/release-candidate/v4-runtime-authorization.json",
315
+ );
316
+ if (!fs.existsSync(authorizationPath)) {
317
+ throw new Error(
318
+ "cross-runtime recovery requires a fresh runtime authorization receipt",
319
+ );
320
+ }
321
+ const delegated = readOnlyJson([{ absolutePath: authorizationPath }], "runtime authorization");
322
+ const delegatedVerification = verifyV4RuntimeAuthorizationReceipt({
323
+ receipt: delegated.receipt,
324
+ receiptRoot: delegated.receiptRoot,
325
+ repository: repoInfo.fullName,
326
+ runtimeSha,
327
+ });
328
+ if (!delegatedVerification.ok) {
329
+ throw new Error(
330
+ `fresh recovery runtime authorization rejected: ${delegatedVerification.failures.join(", ")}`,
331
+ );
332
+ }
333
+ const policy = passport.consumerPolicy.receipt;
334
+ const authorization = authorizeV4RuntimeSelection({
335
+ repository: repoInfo.fullName,
336
+ eventName: "workflow_dispatch",
337
+ mode: "resume",
338
+ actor: delegated.receipt.actor.login,
339
+ actorPermission: delegated.receipt.actor.permission,
340
+ reason:
341
+ `resume sealed candidate run ${passport.workflow.runId} from a tree-equivalent protected source`,
342
+ authorizedAt: new Date().toISOString(),
343
+ sourceSha: sidecar.source.sha,
344
+ sourceTreeSha: sidecar.source.treeSha,
345
+ requestedRef: delegated.receipt.request.ref,
346
+ resolvedRuntimeSha: runtimeSha,
347
+ approvedRefReadbacks: delegated.receipt.runtime.reachableFrom,
348
+ stableContractLockRoot: policy.contractLocks.stable.root,
349
+ alphaContractLockRoot: policy.contractLocks.alpha.root,
350
+ consumerPolicyReceiptRoot: sidecar.consumerPolicyReceiptRoot,
351
+ persistenceScan: trackedRuntimePersistenceScan(),
352
+ });
353
+ const planBody = {
354
+ schemaVersion: 1,
355
+ contract: "kungfu-buildchain-v4-runtime-resume-plan/v1",
356
+ originalRunId: String(passport.workflow.runId),
357
+ resumeRunId: String(env("GITHUB_RUN_ID")),
358
+ recoveryReceiptRoot: recovery.receipt.root,
359
+ requiredPlatforms: stageCapsules.map((entry) => entry.platform),
360
+ reusedCapsuleRoots: stageCapsules.map((entry) => entry.capsuleRoot),
361
+ rebuildPlatforms: [],
362
+ skippedBuildStages: recovery.receipt.skippedBuildStages,
363
+ };
364
+ const plan = { ...planBody, root: v4RuntimeResumeDocumentRoot(planBody) };
365
+ const materialBody = {
366
+ schemaVersion: 1,
367
+ contract: "kungfu-buildchain-v4-runtime-resume-material/v1",
368
+ repository: repoInfo.fullName,
369
+ targetRef,
370
+ runtimeSha,
371
+ version,
372
+ authorization: authorization.receipt,
373
+ authorizationRoot: authorization.receiptRoot,
374
+ buildAttempt: sidecar.buildAttempt,
375
+ resumeAttempt: {
376
+ id: `github-run:${env("GITHUB_RUN_ID")}:attempt:${env("GITHUB_RUN_ATTEMPT", "1")}`,
377
+ runtimeSha,
378
+ },
379
+ source: sidecar.source,
380
+ consumerPolicyReceiptRoot: sidecar.consumerPolicyReceiptRoot,
381
+ requiredPlatforms: stageCapsules.map((entry) => entry.platform),
382
+ stageCapsules,
383
+ resumePlanRoot: plan.root,
384
+ floatingRefBefore: {
385
+ ref: "v4-alpha",
386
+ sha: sidecar.buildAttempt.runtimeSha,
387
+ },
388
+ };
389
+ const material = {
390
+ ...materialBody,
391
+ root: v4RuntimeResumeDocumentRoot(materialBody),
392
+ };
393
+ for (const [name, value] of [
394
+ ["v4-runtime-resume-plan.json", plan],
395
+ ["v4-runtime-resume-material.json", material],
396
+ ]) {
397
+ fs.writeFileSync(
398
+ path.join(outputDir, name),
399
+ `${JSON.stringify(value, null, 2)}\n`,
400
+ );
401
+ }
402
+ return path.join(outputDir, "v4-runtime-resume-evidence.json");
403
+ }
404
+
405
+ export async function finalizeRuntimeResumeEvidence({
406
+ materialPath,
407
+ transaction,
408
+ token = env("GITHUB_TOKEN", env("INPUT_TOKEN")),
409
+ apiUrl = env("GITHUB_API_URL", "https://api.github.com"),
410
+ fetchImpl = globalThis.fetch,
411
+ outputDir = path.dirname(materialPath),
412
+ }) {
413
+ const material = JSON.parse(fs.readFileSync(materialPath, "utf8"));
414
+ const body = { ...material };
415
+ delete body.root;
416
+ if (
417
+ material.contract !== "kungfu-buildchain-v4-runtime-resume-material/v1" ||
418
+ material.root !== v4RuntimeResumeDocumentRoot(body)
419
+ ) {
420
+ throw new Error("cross-runtime resume material root mismatch");
421
+ }
422
+ const repoInfo = splitRepository(material.repository);
423
+ const version = env("BUILDCHAIN_RELEASE_VERSION", material.version);
424
+ const targetRef = env("BUILDCHAIN_RELEASE_TARGET_REF", material.targetRef);
425
+ if (
426
+ version !== material.version ||
427
+ targetRef !== material.targetRef ||
428
+ transaction?.version !== version ||
429
+ transaction?.state !== "complete"
430
+ ) {
431
+ throw new Error(
432
+ "cross-runtime resume finalization does not match the completed publication transaction",
433
+ );
434
+ }
435
+ const readback = await readPublicResumeState({
436
+ repoInfo,
437
+ targetRef,
438
+ runtimeSha: material.runtimeSha,
439
+ version,
440
+ transaction,
441
+ token,
442
+ apiUrl,
443
+ fetchImpl,
444
+ });
445
+ const resumed = createV4RuntimeResumeLineage({
446
+ authorization: material.authorization,
447
+ authorizationRoot: material.authorizationRoot,
448
+ buildAttempt: material.buildAttempt,
449
+ resumeAttempt: material.resumeAttempt,
450
+ source: material.source,
451
+ consumerPolicyReceiptRoot: material.consumerPolicyReceiptRoot,
452
+ requiredPlatforms: material.requiredPlatforms,
453
+ stageCapsules: material.stageCapsules,
454
+ resumePlanRoot: material.resumePlanRoot,
455
+ finalPublicReadbackRoot: readback.root,
456
+ floatingRefBefore: material.floatingRefBefore,
457
+ floatingRefAfter: { ref: "v4-alpha", sha: material.runtimeSha },
458
+ });
459
+ const evidence = {
460
+ authorization: material.authorization,
461
+ authorizationRoot: material.authorizationRoot,
462
+ lineage: resumed.lineage,
463
+ lineageRoot: resumed.lineageRoot,
464
+ };
465
+ const evidencePath = path.join(outputDir, "v4-runtime-resume-evidence.json");
466
+ for (const [name, value] of [
467
+ ["v4-runtime-resume-public-readback.json", readback],
468
+ [path.basename(evidencePath), evidence],
469
+ ]) {
470
+ fs.writeFileSync(
471
+ path.join(outputDir, name),
472
+ `${JSON.stringify(value, null, 2)}\n`,
473
+ );
474
+ }
475
+ return { evidence, path: evidencePath, readback };
476
+ }
477
+
478
+ async function readTransactionAtRef({ repoInfo, apiUrl, token, fetchImpl, stateRef }) {
104
479
  const response = await githubJson({
105
480
  apiUrl,
106
481
  token,
@@ -109,10 +484,65 @@ async function readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, ver
109
484
  path: `/repos/${repoInfo.owner}/${repoInfo.repo}/contents/state.json?ref=${encodeURIComponent(stateRef)}`,
110
485
  });
111
486
  if (!response) return undefined;
112
- if (response.type !== "file" || response.encoding !== "base64" || !response.content) {
113
- throw new Error(`durable transaction ${stateRef} did not expose a base64 state.json file`);
487
+ if (
488
+ response.type !== "file" ||
489
+ response.encoding !== "base64" ||
490
+ !response.content
491
+ ) {
492
+ throw new Error(
493
+ `durable transaction ${stateRef} did not expose a base64 state.json file`,
494
+ );
495
+ }
496
+ return JSON.parse(
497
+ Buffer.from(
498
+ String(response.content).replace(/\s/g, ""),
499
+ "base64",
500
+ ).toString("utf8"),
501
+ );
502
+ }
503
+
504
+ async function readExistingTransaction({
505
+ repoInfo,
506
+ apiUrl,
507
+ token,
508
+ fetchImpl,
509
+ version,
510
+ }) {
511
+ return readTransactionAtRef({
512
+ repoInfo,
513
+ apiUrl,
514
+ token,
515
+ fetchImpl,
516
+ stateRef: releaseTransactionStateRef(version),
517
+ });
518
+ }
519
+
520
+ async function readExistingTransactionById({
521
+ repoInfo,
522
+ apiUrl,
523
+ token,
524
+ fetchImpl,
525
+ transactionId,
526
+ }) {
527
+ if (!transactionId) return undefined;
528
+ const statePrefix = "buildchain/release-state/";
529
+ const refs = await githubJson({
530
+ apiUrl,
531
+ token,
532
+ fetchImpl,
533
+ path: `/repos/${repoInfo.owner}/${repoInfo.repo}/git/matching-refs/heads/${statePrefix}`,
534
+ });
535
+ for (const ref of refs) {
536
+ const transaction = await readTransactionAtRef({
537
+ repoInfo,
538
+ apiUrl,
539
+ token,
540
+ fetchImpl,
541
+ stateRef: String(ref.ref).replace(/^refs\/heads\//, ""),
542
+ });
543
+ if (transaction?.id === transactionId) return transaction;
114
544
  }
115
- return JSON.parse(Buffer.from(String(response.content).replace(/\s/g, ""), "base64").toString("utf8"));
545
+ throw new Error(`durable publication transaction ${transactionId} was not found`);
116
546
  }
117
547
 
118
548
  function outputPath(filePath) {
@@ -349,6 +779,14 @@ async function recoverCandidateEvidence({
349
779
  for (const artifact of [selected.passport, selected.summary]) initialDownloads.push(await downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir, bundleRoot, fetchImpl }));
350
780
  const passport = readOnlyJson(initialDownloads[0].files.filter((file) => path.basename(file.path) === "release-candidate-passport.json"), "release-candidate-passport.json");
351
781
  const buildSummary = readOnlyJson(initialDownloads[1].files.filter((file) => path.basename(file.path) === "build-summary.json"), "build-summary.json");
782
+ const sidecarFiles = initialDownloads[0].files.filter(
783
+ (file) =>
784
+ path.basename(file.path) === "release-candidate-stage-capsules.json",
785
+ );
786
+ const stageCapsuleSidecar =
787
+ sidecarFiles.length === 1
788
+ ? readOnlyJson(sidecarFiles, "release-candidate-stage-capsules.json")
789
+ : undefined;
352
790
  const { names: requiredNames, publicationNames } = candidateArtifactNames({ passport, selected, artifacts, artifactPatterns });
353
791
  const chosen = artifacts.filter((artifact) => requiredNames.has(artifact.name));
354
792
  if (chosen.length !== requiredNames.size) {
@@ -360,7 +798,7 @@ async function recoverCandidateEvidence({
360
798
  for (const artifact of chosen.filter((entry) => ![selected.passport.id, selected.summary.id].includes(entry.id))) downloads.push(await downloadArtifact({ artifact, repoInfo, apiUrl, token, archiveDir, bundleRoot, fetchImpl }));
361
799
  return {
362
800
  run, workflow, selected, resolvedOutput, bundleRoot, initialDownloads,
363
- passport, buildSummary, chosen, downloads, publicationNames,
801
+ passport, buildSummary, stageCapsuleSidecar, chosen, downloads, publicationNames,
364
802
  };
365
803
  }
366
804
 
@@ -370,6 +808,52 @@ async function resolveTargetAdvance({ observedTargetSha, targetSha, transactionI
370
808
  return { status: comparison.status, mergeIsAncestor: ["ahead", "identical"].includes(comparison.status) };
371
809
  }
372
810
 
811
+ async function resolveRecoveryTransaction({
812
+ repoInfo,
813
+ apiUrl,
814
+ token,
815
+ fetchImpl,
816
+ transactionId,
817
+ publicationVersion,
818
+ }) {
819
+ const byId = await readExistingTransactionById({
820
+ repoInfo,
821
+ apiUrl,
822
+ token,
823
+ fetchImpl,
824
+ transactionId,
825
+ });
826
+ const version = byId?.version || publicationVersion;
827
+ const transaction = byId || await readExistingTransaction({
828
+ repoInfo,
829
+ apiUrl,
830
+ token,
831
+ fetchImpl,
832
+ version,
833
+ });
834
+ return { version, transaction };
835
+ }
836
+
837
+ function resolveRecoveredStageCapsules({
838
+ candidateRuntimeSha,
839
+ runtimeSha,
840
+ sidecar,
841
+ passport,
842
+ downloads,
843
+ }) {
844
+ if (candidateRuntimeSha === runtimeSha) return [];
845
+ return verifyReleaseCandidateStageCapsules({ sidecar, passport, downloads });
846
+ }
847
+
848
+ function validateRecoveryCoordinates(candidateRunId, expectedSourceTree, expectedCandidateRoot) {
849
+ const runId = String(candidateRunId || "").trim();
850
+ if (!/^\d+$/.test(runId)) throw new Error("candidate run ID must be numeric");
851
+ if (!String(expectedSourceTree || "").trim() && !String(expectedCandidateRoot || "").trim()) {
852
+ throw new Error("candidate recovery requires expectedSourceTree or expectedCandidateRoot");
853
+ }
854
+ return runId;
855
+ }
856
+
373
857
  export async function resumeFromCandidateRun({
374
858
  repository, targetRepository = repository, candidateRunId,
375
859
  expectedWorkflowFile, expectedWorkflowName,
@@ -384,16 +868,12 @@ export async function resumeFromCandidateRun({
384
868
  recoveryRunId = env("GITHUB_RUN_ID"), fetchImpl = globalThis.fetch,
385
869
  } = {}) {
386
870
  const repoInfo = splitRepository(repository);
387
- const runId = String(candidateRunId || "").trim();
388
- if (!/^\d+$/.test(runId)) throw new Error("candidate run ID must be numeric");
389
- if (!String(expectedSourceTree || "").trim() && !String(expectedCandidateRoot || "").trim()) {
390
- throw new Error("candidate recovery requires expectedSourceTree or expectedCandidateRoot");
391
- }
871
+ const runId = validateRecoveryCoordinates(candidateRunId, expectedSourceTree, expectedCandidateRoot);
392
872
  const archiveDir = fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-resume-"));
393
873
  try {
394
874
  const {
395
875
  run, workflow, selected, resolvedOutput, bundleRoot, initialDownloads,
396
- passport, buildSummary, chosen, downloads, publicationNames,
876
+ passport, buildSummary, stageCapsuleSidecar, chosen, downloads, publicationNames,
397
877
  } = await recoverCandidateEvidence({
398
878
  repoInfo, runId, artifactName, artifactPatterns, requiredArtifactCount,
399
879
  outputDir, apiUrl, token, fetchImpl, archiveDir,
@@ -419,8 +899,24 @@ export async function resumeFromCandidateRun({
419
899
  releasePatterns,
420
900
  platformManifests: platformManifestEvidence.manifests,
421
901
  });
422
- const candidateVersion = publication.version;
423
- const existingTransaction = await readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, version: candidateVersion });
902
+ const stageCapsules = resolveRecoveredStageCapsules({
903
+ candidateRuntimeSha,
904
+ runtimeSha,
905
+ sidecar: stageCapsuleSidecar,
906
+ passport,
907
+ downloads,
908
+ });
909
+ const {
910
+ version: candidateVersion,
911
+ transaction: existingTransaction,
912
+ } = await resolveRecoveryTransaction({
913
+ repoInfo,
914
+ apiUrl,
915
+ token,
916
+ fetchImpl,
917
+ transactionId,
918
+ publicationVersion: publication.version,
919
+ });
424
920
  const targetAdvance = await resolveTargetAdvance({
425
921
  observedTargetSha, targetSha, transactionId, existingTransaction,
426
922
  repoInfo, apiUrl, token, fetchImpl,
@@ -491,6 +987,19 @@ export async function resumeFromCandidateRun({
491
987
  if (publication.manifest) fs.writeFileSync(sealedManifestPath, `${JSON.stringify(publication.manifest, null, 2)}\n`);
492
988
  const publishRequiredArtifacts = publication.publishRequiredArtifacts;
493
989
  fs.writeFileSync(requiredArtifactsPath, `${JSON.stringify(publishRequiredArtifacts, null, 2)}\n`);
990
+ const runtimeResumeEvidencePath = stageCapsules.length
991
+ ? prepareRuntimeResumeEvidence({
992
+ repoInfo,
993
+ targetRef,
994
+ runtimeSha,
995
+ version: candidateVersion,
996
+ passport,
997
+ sidecar: stageCapsuleSidecar,
998
+ stageCapsules,
999
+ recovery,
1000
+ outputDir: resolvedOutput,
1001
+ })
1002
+ : "";
494
1003
  const tarballs = publication.npmArtifacts.map((entry) => outputPath(entry.file.absolutePath));
495
1004
  return {
496
1005
  enabled: true,
@@ -514,6 +1023,9 @@ export async function resumeFromCandidateRun({
514
1023
  sealedBundleRoot: publication.manifest ? outputPath(bundleRoot) : "",
515
1024
  sealedBundleManifest: publication.manifest ? outputPath(sealedManifestPath) : "",
516
1025
  recoveryReceipt: outputPath(recoveryReceiptPath),
1026
+ runtimeResumeEvidence: runtimeResumeEvidencePath
1027
+ ? outputPath(runtimeResumeEvidencePath)
1028
+ : "",
517
1029
  },
518
1030
  };
519
1031
  } finally {
@@ -565,6 +1077,10 @@ export async function resumeFromCandidateRunCli() {
565
1077
  "release-candidate-run-url": result.run.url,
566
1078
  "release-candidate-recovery-receipt-path": result.paths.recoveryReceipt,
567
1079
  "release-candidate-recovery-root": result.receipt.root,
1080
+ "v4-runtime-resume-evidence-path": result.paths.runtimeResumeEvidence,
1081
+ "v4-runtime-resume-finalize-command": result.paths.runtimeResumeEvidence
1082
+ ? "node .buildchain/runtime/promotion-shell/scripts/resume-from-candidate-run.mjs finalize"
1083
+ : "",
568
1084
  "release-candidate-root": result.candidateRoot,
569
1085
  "release-candidate-artifact-root": result.artifactRoot,
570
1086
  "publish-sealed-bundle-root": result.paths.sealedBundleRoot,
@@ -586,7 +1102,21 @@ export async function resumeFromCandidateRunCli() {
586
1102
  }
587
1103
 
588
1104
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
589
- resumeFromCandidateRunCli().catch((error) => {
1105
+ const operation = process.argv[2] === "finalize"
1106
+ ? finalizeRuntimeResumeEvidence({
1107
+ materialPath: requiredEnv("BUILDCHAIN_V4_RUNTIME_RESUME_MATERIAL"),
1108
+ transaction: JSON.parse(
1109
+ requiredEnv("BUILDCHAIN_RELEASE_TRANSACTION_JSON"),
1110
+ ),
1111
+ outputDir: env(
1112
+ "BUILDCHAIN_RELEASE_PASSPORT_OUTPUT_DIR",
1113
+ path.dirname(requiredEnv("BUILDCHAIN_V4_RUNTIME_RESUME_MATERIAL")),
1114
+ ),
1115
+ }).then((result) =>
1116
+ console.log(JSON.stringify({ files: [outputPath(result.path)] })),
1117
+ )
1118
+ : resumeFromCandidateRunCli();
1119
+ operation.catch((error) => {
590
1120
  const failure = error.recoveryFailure || recoveryFailure(error);
591
1121
  console.error(`candidate recovery rejected [${failure.code}]: ${failure.reason}`);
592
1122
  console.error(`next action: ${failure.nextAction}`);
@@ -13,6 +13,11 @@ export function cliCommandMeta(id) {
13
13
  const map = new Map(Object.entries({
14
14
  audit: { group: "release-passport-trust", purpose: "Inspect read-only publication authority audit commands." },
15
15
  architecture: { group: "governance-versioning", purpose: "Inspect and qualify the Buildchain v4 capability and state-machine architecture contract." },
16
+ "adopter-delivery": { group: "reusable-build", purpose: "Run public protocol-neutral adopter delivery, exact readback, N-1 bootstrap, and published archive checks." },
17
+ "adopter-delivery-run": { group: "reusable-build", purpose: "Run one exact public adopter driver and artifact-profile selection offline." },
18
+ "adopter-delivery-verify": { group: "reusable-build", purpose: "Recompute and verify exact adopter delivery readback without authority widening." },
19
+ "adopter-delivery-bootstrap": { group: "reusable-build", purpose: "Qualify exact v3 N-1 bootstrap lineage against the v4 adopter delivery substrate." },
20
+ "adopter-delivery-archive": { group: "reusable-build", purpose: "Load content-addressed public npm archive bytes and verify their exact identity and readback root." },
16
21
  "architecture-list": { group: "governance-versioning", purpose: "Generate the Agent-readable capability list from the validated v4 architecture manifest." },
17
22
  "architecture-qualify": { group: "governance-versioning", purpose: "Qualify an exact candidate against a distinct N-1 Git authority revision without candidate self-qualification." },
18
23
  "architecture-show": { group: "governance-versioning", purpose: "Generate one capability and its state-machine details from the validated v4 architecture manifest." },
@@ -223,6 +228,17 @@ export function nodeApiMeta(exportName) {
223
228
  "./v4-runtime-ref-resume-authority": { group: "release-passport-trust", summary: "Transient exact-runtime authorization, persisted-selector rejection, fresh-attempt Stage Capsule reuse, and A+B Passport lineage APIs." },
224
229
  "./v4-tail-reseal": { group: "release-passport-trust", summary: "Effect-pure retained Stage Capsule validation and explicit Warrant-, credential-, and release-tail-fenced macOS reseal planning APIs." },
225
230
  "./v4-tail-reseal-receipt": { group: "release-passport-trust", summary: "Four-platform byte readback, standard candidate Passport binding, and terminal v4 tail-reseal receipt APIs." },
231
+ "./v4-publication-rehearsal": { group: "release-passport-trust", summary: "Content-addressed v4 publication rehearsal capsules, shared production release-tail execution, replay, provider observation, and fail-closed verification APIs." },
232
+ "./adopter-delivery-gate": { group: "reusable-build", summary: "Protocol-neutral adopter delivery gate, driver interface, artifact profiles, and canonical result APIs." },
233
+ "./kfd-adopter-category-driver": { group: "kfd-trust", summary: "Published KFD adopter-category driver and exact package-cut verification APIs." },
234
+ "./buildchain-delivery-infrastructure": { group: "reusable-build", summary: "Public delivery-infrastructure instance creation and KFD verification APIs." },
235
+ "./buildchain-delivery-bootstrap": { group: "reusable-build", summary: "Protected, published N-1 Buildchain delivery bootstrap qualification APIs." },
236
+ "./buildchain-delivery-self-dogfood": { group: "reusable-build", summary: "Public archive-backed Buildchain delivery-infrastructure self-dogfood APIs." },
237
+ "./published-delivery-authority": { group: "release-passport-trust", summary: "Content-addressed public Buildchain and KFD archive loading and identity verification APIs." },
238
+ "./legacy-kfd-adopter-driver": { group: "kfd-trust", summary: "One-way legacy KFD support-matrix adapter driver APIs." },
239
+ "./adopter-delivery-passport": { group: "release-passport-trust", summary: "Non-authoritative adopter delivery Release Passport binding and substitution checks." },
240
+ "./adopter-delivery-vectors": { group: "reusable-build", summary: "Rooted public adopter delivery conformance vector suite APIs." },
241
+ "./v4-adopter-delivery": { group: "reusable-build", summary: "Public v4 adopter delivery runtime, exact readback, N-1 lineage, and published archive verification APIs." },
226
242
  "./release-candidate-recovery": { group: "release-passport-trust", summary: "Fail-closed verification and immutable receipts for reusing a successful sealed candidate run without rebuilding product payloads." },
227
243
  "./release-train": { group: "governance-versioning", summary: "Frozen Release Cut identity, rooted Release Train transitions, Dev observations, supersession, replay, and legacy read compatibility APIs." },
228
244
  "./next-development-transition": { group: "governance-versioning", summary: "Provider-neutral completed-Alpha preservation and rooted next-development state, version-model, idempotency, and local materialization APIs." },