@kungfu-tech/buildchain 4.0.2-alpha.5 → 4.0.2-alpha.50
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/AGENTS.md +10 -1
- package/CONTRIBUTING.md +24 -0
- package/README.md +2 -2
- package/actions/promote-buildchain-ref/README.md +1 -1
- package/actions/v4-release-candidate-promote/README.md +28 -0
- package/architecture/agent-change-map.md +70 -9
- package/architecture/ci-lane-change-budget.json +384 -0
- package/architecture/internal-capabilities.json +56 -9
- package/architecture/maintainability-debt.json +162 -107
- package/architecture/maintainability-policy.json +34 -30
- package/architecture/release-tail-contract-inventory.json +6 -6
- package/architecture/v4-architecture-constitution.md +23 -20
- package/architecture/v4-cross-platform-adopter-qualification.json +1 -1
- package/architecture/v4-delivery-authority-parity.json +2 -2
- package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.json +1 -1
- package/architecture/v4-floating-consumer-policy.json +2 -2
- package/architecture/v4-next-development-parity.json +5 -5
- package/architecture/v4-partial-mutation-recovery-qualification.json +4 -1
- package/architecture/v4-platform-stage-checkpoints.json +1 -1
- package/architecture/v4-provider-operation-journal-contract.json +5 -2
- package/architecture/v4-publication-facade-patches.json +64 -0
- package/architecture/v4-release-activation-shadow-domain.json +5 -2
- package/architecture/v4-release-topology.json +198 -38
- package/architecture/v4-rust-wasm-production-authority.json +58 -0
- package/architecture/v4-stable-publication-fence.json +5 -2
- package/architecture/v4-stage-capsule-qualification.json +2 -2
- package/architecture/v4-tail-reseal-parity.json +8 -6
- package/architecture/v4-universal-workflow-bootstrap.json +176 -0
- package/architecture/v4-universal-workflow-fault-campaign.json +72 -0
- package/architecture/v4-universal-workflow-train-admission.json +40 -0
- package/architecture/workflow-taxonomy.json +1462 -0
- package/bin/buildchain.mjs +7 -7
- package/contracts/buildchain-v2-residuals-v1.json +57 -48
- package/contracts/dev-delivery-authority-v2.schema.json +16 -2
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/contracts/v4-canonical-contracts-v1.schema.json +1 -0
- package/dist/site/buildchain-contract.json +93 -48
- package/dist/site/buildchain-site.json +276 -72
- package/dist/site/capability-registry.json +5 -5
- package/dist/site/controller-registry.json +50 -13
- package/dist/site/kfd-claims.json +2610 -983
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +14 -14
- package/dist/site/node-api-registry.json +273 -127
- package/dist/site/page-registry.json +257 -53
- package/dist/site/public-surface-audit.json +3239 -1232
- package/dist/site/publication-authority-registry.json +1135 -363
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +2 -1
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +16 -2
- package/dist/site/site-manifest.json +18 -18
- package/dist/site/workflow-registry.json +5097 -1659
- package/docs/MAP.md +2 -0
- package/docs/auditable-demo.md +1 -1
- package/docs/cli.md +21 -8
- package/docs/dev-delivery-warrant.md +1 -1
- package/docs/engineering-housekeeper.md +3 -3
- package/docs/kfd-support.md +26 -2
- package/docs/next-development-transition.md +2 -2
- package/docs/node-api-reference.md +216 -205
- package/docs/oci-publication.md +160 -0
- package/docs/publication-authority.md +2 -2
- package/docs/publish-transaction.md +36 -3
- package/docs/release-candidate.md +3 -3
- package/docs/release-flow.md +67 -19
- package/docs/release-governance.md +8 -8
- package/docs/release-passport.md +27 -2
- package/docs/reusable-build-surface.md +16 -0
- package/docs/v4-adopter-delivery.md +2 -2
- package/docs/v4-production-release.md +71 -8
- package/docs/v4-rust-wasm-production-authority.md +58 -0
- package/docs/v4-stage-capsule.md +2 -2
- package/docs/v4-tail-reseal.md +1 -1
- package/docs/workflow-catalog.md +155 -0
- package/docs/workflow-path-migration.md +109 -0
- package/package.json +12 -7
- package/packages/core/artifact-verification-envelope.js +5 -5
- package/packages/core/buildchain-contract.js +1 -1
- package/packages/core/buildchain-kfd-claims.js +2 -2
- package/packages/core/buildchain-publication-authority.js +43 -26
- package/packages/core/buildchain-v4-domain.wasm +0 -0
- package/packages/core/ci-lane-change-budget.js +14 -1
- package/packages/core/controller-evidence.js +4 -1
- package/packages/core/dev-delivery-candidate-identity.js +40 -6
- package/packages/core/dev-delivery-warrant-state.js +3 -4
- package/packages/core/dev-delivery-warrant.js +12 -2
- package/packages/core/github-governance-authority.js +4 -4
- package/packages/core/kfd-adopter-manifest.js +46 -47
- package/packages/core/next-development-projection.js +2 -2
- package/packages/core/next-development-transition.js +5 -6
- package/packages/core/oci-compose-qualification.js +140 -0
- package/packages/core/oci-publication-bundle.js +281 -0
- package/packages/core/oci-publication-graph.js +206 -0
- package/packages/core/paper-agent-entry.js +30 -4
- package/packages/core/paper-fleet.js +1 -1
- package/packages/core/paper-runtime-channels.js +348 -0
- package/packages/core/paper-scaffold-content.js +10 -0
- package/packages/core/paper.js +37 -71
- package/packages/core/publication-sealed-bundle.js +78 -13
- package/packages/core/release-line-dry-run.js +1 -1
- package/packages/core/release-passport-contract.js +5 -5
- package/packages/core/release-passport.js +5 -4
- package/packages/core/release-tail-product-capabilities.js +38 -0
- package/packages/core/release-tail-provider-plane.js +68 -1048
- package/packages/core/v4-canonical-contracts.js +15 -75
- package/packages/core/v4-domain-wasm-artifact.js +7 -0
- package/packages/core/v4-domain-wasm.js +216 -0
- package/packages/core/v4-floating-consumer-evidence.js +1 -1
- package/packages/core/v4-floating-consumer-policy.js +1 -1
- package/packages/core/v4-partial-mutation-recovery-qualification.js +9 -386
- package/packages/core/v4-product-publication.js +59 -0
- package/packages/core/v4-protected-publication-source.js +19 -21
- package/packages/core/v4-provider-operation-journal.js +18 -487
- package/packages/core/v4-provider-readback-idempotency.js +12 -97
- package/packages/core/v4-release-activation-shadow.js +13 -500
- package/packages/core/v4-release-invocation.js +28 -389
- package/packages/core/v4-stable-publication-fence.js +13 -342
- package/packages/core/v4-universal-workflow-bootstrap.js +586 -0
- package/scripts/binary-publication-evidence.mjs +135 -0
- package/scripts/build-contract-core.mjs +1 -14
- package/scripts/build-v4-domain-wasm.mjs +189 -0
- package/scripts/check-action-bundles.mjs +12 -2
- package/scripts/check-ci-lane-change-budget.mjs +4 -1
- package/scripts/check-core-mechanism-inventory.mjs +4 -3
- package/scripts/check-inventory.mjs +35 -44
- package/scripts/check-maintainability.mjs +16 -113
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +2 -2
- package/scripts/check-v4-public-dogfood-contract.mjs +22 -9
- package/scripts/check-v4-release-topology.mjs +236 -50
- package/scripts/check-v4-runtime-semantic-closure.mjs +4 -1
- package/scripts/check-v4-universal-workflow-bootstrap.mjs +266 -0
- package/scripts/check-workflow-taxonomy.mjs +6 -0
- package/scripts/copy-v4-domain-wasm.mjs +14 -0
- package/scripts/dev-delivery-authority-command-adapters.mjs +1 -1
- package/scripts/dev-delivery-authority.mjs +8 -8
- package/scripts/dev-delivery-source-proof-reuse.mjs +2 -1
- package/scripts/dev-delivery-warrant-options.mjs +14 -9
- package/scripts/dev-delivery-warrant-store.mjs +73 -7
- package/scripts/dev-delivery-warrant-transition.mjs +109 -0
- package/scripts/dev-delivery-warrant.mjs +76 -99
- package/scripts/dev-pr-auto-merge.mjs +15 -15
- package/scripts/generate-channel-build-workflow.mjs +7 -2
- package/scripts/generate-channel-promotion-workflow.mjs +10 -25
- package/scripts/generate-site-bundle.mjs +11 -11
- package/scripts/generate-v4-universal-workflow-facades.mjs +497 -0
- package/scripts/generate-workflow-taxonomy.mjs +25 -0
- package/scripts/github-output.mjs +31 -0
- package/scripts/maintainability-metrics.mjs +16 -5
- package/scripts/maintainability-public-surface.mjs +115 -0
- package/scripts/next-development-review.mjs +307 -0
- package/scripts/oci-compose-preview.mjs +250 -0
- package/scripts/paper-work-fleet-cli.mjs +2 -0
- package/scripts/publication-candidate-kind.mjs +93 -0
- package/scripts/publication-candidate-sealer.mjs +16 -1
- package/scripts/release-asset-client.mjs +91 -0
- package/scripts/release-candidate-resolver.mjs +23 -35
- package/scripts/resume-from-candidate-run.mjs +11 -18
- package/scripts/site-capability-metadata.mjs +1 -0
- package/scripts/source-verification-evidence.mjs +344 -0
- package/scripts/stable-candidate-qualification.mjs +1 -1
- package/scripts/universal-facade-maintainability.mjs +91 -0
- package/scripts/v4-product-publication-intent.mjs +129 -0
- package/scripts/v4-publication-settlement.mjs +211 -0
- package/scripts/v4-universal-workflow-backflow.mjs +212 -0
- package/scripts/v4-universal-workflow-engine.mjs +620 -0
- package/scripts/v4-universal-workflow-self-dogfood.mjs +205 -0
- package/scripts/verify-version-state-delta.mjs +173 -0
- package/scripts/workflow-taxonomy.mjs +507 -0
- package/templates/native-dev-delivery.yml +4 -7
- package/templates/universal-buildchain-bootstrap-recovery.yml +282 -0
- package/templates/universal-buildchain-bootstrap.yml +28 -0
|
@@ -4,47 +4,47 @@ import path from "node:path";
|
|
|
4
4
|
import { createPublicationAuthorityRegistry } from "./publication-authority.js";
|
|
5
5
|
|
|
6
6
|
const DESCRIPTORS = Object.freeze([
|
|
7
|
+
[".github/workflows/public-release-oci-compose-preview.yml", "product-publication", true, ["oci-compose-preview"], "caller-secrets", "none", "fixed", "caller-bound"],
|
|
7
8
|
[".github/workflows/.auditable-demo.yml", "non-publication-oidc"],
|
|
8
9
|
[".github/workflows/.declarative-auditable-demo.yml", "governance-write"],
|
|
9
10
|
[".github/workflows/.build.yml", "non-publication-oidc"],
|
|
10
11
|
[".github/workflows/.release-candidate-promote.yml", "product-publication", true, ["npm-publish", "github-release", "channel-ref"], "trusted-publishing", "none", "fixed", "caller-bound"],
|
|
11
12
|
[".github/workflows/.publication-authority.yml", "evidence-publication"],
|
|
12
13
|
[".github/workflows/.release-verify.yml", "governance-write"],
|
|
13
|
-
[".github/workflows/.sam-release.yml", "retired-deny"],
|
|
14
14
|
[".github/workflows/.sam-verify.yml", "retired-deny"],
|
|
15
15
|
[".github/workflows/.sync-remote-git.yml", "governance-write"],
|
|
16
16
|
[".github/workflows/.web-surface.yml", "product-publication", true, ["web-production"], "oidc", "consumer-defined", "caller-bound", "caller-bound"],
|
|
17
|
-
[".github/workflows/.wheel-release.yml", "retired-deny"],
|
|
18
17
|
[".github/workflows/.wheel-verify.yml", "retired-deny"],
|
|
19
18
|
[".github/workflows/.binary-release-assets.yml", "product-publication", true, ["github-release"], "github-token", "buildchain-release-assets"],
|
|
20
|
-
[".github/workflows/binary-distribution.yml", "evidence-publication"],
|
|
21
|
-
[".github/workflows/
|
|
19
|
+
[".github/workflows/self-build-binary-distribution.yml", "evidence-publication"],
|
|
20
|
+
[".github/workflows/self-release-binary-assets.yml", "governance-write"],
|
|
22
21
|
[".github/workflows/artifact-signing-authority.yml", "evidence-publication"],
|
|
23
|
-
[".github/workflows/
|
|
24
|
-
[".github/workflows/build-
|
|
22
|
+
[".github/workflows/self-build-demo-dogfood.yml", "governance-write"],
|
|
23
|
+
[".github/workflows/self-build-fixture.yml", "non-publication-oidc"],
|
|
25
24
|
[".github/workflows/build.yml", "non-publication-oidc"],
|
|
26
|
-
[".github/workflows/
|
|
27
|
-
[".github/workflows/
|
|
28
|
-
[".github/workflows/
|
|
29
|
-
[".github/workflows/
|
|
30
|
-
[".github/workflows/
|
|
25
|
+
[".github/workflows/bootstrap.yml", "product-publication", true, ["universal-candidate-execution"], "caller-secrets", "none", "fixed", "caller-bound"],
|
|
26
|
+
[".github/workflows/self-build-alpha-dogfood.yml", "non-publication-oidc"],
|
|
27
|
+
[".github/workflows/self-ops-dev-delivery.yml", "governance-write"],
|
|
28
|
+
[".github/workflows/self-ops-patrol-daily.yml", "governance-write"],
|
|
29
|
+
[".github/workflows/self-ops-patrol-monthly.yml", "governance-write"],
|
|
30
|
+
[".github/workflows/self-ops-patrol-weekly.yml", "governance-write"],
|
|
31
31
|
[".github/workflows/buildchain-patrol.yml", "governance-write"],
|
|
32
|
-
[".github/workflows/
|
|
33
|
-
[".github/workflows/
|
|
34
|
-
[".github/workflows/
|
|
32
|
+
[".github/workflows/self-ops-promotion-recovery.yml", "governance-write"],
|
|
33
|
+
[".github/workflows/self-release-promote.yml", "governance-write"],
|
|
34
|
+
[".github/workflows/self-ops-stable-candidate-patrol.yml", "governance-write"],
|
|
35
35
|
[".github/workflows/dev-qualification-patrol.yml", "governance-write"],
|
|
36
36
|
[".github/workflows/dev-alpha-candidate-patrol.yml", "governance-write"],
|
|
37
37
|
[".github/workflows/dev-delivery-warrant-close.yml", "governance-write"],
|
|
38
38
|
[".github/workflows/dev-delivery-warrant-cancel.yml", "governance-write"],
|
|
39
|
-
[".github/workflows/
|
|
39
|
+
[".github/workflows/self-ops-merge-queue.yml", "governance-write"],
|
|
40
40
|
[".github/workflows/dev-pr-auto-merge.yml", "governance-write"],
|
|
41
|
-
[".github/workflows/
|
|
42
|
-
[".github/workflows/
|
|
43
|
-
[".github/workflows/
|
|
41
|
+
[".github/workflows/self-ops-housekeeping-daily.yml", "governance-write"],
|
|
42
|
+
[".github/workflows/self-ops-housekeeping-monthly.yml", "governance-write"],
|
|
43
|
+
[".github/workflows/self-ops-housekeeping-weekly.yml", "governance-write"],
|
|
44
44
|
[".github/workflows/engineering-housekeeper.yml", "governance-write"],
|
|
45
|
-
[".github/workflows/
|
|
45
|
+
[".github/workflows/self-ops-governance-audit.yml", "governance-write"],
|
|
46
46
|
[".github/workflows/github-artifact-attestation.yml", "evidence-publication", true, ["github-artifact-attestation"], "oidc", "consumer-defined", "caller-bound", "caller-bound"],
|
|
47
|
-
[".github/workflows/npm-
|
|
47
|
+
[".github/workflows/self-release-npm-dry-run.yml", "dry-run-only"],
|
|
48
48
|
[".github/workflows/paper-release-sealed.yml", "product-publication", true, ["npm-publish", "github-release"], "trusted-publishing", "none", "fixed", "caller-bound"],
|
|
49
49
|
[".github/workflows/paper-release.yml", "product-publication", true, ["npm-publish", "github-release"], "trusted-publishing", "none", "fixed", "caller-bound"],
|
|
50
50
|
[".github/workflows/patrol-daily.yml", "governance-write"],
|
|
@@ -53,16 +53,18 @@ const DESCRIPTORS = Object.freeze([
|
|
|
53
53
|
[".github/workflows/patrol-weekly.yml", "governance-write"],
|
|
54
54
|
[".github/workflows/publication-artifact.yml", "evidence-publication"],
|
|
55
55
|
[".github/workflows/release-candidate-promote.yml", "product-publication", true, ["npm-publish", "github-release", "channel-ref"], "trusted-publishing", "none", "fixed", "caller-bound"],
|
|
56
|
-
[".github/workflows/release-line-
|
|
56
|
+
[".github/workflows/self-release-line-open.yml", "governance-write"],
|
|
57
57
|
[".github/workflows/release-propagation.yml", "governance-write"],
|
|
58
58
|
[".github/workflows/release-tail.yml", "product-publication", true, ["artifact.publish", "signed-channel.commit", "release.activate", "released-evidence.synthesize"], "caller-secrets", "caller-bound", "caller-bound", "fixed"],
|
|
59
|
-
[".github/workflows/release-verify.yml", "governance-write"],
|
|
59
|
+
[".github/workflows/self-build-release-verify-compat.yml", "governance-write"],
|
|
60
60
|
[".github/workflows/stable-candidate-patrol.yml", "governance-write"],
|
|
61
|
-
[".github/workflows/
|
|
61
|
+
[".github/workflows/universal-bootstrap-recovery.yml", "product-publication", true, ["universal-candidate-execution"], "caller-secrets", "none", "fixed", "caller-bound"],
|
|
62
|
+
[".github/workflows/self-ops-bootstrap-dogfood.yml", "product-publication", true, ["universal-candidate-execution"], "caller-secrets", "none", "fixed", "caller-bound"],
|
|
63
|
+
[".github/workflows/self-release-tail-dogfood.yml", "product-publication", true, ["artifact.publish", "signed-channel.commit", "release.activate", "released-evidence.synthesize"], "caller-secrets", "none", "fixed", "fixed"],
|
|
62
64
|
]);
|
|
63
65
|
|
|
64
|
-
export function buildchainPublicationAuthorityDescriptors() {
|
|
65
|
-
|
|
66
|
+
export function buildchainPublicationAuthorityDescriptors({ root = process.cwd() } = {}) {
|
|
67
|
+
const descriptors = DESCRIPTORS.map(([
|
|
66
68
|
workflowPath,
|
|
67
69
|
authorityClass,
|
|
68
70
|
publicationCapable = false,
|
|
@@ -84,6 +86,21 @@ export function buildchainPublicationAuthorityDescriptors() {
|
|
|
84
86
|
environmentMode,
|
|
85
87
|
runnerPolicy: publicationCapable ? "qualified-measured" : "unqualified",
|
|
86
88
|
}));
|
|
89
|
+
const taxonomyPath = path.join(root, "architecture/workflow-taxonomy.json");
|
|
90
|
+
if (!fs.existsSync(taxonomyPath)) return descriptors;
|
|
91
|
+
const taxonomy = JSON.parse(fs.readFileSync(taxonomyPath, "utf8"));
|
|
92
|
+
for (const entry of taxonomy.entries) {
|
|
93
|
+
if (!entry.compatibility) continue;
|
|
94
|
+
const previous = descriptors.find((item) => item.workflowPath === entry.compatibility.path);
|
|
95
|
+
if (!previous) continue;
|
|
96
|
+
const prefix = entry.role === "component" ? "." : `${entry.role}-`;
|
|
97
|
+
const workflowPath = `.github/workflows/${prefix}${entry.category}-${entry.purpose}.yml`;
|
|
98
|
+
if (fs.readFileSync(path.join(root, workflowPath), "utf8") !== fs.readFileSync(path.join(root, entry.compatibility.path), "utf8")) {
|
|
99
|
+
throw new Error(`publication authority alias differs from canonical workflow: ${workflowPath}`);
|
|
100
|
+
}
|
|
101
|
+
descriptors.push({ ...previous, workflowPath, publisherWorkflowPath: previous.publisherWorkflowPath === previous.workflowPath ? workflowPath : previous.publisherWorkflowPath });
|
|
102
|
+
}
|
|
103
|
+
return descriptors;
|
|
87
104
|
}
|
|
88
105
|
export function createBuildchainPublicationAuthorityRegistry({ root = process.cwd() } = {}) {
|
|
89
106
|
const workflowsDir = path.join(root, ".github", "workflows");
|
|
@@ -94,7 +111,7 @@ export function createBuildchainPublicationAuthorityRegistry({ root = process.cw
|
|
|
94
111
|
text: fs.readFileSync(path.join(workflowsDir, name), "utf8"),
|
|
95
112
|
}));
|
|
96
113
|
return createPublicationAuthorityRegistry({
|
|
97
|
-
descriptors: buildchainPublicationAuthorityDescriptors(),
|
|
114
|
+
descriptors: buildchainPublicationAuthorityDescriptors({ root }),
|
|
98
115
|
workflows,
|
|
99
116
|
});
|
|
100
117
|
}
|
|
Binary file
|
|
@@ -10,6 +10,7 @@ const AUTHORITY_CLASSES = new Set([
|
|
|
10
10
|
"post-merge-advisory",
|
|
11
11
|
"diagnostic",
|
|
12
12
|
"evidence",
|
|
13
|
+
"governed-delegation",
|
|
13
14
|
"release",
|
|
14
15
|
]);
|
|
15
16
|
const TRIGGER_CLASSES = new Set([
|
|
@@ -196,7 +197,19 @@ export function evaluateCiLaneChangeBudget({
|
|
|
196
197
|
}
|
|
197
198
|
const declarations = new Map();
|
|
198
199
|
const diagnostics = [];
|
|
199
|
-
|
|
200
|
+
const familyDeclarations = (policy.declarationFamilies || []).flatMap(
|
|
201
|
+
({ laneIds, ...declaration }) => {
|
|
202
|
+
if (!Array.isArray(laneIds) || laneIds.length === 0)
|
|
203
|
+
throw new Error("CI lane declaration family requires laneIds");
|
|
204
|
+
if (new Set(laneIds).size !== laneIds.length)
|
|
205
|
+
throw new Error("CI lane declaration family laneIds must be unique");
|
|
206
|
+
return laneIds.map((laneId) => ({ ...declaration, laneId }));
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
for (const declaration of [
|
|
210
|
+
...(policy.declarations || []),
|
|
211
|
+
...familyDeclarations,
|
|
212
|
+
]) {
|
|
200
213
|
const laneId = String(declaration?.laneId || "").trim();
|
|
201
214
|
if (!laneId) throw new Error("CI lane declaration laneId is required");
|
|
202
215
|
if (declarations.has(laneId))
|
|
@@ -196,6 +196,9 @@ function descriptorDigest(descriptor) {
|
|
|
196
196
|
|
|
197
197
|
export function createControllerRegistry({ workflows = [] } = {}) {
|
|
198
198
|
const workflowById = new Map(workflows.map((entry) => [entry.id, entry]));
|
|
199
|
+
for (const entry of workflows) {
|
|
200
|
+
if (entry.taxonomy && !entry.taxonomy.compatibilityAlias && !workflowById.has(entry.taxonomy.id)) workflowById.set(entry.taxonomy.id, entry);
|
|
201
|
+
}
|
|
199
202
|
const controllers = CONTROLLER_SPECS.map((spec) => {
|
|
200
203
|
const workflow = workflowById.get(spec.workflowId);
|
|
201
204
|
if (!workflow) throw new Error(`controller ${spec.id} requires workflow descriptor ${spec.workflowId}`);
|
|
@@ -215,7 +218,7 @@ export function createControllerRegistry({ workflows = [] } = {}) {
|
|
|
215
218
|
contract: BUILDCHAIN_CONTROLLER_DESCRIPTOR_CONTRACT,
|
|
216
219
|
id: spec.id,
|
|
217
220
|
version: spec.version,
|
|
218
|
-
workflow: { id: workflow.id, path: workflow.path },
|
|
221
|
+
workflow: { id: workflow.id, path: workflow.path, ...(workflow.taxonomy?.contractPath ? { contractPath: workflow.taxonomy.contractPath } : {}) },
|
|
219
222
|
inputs,
|
|
220
223
|
expected: {
|
|
221
224
|
stages: spec.stages.map((id) => ({ id, required: !optionalStages.has(id) })),
|
|
@@ -20,7 +20,21 @@ export const EXACT_DEV_DELIVERY_PROOF_FIELDS = Object.freeze([
|
|
|
20
20
|
"environmentRoot",
|
|
21
21
|
"sourceWorkflowRunId",
|
|
22
22
|
]);
|
|
23
|
-
|
|
23
|
+
export function devDeliverySourceBinding(input = {}) {
|
|
24
|
+
const [sourceRoot, assignmentRoot, initiativeRoot] = [
|
|
25
|
+
"sourceRoot",
|
|
26
|
+
"assignmentRoot",
|
|
27
|
+
"initiativeRoot",
|
|
28
|
+
].map((field) => String(input[field] || "").trim());
|
|
29
|
+
if (sourceRoot && !assignmentRoot && !initiativeRoot)
|
|
30
|
+
return { sourceRoot: exactRoot(sourceRoot, "sourceRoot") };
|
|
31
|
+
if (!sourceRoot && assignmentRoot && initiativeRoot)
|
|
32
|
+
return {
|
|
33
|
+
assignmentRoot: exactRoot(assignmentRoot, "assignmentRoot"),
|
|
34
|
+
initiativeRoot: exactRoot(initiativeRoot, "initiativeRoot"),
|
|
35
|
+
};
|
|
36
|
+
throw new Error("provide sourceRoot alone or both historical roots");
|
|
37
|
+
}
|
|
24
38
|
export function matchesExactDevDeliveryCandidate(existing, attempted) {
|
|
25
39
|
return (
|
|
26
40
|
existing.sourceHead === attempted.sourceHead &&
|
|
@@ -37,6 +51,29 @@ export function matchesExactDevDeliveryCandidate(existing, attempted) {
|
|
|
37
51
|
attempted.releaseBlockerPriority?.claimRoot
|
|
38
52
|
);
|
|
39
53
|
}
|
|
54
|
+
export function reuseExactActiveDevDeliverySourceProof(active, input) {
|
|
55
|
+
const phaseCompatible =
|
|
56
|
+
["provisional", "qualified"].includes(active?.phase) ||
|
|
57
|
+
(!Object.hasOwn(active || {}, "phase") &&
|
|
58
|
+
active?.deliveryClass === "non-native-fast" &&
|
|
59
|
+
!Object.hasOwn(active || {}, "environmentRoot"));
|
|
60
|
+
const exact =
|
|
61
|
+
phaseCompatible &&
|
|
62
|
+
["sourceRoot", "assignmentRoot", "initiativeRoot"].every(
|
|
63
|
+
(field) => active[field] === input[field],
|
|
64
|
+
) &&
|
|
65
|
+
[
|
|
66
|
+
"pullRequestNumber",
|
|
67
|
+
"sourceIdentityRoot",
|
|
68
|
+
"deliveryClass",
|
|
69
|
+
"priority",
|
|
70
|
+
].every((field) => active[field] === input[field]) &&
|
|
71
|
+
matchesExactDevDeliveryCandidate(active, {
|
|
72
|
+
...input,
|
|
73
|
+
sourceProofRoot: active.sourceProofRoot,
|
|
74
|
+
});
|
|
75
|
+
return exact ? { ...input, sourceProofRoot: active.sourceProofRoot } : input;
|
|
76
|
+
}
|
|
40
77
|
|
|
41
78
|
export function createDevDeliveryCandidateIdentity(
|
|
42
79
|
input,
|
|
@@ -50,8 +87,7 @@ export function createDevDeliveryCandidateIdentity(
|
|
|
50
87
|
input.pullRequestNumber,
|
|
51
88
|
"pullRequestNumber",
|
|
52
89
|
),
|
|
53
|
-
|
|
54
|
-
initiativeRoot: exactRoot(input.initiativeRoot, "initiativeRoot"),
|
|
90
|
+
...devDeliverySourceBinding(input),
|
|
55
91
|
sourceIdentityRoot: exactRoot(
|
|
56
92
|
input.sourceIdentityRoot,
|
|
57
93
|
"sourceIdentityRoot",
|
|
@@ -59,11 +95,10 @@ export function createDevDeliveryCandidateIdentity(
|
|
|
59
95
|
deliveryClass: deliveryClass(input.deliveryClass),
|
|
60
96
|
};
|
|
61
97
|
if (input.identitySemantics) {
|
|
62
|
-
if (text(input.identitySemantics) !== CHAINED_ATTEMPT_IDENTITY)
|
|
98
|
+
if (text(input.identitySemantics) !== CHAINED_ATTEMPT_IDENTITY)
|
|
63
99
|
throw new Error(
|
|
64
100
|
`unsupported candidate identity semantics ${input.identitySemantics}`,
|
|
65
101
|
);
|
|
66
|
-
}
|
|
67
102
|
identity.identitySemantics = CHAINED_ATTEMPT_IDENTITY;
|
|
68
103
|
identity.predecessorCandidateId = exactRoot(
|
|
69
104
|
input.predecessorCandidateId,
|
|
@@ -72,7 +107,6 @@ export function createDevDeliveryCandidateIdentity(
|
|
|
72
107
|
}
|
|
73
108
|
return { ...identity, candidateId: devDeliveryContentRoot(identity) };
|
|
74
109
|
}
|
|
75
|
-
|
|
76
110
|
export function validateDevDeliveryCandidateChain(candidates, terminalStates) {
|
|
77
111
|
const precedingCandidates = new Map();
|
|
78
112
|
const latestByPullRequest = new Map();
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
chainedDevDeliveryAttemptInput,
|
|
14
14
|
createDevDeliveryCandidateIdentity,
|
|
15
|
+
devDeliverySourceBinding,
|
|
15
16
|
EXACT_DEV_DELIVERY_PROOF_FIELDS,
|
|
16
17
|
matchesExactDevDeliveryCandidate,
|
|
17
18
|
validateDevDeliveryCandidateChain,
|
|
@@ -86,8 +87,7 @@ export function issueDevDeliveryWarrant(
|
|
|
86
87
|
candidateId: candidate.candidateId,
|
|
87
88
|
pullRequestNumber: candidate.pullRequestNumber,
|
|
88
89
|
sourceHead: candidate.sourceHead,
|
|
89
|
-
|
|
90
|
-
initiativeRoot: candidate.initiativeRoot,
|
|
90
|
+
...devDeliverySourceBinding(candidate),
|
|
91
91
|
sourceIdentityRoot: candidate.sourceIdentityRoot,
|
|
92
92
|
sourcePatchRoot: candidate.sourcePatchRoot,
|
|
93
93
|
sourceProofRoot: candidate.sourceProofRoot,
|
|
@@ -413,8 +413,7 @@ function submissionReceipt({ before, after, candidate, action, now }) {
|
|
|
413
413
|
candidateId: candidate.candidateId,
|
|
414
414
|
pullRequestNumber: candidate.pullRequestNumber,
|
|
415
415
|
sourceHead: candidate.sourceHead,
|
|
416
|
-
|
|
417
|
-
initiativeRoot: candidate.initiativeRoot,
|
|
416
|
+
...devDeliverySourceBinding(candidate),
|
|
418
417
|
sourceIdentityRoot: candidate.sourceIdentityRoot,
|
|
419
418
|
sourcePatchRoot: candidate.sourcePatchRoot,
|
|
420
419
|
sourceProofRoot: candidate.sourceProofRoot,
|
|
@@ -198,8 +198,8 @@ export function selectDevDeliveryWarrant(
|
|
|
198
198
|
candidateId: transaction.result.candidate.candidateId,
|
|
199
199
|
pullRequestNumber: transaction.result.candidate.pullRequestNumber,
|
|
200
200
|
sourceHead: transaction.result.candidate.sourceHead,
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
...candidateSourceBinding(transaction.result.candidate),
|
|
202
|
+
|
|
203
203
|
sourceIdentityRoot: transaction.result.candidate.sourceIdentityRoot,
|
|
204
204
|
sourcePatchRoot: transaction.result.candidate.sourcePatchRoot,
|
|
205
205
|
sourceProofRoot: transaction.result.candidate.sourceProofRoot,
|
|
@@ -488,6 +488,7 @@ function createDevDeliverySuccessorWake(queue, now) {
|
|
|
488
488
|
"candidateId",
|
|
489
489
|
"pullRequestNumber",
|
|
490
490
|
"sourceHead",
|
|
491
|
+
"sourceRoot",
|
|
491
492
|
"assignmentRoot",
|
|
492
493
|
"initiativeRoot",
|
|
493
494
|
"sourceIdentityRoot",
|
|
@@ -586,3 +587,12 @@ export function observeDevDeliveryQueue(
|
|
|
586
587
|
observedAt: currentTime,
|
|
587
588
|
};
|
|
588
589
|
}
|
|
590
|
+
|
|
591
|
+
function candidateSourceBinding(candidate) {
|
|
592
|
+
return candidate.sourceRoot
|
|
593
|
+
? { sourceRoot: candidate.sourceRoot }
|
|
594
|
+
: {
|
|
595
|
+
assignmentRoot: candidate.assignmentRoot,
|
|
596
|
+
initiativeRoot: candidate.initiativeRoot,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
@@ -118,12 +118,12 @@ const PROTECTED_AUTHORITY_PATHS = Object.freeze([
|
|
|
118
118
|
".github/CODEOWNERS",
|
|
119
119
|
".github/workflows/.publication-authority.yml",
|
|
120
120
|
".github/workflows/.release-candidate-promote.yml",
|
|
121
|
-
".github/workflows/
|
|
122
|
-
".github/workflows/
|
|
123
|
-
".github/workflows/
|
|
121
|
+
".github/workflows/self-ops-promotion-recovery.yml",
|
|
122
|
+
".github/workflows/self-release-promote.yml",
|
|
123
|
+
".github/workflows/self-ops-governance-audit.yml",
|
|
124
124
|
".github/workflows/paper-release-sealed.yml",
|
|
125
125
|
".github/workflows/paper-release.yml",
|
|
126
|
-
".github/workflows/release-line-
|
|
126
|
+
".github/workflows/self-release-line-open.yml",
|
|
127
127
|
".github/workflows/release-candidate-promote.yml",
|
|
128
128
|
"actions/promote-buildchain-ref/action.yml",
|
|
129
129
|
"actions/promote-buildchain-ref/dist/index.js",
|
|
@@ -21,7 +21,7 @@ export const KFD_LEGACY_SUPPORT_MATRIX_CONTRACT = "kungfu-kfd-support-matrix";
|
|
|
21
21
|
|
|
22
22
|
const BUILDCHAIN_ADOPTER_ID = "kungfu-systems/buildchain";
|
|
23
23
|
const ROOT_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
24
|
-
const SHA_PATTERN = /^[0-9a-f]{40}
|
|
24
|
+
const SHA_PATTERN = /^[0-9a-f]{40}$/, SOURCE_REPOSITORY_PATTERN = /^[^@\s]+$/;
|
|
25
25
|
const REQUIRED_DECISIONS = Object.freeze(["KFD-1", "KFD-2", "KFD-3", "KFD-4", "KFD-5", "KFD-7"]);
|
|
26
26
|
const PRODUCT_GATE_STANDARDS = Object.freeze(["kfd-4", "kfd-5", "kfd-7"]);
|
|
27
27
|
const PRODUCT_GATE_SET = new Set(PRODUCT_GATE_STANDARDS);
|
|
@@ -77,29 +77,27 @@ function verifyPublishedManifest(manifest, packageOptions, issues) {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
function
|
|
81
|
-
const match = /^kungfu-systems\/buildchain@([0-9a-f]{40})$/.exec(text(manifest?.adopter?.artifact?.coordinate));
|
|
82
|
-
return match?.[1] || "";
|
|
83
|
-
}
|
|
80
|
+
function adopterExpectation({ expectedAdopterId = BUILDCHAIN_ADOPTER_ID, expectedSourceRepository = "" } = {}) { const adopterId = text(expectedAdopterId) || BUILDCHAIN_ADOPTER_ID; return { adopterId, sourceRepository: text(expectedSourceRepository) || adopterId }; }
|
|
84
81
|
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
82
|
+
function manifestSource(manifest) { const coordinate = text(manifest?.adopter?.artifact?.coordinate), match = /^([^@\s]+)@([0-9a-f]{40})$/.exec(coordinate); return { coordinate, repository: match?.[1] || "", sha: match?.[2] || "" }; }
|
|
83
|
+
|
|
84
|
+
function validateManifestIdentity(manifest, packageArtifactRoot, expectation, expectedSourceSha, issues) {
|
|
85
|
+
if (manifest?.adopter?.id !== expectation.adopterId) issues.push(issue("adopter-identity", "manifest.adopter.id", `manifest adopter must be ${expectation.adopterId}`));
|
|
89
86
|
const pinned = manifest?.kfdCut?.package;
|
|
90
87
|
if (pinned?.name !== kfdPackageJson.name
|
|
91
88
|
|| pinned?.version !== kfdPackageJson.version
|
|
92
89
|
|| pinned?.artifactRoot !== packageArtifactRoot) {
|
|
93
90
|
issues.push(issue("adopter-package-cut", "manifest.kfdCut.package", "manifest must bind the exact installed KFD package cut"));
|
|
94
91
|
}
|
|
95
|
-
const
|
|
96
|
-
if (manifest?.adopter?.artifact?.kind !== "git-commit" || !
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
const source = manifestSource(manifest);
|
|
93
|
+
if (!SOURCE_REPOSITORY_PATTERN.test(expectation.sourceRepository) || manifest?.adopter?.artifact?.kind !== "git-commit" || !source.sha || !ROOT_PATTERN.test(text(manifest?.adopter?.artifact?.root))) {
|
|
94
|
+
issues.push(issue("adopter-source", "manifest.adopter.artifact", "adopter authority must bind one exact repository, git commit, and artifact root"));
|
|
95
|
+
} else if (source.repository !== expectation.sourceRepository) {
|
|
96
|
+
issues.push(issue("adopter-source", "manifest.adopter.artifact.coordinate", `manifest source repository must be ${expectation.sourceRepository}`));
|
|
97
|
+
} else if (expectedSourceSha && source.sha !== expectedSourceSha) {
|
|
100
98
|
issues.push(issue("adopter-source", "manifest.adopter.artifact.coordinate", "manifest source does not match the requested release source"));
|
|
101
99
|
}
|
|
102
|
-
return
|
|
100
|
+
return source;
|
|
103
101
|
}
|
|
104
102
|
|
|
105
103
|
function validateRequiredDecisions(decisions, issues) {
|
|
@@ -131,7 +129,7 @@ function collectProductGates(gateResults, decisions, verificationCut, issues) {
|
|
|
131
129
|
for (const [index, gate] of gateResults.entries()) {
|
|
132
130
|
const validation = validateKfdProductGateResult(gate, verificationCut);
|
|
133
131
|
if (!validation.valid) {
|
|
134
|
-
issues.push(issue("adopter-gate-invalid", `gateResults[${index}]`, "
|
|
132
|
+
issues.push(issue("adopter-gate-invalid", `gateResults[${index}]`, "adopter product gate is invalid", {
|
|
135
133
|
gateIssues: validation.issues,
|
|
136
134
|
}));
|
|
137
135
|
}
|
|
@@ -141,15 +139,16 @@ function collectProductGates(gateResults, decisions, verificationCut, issues) {
|
|
|
141
139
|
if (gates.has(gate?.standard)) {
|
|
142
140
|
issues.push(issue("adopter-gate-duplicate", `gateResults[${index}].standard`, `${gate.standard} gate is duplicated`));
|
|
143
141
|
}
|
|
142
|
+
if (gate?.source?.repository !== verificationCut.expectedSourceRepository) issues.push(issue("adopter-gate-source", `gateResults[${index}].source.repository`, "product gate repository must match the adopter source repository"));
|
|
144
143
|
gates.set(gate?.standard, gate);
|
|
145
144
|
}
|
|
146
145
|
for (const standard of PRODUCT_GATE_STANDARDS) {
|
|
147
146
|
const gate = gates.get(standard);
|
|
148
147
|
const id = standard.toUpperCase();
|
|
149
148
|
if (!gate) {
|
|
150
|
-
issues.push(issue("adopter-gate-missing", `gateResults.${standard}`, `${id} requires its
|
|
149
|
+
issues.push(issue("adopter-gate-missing", `gateResults.${standard}`, `${id} requires its exact adopter product gate`));
|
|
151
150
|
} else if (!(decisions.get(id)?.verificationEvidence || []).some((entry) => entry?.root === gate.gateRoot)) {
|
|
152
|
-
issues.push(issue("adopter-gate-unbound", `manifest.decisions.${id}.verificationEvidence`, `${id} must bind the exact
|
|
151
|
+
issues.push(issue("adopter-gate-unbound", `manifest.decisions.${id}.verificationEvidence`, `${id} must bind the exact adopter gate root`));
|
|
153
152
|
}
|
|
154
153
|
}
|
|
155
154
|
return gates;
|
|
@@ -159,12 +158,12 @@ function projectedProductGates(gates) {
|
|
|
159
158
|
return PRODUCT_GATE_STANDARDS.map((standard) => {
|
|
160
159
|
const gate = gates.get(standard);
|
|
161
160
|
return gate
|
|
162
|
-
? { standard, sourceSha: gate.source.sha, gateRoot: gate.gateRoot, status: gate.status }
|
|
163
|
-
: { standard, sourceSha: "", gateRoot: "", status: "missing" };
|
|
161
|
+
? { standard, sourceRepository: gate.source.repository, sourceSha: gate.source.sha, gateRoot: gate.gateRoot, status: gate.status }
|
|
162
|
+
: { standard, sourceRepository: "", sourceSha: "", gateRoot: "", status: "missing" };
|
|
164
163
|
});
|
|
165
164
|
}
|
|
166
165
|
|
|
167
|
-
function createGateDocument({ manifest, bundle, gates, authorityPath, packageArtifactRoot,
|
|
166
|
+
function createGateDocument({ manifest, bundle, gates, authorityPath, packageArtifactRoot, source, checkedAt, maxAgeSeconds, issues }) {
|
|
168
167
|
const gate = {
|
|
169
168
|
schemaVersion: 1,
|
|
170
169
|
contract: KFD_ADOPTER_MANIFEST_GATE_CONTRACT,
|
|
@@ -174,10 +173,8 @@ function createGateDocument({ manifest, bundle, gates, authorityPath, packageArt
|
|
|
174
173
|
contract: text(manifest?.contract),
|
|
175
174
|
manifestRoot: bundle?.roots?.manifestRoot || "",
|
|
176
175
|
},
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
artifactRoot: text(manifest?.adopter?.artifact?.root),
|
|
180
|
-
},
|
|
176
|
+
adopter: { id: text(manifest?.adopter?.id) },
|
|
177
|
+
source: { repository: source.repository, coordinate: source.coordinate, sha: source.sha, artifactRoot: text(manifest?.adopter?.artifact?.root) },
|
|
181
178
|
verificationCut: { checkedAt, maxAgeSeconds },
|
|
182
179
|
standardPackage: {
|
|
183
180
|
name: kfdPackageJson.name,
|
|
@@ -211,28 +208,28 @@ export function createKfdAdopterManifestGate({
|
|
|
211
208
|
packageArtifactRoot = "",
|
|
212
209
|
gateResults = [],
|
|
213
210
|
authorityPath = ".buildchain/kfd/adopter-manifest.json",
|
|
214
|
-
expectedSourceSha = "",
|
|
211
|
+
expectedAdopterId = BUILDCHAIN_ADOPTER_ID, expectedSourceRepository = "", expectedSourceSha = "",
|
|
215
212
|
checkedAt = new Date().toISOString(),
|
|
216
213
|
maxAgeSeconds = 86400,
|
|
217
214
|
} = {}) {
|
|
218
|
-
const issues = [];
|
|
215
|
+
const issues = [], expectation = adopterExpectation({ expectedAdopterId, expectedSourceRepository });
|
|
219
216
|
const bundle = verifyPublishedManifest(manifest, {
|
|
220
217
|
packageArtifactRoot,
|
|
221
218
|
verifiedAt: checkedAt,
|
|
222
219
|
maxAgeSeconds,
|
|
223
220
|
}, issues);
|
|
224
221
|
const decisions = new Map((manifest?.decisions || []).map((row) => [row.id, row]));
|
|
225
|
-
let
|
|
222
|
+
let source = manifestSource(manifest);
|
|
226
223
|
if (bundle) {
|
|
227
|
-
|
|
224
|
+
source = validateManifestIdentity(manifest, packageArtifactRoot, expectation, expectedSourceSha, issues);
|
|
228
225
|
validateRequiredDecisions(decisions, issues);
|
|
229
226
|
validateWarrantWitness(decisions, issues);
|
|
230
227
|
}
|
|
231
|
-
const gates = collectProductGates(gateResults, decisions, { expectedSourceSha:
|
|
232
|
-
return createGateDocument({ manifest, bundle, gates, authorityPath, packageArtifactRoot,
|
|
228
|
+
const gates = collectProductGates(gateResults, decisions, { expectedSourceRepository: expectation.sourceRepository, expectedSourceSha: source.sha || expectedSourceSha, checkedAt }, issues);
|
|
229
|
+
return createGateDocument({ manifest, bundle, gates, authorityPath, packageArtifactRoot, source, checkedAt, maxAgeSeconds, issues });
|
|
233
230
|
}
|
|
234
231
|
|
|
235
|
-
function validateGateDocument(gate, issues) {
|
|
232
|
+
function validateGateDocument(gate, expectation, issues) {
|
|
236
233
|
const copy = structuredClone(gate);
|
|
237
234
|
const root = copy.gateRoot;
|
|
238
235
|
delete copy.gateRoot;
|
|
@@ -244,12 +241,13 @@ function validateGateDocument(gate, issues) {
|
|
|
244
241
|
|| !ROOT_PATTERN.test(text(gate?.authority?.manifestRoot))) {
|
|
245
242
|
issues.push(issue("adopter-gate-authority", "authority", "adopter gate must bind the standard manifest contract and root"));
|
|
246
243
|
}
|
|
244
|
+
if (gate?.adopter?.id !== expectation.adopterId) issues.push(issue("adopter-gate-identity", "adopter.id", `adopter gate must bind ${expectation.adopterId}`));
|
|
247
245
|
if (gate?.standardPackage?.name !== kfdPackageJson.name || gate?.standardPackage?.version !== kfdPackageJson.version
|
|
248
246
|
|| ![gate?.standardPackage?.artifactRoot, gate?.standardPackage?.registryRoot, gate?.standardPackage?.verifierSetRoot].every((value) => ROOT_PATTERN.test(text(value)))) {
|
|
249
247
|
issues.push(issue("adopter-gate-package", "standardPackage", "adopter gate uses stale KFD package metadata"));
|
|
250
248
|
}
|
|
251
|
-
if (!SHA_PATTERN.test(text(gate?.source?.sha)) || !ROOT_PATTERN.test(text(gate?.source?.artifactRoot))) {
|
|
252
|
-
issues.push(issue("adopter-gate-source", "source", "adopter gate must bind
|
|
249
|
+
if (gate?.source?.repository !== expectation.sourceRepository || gate?.source?.coordinate !== `${expectation.sourceRepository}@${gate?.source?.sha || ""}` || !SHA_PATTERN.test(text(gate?.source?.sha)) || !ROOT_PATTERN.test(text(gate?.source?.artifactRoot))) {
|
|
250
|
+
issues.push(issue("adopter-gate-source", "source", "adopter gate must bind the expected repository, exact source commit, coordinate, and artifact root"));
|
|
253
251
|
}
|
|
254
252
|
if (gate?.decisionWitness?.rootAlgorithm !== "sha256-buildchain-stable-json-v1"
|
|
255
253
|
|| !ROOT_PATTERN.test(text(gate?.decisionWitness?.root))
|
|
@@ -259,7 +257,7 @@ function validateGateDocument(gate, issues) {
|
|
|
259
257
|
}
|
|
260
258
|
}
|
|
261
259
|
|
|
262
|
-
function validateProjectedGates(gate, expectedSourceSha, issues) {
|
|
260
|
+
function validateProjectedGates(gate, expectation, expectedSourceSha, issues) {
|
|
263
261
|
const seenStandards = new Set();
|
|
264
262
|
for (const [index, productGate] of (gate.gateResults || []).entries()) {
|
|
265
263
|
if (!PRODUCT_GATE_SET.has(productGate?.standard) || productGate?.status !== "passed" || !ROOT_PATTERN.test(text(productGate?.gateRoot))) {
|
|
@@ -267,7 +265,7 @@ function validateProjectedGates(gate, expectedSourceSha, issues) {
|
|
|
267
265
|
}
|
|
268
266
|
if (seenStandards.has(productGate?.standard)) issues.push(issue("adopter-gate-result-set", `gateResults[${index}]`, "product gate standards must be unique"));
|
|
269
267
|
seenStandards.add(productGate?.standard);
|
|
270
|
-
if (productGate?.sourceSha !== gate?.source?.sha || (expectedSourceSha && productGate?.sourceSha !== expectedSourceSha)) {
|
|
268
|
+
if (productGate?.sourceRepository !== gate?.source?.repository || productGate?.sourceRepository !== expectation.sourceRepository || productGate?.sourceSha !== gate?.source?.sha || (expectedSourceSha && productGate?.sourceSha !== expectedSourceSha)) {
|
|
271
269
|
issues.push(issue("adopter-gate-source", `gateResults[${index}].sourceSha`, "product gate source must match the release source"));
|
|
272
270
|
}
|
|
273
271
|
}
|
|
@@ -277,15 +275,15 @@ function validateProjectedGates(gate, expectedSourceSha, issues) {
|
|
|
277
275
|
}
|
|
278
276
|
|
|
279
277
|
export function validateKfdAdopterManifestGate(gate, {
|
|
280
|
-
expectedSourceSha = "",
|
|
278
|
+
expectedAdopterId = BUILDCHAIN_ADOPTER_ID, expectedSourceRepository = "", expectedSourceSha = "",
|
|
281
279
|
checkedAt = gate?.checkedAt || new Date().toISOString(),
|
|
282
280
|
} = {}) {
|
|
283
|
-
const issues = [];
|
|
281
|
+
const issues = [], expectation = adopterExpectation({ expectedAdopterId, expectedSourceRepository });
|
|
284
282
|
if (!gate || gate.schemaVersion !== 1 || gate.contract !== KFD_ADOPTER_MANIFEST_GATE_CONTRACT) {
|
|
285
283
|
return { valid: false, issues: [issue("adopter-gate-contract", "", `gate must use ${KFD_ADOPTER_MANIFEST_GATE_CONTRACT} v1`)] };
|
|
286
284
|
}
|
|
287
|
-
validateGateDocument(gate, issues);
|
|
288
|
-
validateProjectedGates(gate, expectedSourceSha, issues);
|
|
285
|
+
validateGateDocument(gate, expectation, issues);
|
|
286
|
+
validateProjectedGates(gate, expectation, expectedSourceSha, issues);
|
|
289
287
|
if (gate.checkedAt !== checkedAt || gate?.verificationCut?.checkedAt !== checkedAt || !Number.isFinite(Date.parse(checkedAt))
|
|
290
288
|
|| !Number.isSafeInteger(gate?.verificationCut?.maxAgeSeconds) || gate.verificationCut.maxAgeSeconds < 0) {
|
|
291
289
|
issues.push(issue("adopter-gate-time", "verificationCut", "adopter gate verification cut does not match the requested cut"));
|
|
@@ -300,7 +298,7 @@ function legacyStatus(row) {
|
|
|
300
298
|
return row.state;
|
|
301
299
|
}
|
|
302
300
|
|
|
303
|
-
function legacyRow(row, gates) {
|
|
301
|
+
function legacyRow(row, gates, adopterId) {
|
|
304
302
|
const key = row.id.toLowerCase();
|
|
305
303
|
const productGate = gates.get(key);
|
|
306
304
|
return {
|
|
@@ -318,7 +316,7 @@ function legacyRow(row, gates) {
|
|
|
318
316
|
releaseQualification: { shippedSupport: false },
|
|
319
317
|
claimClass: "standard-adopter-manifest-projection",
|
|
320
318
|
knownLimitations: [...row.gaps],
|
|
321
|
-
owner:
|
|
319
|
+
owner: adopterId,
|
|
322
320
|
nextGate: "Release Passport artifact binding and independent release decision",
|
|
323
321
|
declaration: { state: row.state, usage: row.usage, root: kfdProductGateDigest(decisionWitnessRow(row)) },
|
|
324
322
|
};
|
|
@@ -326,7 +324,7 @@ function legacyRow(row, gates) {
|
|
|
326
324
|
|
|
327
325
|
export function createKfdLegacySupportMatrixProjection({ manifest, manifestGate } = {}) {
|
|
328
326
|
const validation = validateKfdAdopterManifestGate(manifestGate, {
|
|
329
|
-
expectedSourceSha: manifestGate?.source?.sha,
|
|
327
|
+
expectedAdopterId: manifestGate?.adopter?.id, expectedSourceRepository: manifestGate?.source?.repository, expectedSourceSha: manifestGate?.source?.sha,
|
|
330
328
|
checkedAt: manifestGate?.checkedAt,
|
|
331
329
|
});
|
|
332
330
|
if (!validation.valid || manifest?.contract !== "kfd.adopter-conformance-manifest/v1") {
|
|
@@ -338,9 +336,9 @@ export function createKfdLegacySupportMatrixProjection({ manifest, manifestGate
|
|
|
338
336
|
verifiedAt: manifestGate.verificationCut.checkedAt,
|
|
339
337
|
maxAgeSeconds: manifestGate.verificationCut.maxAgeSeconds,
|
|
340
338
|
}, authorityIssues);
|
|
341
|
-
const
|
|
339
|
+
const source = validateManifestIdentity(manifest, manifestGate.standardPackage.artifactRoot, { adopterId: manifestGate.adopter.id, sourceRepository: manifestGate.source.repository }, manifestGate.source.sha, authorityIssues);
|
|
342
340
|
const decisionRoot = kfdProductGateDigest(manifest.decisions.map(decisionWitnessRow));
|
|
343
|
-
if (!bundle ||
|
|
341
|
+
if (!bundle || source.sha !== manifestGate.source.sha || source.repository !== manifestGate.source.repository || source.coordinate !== manifestGate.source.coordinate || manifest.adopter.artifact.root !== manifestGate.source.artifactRoot
|
|
344
342
|
|| bundle.roots.manifestRoot !== manifestGate.authority.manifestRoot
|
|
345
343
|
|| bundle.roots.verificationReportRoot !== manifestGate.manifestVerificationReportRoot
|
|
346
344
|
|| bundle.bundleRoot !== manifestGate.manifestBundleRoot
|
|
@@ -364,7 +362,8 @@ export function createKfdLegacySupportMatrixProjection({ manifest, manifestGate
|
|
|
364
362
|
path: manifestGate.authority.path,
|
|
365
363
|
contract: manifest.contract,
|
|
366
364
|
root: manifestGate.authority.manifestRoot,
|
|
367
|
-
gateRoot: manifestGate.gateRoot,
|
|
365
|
+
gateRoot: manifestGate.gateRoot, adopterId: manifestGate.adopter.id,
|
|
366
|
+
sourceRepository: manifestGate.source.repository, sourceSha: manifestGate.source.sha,
|
|
368
367
|
},
|
|
369
368
|
upstream: {
|
|
370
369
|
package: manifestGate.standardPackage.name,
|
|
@@ -374,7 +373,7 @@ export function createKfdLegacySupportMatrixProjection({ manifest, manifestGate
|
|
|
374
373
|
verifierSetRoot: manifestGate.standardPackage.verifierSetRoot,
|
|
375
374
|
standardsSha256: standardsFileDigest(),
|
|
376
375
|
},
|
|
377
|
-
rows: manifest.decisions.map((row) => legacyRow(row, gates)),
|
|
376
|
+
rows: manifest.decisions.map((row) => legacyRow(row, gates, manifestGate.adopter.id)),
|
|
378
377
|
};
|
|
379
378
|
}
|
|
380
379
|
|
|
@@ -243,8 +243,8 @@ invalid.
|
|
|
243
243
|
|
|
244
244
|
## Public self-dogfood and adoption
|
|
245
245
|
|
|
246
|
-
\`.github/workflows/
|
|
247
|
-
consumer of the public \`
|
|
246
|
+
\`.github/workflows/self-build-public-consumer-dogfood.yml\` remains the exact thin
|
|
247
|
+
consumer of the public \`public-build-stage-capsule-canary.yml@v4-alpha\` contract. Its
|
|
248
248
|
declared \`lifecycle.verify\` runs \`pnpm run check\`, so the same public path
|
|
249
249
|
executes pure recovery tests that inject a transient durable-state write
|
|
250
250
|
failure, restore the adapter operation on a fresh runner, supersede stale work
|
|
@@ -159,15 +159,14 @@ function normalizeModel(value = {}) {
|
|
|
159
159
|
return model;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
function normalizeCompletedAlpha(value = {}) {
|
|
162
|
+
function normalizeCompletedAlpha(value = {}, model) {
|
|
163
163
|
const version = String(value.version || "").trim();
|
|
164
|
-
|
|
164
|
+
const expression = model?.strategy === "anchored" ? /-alpha\.(0|[1-9]\d*)$/u : ALPHA;
|
|
165
|
+
if (!SEMVER.test(version) || !expression.test(version))
|
|
165
166
|
throw new Error("completedAlpha.version must be an alpha semantic version");
|
|
166
167
|
const exactTag = String(value.exactTag || "").trim();
|
|
167
168
|
if (exactTag !== `v${version}`)
|
|
168
|
-
throw new Error(
|
|
169
|
-
"completedAlpha.exactTag must match completedAlpha.version",
|
|
170
|
-
);
|
|
169
|
+
throw new Error("completedAlpha.exactTag must match completedAlpha.version");
|
|
171
170
|
const normalized = {
|
|
172
171
|
outcome: String(value.outcome || "").trim(),
|
|
173
172
|
version,
|
|
@@ -251,8 +250,8 @@ function initialStateRoot(status, idempotencyKey) {
|
|
|
251
250
|
}
|
|
252
251
|
|
|
253
252
|
export function createNextDevelopmentTransition(input = {}) {
|
|
254
|
-
const completedAlpha = normalizeCompletedAlpha(input.completedAlpha);
|
|
255
253
|
const model = normalizeModel(input.model);
|
|
254
|
+
const completedAlpha = normalizeCompletedAlpha(input.completedAlpha, model);
|
|
256
255
|
const adapter = normalizeAdapter(input);
|
|
257
256
|
const declaredPaths = [
|
|
258
257
|
...adapter.sourcePaths,
|