@kungfu-tech/buildchain 4.0.1-alpha.4 → 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/decisions/0002-next-development-transition.md +101 -0
- package/architecture/internal-capabilities.json +117 -8
- package/architecture/maintainability-policy.json +38 -20
- package/architecture/release-tail-contract-inventory.json +5 -2
- package/architecture/v3-core-mechanism-inventory.json +89 -0
- package/architecture/v4-adopter-delivery-parity.json +32 -5
- package/architecture/v4-capability-state-machine-manifest.json +73 -0
- package/architecture/v4-compatibility-facts-parity.json +77 -0
- package/architecture/v4-next-development-parity.json +192 -0
- package/architecture/v4-publication-rehearsal-parity.json +84 -0
- package/architecture/v4-release-train-parity.json +1 -0
- package/architecture/v4-tail-reseal-parity.json +150 -0
- package/bin/buildchain.mjs +23 -2
- package/bin/internal/adopter-delivery-cli.mjs +70 -0
- package/bin/internal/command-registry.mjs +3 -0
- package/bin/internal/compatibility-facts-cli.mjs +101 -0
- package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -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-compatibility-facts-v1/shared.json +1129 -0
- package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
- 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/fixtures/v4-tail-reseal-v1/valid.json +229 -0
- package/contracts/next-development-request-v1.schema.json +66 -0
- package/contracts/next-development-transition-v1.schema.json +292 -0
- package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
- package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
- package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/contracts/v4-tail-reseal-v1.schema.json +276 -0
- package/dist/site/agent-index.json +4 -0
- package/dist/site/artifact-schemas.json +8 -0
- package/dist/site/buildchain-contract.json +1393 -28
- package/dist/site/buildchain-site.json +462 -27
- package/dist/site/capability-registry.json +17 -14
- package/dist/site/cli-registry.json +159 -1
- package/dist/site/kfd-claims.json +429 -15
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +52 -6
- package/dist/site/node-api-registry.json +13972 -9697
- package/dist/site/page-registry.json +428 -17
- package/dist/site/public-surface-audit.json +429 -11
- package/dist/site/publication-authority-registry.json +22 -4
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +21 -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-compatibility-facts-v1.schema.json +230 -0
- package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/site-manifest.json +34 -10
- package/dist/site/workflow-registry.json +121 -8
- package/docs/MAP.md +7 -2
- package/docs/cli-reference.md +193 -0
- package/docs/cli.md +8 -0
- package/docs/dev-alpha-candidate-patrol.md +8 -0
- package/docs/next-development-transition.md +118 -0
- package/docs/node-api-reference.md +448 -168
- package/docs/release-tail-provider-plane.md +30 -0
- package/docs/v4-adopter-delivery.md +133 -0
- package/docs/v4-compatibility-facts.md +91 -0
- package/docs/v4-publication-rehearsal.md +130 -0
- package/docs/v4-tail-reseal.md +73 -0
- package/docs/versioning.md +4 -3
- package/package.json +29 -6
- 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 +3 -0
- package/packages/core/buildchain-compatibility-authority.js +336 -0
- package/packages/core/buildchain-compatibility-fact.js +394 -0
- package/packages/core/buildchain-compatibility-facts.json +217 -0
- package/packages/core/buildchain-compatibility-proof.js +566 -0
- package/packages/core/buildchain-config.js +84 -0
- package/packages/core/buildchain-contract.js +93 -141
- 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/channel-candidate.js +8 -0
- package/packages/core/channel-promotion-baseline.js +55 -0
- package/packages/core/dev-alpha-candidate-selection.js +10 -2
- package/packages/core/dev-delivery-execution-transfer.js +21 -1
- package/packages/core/dev-delivery-process-boundary.js +19 -6
- package/packages/core/dev-delivery-provider-heartbeat.js +18 -6
- package/packages/core/kungfu-temporal-fact.js +557 -0
- package/packages/core/legacy-kfd-adopter-driver.js +167 -0
- package/packages/core/next-development-candidate-reservation.js +186 -0
- package/packages/core/next-development-controller.js +728 -0
- package/packages/core/next-development-projection.js +287 -0
- package/packages/core/next-development-transition.js +738 -0
- package/packages/core/paper-agent-entry.js +11 -5
- package/packages/core/paper.js +16 -5
- 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-canonical-contracts.js +5 -0
- package/packages/core/v4-floating-consumer-policy.js +29 -9
- 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/packages/core/v4-tail-reseal-contract.js +26 -0
- package/packages/core/v4-tail-reseal-github.js +178 -0
- package/packages/core/v4-tail-reseal-receipt.js +225 -0
- package/packages/core/v4-tail-reseal.js +594 -0
- package/scripts/buildchain-cli-help.mjs +30 -0
- package/scripts/check-inventory.mjs +14 -3
- package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
- package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
- package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
- package/scripts/generate-next-development-guidance.mjs +49 -0
- package/scripts/generate-release-candidate-passport.mjs +152 -0
- package/scripts/generate-site-bundle.mjs +46 -3
- package/scripts/init-repo.mjs +22 -4
- package/scripts/next-development-self-dogfood-harness.mjs +460 -0
- package/scripts/next-development-self-dogfood.mjs +597 -0
- package/scripts/next-development-transition.mjs +47 -0
- package/scripts/release-tail.mjs +51 -3
- package/scripts/resume-from-candidate-run.mjs +545 -15
- package/scripts/site-capability-metadata.mjs +28 -0
- package/scripts/v4-consumer-policy.mjs +1 -5
- package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
- package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
- package/scripts/v4-tail-reseal.mjs +416 -0
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
candidateFromDecision,
|
|
21
21
|
selectLatestQualifiedSource,
|
|
22
22
|
} from "../packages/core/dev-alpha-candidate-selection.js";
|
|
23
|
+
import { readGitHubNextDevelopmentVersionReservation } from "../packages/core/next-development-candidate-reservation.js";
|
|
23
24
|
|
|
24
25
|
export { reconcileActiveReleaseTrain, selectLatestQualifiedSource };
|
|
25
26
|
|
|
@@ -617,6 +618,7 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
617
618
|
comparison,
|
|
618
619
|
workflowEvidence,
|
|
619
620
|
skippedNewerCommitCount,
|
|
621
|
+
versionReservation,
|
|
620
622
|
qualificationError,
|
|
621
623
|
} = await resolvePatrolSourceInputs({
|
|
622
624
|
client,
|
|
@@ -646,6 +648,7 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
646
648
|
observedSourceHeadSha,
|
|
647
649
|
skippedNewerCommitCount,
|
|
648
650
|
...(targetBaseline ? { targetBaseline } : {}),
|
|
651
|
+
...(versionReservation ? { versionReservation } : {}),
|
|
649
652
|
},
|
|
650
653
|
workflowEvidence,
|
|
651
654
|
requiredWorkflowPaths,
|
|
@@ -992,6 +995,20 @@ export function createGitHubChannelCandidateClient({
|
|
|
992
995
|
targetBranch,
|
|
993
996
|
parseCandidate: managedCandidateFromPullRequest,
|
|
994
997
|
}),
|
|
998
|
+
async readNextDevelopmentVersionReservation({
|
|
999
|
+
reservationSha,
|
|
1000
|
+
candidateSha,
|
|
1001
|
+
targetVersion,
|
|
1002
|
+
}) {
|
|
1003
|
+
return readGitHubNextDevelopmentVersionReservation({
|
|
1004
|
+
api,
|
|
1005
|
+
owner,
|
|
1006
|
+
repo,
|
|
1007
|
+
reservationSha,
|
|
1008
|
+
candidateSha,
|
|
1009
|
+
targetVersion,
|
|
1010
|
+
});
|
|
1011
|
+
},
|
|
995
1012
|
async listCompletedWorkflowRuns(workflowPathValue, sourceBranch) {
|
|
996
1013
|
const runs = [];
|
|
997
1014
|
for (let page = 1; page <= 10; page += 1) {
|
|
@@ -1015,7 +1032,11 @@ export function createGitHubChannelCandidateClient({
|
|
|
1015
1032
|
for (const commit of rows) {
|
|
1016
1033
|
const commitSha = text(commit.sha);
|
|
1017
1034
|
if (commitSha === targetSha) return commits;
|
|
1018
|
-
commits.push(
|
|
1035
|
+
commits.push({
|
|
1036
|
+
sha: commitSha,
|
|
1037
|
+
message: text(commit.commit?.message),
|
|
1038
|
+
parents: (commit.parents || []).map((parent) => text(parent.sha)),
|
|
1039
|
+
});
|
|
1019
1040
|
}
|
|
1020
1041
|
if (rows.length < 100) return commits;
|
|
1021
1042
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
const SHA = /^[0-9a-f]{40}$/u;
|
|
6
|
+
|
|
7
|
+
function exactSha(value, label) {
|
|
8
|
+
const normalized = String(value || "")
|
|
9
|
+
.trim()
|
|
10
|
+
.toLowerCase();
|
|
11
|
+
if (!SHA.test(normalized))
|
|
12
|
+
throw new Error(`${label} must be an exact 40-hex Git SHA`);
|
|
13
|
+
return normalized;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function git(args, { cwd, encoding = "utf8" }) {
|
|
17
|
+
const result = spawnSync("git", args, {
|
|
18
|
+
cwd,
|
|
19
|
+
encoding,
|
|
20
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
21
|
+
});
|
|
22
|
+
if (result.status !== 0)
|
|
23
|
+
throw new Error(
|
|
24
|
+
`git ${args.join(" ")} failed: ${String(result.stderr || "").trim()}`,
|
|
25
|
+
);
|
|
26
|
+
return encoding === null ? result.stdout : String(result.stdout || "").trim();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function patchRoot(base, head, cwd) {
|
|
30
|
+
const patch = git(
|
|
31
|
+
[
|
|
32
|
+
"diff",
|
|
33
|
+
"--binary",
|
|
34
|
+
"--full-index",
|
|
35
|
+
"--no-ext-diff",
|
|
36
|
+
`${base}..${head}`,
|
|
37
|
+
],
|
|
38
|
+
{ cwd, encoding: null },
|
|
39
|
+
);
|
|
40
|
+
return `sha256:${crypto.createHash("sha256").update(patch).digest("hex")}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function linearRange(base, head, cwd) {
|
|
44
|
+
try {
|
|
45
|
+
git(["merge-base", "--is-ancestor", base, head], { cwd });
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const commits = git(
|
|
50
|
+
["rev-list", "--reverse", "--topo-order", `${base}..${head}`],
|
|
51
|
+
{ cwd },
|
|
52
|
+
)
|
|
53
|
+
.split(/\s+/u)
|
|
54
|
+
.filter(Boolean);
|
|
55
|
+
if (commits.length === 0) return null;
|
|
56
|
+
let expectedParent = base;
|
|
57
|
+
const trees = [];
|
|
58
|
+
const patchRoots = [];
|
|
59
|
+
for (const rangeCommit of commits) {
|
|
60
|
+
const [observedCommit, ...parents] = git(
|
|
61
|
+
["rev-list", "--parents", "-n", "1", rangeCommit],
|
|
62
|
+
{ cwd },
|
|
63
|
+
).split(/\s+/u);
|
|
64
|
+
if (
|
|
65
|
+
observedCommit !== rangeCommit ||
|
|
66
|
+
parents.length !== 1 ||
|
|
67
|
+
parents[0] !== expectedParent
|
|
68
|
+
) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
trees.push(
|
|
72
|
+
exactSha(
|
|
73
|
+
git(["rev-parse", `${rangeCommit}^{tree}`], { cwd }),
|
|
74
|
+
"replayedCommitTree",
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
patchRoots.push(patchRoot(expectedParent, rangeCommit, cwd));
|
|
78
|
+
expectedParent = rangeCommit;
|
|
79
|
+
}
|
|
80
|
+
return { commits, trees, patchRoots };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function exactMergeGroupBinding(input = {}) {
|
|
84
|
+
const cwd = path.resolve(input.cwd || process.cwd());
|
|
85
|
+
const mergeGroupHead = exactSha(input.mergeGroupHead, "mergeGroupHead");
|
|
86
|
+
const expectedTree = exactSha(input.mergeGroupTree, "mergeGroupTree");
|
|
87
|
+
const qualifiedBase = exactSha(input.qualifiedBase, "qualifiedBase");
|
|
88
|
+
const currentBase = exactSha(input.currentBase, "currentBase");
|
|
89
|
+
const sourceHead = exactSha(input.sourceHead, "sourceHead");
|
|
90
|
+
const sourcePatchRoot = String(input.sourcePatchRoot || "").trim();
|
|
91
|
+
const actualTree = exactSha(
|
|
92
|
+
git(["rev-parse", `${mergeGroupHead}^{tree}`], { cwd }),
|
|
93
|
+
"mergeGroupTree",
|
|
94
|
+
);
|
|
95
|
+
if (actualTree !== expectedTree)
|
|
96
|
+
return { ok: false, reason: "merge-group-tree-mismatch" };
|
|
97
|
+
try {
|
|
98
|
+
git(["merge-base", "--is-ancestor", qualifiedBase, currentBase], { cwd });
|
|
99
|
+
} catch {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
reason: "current-base-not-descendant-of-qualified-base",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (!/^sha256:[0-9a-f]{64}$/u.test(sourcePatchRoot))
|
|
106
|
+
throw new Error("sourcePatchRoot must be an exact SHA-256 root");
|
|
107
|
+
if (patchRoot(currentBase, mergeGroupHead, cwd) !== sourcePatchRoot)
|
|
108
|
+
return { ok: false, reason: "merge-group-source-patch-mismatch" };
|
|
109
|
+
|
|
110
|
+
const [commit, ...parents] = git(
|
|
111
|
+
["rev-list", "--parents", "-n", "1", mergeGroupHead],
|
|
112
|
+
{ cwd },
|
|
113
|
+
).split(/\s+/u);
|
|
114
|
+
if (commit !== mergeGroupHead)
|
|
115
|
+
return { ok: false, reason: "merge-group-parent-mismatch" };
|
|
116
|
+
if (
|
|
117
|
+
parents.length === 2 &&
|
|
118
|
+
parents[0] === currentBase &&
|
|
119
|
+
parents[1] === sourceHead
|
|
120
|
+
) {
|
|
121
|
+
return {
|
|
122
|
+
ok: true,
|
|
123
|
+
mergeGroupHead,
|
|
124
|
+
mergeGroupTree: actualTree,
|
|
125
|
+
parents,
|
|
126
|
+
compositionMode: "two-parent-merge",
|
|
127
|
+
replayedCommitTrees: [],
|
|
128
|
+
replayedCommitPatchRoots: [],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const sourceRange = linearRange(qualifiedBase, sourceHead, cwd);
|
|
133
|
+
const mergeGroupRange = linearRange(currentBase, mergeGroupHead, cwd);
|
|
134
|
+
if (
|
|
135
|
+
!sourceRange ||
|
|
136
|
+
!mergeGroupRange ||
|
|
137
|
+
sourceRange.commits.length !== mergeGroupRange.commits.length ||
|
|
138
|
+
sourceRange.patchRoots.some(
|
|
139
|
+
(root, index) => root !== mergeGroupRange.patchRoots[index],
|
|
140
|
+
)
|
|
141
|
+
) {
|
|
142
|
+
return { ok: false, reason: "merge-group-parent-mismatch" };
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
ok: true,
|
|
146
|
+
mergeGroupHead,
|
|
147
|
+
mergeGroupTree: actualTree,
|
|
148
|
+
parents,
|
|
149
|
+
compositionMode: "linear-replay",
|
|
150
|
+
replayedCommitTrees: mergeGroupRange.trees,
|
|
151
|
+
replayedCommitPatchRoots: mergeGroupRange.patchRoots,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -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,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
import { nextDevelopmentManual } from "../packages/core/next-development-projection.js";
|
|
8
|
+
|
|
9
|
+
export const NEXT_DEVELOPMENT_MANUAL_PATH =
|
|
10
|
+
"docs/next-development-transition.md";
|
|
11
|
+
|
|
12
|
+
export function generateNextDevelopmentGuidance({
|
|
13
|
+
cwd = process.cwd(),
|
|
14
|
+
check = false,
|
|
15
|
+
} = {}) {
|
|
16
|
+
const target = path.resolve(cwd, NEXT_DEVELOPMENT_MANUAL_PATH);
|
|
17
|
+
const expected = nextDevelopmentManual();
|
|
18
|
+
const current = fs.existsSync(target) ? fs.readFileSync(target, "utf8") : "";
|
|
19
|
+
if (check) {
|
|
20
|
+
if (current !== expected) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`${NEXT_DEVELOPMENT_MANUAL_PATH} drifted from its contract source`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return { ok: true, changed: false, path: NEXT_DEVELOPMENT_MANUAL_PATH };
|
|
26
|
+
}
|
|
27
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
28
|
+
if (current !== expected) fs.writeFileSync(target, expected);
|
|
29
|
+
return {
|
|
30
|
+
ok: true,
|
|
31
|
+
changed: current !== expected,
|
|
32
|
+
path: NEXT_DEVELOPMENT_MANUAL_PATH,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
process.argv[1] &&
|
|
38
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
39
|
+
) {
|
|
40
|
+
try {
|
|
41
|
+
const result = generateNextDevelopmentGuidance({
|
|
42
|
+
check: process.argv.includes("--check"),
|
|
43
|
+
});
|
|
44
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
process.stderr.write(`next-development guidance: ${error.message}\n`);
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -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
|
}
|