@kungfu-tech/buildchain 3.0.6-alpha.0 → 3.0.6-alpha.2
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/README.md +4 -4
- package/actions/promote-buildchain-ref/README.md +8 -0
- package/bin/buildchain.mjs +13 -1
- package/contracts/auditable-demo-scenario-v1.schema.json +52 -0
- package/dist/site/buildchain-contract.json +47 -27
- package/dist/site/buildchain-site.json +165 -44
- package/dist/site/capability-registry.json +3 -3
- package/dist/site/cli-registry.json +40 -4
- package/dist/site/controller-registry.json +20 -4
- package/dist/site/kfd-claims.json +140 -19
- package/dist/site/kfd-upstream-aggregate.json +9 -9
- package/dist/site/manual-registry.json +9 -9
- package/dist/site/node-api-registry.json +1161 -180
- package/dist/site/page-registry.json +152 -31
- package/dist/site/public-surface-audit.json +386 -19
- package/dist/site/publication-authority-registry.json +61 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +1 -0
- package/dist/site/site-manifest.json +13 -13
- package/dist/site/workflow-registry.json +151 -13
- package/docs/MAP.md +2 -0
- package/docs/auditable-demo.md +58 -11
- package/docs/aws-us-elastic-runner-burst-plane.md +114 -80
- package/docs/cli-reference.md +154 -0
- package/docs/dev-alpha-candidate-patrol.md +13 -5
- package/docs/dev-delivery-warrant.md +158 -0
- package/docs/node-api-reference.md +54 -15
- package/docs/publication-authority.md +11 -0
- package/docs/release-candidate.md +19 -2
- package/docs/release-governance.md +60 -1
- package/docs/reusable-build-surface.md +11 -1
- package/docs/shifu-gate-profiles.md +12 -1
- package/docs/versioning.md +2 -0
- package/package.json +4 -2
- package/packages/core/buildchain-publication-authority.js +3 -1
- package/packages/core/channel-candidate.js +2 -21
- package/packages/core/channel-promotion-baseline.js +199 -0
- package/packages/core/dev-delivery-candidate-identity.js +94 -0
- package/packages/core/dev-delivery-common.js +73 -0
- package/packages/core/dev-delivery-proof.js +252 -0
- package/packages/core/dev-delivery-warrant-cancellation.js +94 -0
- package/packages/core/dev-delivery-warrant-settlement.js +73 -0
- package/packages/core/dev-delivery-warrant.js +591 -0
- package/scripts/auditable-demo-bundle-verification.mjs +148 -0
- package/scripts/auditable-demo-platform.mjs +86 -50
- package/scripts/auditable-demo-presentation.mjs +83 -0
- package/scripts/auditable-demo-renditions.mjs +264 -0
- package/scripts/auditable-demo.mjs +24 -30
- package/scripts/aws-windows-jit-campaign-core.mjs +7 -8
- package/scripts/aws-windows-jit-controller.mjs +1 -0
- package/scripts/aws-windows-jit-core.mjs +1 -1
- package/scripts/build-contract-core.mjs +58 -3
- package/scripts/buildchain-cli-help.mjs +8 -0
- package/scripts/buildchain-patrol.mjs +9 -0
- package/scripts/check-inventory.mjs +1 -0
- package/scripts/dev-alpha-candidate-patrol.mjs +45 -48
- package/scripts/dev-delivery-proof.mjs +193 -0
- package/scripts/dev-delivery-warrant.mjs +426 -0
- package/scripts/dev-pr-auto-merge.mjs +497 -55
- package/scripts/dev-pr-delivery-warrant.mjs +209 -0
- package/scripts/dispatch-artifact-signing-authority.mjs +2 -4
- package/scripts/gate-profile-core.mjs +24 -0
- package/scripts/generate-site-bundle.mjs +2 -2
- package/scripts/git-fetch-process-tree.mjs +142 -0
- package/scripts/lifecycle-substage-evidence.mjs +274 -0
- package/scripts/locked-source-checkout.mjs +6 -3
- package/scripts/resolve-artifact-transfer-mode.mjs +9 -0
- package/scripts/resolve-build-contract.mjs +7 -0
- package/scripts/route-offline-runners.mjs +1 -0
- package/scripts/run-lifecycle-core.mjs +9 -9
- package/scripts/shifu-gate-profile.mjs +10 -16
- package/scripts/site-capability-metadata.mjs +14 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
const SHA = /^[0-9a-f]{40}$/;
|
|
4
|
+
|
|
5
|
+
function text(value = "") {
|
|
6
|
+
return String(value ?? "").trim();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function exactSha(value, name) {
|
|
10
|
+
const normalized = text(value).toLowerCase();
|
|
11
|
+
if (!SHA.test(normalized))
|
|
12
|
+
throw new Error(`${name} must be an exact 40-character SHA`);
|
|
13
|
+
return normalized;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function normalizeManagedPromotionBaseline(input) {
|
|
17
|
+
const pullRequestNumber = Number(input?.pullRequestNumber);
|
|
18
|
+
if (!Number.isSafeInteger(pullRequestNumber) || pullRequestNumber <= 0) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
"targetBaseline.pullRequestNumber must be a positive integer",
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
const pullRequestUrl = text(input?.pullRequestUrl);
|
|
24
|
+
if (!pullRequestUrl)
|
|
25
|
+
throw new Error("targetBaseline.pullRequestUrl is required");
|
|
26
|
+
return {
|
|
27
|
+
mode: text(input?.mode) || "managed-candidate-merge-source",
|
|
28
|
+
targetSha: exactSha(input?.targetSha, "targetBaseline.targetSha"),
|
|
29
|
+
sourceSha: exactSha(input?.sourceSha, "targetBaseline.sourceSha"),
|
|
30
|
+
pullRequestNumber,
|
|
31
|
+
pullRequestUrl,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function normalizeChannelCandidateSelection(input) {
|
|
36
|
+
if (!input) return undefined;
|
|
37
|
+
const mode = text(input.mode);
|
|
38
|
+
if (!mode) throw new Error("selection.mode is required");
|
|
39
|
+
const skippedNewerCommitCount = Number(input.skippedNewerCommitCount || 0);
|
|
40
|
+
if (
|
|
41
|
+
!Number.isSafeInteger(skippedNewerCommitCount) ||
|
|
42
|
+
skippedNewerCommitCount < 0
|
|
43
|
+
) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
"selection.skippedNewerCommitCount must be a non-negative integer",
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
mode,
|
|
50
|
+
observedSourceHeadSha: exactSha(
|
|
51
|
+
input.observedSourceHeadSha,
|
|
52
|
+
"selection.observedSourceHeadSha",
|
|
53
|
+
),
|
|
54
|
+
skippedNewerCommitCount,
|
|
55
|
+
...(input.targetBaseline
|
|
56
|
+
? {
|
|
57
|
+
targetBaseline: normalizeManagedPromotionBaseline(
|
|
58
|
+
input.targetBaseline,
|
|
59
|
+
),
|
|
60
|
+
}
|
|
61
|
+
: {}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function resolveChannelComparisonBaseline({
|
|
66
|
+
client,
|
|
67
|
+
targetSha,
|
|
68
|
+
targetBranch,
|
|
69
|
+
sourceSha,
|
|
70
|
+
directComparison,
|
|
71
|
+
}) {
|
|
72
|
+
if (
|
|
73
|
+
directComparison.status !== "diverged" ||
|
|
74
|
+
!client.resolveManagedPromotionBaseline
|
|
75
|
+
) {
|
|
76
|
+
return { comparison: directComparison, targetBaseline: undefined };
|
|
77
|
+
}
|
|
78
|
+
const promoted = await client.resolveManagedPromotionBaseline(
|
|
79
|
+
targetSha,
|
|
80
|
+
targetBranch,
|
|
81
|
+
);
|
|
82
|
+
if (!promoted)
|
|
83
|
+
return { comparison: directComparison, targetBaseline: undefined };
|
|
84
|
+
const comparison = await client.compare(promoted.sourceSha, sourceSha);
|
|
85
|
+
if (!["ahead", "identical"].includes(comparison.status)) {
|
|
86
|
+
return { comparison: directComparison, targetBaseline: undefined };
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
comparison,
|
|
90
|
+
targetBaseline: normalizeManagedPromotionBaseline(promoted),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function resolvePatrolSourceInputs({
|
|
95
|
+
client,
|
|
96
|
+
options,
|
|
97
|
+
requiredWorkflowPaths,
|
|
98
|
+
selectQualifiedSource,
|
|
99
|
+
}) {
|
|
100
|
+
const [observedSourceHeadSha, targetSha] = await Promise.all([
|
|
101
|
+
client.resolveBranch(options.sourceBranch),
|
|
102
|
+
client.resolveBranch(options.targetBranch),
|
|
103
|
+
]);
|
|
104
|
+
const directComparison = await client.compare(
|
|
105
|
+
targetSha,
|
|
106
|
+
observedSourceHeadSha,
|
|
107
|
+
);
|
|
108
|
+
const { comparison: headComparison, targetBaseline } =
|
|
109
|
+
await resolveChannelComparisonBaseline({
|
|
110
|
+
client,
|
|
111
|
+
targetSha,
|
|
112
|
+
targetBranch: options.targetBranch,
|
|
113
|
+
sourceSha: observedSourceHeadSha,
|
|
114
|
+
directComparison,
|
|
115
|
+
});
|
|
116
|
+
let sourceSha = observedSourceHeadSha;
|
|
117
|
+
let comparison = headComparison;
|
|
118
|
+
let workflowEvidence = [];
|
|
119
|
+
let skippedNewerCommitCount = 0;
|
|
120
|
+
let qualificationError;
|
|
121
|
+
if (
|
|
122
|
+
headComparison.status === "ahead" &&
|
|
123
|
+
Number(headComparison.ahead_by) > 0
|
|
124
|
+
) {
|
|
125
|
+
const boundarySha = targetBaseline?.sourceSha || targetSha;
|
|
126
|
+
const [sourceHistory, ...workflowRunSets] = await Promise.all([
|
|
127
|
+
client.listBranchHistory(options.sourceBranch, boundarySha),
|
|
128
|
+
...requiredWorkflowPaths.map((workflow) =>
|
|
129
|
+
client.listCompletedWorkflowRuns(workflow, options.sourceBranch),
|
|
130
|
+
),
|
|
131
|
+
]);
|
|
132
|
+
try {
|
|
133
|
+
const selected = selectQualifiedSource({
|
|
134
|
+
sourceHistory,
|
|
135
|
+
workflowRunsByPath: new Map(
|
|
136
|
+
requiredWorkflowPaths.map((workflow, index) => [
|
|
137
|
+
workflow,
|
|
138
|
+
workflowRunSets[index],
|
|
139
|
+
]),
|
|
140
|
+
),
|
|
141
|
+
requiredWorkflowPaths,
|
|
142
|
+
now: options.now,
|
|
143
|
+
maxAgeSeconds: options.maxAgeSeconds,
|
|
144
|
+
});
|
|
145
|
+
({ sourceSha, skippedNewerCommitCount, workflowEvidence } = selected);
|
|
146
|
+
if (sourceSha !== observedSourceHeadSha)
|
|
147
|
+
comparison = await client.compare(boundarySha, sourceSha);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
qualificationError = error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
observedSourceHeadSha,
|
|
154
|
+
targetSha,
|
|
155
|
+
headComparison,
|
|
156
|
+
targetBaseline,
|
|
157
|
+
sourceSha,
|
|
158
|
+
comparison,
|
|
159
|
+
workflowEvidence,
|
|
160
|
+
skippedNewerCommitCount,
|
|
161
|
+
qualificationError,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function resolveManagedPromotionBaseline({
|
|
166
|
+
api,
|
|
167
|
+
targetSha,
|
|
168
|
+
targetBranch,
|
|
169
|
+
parseCandidate,
|
|
170
|
+
}) {
|
|
171
|
+
const commit = await api(`/git/commits/${targetSha}`);
|
|
172
|
+
const parents = Array.isArray(commit.parents) ? commit.parents : [];
|
|
173
|
+
if (text(commit.sha) !== targetSha || parents.length !== 2) return null;
|
|
174
|
+
const sourceSha = text(parents[1]?.sha);
|
|
175
|
+
if (!SHA.test(sourceSha)) return null;
|
|
176
|
+
const associated = await api(`/commits/${targetSha}/pulls`);
|
|
177
|
+
const managed = associated
|
|
178
|
+
.filter(
|
|
179
|
+
(pullRequest) =>
|
|
180
|
+
text(pullRequest.merged_at) &&
|
|
181
|
+
text(pullRequest.merge_commit_sha) === targetSha &&
|
|
182
|
+
text(pullRequest.base?.ref) === targetBranch &&
|
|
183
|
+
text(pullRequest.head?.sha) === sourceSha,
|
|
184
|
+
)
|
|
185
|
+
.map((pullRequest) => ({
|
|
186
|
+
pullRequest,
|
|
187
|
+
candidate: parseCandidate(pullRequest, targetBranch),
|
|
188
|
+
}))
|
|
189
|
+
.filter(({ candidate }) => candidate?.sourceSha === sourceSha);
|
|
190
|
+
if (managed.length > 1)
|
|
191
|
+
throw new Error(`multiple managed promotion PRs bind target ${targetSha}`);
|
|
192
|
+
if (managed.length === 0) return null;
|
|
193
|
+
return normalizeManagedPromotionBaseline({
|
|
194
|
+
targetSha,
|
|
195
|
+
sourceSha,
|
|
196
|
+
pullRequestNumber: managed[0].pullRequest.number,
|
|
197
|
+
pullRequestUrl: managed[0].pullRequest.html_url,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
devDeliveryContentRoot,
|
|
3
|
+
devDeliveryExactRoot as exactRoot,
|
|
4
|
+
devDeliveryPositiveInteger as positiveInteger,
|
|
5
|
+
devDeliveryText as text,
|
|
6
|
+
} from "./dev-delivery-common.js";
|
|
7
|
+
|
|
8
|
+
export const CHAINED_ATTEMPT_IDENTITY = "chained-attempt-v2";
|
|
9
|
+
|
|
10
|
+
export function createDevDeliveryCandidateIdentity(
|
|
11
|
+
input,
|
|
12
|
+
expected,
|
|
13
|
+
deliveryClass,
|
|
14
|
+
) {
|
|
15
|
+
const identity = {
|
|
16
|
+
repository: expected.repository,
|
|
17
|
+
protectedBase: expected.protectedBase,
|
|
18
|
+
pullRequestNumber: positiveInteger(
|
|
19
|
+
input.pullRequestNumber,
|
|
20
|
+
"pullRequestNumber",
|
|
21
|
+
),
|
|
22
|
+
assignmentRoot: exactRoot(input.assignmentRoot, "assignmentRoot"),
|
|
23
|
+
initiativeRoot: exactRoot(input.initiativeRoot, "initiativeRoot"),
|
|
24
|
+
sourceIdentityRoot: exactRoot(
|
|
25
|
+
input.sourceIdentityRoot,
|
|
26
|
+
"sourceIdentityRoot",
|
|
27
|
+
),
|
|
28
|
+
deliveryClass: deliveryClass(input.deliveryClass),
|
|
29
|
+
};
|
|
30
|
+
if (input.identitySemantics) {
|
|
31
|
+
if (text(input.identitySemantics) !== CHAINED_ATTEMPT_IDENTITY) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`unsupported candidate identity semantics ${input.identitySemantics}`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
identity.identitySemantics = CHAINED_ATTEMPT_IDENTITY;
|
|
37
|
+
identity.predecessorCandidateId = exactRoot(
|
|
38
|
+
input.predecessorCandidateId,
|
|
39
|
+
"predecessorCandidateId",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return { ...identity, candidateId: devDeliveryContentRoot(identity) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function validateDevDeliveryCandidateChain(candidates, terminalStates) {
|
|
46
|
+
const precedingCandidates = new Map();
|
|
47
|
+
for (const candidate of candidates) {
|
|
48
|
+
if (candidate.identitySemantics === CHAINED_ATTEMPT_IDENTITY) {
|
|
49
|
+
const predecessor = precedingCandidates.get(
|
|
50
|
+
candidate.predecessorCandidateId,
|
|
51
|
+
);
|
|
52
|
+
if (!predecessor)
|
|
53
|
+
throw new Error(
|
|
54
|
+
"chained candidate predecessor must appear earlier in the queue",
|
|
55
|
+
);
|
|
56
|
+
if (predecessor.pullRequestNumber !== candidate.pullRequestNumber) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"chained candidate predecessor must belong to the same pull request",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (!terminalStates.has(predecessor.status)) {
|
|
62
|
+
throw new Error("chained candidate predecessor must be terminal");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
precedingCandidates.set(candidate.candidateId, candidate);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function chainedDevDeliveryAttemptInput({
|
|
70
|
+
queue,
|
|
71
|
+
candidate,
|
|
72
|
+
input,
|
|
73
|
+
currentTime,
|
|
74
|
+
terminalStates,
|
|
75
|
+
}) {
|
|
76
|
+
const predecessor = [...queue.candidates]
|
|
77
|
+
.reverse()
|
|
78
|
+
.find(
|
|
79
|
+
(entry) =>
|
|
80
|
+
entry.pullRequestNumber === candidate.pullRequestNumber &&
|
|
81
|
+
terminalStates.has(entry.status),
|
|
82
|
+
);
|
|
83
|
+
if (!predecessor) return null;
|
|
84
|
+
return {
|
|
85
|
+
...input,
|
|
86
|
+
identitySemantics: CHAINED_ATTEMPT_IDENTITY,
|
|
87
|
+
predecessorCandidateId: predecessor.candidateId,
|
|
88
|
+
enqueuedAt: input.enqueuedAt || currentTime,
|
|
89
|
+
updatedAt: currentTime,
|
|
90
|
+
attempts: input.attempts || 1,
|
|
91
|
+
recoveries: input.recoveries || 0,
|
|
92
|
+
status: "queued",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const SHA_PATTERN = /^[0-9a-f]{40}$/;
|
|
4
|
+
const ROOT_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
5
|
+
|
|
6
|
+
export function devDeliveryText(value = "") {
|
|
7
|
+
return String(value ?? "").trim();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function devDeliveryClone(value) {
|
|
11
|
+
return JSON.parse(JSON.stringify(value));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function stableValue(value) {
|
|
15
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
16
|
+
if (value && typeof value === "object") {
|
|
17
|
+
return Object.fromEntries(
|
|
18
|
+
Object.entries(value)
|
|
19
|
+
.filter(([, entry]) => entry !== undefined)
|
|
20
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
21
|
+
.map(([key, entry]) => [key, stableValue(entry)]),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function devDeliveryContentRoot(value) {
|
|
28
|
+
return `sha256:${crypto
|
|
29
|
+
.createHash("sha256")
|
|
30
|
+
.update(`${JSON.stringify(stableValue(value))}\n`)
|
|
31
|
+
.digest("hex")}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function devDeliveryExactRoot(value, label) {
|
|
35
|
+
const normalized = devDeliveryText(value).toLowerCase();
|
|
36
|
+
if (!ROOT_PATTERN.test(normalized)) throw new Error(`${label} must be a sha256 content root`);
|
|
37
|
+
return normalized;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function devDeliveryExactSha(value, label) {
|
|
41
|
+
const normalized = devDeliveryText(value).toLowerCase();
|
|
42
|
+
if (!SHA_PATTERN.test(normalized)) throw new Error(`${label} must be a 40-character Git SHA`);
|
|
43
|
+
return normalized;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function devDeliveryPositiveInteger(value, label, fallback) {
|
|
47
|
+
const parsed = Number(value ?? fallback);
|
|
48
|
+
if (!Number.isInteger(parsed) || parsed < 1) throw new Error(`${label} must be a positive integer`);
|
|
49
|
+
return parsed;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function devDeliveryTimestamp(value, label) {
|
|
53
|
+
const normalized = devDeliveryText(value);
|
|
54
|
+
const milliseconds = Date.parse(normalized);
|
|
55
|
+
if (!normalized || !Number.isFinite(milliseconds)) throw new Error(`${label} must be an ISO-8601 timestamp`);
|
|
56
|
+
return new Date(milliseconds).toISOString();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function devDeliveryRepository(value) {
|
|
60
|
+
const normalized = devDeliveryText(value);
|
|
61
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(normalized)) {
|
|
62
|
+
throw new Error(`repository must be owner/repo, got ${normalized || "<empty>"}`);
|
|
63
|
+
}
|
|
64
|
+
return normalized;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function devDeliveryProtectedBase(value) {
|
|
68
|
+
const normalized = devDeliveryText(value).replace(/^refs\/heads\//, "");
|
|
69
|
+
if (!/^dev\/v\d+\/v\d+\.\d+$/.test(normalized)) {
|
|
70
|
+
throw new Error(`protectedBase must be dev/vN/vN.M, got ${normalized || "<empty>"}`);
|
|
71
|
+
}
|
|
72
|
+
return normalized;
|
|
73
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { devDeliveryClone as clone, devDeliveryContentRoot, devDeliveryExactRoot as exactRoot, devDeliveryExactSha as exactSha, devDeliveryPositiveInteger as positiveInteger, devDeliveryProtectedBase as protectedBase, devDeliveryRepository as repository, devDeliveryText as text, devDeliveryTimestamp as timestamp } from "./dev-delivery-common.js";
|
|
2
|
+
|
|
3
|
+
export const SOURCE_QUALIFICATION_PROOF_SCHEMA = "kungfu.buildchain.source-qualification-proof/v1";
|
|
4
|
+
export const PROJECT_CUT_REPLAY_PROOF_SCHEMA = "kungfu.buildchain.project-cut-replay-proof/v1";
|
|
5
|
+
export const INTEGRATION_DELIVERY_PROOF_SCHEMA = "kungfu.buildchain.integration-delivery-proof/v1";
|
|
6
|
+
|
|
7
|
+
const SOURCE_QUALIFICATION_ROOT_SEMANTICS = "qualification-identity-v1";
|
|
8
|
+
|
|
9
|
+
const DEV_DELIVERY_WARRANT_SCHEMA = "kungfu.buildchain.dev-delivery-warrant/v1";
|
|
10
|
+
|
|
11
|
+
function exactRoots(values, label) {
|
|
12
|
+
if (!Array.isArray(values) || values.length === 0) throw new Error(`${label} must contain at least one content root`);
|
|
13
|
+
return [...new Set(values.map((value) => exactRoot(value, label)))].sort();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function rootedProof(body) {
|
|
17
|
+
return { ...body, proofRoot: devDeliveryContentRoot(body) };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function sourceQualificationIdentity(body) {
|
|
21
|
+
const identity = clone(body);
|
|
22
|
+
delete identity.qualifiedAt;
|
|
23
|
+
delete identity.observationRoot;
|
|
24
|
+
return identity;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function sourceQualificationObservation(body) {
|
|
28
|
+
return { qualifiedAt: body.qualifiedAt };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createSourceQualificationProof(input = {}) {
|
|
32
|
+
const body = {
|
|
33
|
+
schema: SOURCE_QUALIFICATION_PROOF_SCHEMA,
|
|
34
|
+
rootSemantics: SOURCE_QUALIFICATION_ROOT_SEMANTICS,
|
|
35
|
+
repository: repository(input.repository),
|
|
36
|
+
protectedBase: protectedBase(input.protectedBase),
|
|
37
|
+
sourceIdentityRoot: exactRoot(input.sourceIdentityRoot, "sourceIdentityRoot"),
|
|
38
|
+
sourceHead: exactSha(input.sourceHead, "sourceHead"),
|
|
39
|
+
sourcePatchRoot: exactRoot(input.sourcePatchRoot, "sourcePatchRoot"),
|
|
40
|
+
planRoot: exactRoot(input.planRoot, "planRoot"),
|
|
41
|
+
closureRoot: exactRoot(input.closureRoot, "closureRoot"),
|
|
42
|
+
dependencyRoot: exactRoot(input.dependencyRoot, "dependencyRoot"),
|
|
43
|
+
toolchainRoot: exactRoot(input.toolchainRoot, "toolchainRoot"),
|
|
44
|
+
affectedPaths: [...new Set((input.affectedPaths || []).map(text).filter(Boolean))].sort(),
|
|
45
|
+
shardEvidenceRoots: exactRoots(input.shardEvidenceRoots, "shardEvidenceRoots"),
|
|
46
|
+
qualifiedAt: timestamp(input.qualifiedAt, "qualifiedAt"),
|
|
47
|
+
};
|
|
48
|
+
body.observationRoot = devDeliveryContentRoot(sourceQualificationObservation(body));
|
|
49
|
+
return { ...body, proofRoot: devDeliveryContentRoot(sourceQualificationIdentity(body)) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function verifySourceQualificationProof(proofInput, expected = {}) {
|
|
53
|
+
try {
|
|
54
|
+
const proof = clone(proofInput || {});
|
|
55
|
+
if (proof.schema !== SOURCE_QUALIFICATION_PROOF_SCHEMA) return { ok: false, reason: "unsupported-schema" };
|
|
56
|
+
const proofRoot = proof.proofRoot;
|
|
57
|
+
delete proof.proofRoot;
|
|
58
|
+
if (proof.rootSemantics === undefined) {
|
|
59
|
+
if (devDeliveryContentRoot(proof) !== proofRoot) return { ok: false, reason: "proof-root-drift" };
|
|
60
|
+
} else {
|
|
61
|
+
if (proof.rootSemantics !== SOURCE_QUALIFICATION_ROOT_SEMANTICS) return { ok: false, reason: "unsupported-root-semantics" };
|
|
62
|
+
if (devDeliveryContentRoot(sourceQualificationIdentity(proof)) !== proofRoot) return { ok: false, reason: "proof-root-drift" };
|
|
63
|
+
if (devDeliveryContentRoot(sourceQualificationObservation(proof)) !== proof.observationRoot) return { ok: false, reason: "observation-root-drift" };
|
|
64
|
+
}
|
|
65
|
+
for (const [field, value] of Object.entries(expected)) {
|
|
66
|
+
if (value !== undefined && proof[field] !== value) return { ok: false, reason: `${field}-mismatch` };
|
|
67
|
+
}
|
|
68
|
+
createSourceQualificationProof(proof);
|
|
69
|
+
return { ok: true, reason: "exact-source-proof", proofRoot };
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return { ok: false, reason: "invalid-proof", error: error.message };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function classifyDevDeliveryDelta({ proof, current = {} } = {}) {
|
|
76
|
+
const verification = verifySourceQualificationProof(proof);
|
|
77
|
+
if (!verification.ok)
|
|
78
|
+
return {
|
|
79
|
+
action: "rerun-full-source-qualification",
|
|
80
|
+
reason: verification.reason,
|
|
81
|
+
reusable: false,
|
|
82
|
+
};
|
|
83
|
+
const exactPredicates = ["sourceIdentityRoot", "sourcePatchRoot", "planRoot", "closureRoot", "dependencyRoot", "toolchainRoot"];
|
|
84
|
+
for (const field of exactPredicates) {
|
|
85
|
+
if (!current[field] || current[field] !== proof[field]) {
|
|
86
|
+
return {
|
|
87
|
+
action: "rerun-full-source-qualification",
|
|
88
|
+
reason: `${field}-changed-or-unknown`,
|
|
89
|
+
reusable: false,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (current.graphKnown !== true || !Array.isArray(current.changedPaths)) {
|
|
94
|
+
return {
|
|
95
|
+
action: "rerun-full-source-qualification",
|
|
96
|
+
reason: "dependency-attribution-unknown",
|
|
97
|
+
reusable: false,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const affected = new Set(proof.affectedPaths || []);
|
|
101
|
+
const overlap = [...new Set(current.changedPaths.map(text).filter((entry) => affected.has(entry)))].sort();
|
|
102
|
+
if (overlap.length > 0) {
|
|
103
|
+
return {
|
|
104
|
+
action: "rerun-affected-source-shards",
|
|
105
|
+
reason: "dev-delta-overlaps-affected-closure",
|
|
106
|
+
reusable: false,
|
|
107
|
+
overlappingPaths: overlap,
|
|
108
|
+
requiredFinalGate: "exact-integration-delivery-proof",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
action: "reuse-source-qualification",
|
|
113
|
+
reason: "unrelated-dev-delta",
|
|
114
|
+
reusable: true,
|
|
115
|
+
proofRoot: proof.proofRoot,
|
|
116
|
+
requiredReplay: "cheap-project-cut-replay",
|
|
117
|
+
requiredFinalGate: "exact-integration-delivery-proof",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function createProjectCutReplayPlan(input = {}) {
|
|
122
|
+
const sourceHead = exactSha(input.sourceHead, "sourceHead");
|
|
123
|
+
const previousBase = exactSha(input.previousBase, "previousBase");
|
|
124
|
+
const currentBase = exactSha(input.currentBase, "currentBase");
|
|
125
|
+
return rootedProof({
|
|
126
|
+
schema: "kungfu.buildchain.project-cut-replay-plan/v1",
|
|
127
|
+
repository: repository(input.repository),
|
|
128
|
+
protectedBase: protectedBase(input.protectedBase),
|
|
129
|
+
pullRequestNumber: positiveInteger(input.pullRequestNumber, "pullRequestNumber"),
|
|
130
|
+
sourceHead,
|
|
131
|
+
previousBase,
|
|
132
|
+
currentBase,
|
|
133
|
+
sourcePatchRoot: exactRoot(input.sourcePatchRoot, "sourcePatchRoot"),
|
|
134
|
+
replayTree: exactSha(input.replayTree, "replayTree"),
|
|
135
|
+
sourceHeadMutationRequired: false,
|
|
136
|
+
action: previousBase === currentBase ? "verify-existing-replay" : "replay-on-latest-base",
|
|
137
|
+
finalAuthority: "github-merge-group",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function createProjectCutReplayProof(input = {}) {
|
|
142
|
+
const qualification = input.qualificationReceipt || input.qualification || {};
|
|
143
|
+
if (qualification.schema !== "project.cut.merge-queue-admission/v1") throw new Error("Project Cut qualification schema is invalid");
|
|
144
|
+
if (qualification.ok !== true || qualification.decision !== "qualified") throw new Error("Project Cut qualification is not qualified");
|
|
145
|
+
if (!Array.isArray(qualification.reasonCodes) || qualification.reasonCodes.length !== 0) throw new Error("Project Cut qualification retains failure reasons");
|
|
146
|
+
if (typeof qualification.compositionChanged !== "boolean") throw new Error("Project Cut qualification compositionChanged is invalid");
|
|
147
|
+
if (!qualification.compositionChanged && qualification.compositionRoot != null) throw new Error("unchanged Project Cut qualification must not claim a compositionRoot");
|
|
148
|
+
const sourceHead = exactSha(input.sourceHead, "sourceHead");
|
|
149
|
+
const currentBase = exactSha(input.currentBase, "currentBase");
|
|
150
|
+
const replayTree = exactSha(input.replayTree, "replayTree");
|
|
151
|
+
if (exactSha(qualification.baseCommitOid, "qualification.baseCommitOid") !== currentBase) throw new Error("Project Cut qualification base does not match currentBase");
|
|
152
|
+
if (exactSha(qualification.headCommitOid, "qualification.headCommitOid") !== sourceHead) throw new Error("Project Cut qualification head does not match sourceHead");
|
|
153
|
+
if (exactSha(qualification.candidateTreeOid, "qualification.candidateTreeOid") !== replayTree) throw new Error("Project Cut qualification tree does not match replayTree");
|
|
154
|
+
const normalizedQualification = {
|
|
155
|
+
schema: qualification.schema,
|
|
156
|
+
ok: true,
|
|
157
|
+
decision: "qualified",
|
|
158
|
+
baseCommitOid: currentBase,
|
|
159
|
+
headCommitOid: sourceHead,
|
|
160
|
+
candidateCommitOid: exactSha(qualification.candidateCommitOid, "qualification.candidateCommitOid"),
|
|
161
|
+
candidateTreeOid: replayTree,
|
|
162
|
+
replayedCommitCount: positiveInteger(qualification.replayedCommitCount, "qualification.replayedCommitCount"),
|
|
163
|
+
compositionChanged: Boolean(qualification.compositionChanged),
|
|
164
|
+
compositionRoot: qualification.compositionChanged
|
|
165
|
+
? exactRoot(qualification.compositionRoot, "qualification.compositionRoot")
|
|
166
|
+
: null,
|
|
167
|
+
reasonCodes: [],
|
|
168
|
+
};
|
|
169
|
+
return rootedProof({
|
|
170
|
+
schema: PROJECT_CUT_REPLAY_PROOF_SCHEMA,
|
|
171
|
+
repository: repository(input.repository),
|
|
172
|
+
protectedBase: protectedBase(input.protectedBase),
|
|
173
|
+
pullRequestNumber: positiveInteger(input.pullRequestNumber, "pullRequestNumber"),
|
|
174
|
+
sourceHead,
|
|
175
|
+
sourcePatchRoot: exactRoot(input.sourcePatchRoot, "sourcePatchRoot"),
|
|
176
|
+
currentBase,
|
|
177
|
+
replayTree,
|
|
178
|
+
qualification: normalizedQualification,
|
|
179
|
+
qualificationRoot: devDeliveryContentRoot(normalizedQualification),
|
|
180
|
+
requiredContextRoots: exactRoots(input.requiredContextRoots, "requiredContextRoots"),
|
|
181
|
+
verifiedAt: timestamp(input.verifiedAt, "verifiedAt"),
|
|
182
|
+
sourceHeadMutationRequired: false,
|
|
183
|
+
finalAuthority: "exact-project-cut-replay",
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function verifyProjectCutReplayProof(proofInput, expected = {}) {
|
|
188
|
+
try {
|
|
189
|
+
const proof = clone(proofInput || {});
|
|
190
|
+
if (proof.schema !== PROJECT_CUT_REPLAY_PROOF_SCHEMA) return { ok: false, reason: "unsupported-schema" };
|
|
191
|
+
const proofRoot = proof.proofRoot;
|
|
192
|
+
delete proof.proofRoot;
|
|
193
|
+
if (devDeliveryContentRoot(proof) !== proofRoot) return { ok: false, reason: "proof-root-drift" };
|
|
194
|
+
if (devDeliveryContentRoot(proof.qualification) !== proof.qualificationRoot) return { ok: false, reason: "qualification-root-drift" };
|
|
195
|
+
if (proof.finalAuthority !== "exact-project-cut-replay") return { ok: false, reason: "non-exact-final-authority" };
|
|
196
|
+
if (proof.sourceHeadMutationRequired !== false) return { ok: false, reason: "source-head-mutation-required" };
|
|
197
|
+
for (const [field, value] of Object.entries(expected)) {
|
|
198
|
+
if (value !== undefined && proof[field] !== value) return { ok: false, reason: `${field}-mismatch` };
|
|
199
|
+
}
|
|
200
|
+
createProjectCutReplayProof(proof);
|
|
201
|
+
return { ok: true, reason: "exact-project-cut-replay", proofRoot };
|
|
202
|
+
} catch (error) {
|
|
203
|
+
return { ok: false, reason: "invalid-proof", error: error.message };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function createIntegrationDeliveryProof(input = {}) {
|
|
208
|
+
const warrant = input.warrant || {};
|
|
209
|
+
if (warrant.schema !== DEV_DELIVERY_WARRANT_SCHEMA) throw new Error("integration proof requires a Delivery Warrant");
|
|
210
|
+
return rootedProof({
|
|
211
|
+
schema: INTEGRATION_DELIVERY_PROOF_SCHEMA,
|
|
212
|
+
repository: repository(input.repository),
|
|
213
|
+
protectedBase: protectedBase(input.protectedBase),
|
|
214
|
+
sourceProofRoot: exactRoot(input.sourceProofRoot, "sourceProofRoot"),
|
|
215
|
+
currentBase: exactSha(input.currentBase, "currentBase"),
|
|
216
|
+
replayTree: exactSha(input.replayTree, "replayTree"),
|
|
217
|
+
mergeGroupHead: exactSha(input.mergeGroupHead, "mergeGroupHead"),
|
|
218
|
+
mergeGroupTree: exactSha(input.mergeGroupTree, "mergeGroupTree"),
|
|
219
|
+
warrantCandidateId: exactRoot(warrant.candidateId, "Warrant candidateId"),
|
|
220
|
+
warrantFencingToken: exactRoot(warrant.fencingToken, "Warrant fencingToken"),
|
|
221
|
+
warrantGeneration: positiveInteger(warrant.generation, "Warrant generation"),
|
|
222
|
+
requiredContextRoots: exactRoots(input.requiredContextRoots, "requiredContextRoots"),
|
|
223
|
+
verifiedAt: timestamp(input.verifiedAt, "verifiedAt"),
|
|
224
|
+
finalAuthority: "exact-github-merge-group",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function verifyIntegrationDeliveryProof(proofInput, expected = {}) {
|
|
229
|
+
try {
|
|
230
|
+
const proof = clone(proofInput || {});
|
|
231
|
+
if (proof.schema !== INTEGRATION_DELIVERY_PROOF_SCHEMA) return { ok: false, reason: "unsupported-schema" };
|
|
232
|
+
const proofRoot = proof.proofRoot;
|
|
233
|
+
delete proof.proofRoot;
|
|
234
|
+
if (devDeliveryContentRoot(proof) !== proofRoot) return { ok: false, reason: "proof-root-drift" };
|
|
235
|
+
if (proof.finalAuthority !== "exact-github-merge-group") return { ok: false, reason: "non-exact-final-authority" };
|
|
236
|
+
for (const [field, value] of Object.entries(expected)) {
|
|
237
|
+
if (value !== undefined && proof[field] !== value) return { ok: false, reason: `${field}-mismatch` };
|
|
238
|
+
}
|
|
239
|
+
createIntegrationDeliveryProof({
|
|
240
|
+
...proof,
|
|
241
|
+
warrant: {
|
|
242
|
+
schema: DEV_DELIVERY_WARRANT_SCHEMA,
|
|
243
|
+
candidateId: proof.warrantCandidateId,
|
|
244
|
+
fencingToken: proof.warrantFencingToken,
|
|
245
|
+
generation: proof.warrantGeneration,
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
return { ok: true, reason: "exact-integration-proof", proofRoot };
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return { ok: false, reason: "invalid-proof", error: error.message };
|
|
251
|
+
}
|
|
252
|
+
}
|