@kungfu-tech/buildchain 3.0.9-alpha.0 → 3.0.9-alpha.10
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/bin/buildchain.mjs +9 -1
- package/contracts/dev-delivery-authority-v2.schema.json +253 -0
- package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-perspective.json +16 -0
- package/contracts/fixtures/kfd-adopter-release-v1/kfd-4-replay.json +57 -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/next-development-request-v1.schema.json +66 -0
- package/contracts/next-development-transition-v1.schema.json +292 -0
- package/contracts/release-cut-v1.schema.json +1 -0
- package/contracts/release-train-transition-v1.schema.json +1 -0
- package/dist/site/agent-index.json +1 -0
- package/dist/site/artifact-schemas.json +2 -0
- package/dist/site/buildchain-contract.json +91 -32
- package/dist/site/buildchain-site.json +353 -45
- package/dist/site/capability-registry.json +7 -6
- package/dist/site/cli-registry.json +41 -2
- package/dist/site/controller-registry.json +8 -4
- package/dist/site/kfd-claims.json +193 -9
- package/dist/site/kfd-upstream-aggregate.json +9 -9
- package/dist/site/manual-registry.json +26 -10
- package/dist/site/node-api-registry.json +8012 -4574
- package/dist/site/page-registry.json +331 -31
- package/dist/site/public-surface-audit.json +353 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-passport-check-manifest.json +1 -0
- package/dist/site/release-provenance.json +12 -0
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +442 -0
- package/dist/site/schemas/release-passport-v1.schema.json +3 -0
- package/dist/site/site-manifest.json +22 -14
- package/dist/site/workflow-registry.json +15 -7
- package/docs/MAP.md +5 -2
- package/docs/adopter-delivery-gate.md +106 -0
- package/docs/aws-us-elastic-runner-burst-plane.md +30 -3
- package/docs/cli-reference.md +200 -13
- package/docs/dev-alpha-candidate-patrol.md +8 -0
- package/docs/dev-delivery-qualification-landing-adr.md +137 -0
- package/docs/dev-delivery-warrant.md +164 -27
- package/docs/kfd-support.md +7 -2
- package/docs/next-development-transition.md +119 -0
- package/docs/node-api-reference.md +464 -277
- package/docs/release-governance.md +10 -7
- package/docs/release-passport.md +15 -2
- package/docs/release-train.md +46 -4
- package/docs/reusable-build-surface.md +26 -9
- package/docs/runtime-train-validation.md +6 -0
- package/docs/versioning.md +6 -2
- package/package.json +21 -6
- package/packages/core/README.md +25 -0
- package/packages/core/adopter-delivery-gate.js +576 -0
- package/packages/core/adopter-delivery-json.js +66 -0
- 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/artifact-verification-envelope.js +5 -5
- package/packages/core/buildchain-agent-manuals.js +1 -0
- package/packages/core/buildchain-channel-identity.js +118 -0
- package/packages/core/buildchain-compatibility-proof.js +54 -0
- package/packages/core/buildchain-contract.js +7 -0
- package/packages/core/buildchain-delivery-bootstrap.js +240 -0
- package/packages/core/buildchain-delivery-infrastructure.js +164 -0
- package/packages/core/channel-candidate.js +8 -0
- package/packages/core/channel-promotion-baseline.js +52 -0
- package/packages/core/controller-evidence.js +2 -1
- package/packages/core/dev-alpha-candidate-selection.js +10 -2
- package/packages/core/dev-delivery-authority.js +996 -0
- package/packages/core/dev-delivery-native-proof.js +418 -0
- package/packages/core/dev-delivery-warrant-settlement.js +67 -20
- package/packages/core/dev-delivery-warrant.js +141 -48
- package/packages/core/kfd-adopter-category-driver.js +167 -0
- package/packages/core/kfd-adopter-manifest.js +49 -50
- 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 +726 -0
- package/packages/core/next-development-projection.js +288 -0
- package/packages/core/next-development-transition.js +738 -0
- package/packages/core/paper-agent-entry.js +7 -4
- package/packages/core/paper.js +14 -7
- package/packages/core/release-blocker-priority.js +192 -0
- package/packages/core/release-passport-contract.js +10 -6
- package/packages/core/release-passport.js +29 -12
- package/packages/core/release-train.js +220 -0
- package/scripts/aws-macos-jit-controller-core.mjs +239 -4
- package/scripts/aws-macos-jit-controller.mjs +198 -2
- package/scripts/aws-macos-jit-instance-rehydrate.mjs +260 -0
- package/scripts/aws-macos-jit-source-rebind.mjs +235 -14
- package/scripts/aws-macos-jit.mjs +12 -0
- package/scripts/buildchain-channel-router.mjs +18 -2
- package/scripts/buildchain-cli-help.mjs +9 -2
- package/scripts/buildchain-contract-lock.mjs +11 -1
- package/scripts/check-internal-architecture.mjs +1 -0
- package/scripts/check-inventory.mjs +14 -3
- package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
- package/scripts/dev-delivery-authority.mjs +368 -0
- package/scripts/dev-delivery-native-run.mjs +235 -0
- package/scripts/dev-delivery-proof.mjs +64 -2
- package/scripts/dev-delivery-two-phase-resume.mjs +90 -0
- package/scripts/dev-delivery-two-phase.mjs +577 -0
- package/scripts/dev-delivery-warrant.mjs +47 -5
- package/scripts/dev-pr-delivery-warrant.mjs +19 -14
- package/scripts/generate-buildchain-kfd-witnesses.mjs +88 -94
- package/scripts/generate-channel-build-workflow.mjs +82 -31
- package/scripts/generate-next-development-guidance.mjs +49 -0
- package/scripts/generate-site-bundle.mjs +8 -3
- package/scripts/init-repo.mjs +135 -62
- package/scripts/next-development-self-dogfood-harness.mjs +409 -0
- package/scripts/next-development-self-dogfood.mjs +532 -0
- package/scripts/next-development-transition.mjs +47 -0
- package/scripts/promotion-channel-router.mjs +5 -3
- package/scripts/release-candidate-tail-reseal.mjs +426 -0
- package/scripts/release-train-self-dogfood.mjs +579 -0
- package/scripts/runtime-ref-core.mjs +4 -17
- package/scripts/seal-artifact-signing-requests.mjs +7 -4
- package/scripts/site-capability-metadata.mjs +23 -1
- package/scripts/stable-candidate-qualification.mjs +7 -7
- package/scripts/workflow-call-contract.mjs +1 -1
- package/templates/native-dev-delivery.yml +86 -0
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
devDeliveryRepository as repository,
|
|
8
8
|
devDeliveryText as text,
|
|
9
9
|
} from "./dev-delivery-common.js";
|
|
10
|
+
import { evaluateBuildchainChannelBinding } from "./buildchain-channel-identity.js";
|
|
10
11
|
|
|
11
12
|
export const BUILDCHAIN_COMPATIBILITY_PROOF_SCHEMA = "kungfu.buildchain.compatibility-proof/v1";
|
|
12
13
|
export const BUILDCHAIN_COMPATIBILITY_PROOF_REGISTRY_SCHEMA = "kungfu.buildchain.compatibility-proof-registry/v1";
|
|
@@ -109,6 +110,28 @@ const HISTORICAL_ACCEPTANCES = [
|
|
|
109
110
|
compatibilityDigest: "sha256:32a651504c57829085dc8863f0e41dee98eb60708ff6dec958c9c7e4b1010102",
|
|
110
111
|
},
|
|
111
112
|
},
|
|
113
|
+
{
|
|
114
|
+
proofId: "build-channel-router-d40dd0-to-30549a",
|
|
115
|
+
surfaceId: "controller:build-channel-router",
|
|
116
|
+
surfaceKind: "controller",
|
|
117
|
+
sourceBreakingDigest: "sha256:d40dd05d7e07473d4b2dc1b62fbb0ed6f06449b4d3a7c44a2e8014313e3c0273",
|
|
118
|
+
targetBreakingDigest: "sha256:30549aa26cd1e1dd81f10fe3529d3c6e3f8438a6efa0100c47ebae4d2f3d71df",
|
|
119
|
+
evidence: {
|
|
120
|
+
kind: "protected-pull-request-contract-acceptance",
|
|
121
|
+
pullRequest: "https://github.com/kungfu-systems/buildchain/pull/2549",
|
|
122
|
+
protectedBase: "dev/v3/v3.0",
|
|
123
|
+
sourceCommit: "0d87b772265066f5e6157e1ba6a2ba1021fb5225",
|
|
124
|
+
mergeCommit: "34fad9d3a94c450d75c0d06fd6ed00470c992ff7",
|
|
125
|
+
contractDigest: "sha256:2584ba36ab54cd5e9bab9e34dfc08d87a55dc57de1be7641492bf7ceb1366350",
|
|
126
|
+
compatibilityDigest: "sha256:80f65cc191b1f771475184edf8a1bf9c10dd69bb58060aa09313d0e1e57f508a",
|
|
127
|
+
},
|
|
128
|
+
cut: {
|
|
129
|
+
kind: "protected-git-cut",
|
|
130
|
+
repository: "kungfu-systems/buildchain",
|
|
131
|
+
protectedBase: "dev/v3/v3.0",
|
|
132
|
+
commit: "34fad9d3a94c450d75c0d06fd6ed00470c992ff7",
|
|
133
|
+
},
|
|
134
|
+
},
|
|
112
135
|
];
|
|
113
136
|
|
|
114
137
|
function exactText(value, label) {
|
|
@@ -437,6 +460,19 @@ function evaluateSurfaceRelation({ oldSurface, nextSurface, proofRegistry, major
|
|
|
437
460
|
};
|
|
438
461
|
}
|
|
439
462
|
|
|
463
|
+
function evaluateChannelBinding({ lock, workflowShellRef, runtimeRef, expectedChannel, expectedMajor, allowOpaqueRuntime }) {
|
|
464
|
+
if (!workflowShellRef && !expectedChannel && !expectedMajor) return null;
|
|
465
|
+
return evaluateBuildchainChannelBinding({
|
|
466
|
+
workflowShellRef,
|
|
467
|
+
runtimeRef,
|
|
468
|
+
lockRef: lock?.buildchain?.ref || "",
|
|
469
|
+
lockMajorLine: lock?.buildchain?.majorLine || "",
|
|
470
|
+
expectedChannel,
|
|
471
|
+
expectedMajor,
|
|
472
|
+
allowOpaqueRuntime,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
440
476
|
export function evaluateBuildchainContractLock({
|
|
441
477
|
lock,
|
|
442
478
|
current,
|
|
@@ -444,11 +480,29 @@ export function evaluateBuildchainContractLock({
|
|
|
444
480
|
runtimeSha = "",
|
|
445
481
|
runtimeClass = "",
|
|
446
482
|
compatibilityPolicy = "",
|
|
483
|
+
workflowShellRef = "",
|
|
484
|
+
expectedChannel = "",
|
|
485
|
+
expectedMajor = "",
|
|
486
|
+
allowOpaqueRuntime = false,
|
|
447
487
|
} = {}) {
|
|
448
488
|
if (!current || current.contract !== BUILDCHAIN_CONTRACT) {
|
|
449
489
|
throw new Error("current must be a Buildchain runtime contract world");
|
|
450
490
|
}
|
|
451
491
|
assertBuildchainCompatibilityProjection(current);
|
|
492
|
+
const binding = evaluateChannelBinding({
|
|
493
|
+
lock, workflowShellRef, runtimeRef, expectedChannel, expectedMajor, allowOpaqueRuntime,
|
|
494
|
+
});
|
|
495
|
+
if (binding?.ok === false) {
|
|
496
|
+
return {
|
|
497
|
+
ok: false,
|
|
498
|
+
status: binding.status,
|
|
499
|
+
drift: false,
|
|
500
|
+
compatible: false,
|
|
501
|
+
issueRecommended: false,
|
|
502
|
+
reasons: binding.reasons,
|
|
503
|
+
channelBinding: binding,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
452
506
|
if (!new Set(["stable", "alpha"]).has(runtimeClass)) {
|
|
453
507
|
return {
|
|
454
508
|
ok: true,
|
|
@@ -9,6 +9,13 @@ import {
|
|
|
9
9
|
import { createControllerRegistry } from "./controller-evidence.js";
|
|
10
10
|
import { enumerateWorkflowInputs } from "./public-surface-audit.js";
|
|
11
11
|
|
|
12
|
+
export {
|
|
13
|
+
BUILDCHAIN_CHANNELS,
|
|
14
|
+
evaluateBuildchainChannelBinding,
|
|
15
|
+
normalizeBuildchainRef,
|
|
16
|
+
parseBuildchainRefIdentity,
|
|
17
|
+
} from "./buildchain-channel-identity.js";
|
|
18
|
+
|
|
12
19
|
export {
|
|
13
20
|
BUILDCHAIN_COMPATIBILITY_PROOF_REGISTRY_SCHEMA,
|
|
14
21
|
BUILDCHAIN_COMPATIBILITY_PROOF_SCHEMA,
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { adopterDeliveryGateDigest } from "./adopter-delivery-gate.js";
|
|
2
|
+
import { validateAdopterDeliveryGateResult } from "./adopter-delivery-passport.js";
|
|
3
|
+
|
|
4
|
+
export const BUILDCHAIN_DELIVERY_BOOTSTRAP_CONTRACT =
|
|
5
|
+
"kungfu-buildchain-delivery-infrastructure-bootstrap/v1";
|
|
6
|
+
|
|
7
|
+
const ROOT_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
8
|
+
const SHA_PATTERN = /^[0-9a-f]{40}$/;
|
|
9
|
+
const SEMVER_PATTERN =
|
|
10
|
+
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
11
|
+
|
|
12
|
+
function issue(code, message) {
|
|
13
|
+
return { code, message };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function exactRoot(value) {
|
|
17
|
+
return ROOT_PATTERN.test(value ?? "");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function exactCut(cut) {
|
|
21
|
+
return (
|
|
22
|
+
cut &&
|
|
23
|
+
typeof cut === "object" &&
|
|
24
|
+
typeof cut.version === "string" &&
|
|
25
|
+
SHA_PATTERN.test(cut.sourceCommit ?? "") &&
|
|
26
|
+
exactRoot(cut.packageRoot) &&
|
|
27
|
+
exactRoot(cut.gateRoot) &&
|
|
28
|
+
typeof cut.protocol?.id === "string" &&
|
|
29
|
+
typeof cut.protocol?.version === "string" &&
|
|
30
|
+
exactRoot(cut.protocol?.root) &&
|
|
31
|
+
typeof cut.profile?.id === "string" &&
|
|
32
|
+
typeof cut.profile?.version === "string" &&
|
|
33
|
+
exactRoot(cut.profile?.root)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseSemver(value) {
|
|
38
|
+
const match = SEMVER_PATTERN.exec(value ?? "");
|
|
39
|
+
if (!match) return null;
|
|
40
|
+
return {
|
|
41
|
+
core: match.slice(1, 4).map(Number),
|
|
42
|
+
prerelease: match[4] ? match[4].split(".") : [],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function compareIdentifier(left, right) {
|
|
47
|
+
const leftNumber = /^\d+$/.test(left) ? Number(left) : null;
|
|
48
|
+
const rightNumber = /^\d+$/.test(right) ? Number(right) : null;
|
|
49
|
+
if (leftNumber !== null && rightNumber !== null) {
|
|
50
|
+
return Math.sign(leftNumber - rightNumber);
|
|
51
|
+
}
|
|
52
|
+
if (leftNumber !== null) return -1;
|
|
53
|
+
if (rightNumber !== null) return 1;
|
|
54
|
+
return left === right ? 0 : left < right ? -1 : 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function compareSemver(leftValue, rightValue) {
|
|
58
|
+
const left = parseSemver(leftValue);
|
|
59
|
+
const right = parseSemver(rightValue);
|
|
60
|
+
if (!left || !right) return null;
|
|
61
|
+
for (let index = 0; index < left.core.length; index += 1) {
|
|
62
|
+
if (left.core[index] !== right.core[index]) {
|
|
63
|
+
return Math.sign(left.core[index] - right.core[index]);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (left.prerelease.length === 0 || right.prerelease.length === 0) {
|
|
67
|
+
if (left.prerelease.length === right.prerelease.length) return 0;
|
|
68
|
+
return left.prerelease.length === 0 ? 1 : -1;
|
|
69
|
+
}
|
|
70
|
+
const width = Math.max(left.prerelease.length, right.prerelease.length);
|
|
71
|
+
for (let index = 0; index < width; index += 1) {
|
|
72
|
+
if (left.prerelease[index] === undefined) return -1;
|
|
73
|
+
if (right.prerelease[index] === undefined) return 1;
|
|
74
|
+
const comparison = compareIdentifier(
|
|
75
|
+
left.prerelease[index],
|
|
76
|
+
right.prerelease[index],
|
|
77
|
+
);
|
|
78
|
+
if (comparison !== 0) return comparison;
|
|
79
|
+
}
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function sameProtocol(left, right) {
|
|
84
|
+
return (
|
|
85
|
+
left?.id === right?.id &&
|
|
86
|
+
left?.version === right?.version &&
|
|
87
|
+
left?.root === right?.root
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function validTransition(transition, authority, candidate) {
|
|
92
|
+
if (!transition || typeof transition !== "object") return false;
|
|
93
|
+
const body = structuredClone(transition);
|
|
94
|
+
const transitionRoot = body.transitionRoot;
|
|
95
|
+
delete body.transitionRoot;
|
|
96
|
+
return (
|
|
97
|
+
exactRoot(transitionRoot) &&
|
|
98
|
+
adopterDeliveryGateDigest(body) === transitionRoot &&
|
|
99
|
+
body.fromGateRoot === authority.gateRoot &&
|
|
100
|
+
body.toGateRoot === candidate.gateRoot &&
|
|
101
|
+
body.fromProtocolRoot === authority.protocol.root &&
|
|
102
|
+
body.toProtocolRoot === candidate.protocol.root &&
|
|
103
|
+
body.fromProfileRoot === authority.profile.root &&
|
|
104
|
+
body.toProfileRoot === candidate.profile.root &&
|
|
105
|
+
exactRoot(body.compatibilityProofRoot) &&
|
|
106
|
+
exactRoot(body.independentReviewRoot)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function validateWarrant(warrant, candidate) {
|
|
111
|
+
return (
|
|
112
|
+
warrant &&
|
|
113
|
+
typeof warrant === "object" &&
|
|
114
|
+
warrant.outcome === "merged" &&
|
|
115
|
+
warrant.sourceHead === candidate.sourceCommit &&
|
|
116
|
+
exactRoot(warrant.sourceProofRoot) &&
|
|
117
|
+
exactRoot(warrant.nativeProofRoot) &&
|
|
118
|
+
exactRoot(warrant.integrationProofRoot)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function qualifyBuildchainDeliveryInfrastructureBootstrap({
|
|
123
|
+
authority,
|
|
124
|
+
candidate,
|
|
125
|
+
gateResult,
|
|
126
|
+
warrant,
|
|
127
|
+
transitionEvidence,
|
|
128
|
+
} = {}) {
|
|
129
|
+
const issues = [];
|
|
130
|
+
if (!exactCut(authority)) {
|
|
131
|
+
issues.push(
|
|
132
|
+
issue("bootstrap.authority.cut", "N-1 authority cut is incomplete"),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (!exactCut(candidate)) {
|
|
136
|
+
issues.push(
|
|
137
|
+
issue("bootstrap.candidate.cut", "candidate cut is incomplete"),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (authority?.protected !== true || authority?.published !== true) {
|
|
141
|
+
issues.push(
|
|
142
|
+
issue(
|
|
143
|
+
"bootstrap.authority.release",
|
|
144
|
+
"N-1 authority must be both protected and publicly released",
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (candidate?.authorityVersion !== authority?.version) {
|
|
149
|
+
issues.push(
|
|
150
|
+
issue(
|
|
151
|
+
"bootstrap.authority.stale",
|
|
152
|
+
"candidate must name the exact N-1 authority version",
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const versionOrder = compareSemver(authority?.version, candidate?.version);
|
|
157
|
+
if (versionOrder === null || versionOrder >= 0) {
|
|
158
|
+
issues.push(
|
|
159
|
+
issue(
|
|
160
|
+
"bootstrap.authority.order",
|
|
161
|
+
"N-1 authority version must precede the candidate version",
|
|
162
|
+
),
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (
|
|
166
|
+
authority?.sourceCommit === candidate?.sourceCommit ||
|
|
167
|
+
authority?.packageRoot === candidate?.packageRoot
|
|
168
|
+
) {
|
|
169
|
+
issues.push(
|
|
170
|
+
issue(
|
|
171
|
+
"bootstrap.self-authorization",
|
|
172
|
+
"candidate source or package cannot serve as its own N-1 authority",
|
|
173
|
+
),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const gateValidation = validateAdopterDeliveryGateResult(gateResult);
|
|
178
|
+
if (!gateValidation.passed) {
|
|
179
|
+
issues.push(
|
|
180
|
+
issue(
|
|
181
|
+
"bootstrap.gate.failed",
|
|
182
|
+
"candidate must retain a passed public adopter delivery gate result",
|
|
183
|
+
),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
if (
|
|
187
|
+
gateResult?.artifact?.root !== candidate?.packageRoot ||
|
|
188
|
+
gateResult?.project?.adopterId !== "kungfu-systems/buildchain"
|
|
189
|
+
) {
|
|
190
|
+
issues.push(
|
|
191
|
+
issue(
|
|
192
|
+
"bootstrap.gate.candidate",
|
|
193
|
+
"gate result must bind the exact Buildchain candidate package",
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (!validateWarrant(warrant, candidate)) {
|
|
198
|
+
issues.push(
|
|
199
|
+
issue(
|
|
200
|
+
"bootstrap.warrant",
|
|
201
|
+
"merged Delivery Warrant evidence must bind the candidate source",
|
|
202
|
+
),
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const transitionRequired =
|
|
207
|
+
!sameProtocol(authority?.protocol, candidate?.protocol) ||
|
|
208
|
+
!sameProtocol(authority?.profile, candidate?.profile) ||
|
|
209
|
+
authority?.gateRoot !== candidate?.gateRoot;
|
|
210
|
+
if (
|
|
211
|
+
transitionRequired &&
|
|
212
|
+
!validTransition(transitionEvidence, authority, candidate)
|
|
213
|
+
) {
|
|
214
|
+
issues.push(
|
|
215
|
+
issue(
|
|
216
|
+
"bootstrap.transition",
|
|
217
|
+
"gate, protocol, or profile changes require exact independent transition evidence",
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const result = {
|
|
223
|
+
schemaVersion: 1,
|
|
224
|
+
contract: BUILDCHAIN_DELIVERY_BOOTSTRAP_CONTRACT,
|
|
225
|
+
status: issues.length === 0 ? "passed" : "failed",
|
|
226
|
+
authority: structuredClone(authority ?? null),
|
|
227
|
+
candidate: structuredClone(candidate ?? null),
|
|
228
|
+
gateResultRoot: gateResult?.gateRoot ?? null,
|
|
229
|
+
warrant: structuredClone(warrant ?? null),
|
|
230
|
+
transitionRequired,
|
|
231
|
+
transitionEvidenceRoot: transitionEvidence?.transitionRoot ?? null,
|
|
232
|
+
issues,
|
|
233
|
+
qualifying: false,
|
|
234
|
+
selfCertified: false,
|
|
235
|
+
releaseAuthorized: false,
|
|
236
|
+
finalAuthority: "protected-n-minus-one-plus-exact-delivery-evidence",
|
|
237
|
+
};
|
|
238
|
+
result.bootstrapRoot = adopterDeliveryGateDigest(result);
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { semanticRoot } from "@kungfu-tech/kfd/scripts/self-conformance-contract.mjs";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
resolvePublishedKfdAdopterCategoryProfiles,
|
|
5
|
+
verifyPublishedKfdAdopterCategoryInstance,
|
|
6
|
+
} from "./kfd-adopter-category-driver.js";
|
|
7
|
+
|
|
8
|
+
export const BUILDCHAIN_DELIVERY_INFRASTRUCTURE_ADOPTER_ID =
|
|
9
|
+
"kungfu-systems/buildchain";
|
|
10
|
+
export const BUILDCHAIN_DELIVERY_INFRASTRUCTURE_PROFILE = Object.freeze({
|
|
11
|
+
id: "kfd.adopter-category/delivery-infrastructure",
|
|
12
|
+
version: "1.0.0",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const CATEGORY_INSTANCE_SCHEMA =
|
|
16
|
+
"https://kfd.libkungfu.dev/schemas/kfd-adopter-conformance/category-instance-manifest.schema.json";
|
|
17
|
+
const CATEGORY_INSTANCE_CONTRACT = "kfd.adopter-category-instance-manifest/v1";
|
|
18
|
+
const ROOT_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
19
|
+
const CLAIM_BOUNDARY = Object.freeze({
|
|
20
|
+
categoryConformanceIsDeclarationOnly: true,
|
|
21
|
+
evidenceTransfer: false,
|
|
22
|
+
runtimePermission: false,
|
|
23
|
+
releaseAuthorization: false,
|
|
24
|
+
independentCertification: false,
|
|
25
|
+
semanticAuthorityTransfer: false,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function exactEvidenceReference(value, index) {
|
|
29
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
30
|
+
throw new TypeError(`evidence[${index}] must be an object`);
|
|
31
|
+
}
|
|
32
|
+
const fields = ["requirementId", "kind", "coordinate", "root", "observedAt"];
|
|
33
|
+
const actual = Object.keys(value).sort();
|
|
34
|
+
const expected = [...fields].sort();
|
|
35
|
+
if (
|
|
36
|
+
actual.length !== expected.length ||
|
|
37
|
+
actual.some((field, fieldIndex) => field !== expected[fieldIndex])
|
|
38
|
+
) {
|
|
39
|
+
throw new TypeError(`evidence[${index}] has an invalid field set`);
|
|
40
|
+
}
|
|
41
|
+
for (const field of ["requirementId", "kind", "coordinate", "observedAt"]) {
|
|
42
|
+
if (typeof value[field] !== "string" || value[field].length === 0) {
|
|
43
|
+
throw new TypeError(`evidence[${index}].${field} must be non-empty`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!ROOT_PATTERN.test(value.root ?? "")) {
|
|
47
|
+
throw new TypeError(`evidence[${index}].root must be a sha256 root`);
|
|
48
|
+
}
|
|
49
|
+
return structuredClone(value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function releaseBound(adopterManifest, artifact, release) {
|
|
53
|
+
return (adopterManifest?.releaseBindings ?? []).some(
|
|
54
|
+
(binding) =>
|
|
55
|
+
semanticRoot(binding.artifact) === semanticRoot(artifact) &&
|
|
56
|
+
semanticRoot(binding.releasePassport) === semanticRoot(release) &&
|
|
57
|
+
binding.kfdPackageRoot === adopterManifest.kfdCut.package.artifactRoot,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createBuildchainDeliveryInfrastructureInstanceManifest({
|
|
62
|
+
instanceId,
|
|
63
|
+
adopterManifest,
|
|
64
|
+
source,
|
|
65
|
+
artifact,
|
|
66
|
+
release,
|
|
67
|
+
evidence = [],
|
|
68
|
+
} = {}) {
|
|
69
|
+
if (typeof instanceId !== "string" || instanceId.length === 0) {
|
|
70
|
+
throw new TypeError("instanceId must be non-empty");
|
|
71
|
+
}
|
|
72
|
+
if (
|
|
73
|
+
adopterManifest?.adopter?.id !==
|
|
74
|
+
BUILDCHAIN_DELIVERY_INFRASTRUCTURE_ADOPTER_ID
|
|
75
|
+
) {
|
|
76
|
+
throw new TypeError(
|
|
77
|
+
`adopterManifest must identify ${BUILDCHAIN_DELIVERY_INFRASTRUCTURE_ADOPTER_ID}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (!releaseBound(adopterManifest, artifact, release)) {
|
|
81
|
+
throw new TypeError(
|
|
82
|
+
"adopterManifest must bind the exact Buildchain artifact and release",
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const selection = {
|
|
87
|
+
schemaVersion: 1,
|
|
88
|
+
contract: "kfd.adopter-category-profile-selection/v1",
|
|
89
|
+
profiles: [structuredClone(BUILDCHAIN_DELIVERY_INFRASTRUCTURE_PROFILE)],
|
|
90
|
+
};
|
|
91
|
+
const resolution = resolvePublishedKfdAdopterCategoryProfiles(selection);
|
|
92
|
+
if (!resolution.valid) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`published delivery-infrastructure profile did not resolve: ${JSON.stringify(resolution.issues)}`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const project = {
|
|
99
|
+
adopterId: BUILDCHAIN_DELIVERY_INFRASTRUCTURE_ADOPTER_ID,
|
|
100
|
+
source: structuredClone(source),
|
|
101
|
+
artifact: structuredClone(artifact),
|
|
102
|
+
release: structuredClone(release),
|
|
103
|
+
};
|
|
104
|
+
const projectRoot = semanticRoot(project);
|
|
105
|
+
const adopterManifestRoot = semanticRoot(adopterManifest);
|
|
106
|
+
const packageRoot = adopterManifest.kfdCut.package.artifactRoot;
|
|
107
|
+
const requirementIds = new Set(
|
|
108
|
+
resolution.requirements.map((requirement) => requirement.id),
|
|
109
|
+
);
|
|
110
|
+
const suppliedEvidence = evidence.map(exactEvidenceReference);
|
|
111
|
+
for (const [index, entry] of suppliedEvidence.entries()) {
|
|
112
|
+
if (!requirementIds.has(entry.requirementId)) {
|
|
113
|
+
throw new TypeError(
|
|
114
|
+
`evidence[${index}].requirementId is not selected by the published profile`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
$schema: CATEGORY_INSTANCE_SCHEMA,
|
|
121
|
+
schemaVersion: 1,
|
|
122
|
+
contract: CATEGORY_INSTANCE_CONTRACT,
|
|
123
|
+
instanceId,
|
|
124
|
+
rootAlgorithm: "sha256-kfd-canonical-json-v1",
|
|
125
|
+
project,
|
|
126
|
+
adopterManifest: {
|
|
127
|
+
contract: adopterManifest.contract,
|
|
128
|
+
manifestId: adopterManifest.manifestId,
|
|
129
|
+
root: adopterManifestRoot,
|
|
130
|
+
},
|
|
131
|
+
kfdCut: {
|
|
132
|
+
packageVersion: adopterManifest.kfdCut.package.version,
|
|
133
|
+
packageRoot,
|
|
134
|
+
categoryCatalogRoot: resolution.catalogRoot,
|
|
135
|
+
},
|
|
136
|
+
selection,
|
|
137
|
+
selectionRoot: resolution.selectionRoot,
|
|
138
|
+
requirements: resolution.requirements.map((requirement) => ({
|
|
139
|
+
id: requirement.id,
|
|
140
|
+
evidence: suppliedEvidence
|
|
141
|
+
.filter((entry) => entry.requirementId === requirement.id)
|
|
142
|
+
.map(({ requirementId: _requirementId, ...entry }) => ({
|
|
143
|
+
...entry,
|
|
144
|
+
projectInstanceId: instanceId,
|
|
145
|
+
projectRoot,
|
|
146
|
+
adopterManifestRoot,
|
|
147
|
+
kfdPackageRoot: packageRoot,
|
|
148
|
+
categorySelectionRoot: resolution.selectionRoot,
|
|
149
|
+
})),
|
|
150
|
+
})),
|
|
151
|
+
claimBoundary: structuredClone(CLAIM_BOUNDARY),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function verifyBuildchainDeliveryInfrastructureInstance(
|
|
156
|
+
manifest,
|
|
157
|
+
{ adopterManifest, verifiedAt, maxAgeSeconds } = {},
|
|
158
|
+
) {
|
|
159
|
+
return verifyPublishedKfdAdopterCategoryInstance(manifest, {
|
|
160
|
+
adopterManifest,
|
|
161
|
+
verifiedAt,
|
|
162
|
+
maxAgeSeconds,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
@@ -97,6 +97,14 @@ export function decideChannelCandidate(input) {
|
|
|
97
97
|
const sourceSha = sha(input.sourceSha, "sourceSha");
|
|
98
98
|
const targetSha = sha(input.targetSha, "targetSha");
|
|
99
99
|
const selection = normalizeChannelCandidateSelection(input.selection);
|
|
100
|
+
if (
|
|
101
|
+
selection?.versionReservation &&
|
|
102
|
+
selection.versionReservation.candidateSha !== sourceSha
|
|
103
|
+
) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
"selection version reservation must bind the selected source SHA",
|
|
106
|
+
);
|
|
107
|
+
}
|
|
100
108
|
const now = required(input.now || new Date().toISOString(), "now");
|
|
101
109
|
const maxAgeSeconds = Number(input.maxAgeSeconds ?? 7 * 24 * 60 * 60);
|
|
102
110
|
if (!Number.isSafeInteger(maxAgeSeconds) || maxAgeSeconds <= 0) {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
nextDevelopmentPatrolContext,
|
|
5
|
+
normalizeNextDevelopmentVersionReservation,
|
|
6
|
+
} from "./next-development-candidate-reservation.js";
|
|
7
|
+
|
|
3
8
|
const SHA = /^[0-9a-f]{40}$/;
|
|
4
9
|
|
|
5
10
|
function text(value = "") {
|
|
@@ -59,6 +64,18 @@ export function normalizeChannelCandidateSelection(input) {
|
|
|
59
64
|
),
|
|
60
65
|
}
|
|
61
66
|
: {}),
|
|
67
|
+
...(input.versionReservation
|
|
68
|
+
? {
|
|
69
|
+
versionReservation: normalizeNextDevelopmentVersionReservation(
|
|
70
|
+
input.versionReservation,
|
|
71
|
+
{
|
|
72
|
+
reservationSha: input.versionReservation.reservationSha,
|
|
73
|
+
candidateSha: input.versionReservation.candidateSha,
|
|
74
|
+
targetVersion: input.versionReservation.targetVersion,
|
|
75
|
+
},
|
|
76
|
+
),
|
|
77
|
+
}
|
|
78
|
+
: {}),
|
|
62
79
|
};
|
|
63
80
|
}
|
|
64
81
|
|
|
@@ -117,6 +134,7 @@ export async function resolvePatrolSourceInputs({
|
|
|
117
134
|
let comparison = headComparison;
|
|
118
135
|
let workflowEvidence = [];
|
|
119
136
|
let skippedNewerCommitCount = 0;
|
|
137
|
+
let versionReservation;
|
|
120
138
|
let qualificationError;
|
|
121
139
|
if (
|
|
122
140
|
headComparison.status === "ahead" &&
|
|
@@ -130,6 +148,8 @@ export async function resolvePatrolSourceInputs({
|
|
|
130
148
|
),
|
|
131
149
|
]);
|
|
132
150
|
try {
|
|
151
|
+
const { preparations, ignoredSourceShas } =
|
|
152
|
+
nextDevelopmentPatrolContext(sourceHistory);
|
|
133
153
|
const selected = selectQualifiedSource({
|
|
134
154
|
sourceHistory,
|
|
135
155
|
workflowRunsByPath: new Map(
|
|
@@ -141,8 +161,39 @@ export async function resolvePatrolSourceInputs({
|
|
|
141
161
|
requiredWorkflowPaths,
|
|
142
162
|
now: options.now,
|
|
143
163
|
maxAgeSeconds: options.maxAgeSeconds,
|
|
164
|
+
ignoredSourceShas,
|
|
144
165
|
});
|
|
145
166
|
({ sourceSha, skippedNewerCommitCount, workflowEvidence } = selected);
|
|
167
|
+
const selectedIndex = sourceHistory.findIndex((entry) =>
|
|
168
|
+
(typeof entry === "string" ? entry : entry?.sha) === sourceSha,
|
|
169
|
+
);
|
|
170
|
+
const reservation = preparations.find(
|
|
171
|
+
({ index }) => index > selectedIndex,
|
|
172
|
+
)?.preparation;
|
|
173
|
+
if (reservation) {
|
|
174
|
+
if (typeof client.readNextDevelopmentVersionReservation !== "function") {
|
|
175
|
+
throw new Error(
|
|
176
|
+
"candidate follows next-development preparation but reservation readback is unavailable",
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
versionReservation = normalizeNextDevelopmentVersionReservation(
|
|
180
|
+
await client.readNextDevelopmentVersionReservation({
|
|
181
|
+
reservationSha: reservation.sha,
|
|
182
|
+
candidateSha: sourceSha,
|
|
183
|
+
targetVersion: reservation.targetVersion,
|
|
184
|
+
}),
|
|
185
|
+
{
|
|
186
|
+
reservationSha: reservation.sha,
|
|
187
|
+
candidateSha: sourceSha,
|
|
188
|
+
targetVersion: reservation.targetVersion,
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
if (versionReservation.status !== "current") {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`next-development version reservation is ${versionReservation.status} for candidate ${sourceSha}`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
146
197
|
if (sourceSha !== observedSourceHeadSha)
|
|
147
198
|
comparison = await client.compare(boundarySha, sourceSha);
|
|
148
199
|
} catch (error) {
|
|
@@ -158,6 +209,7 @@ export async function resolvePatrolSourceInputs({
|
|
|
158
209
|
comparison,
|
|
159
210
|
workflowEvidence,
|
|
160
211
|
skippedNewerCommitCount,
|
|
212
|
+
versionReservation,
|
|
161
213
|
qualificationError,
|
|
162
214
|
};
|
|
163
215
|
}
|
|
@@ -53,7 +53,8 @@ const CONTROLLER_SPECS = [
|
|
|
53
53
|
workflowId: "build",
|
|
54
54
|
version: 1,
|
|
55
55
|
capabilities: ["channel-selection", "runtime-selection", "build-delegation"],
|
|
56
|
-
stages: ["resolve-channel", "
|
|
56
|
+
stages: ["resolve-channel", "alpha", "stable", "aggregate"],
|
|
57
|
+
optionalStages: ["alpha", "stable"],
|
|
57
58
|
evidence: ["nested-controller-receipt", "controller-receipt"],
|
|
58
59
|
},
|
|
59
60
|
{
|
|
@@ -82,7 +82,9 @@ export function selectLatestQualifiedSource({
|
|
|
82
82
|
requiredWorkflowPaths,
|
|
83
83
|
now,
|
|
84
84
|
maxAgeSeconds,
|
|
85
|
+
ignoredSourceShas = [],
|
|
85
86
|
}) {
|
|
87
|
+
const ignored = new Set(ignoredSourceShas);
|
|
86
88
|
const latestByPath = new Map(
|
|
87
89
|
requiredWorkflowPaths.map((workflow) => [
|
|
88
90
|
workflow,
|
|
@@ -90,7 +92,11 @@ export function selectLatestQualifiedSource({
|
|
|
90
92
|
]),
|
|
91
93
|
);
|
|
92
94
|
for (let index = 0; index < sourceHistory.length; index += 1) {
|
|
93
|
-
const sourceSha =
|
|
95
|
+
const sourceSha =
|
|
96
|
+
typeof sourceHistory[index] === "string"
|
|
97
|
+
? sourceHistory[index]
|
|
98
|
+
: sourceHistory[index]?.sha;
|
|
99
|
+
if (ignored.has(sourceSha)) continue;
|
|
94
100
|
const rows = requiredWorkflowPaths.map((workflow) =>
|
|
95
101
|
latestByPath.get(workflow).get(sourceSha),
|
|
96
102
|
);
|
|
@@ -115,7 +121,9 @@ export function selectLatestQualifiedSource({
|
|
|
115
121
|
};
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
|
-
const staleSuccessfulPair = sourceHistory.some((
|
|
124
|
+
const staleSuccessfulPair = sourceHistory.some((entry) => {
|
|
125
|
+
const sourceSha = typeof entry === "string" ? entry : entry?.sha;
|
|
126
|
+
if (ignored.has(sourceSha)) return false;
|
|
119
127
|
const rows = requiredWorkflowPaths.map((workflow) =>
|
|
120
128
|
latestByPath.get(workflow).get(sourceSha),
|
|
121
129
|
);
|