@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
@@ -11,6 +11,7 @@ import {
11
11
  verifySourceQualificationProof,
12
12
  } from "../packages/core/dev-delivery-warrant.js";
13
13
  import { validateControllerReceipt } from "../packages/core/controller-evidence.js";
14
+ import { exactMergeGroupBinding } from "./dev-delivery-source-proof-replay.mjs";
14
15
 
15
16
  const REUSE_DECISION_SCHEMA =
16
17
  "kungfu.buildchain.source-qualification-reuse-decision/v1";
@@ -113,98 +114,6 @@ function gitPathRoot(ref, paths, label, cwd) {
113
114
  });
114
115
  }
115
116
 
116
- function exactMergeGroupBinding(input = {}) {
117
- const cwd = path.resolve(input.cwd || process.cwd());
118
- const mergeGroupHead = exactSha(input.mergeGroupHead, "mergeGroupHead");
119
- const expectedTree = exactSha(input.mergeGroupTree, "mergeGroupTree");
120
- const currentBase = exactSha(input.currentBase, "currentBase");
121
- const sourceHead = exactSha(input.sourceHead, "sourceHead");
122
- const actualTree = exactSha(
123
- git(["rev-parse", `${mergeGroupHead}^{tree}`], { cwd }),
124
- "mergeGroupTree",
125
- );
126
- if (actualTree !== expectedTree)
127
- return { ok: false, reason: "merge-group-tree-mismatch" };
128
- const [commit, ...parents] = git(
129
- ["rev-list", "--parents", "-n", "1", mergeGroupHead],
130
- { cwd },
131
- ).split(/\s+/u);
132
- if (commit !== mergeGroupHead) {
133
- return { ok: false, reason: "merge-group-parent-mismatch" };
134
- }
135
- if (
136
- parents.length === 2 &&
137
- parents[0] === currentBase &&
138
- parents[1] === sourceHead
139
- ) {
140
- return {
141
- ok: true,
142
- mergeGroupHead,
143
- mergeGroupTree: actualTree,
144
- parents,
145
- compositionMode: "two-parent-merge",
146
- replayedCommitTrees: [],
147
- };
148
- }
149
-
150
- const linearRange = (head) => {
151
- try {
152
- git(["merge-base", "--is-ancestor", currentBase, head], { cwd });
153
- } catch {
154
- return null;
155
- }
156
- const commits = git(
157
- ["rev-list", "--reverse", "--topo-order", `${currentBase}..${head}`],
158
- { cwd },
159
- )
160
- .split(/\s+/u)
161
- .filter(Boolean);
162
- if (commits.length === 0) return null;
163
- let expectedParent = currentBase;
164
- const trees = [];
165
- for (const rangeCommit of commits) {
166
- const [observedCommit, ...rangeParents] = git(
167
- ["rev-list", "--parents", "-n", "1", rangeCommit],
168
- { cwd },
169
- ).split(/\s+/u);
170
- if (
171
- observedCommit !== rangeCommit ||
172
- rangeParents.length !== 1 ||
173
- rangeParents[0] !== expectedParent
174
- ) {
175
- return null;
176
- }
177
- trees.push(
178
- exactSha(
179
- git(["rev-parse", `${rangeCommit}^{tree}`], { cwd }),
180
- "replayedCommitTree",
181
- ),
182
- );
183
- expectedParent = rangeCommit;
184
- }
185
- return trees;
186
- };
187
-
188
- const sourceTrees = linearRange(sourceHead);
189
- const mergeGroupTrees = linearRange(mergeGroupHead);
190
- if (
191
- !sourceTrees ||
192
- !mergeGroupTrees ||
193
- sourceTrees.length !== mergeGroupTrees.length ||
194
- sourceTrees.some((tree, index) => tree !== mergeGroupTrees[index])
195
- ) {
196
- return { ok: false, reason: "merge-group-parent-mismatch" };
197
- }
198
- return {
199
- ok: true,
200
- mergeGroupHead,
201
- mergeGroupTree: actualTree,
202
- parents,
203
- compositionMode: "linear-replay",
204
- replayedCommitTrees: mergeGroupTrees,
205
- };
206
- }
207
-
208
117
  export function sourceQualificationPredicates(input = {}) {
209
118
  const cwd = path.resolve(input.cwd || process.cwd());
210
119
  const repository = required(input.repository, "repository");
@@ -397,7 +306,8 @@ export function materializeReuseLifecycleEvidence({
397
306
  decisionRoot: decision.decisionRoot,
398
307
  sourceWorkflowRunId: decision.sourceWorkflowRunId,
399
308
  sourceHead: decision.sourceHead,
400
- qualifiedBase: decision.currentBase,
309
+ qualifiedBase: decision.qualifiedBase,
310
+ currentBase: decision.currentBase,
401
311
  mergeGroupHead: decision.mergeGroupHead,
402
312
  mergeGroupTree: decision.mergeGroupTree,
403
313
  },
@@ -446,7 +356,7 @@ export function verifySourceQualificationReuse(input = {}) {
446
356
  const verification = verifySourceQualificationProof(proof, {
447
357
  repository: required(input.repository, "repository"),
448
358
  protectedBase: required(input.protectedBase, "protectedBase"),
449
- qualifiedBase: exactSha(input.currentBase, "currentBase"),
359
+ qualifiedBase: exactSha(proof.qualifiedBase, "qualifiedBase"),
450
360
  sourceHead: exactSha(input.sourceHead, "sourceHead"),
451
361
  sourceWorkflowRunId: Number(input.sourceWorkflowRunId),
452
362
  controllerReceiptRoot: receipt.digest,
@@ -462,7 +372,7 @@ export function verifySourceQualificationReuse(input = {}) {
462
372
  }
463
373
  const current = sourceQualificationPredicates({
464
374
  ...input,
465
- qualifiedBase: input.currentBase,
375
+ qualifiedBase: proof.qualifiedBase,
466
376
  });
467
377
  const classification = classifyDevDeliveryDelta({
468
378
  proof,
@@ -476,7 +386,11 @@ export function verifySourceQualificationReuse(input = {}) {
476
386
  reason: classification.reason,
477
387
  });
478
388
  }
479
- const mergeGroup = exactMergeGroupBinding(input);
389
+ const mergeGroup = exactMergeGroupBinding({
390
+ ...input,
391
+ qualifiedBase: proof.qualifiedBase,
392
+ sourcePatchRoot: proof.sourcePatchRoot,
393
+ });
480
394
  if (!mergeGroup.ok) {
481
395
  return rootedDecision({
482
396
  schema: REUSE_DECISION_SCHEMA,
@@ -493,12 +407,14 @@ export function verifySourceQualificationReuse(input = {}) {
493
407
  sourceProofRoot: proof.proofRoot,
494
408
  sourceWorkflowRunId: proof.sourceWorkflowRunId,
495
409
  sourceHead: proof.sourceHead,
496
- currentBase: proof.qualifiedBase,
410
+ qualifiedBase: proof.qualifiedBase,
411
+ currentBase: exactSha(input.currentBase, "currentBase"),
497
412
  mergeGroupHead: mergeGroup.mergeGroupHead,
498
413
  mergeGroupTree: mergeGroup.mergeGroupTree,
499
414
  mergeGroupParents: mergeGroup.parents,
500
415
  mergeGroupCompositionMode: mergeGroup.compositionMode,
501
416
  mergeGroupReplayTrees: mergeGroup.replayedCommitTrees,
417
+ mergeGroupReplayPatchRoots: mergeGroup.replayedCommitPatchRoots,
502
418
  predicateRoots: {
503
419
  sourceIdentityRoot: proof.sourceIdentityRoot,
504
420
  sourcePatchRoot: proof.sourcePatchRoot,
@@ -5,11 +5,160 @@ import { execFileSync } from "node:child_process";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import { createReleaseCandidatePassport, validateReleaseCandidatePassport } from "../packages/core/release-candidate.js";
7
7
  import { scanV4FloatingConsumerPolicy, v4ConsumerPolicyScannerRoot } from "../packages/core/v4-floating-consumer-policy.js";
8
+ import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
9
+ import { V4_STAGE_CAPSULE_CONTRACT, V4_STAGE_CAPSULE_IDENTITY_CONTRACT, v4StageCapsuleIdentityRoot, v4StageCapsuleRoot, validateV4StageCapsule } from "../packages/core/v4-stage-capsule.js";
10
+ import { v4RuntimeResumeDocumentRoot } from "../packages/core/v4-runtime-ref-resume-authority.js";
8
11
  import { writeGitHubOutputs } from "./build-contract-core.mjs";
9
12
 
10
13
  const env = (name, fallback = "") => process.env[name] || fallback;
11
14
  const readJsonFile = (filePath) => JSON.parse(fs.readFileSync(filePath, "utf8"));
12
15
 
16
+ export function createReleaseCandidateStageCapsules({
17
+ passport,
18
+ buildSummary,
19
+ coordinates,
20
+ }) {
21
+ if (!passport.consumerPolicy?.receiptRoot) return undefined;
22
+ if (
23
+ coordinates.repository !== passport.repository ||
24
+ String(coordinates.runId) !== String(passport.workflow.runId) ||
25
+ String(coordinates.runAttempt) !== String(passport.workflow.runAttempt) ||
26
+ coordinates.sourceSha !== passport.source.headSha
27
+ ) {
28
+ throw new Error(
29
+ "Stage Capsule coordinate set does not bind the candidate run",
30
+ );
31
+ }
32
+ const coordinateByPlatform = new Map(
33
+ coordinates.artifacts.map((entry) => [entry.platformId, entry]),
34
+ );
35
+ const qualificationRoot = passport.controllerReceipts?.[0]?.receiptDigest;
36
+ const entries = passport.platformMatrix
37
+ .map((platform) => {
38
+ const coordinate = coordinateByPlatform.get(platform.platformId);
39
+ if (!coordinate || coordinate.name !== platform.artifactName) {
40
+ throw new Error(
41
+ `Stage Capsule coordinate missing for ${platform.platformId}`,
42
+ );
43
+ }
44
+ const manifest = readJsonFile(platform.manifestPath);
45
+ const identity = {
46
+ schema: V4_STAGE_CAPSULE_IDENTITY_CONTRACT,
47
+ sourceRoot: v4ContentRoot("candidate-identity", passport.source),
48
+ platform: platform.platformId,
49
+ platformRoot: v4ContentRoot("candidate-identity", manifest.platform),
50
+ stage: "verify",
51
+ toolchainRoots: [],
52
+ runtimeRoot: v4ContentRoot("candidate-identity", {
53
+ sha: passport.buildchain.sha,
54
+ }),
55
+ policyRoot: passport.consumerPolicy.receiptRoot,
56
+ declaredInputs: [
57
+ {
58
+ name: "release-candidate",
59
+ root: `sha256:${passport.candidateHash}`,
60
+ },
61
+ ],
62
+ transformationRoot: v4ContentRoot("candidate-identity", {
63
+ lifecycle: manifest.lifecycle,
64
+ summary: platform.summary,
65
+ }),
66
+ outputManifestRoot: v4ContentRoot(
67
+ "stage-capsule-artifact-manifest",
68
+ { artifact: coordinate, manifest },
69
+ ),
70
+ qualificationRoot,
71
+ observationRoots: [
72
+ {
73
+ name: "provider-coordinate",
74
+ root: v4ContentRoot("provider-readback-sample", coordinate),
75
+ },
76
+ ],
77
+ };
78
+ const capsule = {
79
+ schema: V4_STAGE_CAPSULE_CONTRACT,
80
+ writerAuthority: "typescript-v3",
81
+ rustAuthority: "validation-only",
82
+ identity,
83
+ identityRoot: v4StageCapsuleIdentityRoot(identity),
84
+ retentionPromise: {
85
+ class: "github-artifact",
86
+ retainUntil: coordinate.expiresAt,
87
+ },
88
+ capsuleRoot: `sha256:${"0".repeat(64)}`,
89
+ };
90
+ capsule.capsuleRoot = v4StageCapsuleRoot(capsule);
91
+ validateV4StageCapsule(capsule);
92
+ return {
93
+ platform: platform.platformId,
94
+ artifactName: coordinate.name,
95
+ artifactDigest: coordinate.digest,
96
+ artifact: coordinate,
97
+ capsule,
98
+ };
99
+ })
100
+ .sort((left, right) => left.platform.localeCompare(right.platform));
101
+ const body = {
102
+ schemaVersion: 1,
103
+ contract: "kungfu-buildchain-v4-release-candidate-stage-capsules/v1",
104
+ status: "sealed",
105
+ repository: passport.repository,
106
+ buildAttempt: {
107
+ id: `github-run:${passport.workflow.runId}:attempt:${passport.workflow.runAttempt}`,
108
+ runtimeSha: passport.buildchain.sha,
109
+ },
110
+ source: {
111
+ sha: passport.source.headSha,
112
+ treeSha: passport.source.treeHash,
113
+ },
114
+ consumerPolicyReceiptRoot: passport.consumerPolicy.receiptRoot,
115
+ capsules: entries,
116
+ };
117
+ return { ...body, root: v4RuntimeResumeDocumentRoot(body) };
118
+ }
119
+
120
+ function writeReleaseCandidateStageCapsules({
121
+ passport,
122
+ buildSummary,
123
+ outputPath,
124
+ }) {
125
+ const coordinatesPath = path.resolve(
126
+ env(
127
+ "BUILDCHAIN_ARTIFACT_COORDINATES_PATH",
128
+ ".buildchain/artifacts/artifact-coordinates.json",
129
+ ),
130
+ );
131
+ if (!fs.existsSync(coordinatesPath)) {
132
+ if (passport.consumerPolicy?.receiptRoot) {
133
+ throw new Error(
134
+ "v4 release candidate Stage Capsules require exact artifact coordinates",
135
+ );
136
+ }
137
+ return { path: "", root: "" };
138
+ }
139
+ const stageCapsules = createReleaseCandidateStageCapsules({
140
+ passport,
141
+ buildSummary,
142
+ coordinates: readJsonFile(coordinatesPath),
143
+ });
144
+ if (!stageCapsules) return { path: "", root: "" };
145
+ const stageCapsulesPath = path.join(
146
+ path.dirname(outputPath),
147
+ "release-candidate-stage-capsules.json",
148
+ );
149
+ fs.writeFileSync(
150
+ stageCapsulesPath,
151
+ `${JSON.stringify(stageCapsules, null, 2)}\n`,
152
+ );
153
+ return {
154
+ path: path
155
+ .relative(process.cwd(), stageCapsulesPath)
156
+ .split(path.sep)
157
+ .join("/"),
158
+ root: stageCapsules.root,
159
+ };
160
+ }
161
+
13
162
  export function resolveLegacyConsumerPolicyReceipt(options = {}) {
14
163
  if (options.json) return { receipt: JSON.parse(options.json), receiptRoot: options.receiptRoot };
15
164
  if (options.repository !== "kungfu-systems/buildchain" || options.targetChannel !== "alpha" || options.workflowShellRef !== "v4-alpha" || options.runtimeOverride !== true) return undefined;
@@ -80,6 +229,7 @@ export function generateReleaseCandidatePassportCli() {
80
229
  if (!validation.ok) throw new Error(`release candidate passport invalid: ${validation.errors.join("; ")}`);
81
230
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
82
231
  fs.writeFileSync(outputPath, `${JSON.stringify(passport, null, 2)}\n`);
232
+ const stageCapsules = writeReleaseCandidateStageCapsules({ passport, buildSummary, outputPath });
83
233
  writeGitHubOutputs({
84
234
  "release-candidate-passport-path": path.relative(process.cwd(), outputPath).split(path.sep).join("/"),
85
235
  "release-candidate-passport-json": JSON.stringify({
@@ -94,6 +244,8 @@ export function generateReleaseCandidatePassportCli() {
94
244
  consumerPolicy: passport.consumerPolicy,
95
245
  controllerReceipts: passport.controllerReceipts || [],
96
246
  }),
247
+ "release-candidate-stage-capsules-path": stageCapsules.path,
248
+ "release-candidate-stage-capsules-root": stageCapsules.root,
97
249
  });
98
250
  return passport;
99
251
  }
@@ -54,6 +54,23 @@ const V4_COMPATIBILITY_FACTS_SCHEMA =
54
54
  "schemas/v4-compatibility-facts-v1.schema.json";
55
55
  const V4_COMPATIBILITY_FACTS_SCHEMA_SOURCE =
56
56
  "contracts/v4-compatibility-facts-v1.schema.json";
57
+ const V4_PUBLICATION_REHEARSAL_SCHEMA =
58
+ "schemas/v4-publication-rehearsal-capsule-v1.schema.json";
59
+ const V4_PUBLICATION_REHEARSAL_SCHEMA_SOURCE =
60
+ "contracts/v4-publication-rehearsal-capsule-v1.schema.json";
61
+ const V4_ADOPTER_DELIVERY_SCHEMA =
62
+ "schemas/v4-adopter-delivery-v1.schema.json";
63
+ const V4_ADOPTER_DELIVERY_SCHEMA_SOURCE =
64
+ "contracts/v4-adopter-delivery-v1.schema.json";
65
+ const RELEASE_TAIL_CAPABILITIES_SCHEMA =
66
+ "schemas/release-tail-capabilities-v1.schema.json";
67
+ const RELEASE_TAIL_CAPABILITIES_SCHEMA_SOURCE =
68
+ "contracts/release-tail-capabilities-v1.schema.json";
69
+ const PUBLICATION_REHEARSAL_SITE_SCHEMAS = [
70
+ V4_PUBLICATION_REHEARSAL_SCHEMA,
71
+ V4_ADOPTER_DELIVERY_SCHEMA,
72
+ RELEASE_TAIL_CAPABILITIES_SCHEMA,
73
+ ];
57
74
  const root = path.resolve(import.meta.dirname, "..");
58
75
  const outputDir = path.join(root, "dist", "site");
59
76
  const requireFromHere = createRequire(import.meta.url);
@@ -84,6 +101,9 @@ function stableJson(value) {
84
101
  function siteFileBytes(name, value) {
85
102
  if (name === DEV_DELIVERY_AUTHORITY_SCHEMA) return readText(DEV_DELIVERY_AUTHORITY_SCHEMA_SOURCE);
86
103
  if (name === V4_COMPATIBILITY_FACTS_SCHEMA) return readText(V4_COMPATIBILITY_FACTS_SCHEMA_SOURCE);
104
+ if (name === V4_PUBLICATION_REHEARSAL_SCHEMA) return readText(V4_PUBLICATION_REHEARSAL_SCHEMA_SOURCE);
105
+ if (name === V4_ADOPTER_DELIVERY_SCHEMA) return readText(V4_ADOPTER_DELIVERY_SCHEMA_SOURCE);
106
+ if (name === RELEASE_TAIL_CAPABILITIES_SCHEMA) return readText(RELEASE_TAIL_CAPABILITIES_SCHEMA_SOURCE);
87
107
  return stableJson(value);
88
108
  }
89
109
 
@@ -396,6 +416,7 @@ const manualMetaById = new Map(Object.entries({
396
416
  "observed-evidence-patrol": { capabilityGroup: "governance-versioning", audience: ["release-operator", "consumer", "agent"], maturity: "preview", order: 140 },
397
417
  "engineering-housekeeper": { capabilityGroup: "governance-versioning", audience: ["maintainer", "consumer", "agent"], maturity: "preview", order: 145 },
398
418
  "reusable-build-surface": { capabilityGroup: "reusable-build", audience: ["consumer", "release-operator"], maturity: "stable", order: 200 },
419
+ "v4-adopter-delivery": { capabilityGroup: "reusable-build", audience: ["consumer", "developer", "agent"], maturity: "preview", order: 205 },
399
420
  "lifecycle-protocol": { capabilityGroup: "reusable-build", audience: ["consumer", "developer"], maturity: "stable", order: 210 },
400
421
  "runtime-train-validation": { capabilityGroup: "governance-versioning", audience: ["maintainer", "consumer"], maturity: "stable", order: 220 },
401
422
  "kfd-support": { capabilityGroup: "kfd-trust", audience: ["agent", "maintainer"], maturity: "stable", order: 300 },
@@ -956,6 +977,7 @@ function buildSiteBundle() {
956
977
  "schemas/kfd-product-gate-input-v1.schema.json",
957
978
  "schemas/dev-delivery-authority-v2.schema.json",
958
979
  "schemas/v4-compatibility-facts-v1.schema.json",
980
+ ...PUBLICATION_REHEARSAL_SITE_SCHEMAS,
959
981
  "kfd-support.json",
960
982
  "artifact-evidence.json",
961
983
  "product-mechanism.json",
@@ -992,6 +1014,7 @@ function buildSiteBundle() {
992
1014
  "schemas/kfd-product-gate-input-v1.schema.json",
993
1015
  "schemas/dev-delivery-authority-v2.schema.json",
994
1016
  "schemas/v4-compatibility-facts-v1.schema.json",
1017
+ ...PUBLICATION_REHEARSAL_SITE_SCHEMAS,
995
1018
  "buildchain-contract.json",
996
1019
  "kfd-claims.json",
997
1020
  "product-mechanism.json",
@@ -1124,6 +1147,7 @@ function buildSiteBundle() {
1124
1147
  "schemas/kfd-product-gate-input-v1.schema.json",
1125
1148
  "schemas/dev-delivery-authority-v2.schema.json",
1126
1149
  "schemas/v4-compatibility-facts-v1.schema.json",
1150
+ ...PUBLICATION_REHEARSAL_SITE_SCHEMAS,
1127
1151
  "buildchain-contract.json",
1128
1152
  "kfd-upstream-aggregate.json",
1129
1153
  "kfd-claims.json",
@@ -1304,6 +1328,12 @@ function buildSiteBundle() {
1304
1328
  [V4_COMPATIBILITY_FACTS_SCHEMA]: readJson(
1305
1329
  V4_COMPATIBILITY_FACTS_SCHEMA_SOURCE,
1306
1330
  ),
1331
+ ...Object.fromEntries(
1332
+ PUBLICATION_REHEARSAL_SITE_SCHEMAS.map((name) => [
1333
+ name,
1334
+ JSON.parse(siteFileBytes(name)),
1335
+ ]),
1336
+ ),
1307
1337
  "badge-endpoint-registry.json": badgeEndpointRegistry,
1308
1338
  "publication-registry.json": publicationRegistry,
1309
1339
  "buildchain-contract.json": createBuildchainContractWorld({ root, controllerRegistry }),
@@ -11,6 +11,10 @@ import {
11
11
  writeReleaseTailTransaction,
12
12
  } from "../packages/core/release-tail-provider-plane.js";
13
13
  import { diagnoseLegacyReleaseTailHooks } from "../packages/core/release-tail-compatibility.js";
14
+ import {
15
+ V4_PUBLICATION_REHEARSAL_EVIDENCE_CONTRACT,
16
+ executeV4PublicationRehearsal,
17
+ } from "../packages/core/v4-publication-rehearsal.js";
14
18
 
15
19
  function flag(args, name, fallback = "") {
16
20
  const index = args.indexOf(`--${name}`);
@@ -34,8 +38,52 @@ function output(value, filePath = "") {
34
38
  process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
35
39
  }
36
40
 
37
- export function runReleaseTailCli(args = process.argv.slice(2)) {
41
+ export async function runReleaseTailCli(args = process.argv.slice(2)) {
38
42
  const [mode = "status", ...options] = args;
43
+ if (mode === "rehearse") {
44
+ const capsulePath = flag(options, "capsule");
45
+ const candidateRoot = flag(options, "candidate-root");
46
+ const statePath = flag(options, "state");
47
+ const evidencePath = flag(options, "evidence");
48
+ for (const [name, value] of Object.entries({
49
+ "--capsule": capsulePath,
50
+ "--candidate-root": candidateRoot,
51
+ "--state": statePath,
52
+ "--evidence": evidencePath,
53
+ }))
54
+ if (!value || !path.isAbsolute(value))
55
+ throw new Error(`${name} must be an explicit absolute path`);
56
+ const rehearsalMode = flag(options, "mode", "simulate");
57
+ if (!new Set(["simulate", "replay"]).has(rehearsalMode))
58
+ throw new Error(
59
+ "local rehearsal --mode must be simulate or replay; authorized provider rehearsal uses the reusable workflow",
60
+ );
61
+ try {
62
+ const result = await executeV4PublicationRehearsal({
63
+ capsule: readJson(capsulePath, "--capsule"),
64
+ candidateRoot,
65
+ mode: rehearsalMode,
66
+ checkpoint: (transaction) =>
67
+ writeReleaseTailTransaction(statePath, transaction),
68
+ });
69
+ writeReleaseTailTransaction(statePath, result.transaction);
70
+ output(result.evidence, evidencePath);
71
+ return result;
72
+ } catch (error) {
73
+ output(
74
+ {
75
+ schema: V4_PUBLICATION_REHEARSAL_EVIDENCE_CONTRACT,
76
+ status: "rejected",
77
+ code: error.code || "publication-rehearsal-runtime-error",
78
+ message: error.message,
79
+ productionAuthority: false,
80
+ releasePassport: null,
81
+ },
82
+ evidencePath,
83
+ );
84
+ throw error;
85
+ }
86
+ }
39
87
  if (mode === "plan") {
40
88
  const plan = compileReleaseTailDeclaration(
41
89
  readJson(flag(options, "declaration"), "--declaration"),
@@ -95,7 +143,7 @@ export function runReleaseTailCli(args = process.argv.slice(2)) {
95
143
  return report;
96
144
  }
97
145
  throw new Error(
98
- "usage: buildchain release-tail <plan|init|status|verify|compat> [--declaration <json-or-path>] [--state <path>] [--hooks-json <json-or-path>] [--output <path>]",
146
+ "usage: buildchain release-tail <plan|init|status|verify|compat|rehearse> [--declaration <json-or-path>] [--capsule <absolute-path>] [--candidate-root <absolute-path>] [--mode <simulate|replay>] [--state <absolute-path>] [--evidence <absolute-path>] [--hooks-json <json-or-path>] [--output <path>]",
99
147
  );
100
148
  }
101
149
 
@@ -105,7 +153,7 @@ if (
105
153
  import.meta.url === pathToFileURL(process.argv[1]).href
106
154
  ) {
107
155
  try {
108
- runReleaseTailCli();
156
+ await runReleaseTailCli();
109
157
  } catch (error) {
110
158
  console.error(`release-tail: ${error.message}`);
111
159
  process.exitCode = 1;