@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.
- package/actions/release-tail/README.md +8 -0
- package/architecture/internal-capabilities.json +65 -5
- package/architecture/maintainability-policy.json +22 -16
- package/architecture/release-tail-contract-inventory.json +5 -2
- package/architecture/v4-adopter-delivery-parity.json +32 -5
- package/architecture/v4-publication-rehearsal-parity.json +84 -0
- package/bin/buildchain.mjs +7 -1
- package/bin/internal/adopter-delivery-cli.mjs +70 -0
- package/bin/internal/command-registry.mjs +1 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
- package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
- package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/agent-index.json +3 -0
- package/dist/site/artifact-schemas.json +6 -0
- package/dist/site/buildchain-contract.json +14 -14
- package/dist/site/buildchain-site.json +219 -22
- package/dist/site/capability-registry.json +10 -9
- package/dist/site/cli-registry.json +53 -1
- package/dist/site/kfd-claims.json +253 -14
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +20 -5
- package/dist/site/node-api-registry.json +11501 -9312
- package/dist/site/page-registry.json +202 -13
- package/dist/site/public-surface-audit.json +195 -11
- package/dist/site/publication-authority-registry.json +17 -4
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +14 -0
- package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
- package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
- package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/site-manifest.json +17 -9
- package/dist/site/workflow-registry.json +89 -8
- package/docs/MAP.md +3 -1
- package/docs/cli-reference.md +68 -0
- package/docs/cli.md +8 -0
- package/docs/node-api-reference.md +198 -51
- package/docs/release-tail-provider-plane.md +30 -0
- package/docs/v4-adopter-delivery.md +133 -0
- package/docs/v4-publication-rehearsal.md +130 -0
- package/package.json +17 -3
- package/packages/core/adopter-delivery-passport.js +259 -0
- package/packages/core/adopter-delivery-vectors.js +158 -0
- package/packages/core/adopter-delivery-vectors.json +77 -0
- package/packages/core/buildchain-agent-manuals.js +1 -0
- package/packages/core/buildchain-config.js +84 -0
- package/packages/core/buildchain-delivery-bootstrap.js +240 -0
- package/packages/core/buildchain-delivery-infrastructure.js +164 -0
- package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
- package/packages/core/legacy-kfd-adopter-driver.js +167 -0
- package/packages/core/published-delivery-authority.js +266 -0
- package/packages/core/release-passport.js +26 -8
- package/packages/core/v4-adopter-delivery-parity.js +3 -3
- package/packages/core/v4-adopter-delivery.js +359 -0
- package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
- package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
- package/packages/core/v4-publication-rehearsal.js +481 -0
- package/scripts/buildchain-cli-help.mjs +10 -0
- package/scripts/check-inventory.mjs +3 -3
- package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
- package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
- package/scripts/generate-release-candidate-passport.mjs +152 -0
- package/scripts/generate-site-bundle.mjs +30 -0
- package/scripts/release-tail.mjs +51 -3
- package/scripts/resume-from-candidate-run.mjs +545 -15
- package/scripts/site-capability-metadata.mjs +16 -0
- 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.
|
|
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(
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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 }),
|
package/scripts/release-tail.mjs
CHANGED
|
@@ -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;
|