@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
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
evaluateTestBudgets,
|
|
20
20
|
evaluateWorkflowBudgets,
|
|
21
21
|
} from "./maintainability-governance.mjs";
|
|
22
|
+
import { governUniversalFacadeWorkflowMetrics } from "./universal-facade-maintainability.mjs";
|
|
23
|
+
import { evaluatePublicSurface } from "./maintainability-public-surface.mjs";
|
|
22
24
|
|
|
23
25
|
function collectHotspots(
|
|
24
26
|
root,
|
|
@@ -162,116 +164,6 @@ function selectedFunction(metrics, file, name) {
|
|
|
162
164
|
return metrics.files[file]?.functions.find((entry) => entry.name === name);
|
|
163
165
|
}
|
|
164
166
|
|
|
165
|
-
function readJsonAtRevision(root, revision, file) {
|
|
166
|
-
return JSON.parse(gitOutput(root, ["show", `${revision}:${file}`]));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function publicSurfaceContract(entry, kind) {
|
|
170
|
-
if (kind === "cli") return { id: entry.id, usage: entry.usage };
|
|
171
|
-
if (kind === "node")
|
|
172
|
-
return {
|
|
173
|
-
export: entry.export,
|
|
174
|
-
specifier: entry.specifier,
|
|
175
|
-
target: entry.target,
|
|
176
|
-
};
|
|
177
|
-
return {
|
|
178
|
-
id: entry.id,
|
|
179
|
-
path: entry.path,
|
|
180
|
-
reusable: entry.reusable,
|
|
181
|
-
inputs: entry.inputs || [],
|
|
182
|
-
secrets: entry.secrets || [],
|
|
183
|
-
outputs: entry.outputs || [],
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function evaluatePublicSurface({ root, revision, policy }) {
|
|
188
|
-
const issues = [];
|
|
189
|
-
const lifecycleFields = policy.publicSurfacePolicy.requiredLifecycleFields;
|
|
190
|
-
const capabilityGroups = new Set(
|
|
191
|
-
readJson(root, "dist/site/capability-registry.json").groups.map(
|
|
192
|
-
(entry) => entry.id,
|
|
193
|
-
),
|
|
194
|
-
);
|
|
195
|
-
const definitions = [
|
|
196
|
-
{
|
|
197
|
-
file: "dist/site/cli-registry.json",
|
|
198
|
-
collection: "commands",
|
|
199
|
-
kind: "cli",
|
|
200
|
-
key: "id",
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
file: "dist/site/node-api-registry.json",
|
|
204
|
-
collection: "exports",
|
|
205
|
-
kind: "node",
|
|
206
|
-
key: "export",
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
file: "dist/site/workflow-registry.json",
|
|
210
|
-
collection: "workflows",
|
|
211
|
-
kind: "workflow",
|
|
212
|
-
key: "id",
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
file: "dist/site/workflow-registry.json",
|
|
216
|
-
collection: "actions",
|
|
217
|
-
kind: "action",
|
|
218
|
-
key: "id",
|
|
219
|
-
},
|
|
220
|
-
];
|
|
221
|
-
for (const definition of definitions) {
|
|
222
|
-
const current =
|
|
223
|
-
readJson(root, definition.file)[definition.collection] || [];
|
|
224
|
-
const baseline =
|
|
225
|
-
readJsonAtRevision(root, revision, definition.file)[
|
|
226
|
-
definition.collection
|
|
227
|
-
] || [];
|
|
228
|
-
const baselineByKey = new Map(
|
|
229
|
-
baseline.map((entry) => [entry[definition.key], entry]),
|
|
230
|
-
);
|
|
231
|
-
for (const entry of current) {
|
|
232
|
-
const label = `${definition.kind}:${entry[definition.key]}`;
|
|
233
|
-
for (const field of lifecycleFields) {
|
|
234
|
-
if (
|
|
235
|
-
!Object.prototype.hasOwnProperty.call(entry, field) ||
|
|
236
|
-
typeof entry[field] !== "string"
|
|
237
|
-
) {
|
|
238
|
-
issues.push(`${label}: lifecycle field ${field} is missing`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
if (!capabilityGroups.has(entry.capabilityGroup)) {
|
|
242
|
-
issues.push(
|
|
243
|
-
`${label}: capability group ${entry.capabilityGroup || "<empty>"} is not registered`,
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
const previous = baselineByKey.get(entry[definition.key]);
|
|
247
|
-
const currentContract = publicSurfaceContract(entry, definition.kind);
|
|
248
|
-
if (
|
|
249
|
-
previous &&
|
|
250
|
-
JSON.stringify(currentContract) !==
|
|
251
|
-
JSON.stringify(publicSurfaceContract(previous, definition.kind))
|
|
252
|
-
) {
|
|
253
|
-
const approval = policy.approvedPublicSurfaceTransitions?.[label];
|
|
254
|
-
const approved =
|
|
255
|
-
approval?.fromRevision === revision &&
|
|
256
|
-
String(approval?.rationale || "").trim() &&
|
|
257
|
-
JSON.stringify(approval?.contract) ===
|
|
258
|
-
JSON.stringify(currentContract);
|
|
259
|
-
if (!approved) {
|
|
260
|
-
issues.push(
|
|
261
|
-
`${label}: existing public contract drifted from ${revision}`,
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
if (!previous && !entry.nonDuplicationRationale) {
|
|
266
|
-
issues.push(
|
|
267
|
-
`${label}: new public surface requires a non-duplication rationale`,
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return issues;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
167
|
function evaluateAddedFunctionBudgets({
|
|
276
168
|
file,
|
|
277
169
|
metrics,
|
|
@@ -497,12 +389,18 @@ function checkMaintainability({ root = process.cwd() } = {}) {
|
|
|
497
389
|
root,
|
|
498
390
|
extendedCoverageRevision,
|
|
499
391
|
);
|
|
392
|
+
const { governed: governedCurrent, migration: facadeMigration } =
|
|
393
|
+
governUniversalFacadeWorkflowMetrics({
|
|
394
|
+
root,
|
|
395
|
+
current,
|
|
396
|
+
workflowMetricsAtRevision,
|
|
397
|
+
});
|
|
500
398
|
const issues = evaluateExceptionGovernance({ policy });
|
|
501
399
|
issues.push(...evaluateExceptionBudget({ policy }));
|
|
502
400
|
const hotspots = collectHotspots(root, current, 20, debt.hotspots || []);
|
|
503
401
|
issues.push(
|
|
504
402
|
...evaluateDebtAuthority({
|
|
505
|
-
current,
|
|
403
|
+
current: governedCurrent,
|
|
506
404
|
policy,
|
|
507
405
|
debt,
|
|
508
406
|
capabilityIds: new Set(
|
|
@@ -518,14 +416,19 @@ function checkMaintainability({ root = process.cwd() } = {}) {
|
|
|
518
416
|
);
|
|
519
417
|
issues.push(
|
|
520
418
|
...evaluateWorkflowBudgets({
|
|
521
|
-
current,
|
|
419
|
+
current: governedCurrent,
|
|
522
420
|
baselineFiles: baselineWorkflows,
|
|
523
421
|
policy,
|
|
524
422
|
}),
|
|
525
423
|
);
|
|
526
424
|
issues.push(...evaluateRepositoryBudgets({ current, policy }));
|
|
527
425
|
issues.push(
|
|
528
|
-
...evaluatePublicSurface({
|
|
426
|
+
...evaluatePublicSurface({
|
|
427
|
+
root,
|
|
428
|
+
revision: enforcementRevision,
|
|
429
|
+
policy,
|
|
430
|
+
migration: facadeMigration,
|
|
431
|
+
}),
|
|
529
432
|
);
|
|
530
433
|
if (issues.length > 0) {
|
|
531
434
|
throw new Error(`maintainability check failed:\n- ${issues.join("\n- ")}`);
|
|
@@ -80,7 +80,7 @@ function assertPersistedSelectors() {
|
|
|
80
80
|
const selector = match[2];
|
|
81
81
|
const protectedBootstrap =
|
|
82
82
|
relative ===
|
|
83
|
-
".github/workflows/
|
|
83
|
+
".github/workflows/self-ops-promotion-recovery.yml" &&
|
|
84
84
|
match[1] === ".github/workflows/.release-candidate-promote.yml" &&
|
|
85
85
|
selector === "alpha/v4/v4.0";
|
|
86
86
|
const isV4 =
|
|
@@ -148,7 +148,7 @@ export function checkV4FloatingConsumerPolicyContract() {
|
|
|
148
148
|
"Resolve controller identities",
|
|
149
149
|
"Install Buildchain runtime dependencies",
|
|
150
150
|
]);
|
|
151
|
-
const stageCanary = read(".github/workflows/
|
|
151
|
+
const stageCanary = read(".github/workflows/public-build-stage-capsule-canary.yml");
|
|
152
152
|
if (
|
|
153
153
|
!stageCanary.includes("consumer-admission:") ||
|
|
154
154
|
!stageCanary.includes("needs: consumer-admission")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
|
+
import { readWorkflowTaxonomy, workflowPath } from "./workflow-taxonomy.mjs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
|
|
@@ -8,8 +9,8 @@ const DEFAULT_ROOT = path.resolve(
|
|
|
8
9
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
9
10
|
"..",
|
|
10
11
|
);
|
|
11
|
-
const CALLER_PATH = ".github/workflows/
|
|
12
|
-
const REUSABLE_PATH = ".github/workflows/
|
|
12
|
+
const CALLER_PATH = ".github/workflows/self-build-public-consumer-dogfood.yml";
|
|
13
|
+
const REUSABLE_PATH = ".github/workflows/public-build-stage-capsule-canary.yml";
|
|
13
14
|
export const V4_PUBLIC_DOGFOOD_ALPHA_REF = "v4-alpha";
|
|
14
15
|
const PRIVATE_CONSUMER = ["buildchain", "self", "dogfood"].join("-");
|
|
15
16
|
const PRIVATE_SHADOW = ["kungfu", "shadow"].join("-");
|
|
@@ -26,7 +27,7 @@ permissions:
|
|
|
26
27
|
|
|
27
28
|
jobs:
|
|
28
29
|
dogfood:
|
|
29
|
-
uses: kungfu-systems/buildchain/.github/workflows/
|
|
30
|
+
uses: kungfu-systems/buildchain/.github/workflows/public-build-stage-capsule-canary.yml@${validationRef}
|
|
30
31
|
with:
|
|
31
32
|
consumer: buildchain
|
|
32
33
|
node-version: "24"
|
|
@@ -93,10 +94,19 @@ function assertReusableWorkflow(root) {
|
|
|
93
94
|
|
|
94
95
|
function assertWorkflowInventory(root) {
|
|
95
96
|
const workflowRoot = path.join(root, ".github/workflows");
|
|
97
|
+
const taxonomy = readWorkflowTaxonomy(root);
|
|
96
98
|
for (const entry of fs.readdirSync(workflowRoot, { withFileTypes: true })) {
|
|
97
99
|
if (!entry.isFile() || !/\.ya?ml$/u.test(entry.name)) continue;
|
|
98
|
-
|
|
100
|
+
let relative = `.github/workflows/${entry.name}`;
|
|
99
101
|
const text = read(root, relative);
|
|
102
|
+
const canonical = taxonomy?.entries.find(
|
|
103
|
+
(item) => workflowPath(item) === relative && item.compatibility,
|
|
104
|
+
);
|
|
105
|
+
if (canonical) {
|
|
106
|
+
if (read(root, canonical.compatibility.path) !== text)
|
|
107
|
+
fail(`${relative} differs from its public compatibility identity`);
|
|
108
|
+
relative = canonical.compatibility.path;
|
|
109
|
+
}
|
|
100
110
|
if (
|
|
101
111
|
relative !== REUSABLE_PATH &&
|
|
102
112
|
text.includes("scripts/v4-stage-capsule-qualification.mjs")
|
|
@@ -104,7 +114,7 @@ function assertWorkflowInventory(root) {
|
|
|
104
114
|
fail(`${relative} invokes the private qualification script directly`);
|
|
105
115
|
if (
|
|
106
116
|
relative !== CALLER_PATH &&
|
|
107
|
-
text.includes(".github/workflows/
|
|
117
|
+
text.includes(".github/workflows/public-build-stage-capsule-canary.yml@")
|
|
108
118
|
)
|
|
109
119
|
fail(`${relative} creates a second v4 dogfood caller`);
|
|
110
120
|
if (relative !== REUSABLE_PATH && relative !== CALLER_PATH)
|
|
@@ -113,11 +123,13 @@ function assertWorkflowInventory(root) {
|
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
function assertProtectedVerify(root) {
|
|
116
|
-
const verify = read(root, ".github/workflows/verify.yml");
|
|
126
|
+
const verify = read(root, ".github/workflows/self-build-verify.yml");
|
|
117
127
|
for (const required of [
|
|
118
128
|
"needs: stage-capsule-checkpoints",
|
|
119
|
-
"name:
|
|
120
|
-
"
|
|
129
|
+
"name: Run declared verify lifecycle (full source tests and generated artifact checks)",
|
|
130
|
+
"node .buildchain/runtime/bin/buildchain.mjs lifecycle run verify",
|
|
131
|
+
"run: node scripts/source-verification-evidence.mjs plan",
|
|
132
|
+
"run: node scripts/source-verification-evidence.mjs seal",
|
|
121
133
|
])
|
|
122
134
|
if (!verify.includes(required))
|
|
123
135
|
fail(`Verify is missing protected gate ${required}`);
|
|
@@ -150,7 +162,7 @@ function assertArchitecture(root) {
|
|
|
150
162
|
!dogfood ||
|
|
151
163
|
dogfood.callerWorkflow !== CALLER_PATH ||
|
|
152
164
|
dogfood.reusableWorkflow !==
|
|
153
|
-
"kungfu-systems/buildchain/.github/workflows/
|
|
165
|
+
"kungfu-systems/buildchain/.github/workflows/public-build-stage-capsule-canary.yml" ||
|
|
154
166
|
dogfood.runtimeBinding !== "job.workflow_sha" ||
|
|
155
167
|
dogfood.consumerSourceBinding !== "github.sha" ||
|
|
156
168
|
JSON.stringify(dogfood.executableStages) !==
|
|
@@ -193,6 +205,7 @@ function assertConsumerLifecycle(root) {
|
|
|
193
205
|
'[lifecycle.install]\ncommand = "corepack enable pnpm && corepack pnpm@11.7.0 install --frozen-lockfile"',
|
|
194
206
|
'[lifecycle.build]\ncommand = "corepack pnpm@11.7.0 run build && corepack pnpm@11.7.0 run generate:site"',
|
|
195
207
|
"[lifecycle.verify]\ncommands = [",
|
|
208
|
+
'"corepack pnpm@11.7.0 run check",',
|
|
196
209
|
])
|
|
197
210
|
if (!lifecycle.includes(declaration))
|
|
198
211
|
fail(
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import assert from "node:assert/strict";
|
|
4
|
+
import crypto from "node:crypto";
|
|
4
5
|
import fs from "node:fs";
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
import { workflowCompatibilityIdentity } from "./workflow-taxonomy.mjs";
|
|
5
8
|
import path from "node:path";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
10
|
|
|
8
11
|
import {
|
|
9
12
|
parseWorkflowDocument,
|
|
@@ -17,6 +20,101 @@ function read(relative) {
|
|
|
17
20
|
return fs.readFileSync(path.join(root, relative), "utf8");
|
|
18
21
|
}
|
|
19
22
|
|
|
23
|
+
const LOCAL_MODULE_EXTENSIONS = ["", ".js", ".mjs", ".cjs"];
|
|
24
|
+
const PRIVILEGED_ENTRYPOINTS = [
|
|
25
|
+
"actions/v4-release-candidate-promote/index.js",
|
|
26
|
+
"scripts/binary-publication-evidence.mjs",
|
|
27
|
+
"scripts/next-development-review.mjs",
|
|
28
|
+
"scripts/oci-compose-preview.mjs",
|
|
29
|
+
"scripts/v4-publication-settlement.mjs",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export function localModuleSpecifiers(source) {
|
|
33
|
+
const specifiers = [];
|
|
34
|
+
const visit = (node) => {
|
|
35
|
+
let value;
|
|
36
|
+
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node))
|
|
37
|
+
value = node.moduleSpecifier;
|
|
38
|
+
else if (
|
|
39
|
+
ts.isCallExpression(node) &&
|
|
40
|
+
node.expression.kind === ts.SyntaxKind.ImportKeyword
|
|
41
|
+
)
|
|
42
|
+
value = node.arguments[0];
|
|
43
|
+
if (value && ts.isStringLiteral(value) && value.text.startsWith("."))
|
|
44
|
+
specifiers.push(value.text);
|
|
45
|
+
ts.forEachChild(node, visit);
|
|
46
|
+
};
|
|
47
|
+
visit(
|
|
48
|
+
ts.createSourceFile(
|
|
49
|
+
"module.mjs",
|
|
50
|
+
source,
|
|
51
|
+
ts.ScriptTarget.Latest,
|
|
52
|
+
true,
|
|
53
|
+
ts.ScriptKind.JS,
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
return [...new Set(specifiers)].sort();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolveLocalModule(importer, specifier) {
|
|
60
|
+
const unresolved = path.posix.normalize(
|
|
61
|
+
path.posix.join(path.posix.dirname(importer), specifier),
|
|
62
|
+
);
|
|
63
|
+
assert.ok(
|
|
64
|
+
!unresolved.startsWith("../") && !path.posix.isAbsolute(unresolved),
|
|
65
|
+
`local module import escapes the repository: ${importer} -> ${specifier}`,
|
|
66
|
+
);
|
|
67
|
+
const candidates = [
|
|
68
|
+
...LOCAL_MODULE_EXTENSIONS.map((extension) => `${unresolved}${extension}`),
|
|
69
|
+
...LOCAL_MODULE_EXTENSIONS.slice(1).map((extension) =>
|
|
70
|
+
path.posix.join(unresolved, `index${extension}`),
|
|
71
|
+
),
|
|
72
|
+
];
|
|
73
|
+
const matches = candidates.filter((relative) => {
|
|
74
|
+
const absolute = path.join(root, relative);
|
|
75
|
+
return fs.existsSync(absolute) && fs.statSync(absolute).isFile();
|
|
76
|
+
});
|
|
77
|
+
assert.equal(
|
|
78
|
+
matches.length,
|
|
79
|
+
1,
|
|
80
|
+
`local module import must resolve exactly once: ${importer} -> ${specifier}`,
|
|
81
|
+
);
|
|
82
|
+
return matches[0];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function discoverStaticModuleClosure(entrypoints) {
|
|
86
|
+
const pending = [...entrypoints];
|
|
87
|
+
const visited = new Set();
|
|
88
|
+
while (pending.length > 0) {
|
|
89
|
+
const relative = pending.pop();
|
|
90
|
+
if (visited.has(relative)) continue;
|
|
91
|
+
assert.ok(
|
|
92
|
+
fs.statSync(path.join(root, relative)).isFile(),
|
|
93
|
+
`privileged entry is not a file: ${relative}`,
|
|
94
|
+
);
|
|
95
|
+
visited.add(relative);
|
|
96
|
+
for (const specifier of localModuleSpecifiers(read(relative))) {
|
|
97
|
+
const dependency = resolveLocalModule(relative, specifier);
|
|
98
|
+
if (!visited.has(dependency)) pending.push(dependency);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return [...visited].sort();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function rootedPathSet(paths) {
|
|
105
|
+
return `sha256:${crypto
|
|
106
|
+
.createHash("sha256")
|
|
107
|
+
.update(JSON.stringify([...paths].sort()))
|
|
108
|
+
.digest("hex")}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function fileSha256(relative) {
|
|
112
|
+
return `sha256:${crypto
|
|
113
|
+
.createHash("sha256")
|
|
114
|
+
.update(fs.readFileSync(path.join(root, relative)))
|
|
115
|
+
.digest("hex")}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
20
118
|
function productionFiles(relative, extensions) {
|
|
21
119
|
const absolute = path.join(root, relative);
|
|
22
120
|
if (!fs.existsSync(absolute)) return [];
|
|
@@ -61,6 +159,23 @@ export function discoverV4ReleaseAuthorityClosure() {
|
|
|
61
159
|
[".yml", ".yaml", ".js", ".mjs", ".cjs"],
|
|
62
160
|
/(?:createV4ReleaseReceipt|release-receipt\.json|V4_RELEASE_RECEIPT_CONTRACT)/u,
|
|
63
161
|
);
|
|
162
|
+
const privilegedModules = discoverStaticModuleClosure(PRIVILEGED_ENTRYPOINTS);
|
|
163
|
+
const rustWasmArtifact = "packages/core/buildchain-v4-domain.wasm";
|
|
164
|
+
const rustWasmDistributions = [
|
|
165
|
+
"actions/promote-buildchain-ref/dist/buildchain-v4-domain.wasm",
|
|
166
|
+
"actions/release-tail/dist/buildchain-v4-domain.wasm",
|
|
167
|
+
"actions/v4-release-candidate-promote/dist/buildchain-v4-domain.wasm",
|
|
168
|
+
];
|
|
169
|
+
const legacyEngineModules = productionFiles(
|
|
170
|
+
"actions/promote-buildchain-ref",
|
|
171
|
+
[".js", ".mjs", ".cjs"],
|
|
172
|
+
)
|
|
173
|
+
.filter((relative) =>
|
|
174
|
+
/(?:^|\/)(?:lib|promote-(?:alpha|release|major)-channel|durable-transaction-operations)\.js$/u.test(
|
|
175
|
+
relative,
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
.sort();
|
|
64
179
|
return {
|
|
65
180
|
providerAdapters: [
|
|
66
181
|
...new Set([
|
|
@@ -71,6 +186,23 @@ export function discoverV4ReleaseAuthorityClosure() {
|
|
|
71
186
|
runtimeSelectors,
|
|
72
187
|
runtimeEngines: ["actions/v4-release-candidate-promote/index.js"],
|
|
73
188
|
terminalProjections,
|
|
189
|
+
privilegedExecutableClosure: {
|
|
190
|
+
entrypoints: PRIVILEGED_ENTRYPOINTS,
|
|
191
|
+
modules: privilegedModules,
|
|
192
|
+
root: rootedPathSet(privilegedModules),
|
|
193
|
+
},
|
|
194
|
+
rustWasmAuthority: {
|
|
195
|
+
loader: "packages/core/v4-domain-wasm.js",
|
|
196
|
+
metadata: "packages/core/v4-domain-wasm-artifact.js",
|
|
197
|
+
artifact: rustWasmArtifact,
|
|
198
|
+
artifactRoot: fileSha256(rustWasmArtifact),
|
|
199
|
+
distributedArtifacts: rustWasmDistributions,
|
|
200
|
+
byteIdenticalDistributions: rustWasmDistributions.every(
|
|
201
|
+
(relative) => fileSha256(relative) === fileSha256(rustWasmArtifact),
|
|
202
|
+
),
|
|
203
|
+
fallbackWriterCount: 0,
|
|
204
|
+
},
|
|
205
|
+
legacyEngineModules,
|
|
74
206
|
};
|
|
75
207
|
}
|
|
76
208
|
|
|
@@ -97,33 +229,46 @@ function permission(block, name) {
|
|
|
97
229
|
function workflowSnapshot(relative) {
|
|
98
230
|
const source = read(relative);
|
|
99
231
|
const document = parseWorkflowDocument(source);
|
|
100
|
-
const jobs = document.jobs
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
232
|
+
const jobs = document.jobs
|
|
233
|
+
.filter((job) => job.id !== "universal-bootstrap")
|
|
234
|
+
.map((job) => {
|
|
235
|
+
const block = jobBlock(source, job.id);
|
|
236
|
+
const uses = job.uses || null;
|
|
237
|
+
// A built-in publisher still requires mutation authority when the caller supplies its envelope.
|
|
238
|
+
const inheritedPublisher =
|
|
239
|
+
!/^ permissions:/mu.test(block) &&
|
|
240
|
+
/uses:.*actions\/v4-release-candidate-promote/u.test(block);
|
|
241
|
+
return {
|
|
242
|
+
id: job.id,
|
|
243
|
+
kind: uses ? "reusable-call" : "runner",
|
|
244
|
+
uses,
|
|
245
|
+
permissions: {
|
|
246
|
+
contents:
|
|
247
|
+
permission(block, "contents") ||
|
|
248
|
+
(inheritedPublisher ? "inherited" : null),
|
|
249
|
+
idToken:
|
|
250
|
+
permission(block, "id-token") ||
|
|
251
|
+
(inheritedPublisher ? "inherited" : null),
|
|
252
|
+
},
|
|
253
|
+
carriers: {
|
|
254
|
+
artifactDownload: /uses:\s+actions\/download-artifact@/u.test(block),
|
|
255
|
+
artifactUpload: /uses:\s+actions\/upload-artifact@/u.test(block),
|
|
256
|
+
jobOutput: /GITHUB_OUTPUT/u.test(block),
|
|
257
|
+
},
|
|
258
|
+
mutationSignals: [
|
|
259
|
+
(/contents:\s*write/u.test(block) || inheritedPublisher) &&
|
|
260
|
+
"contents-write",
|
|
261
|
+
(/id-token:\s*write/u.test(block) || inheritedPublisher) &&
|
|
262
|
+
"oidc-write",
|
|
263
|
+
/(?:promote-buildchain-ref|v4-release-candidate-promote)/u.test(
|
|
264
|
+
block,
|
|
265
|
+
) && "promotion-runtime",
|
|
266
|
+
/(?:git push|npm publish|gh release (?:create|upload))/u.test(
|
|
267
|
+
block,
|
|
268
|
+
) && "direct-publication-command",
|
|
269
|
+
].filter(Boolean),
|
|
270
|
+
};
|
|
271
|
+
});
|
|
127
272
|
return {
|
|
128
273
|
path: relative,
|
|
129
274
|
triggers: document.triggers,
|
|
@@ -131,6 +276,7 @@ function workflowSnapshot(relative) {
|
|
|
131
276
|
reusableEdges: parseYamlUses(source)
|
|
132
277
|
.map(({ value }) => value)
|
|
133
278
|
.filter((value) => /\.github\/workflows\//u.test(value))
|
|
279
|
+
.filter((value) => !/\/bootstrap\.yml(?:@v4)?$/u.test(value))
|
|
134
280
|
.sort(),
|
|
135
281
|
};
|
|
136
282
|
}
|
|
@@ -230,7 +376,12 @@ export function findUnknownV4ReleaseTopology(
|
|
|
230
376
|
) {
|
|
231
377
|
const declared = new Set(workflowPaths);
|
|
232
378
|
return allWorkflowPaths
|
|
233
|
-
.filter(
|
|
379
|
+
.filter(
|
|
380
|
+
(relative) =>
|
|
381
|
+
!declared.has(
|
|
382
|
+
workflowCompatibilityIdentity(root, relative, readWorkflow(relative)),
|
|
383
|
+
),
|
|
384
|
+
)
|
|
234
385
|
.filter((relative) => {
|
|
235
386
|
const source = readWorkflow(relative);
|
|
236
387
|
const usesReleaseAuthority = parseYamlUses(source).some(({ value }) =>
|
|
@@ -274,6 +425,7 @@ function assertAuthorityClosure(ledger) {
|
|
|
274
425
|
"runtimeSelectors",
|
|
275
426
|
"runtimeEngines",
|
|
276
427
|
"terminalProjections",
|
|
428
|
+
"legacyEngineModules",
|
|
277
429
|
]) {
|
|
278
430
|
assert.ok(
|
|
279
431
|
Array.isArray(closure[className]) && closure[className].length > 0,
|
|
@@ -292,6 +444,24 @@ function assertAuthorityClosure(ledger) {
|
|
|
292
444
|
discovered[className],
|
|
293
445
|
`authority closure class drifted: ${className}`,
|
|
294
446
|
);
|
|
447
|
+
assert.deepEqual(
|
|
448
|
+
closure.privilegedExecutableClosure,
|
|
449
|
+
discovered.privilegedExecutableClosure,
|
|
450
|
+
"privileged executable transitive closure drifted",
|
|
451
|
+
);
|
|
452
|
+
assert.match(
|
|
453
|
+
closure.privilegedExecutableClosure.root,
|
|
454
|
+
/^sha256:[0-9a-f]{64}$/u,
|
|
455
|
+
);
|
|
456
|
+
const reachableLegacyEngines = closure.legacyEngineModules.filter(
|
|
457
|
+
(relative) =>
|
|
458
|
+
closure.privilegedExecutableClosure.modules.includes(relative),
|
|
459
|
+
);
|
|
460
|
+
assert.deepEqual(
|
|
461
|
+
reachableLegacyEngines,
|
|
462
|
+
[],
|
|
463
|
+
`legacy release engines remain reachable from APPLY: ${reachableLegacyEngines.join(", ")}`,
|
|
464
|
+
);
|
|
295
465
|
assert.deepEqual(closure.runtimeEngines, [
|
|
296
466
|
"actions/v4-release-candidate-promote/index.js",
|
|
297
467
|
]);
|
|
@@ -301,11 +471,11 @@ function assertAuthorityClosure(ledger) {
|
|
|
301
471
|
);
|
|
302
472
|
assert.equal(
|
|
303
473
|
closure.recoveryEntry,
|
|
304
|
-
".github/workflows/
|
|
474
|
+
".github/workflows/self-ops-promotion-recovery.yml",
|
|
305
475
|
);
|
|
306
476
|
const engineSurface = [
|
|
307
477
|
".github/workflows/.release-candidate-promote.yml",
|
|
308
|
-
...closure.
|
|
478
|
+
...closure.privilegedExecutableClosure.modules,
|
|
309
479
|
]
|
|
310
480
|
.map(read)
|
|
311
481
|
.join("\n");
|
|
@@ -333,6 +503,17 @@ export function checkV4ReleaseTopology() {
|
|
|
333
503
|
assert.equal(ledger.contract, "kungfu-buildchain-v4-release-topology/v1");
|
|
334
504
|
assertClosedWorld(ledger.closedWorld.workflowPaths);
|
|
335
505
|
assertAuthorityClosure(ledger);
|
|
506
|
+
const preview = ledger.postPublicationScope;
|
|
507
|
+
assert.deepEqual(preview.workflowPaths, [
|
|
508
|
+
".github/workflows/public-release-oci-compose-preview.yml",
|
|
509
|
+
]);
|
|
510
|
+
const previewTopology = discoverV4ReleaseTopology(
|
|
511
|
+
preview.workflowPaths,
|
|
512
|
+
preview.workflowPaths,
|
|
513
|
+
);
|
|
514
|
+
assert.equal(previewTopology.semanticMetrics.contentsWriteJobCount, 1);
|
|
515
|
+
assert.equal(previewTopology.semanticMetrics.oidcWriteJobCount, 0);
|
|
516
|
+
assert.deepEqual(preview.observedTopology, previewTopology);
|
|
336
517
|
const actual = discoverV4ReleaseTopology(
|
|
337
518
|
ledger.closedWorld.workflowPaths,
|
|
338
519
|
ledger.semanticScope.workflowPaths,
|
|
@@ -361,23 +542,28 @@ export function checkV4ReleaseTopology() {
|
|
|
361
542
|
return actual;
|
|
362
543
|
}
|
|
363
544
|
|
|
364
|
-
if (
|
|
365
|
-
process.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
545
|
+
if (
|
|
546
|
+
process.argv[1] &&
|
|
547
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
548
|
+
) {
|
|
549
|
+
if (process.argv.includes("--print-closure")) {
|
|
550
|
+
process.stdout.write(
|
|
551
|
+
`${JSON.stringify(discoverV4ReleaseAuthorityClosure(), null, 2)}\n`,
|
|
552
|
+
);
|
|
553
|
+
} else if (process.argv.includes("--print")) {
|
|
554
|
+
const ledger = JSON.parse(fs.readFileSync(ledgerPath, "utf8"));
|
|
555
|
+
process.stdout.write(
|
|
556
|
+
`${JSON.stringify(
|
|
557
|
+
discoverV4ReleaseTopology(
|
|
558
|
+
ledger.closedWorld.workflowPaths,
|
|
559
|
+
ledger.semanticScope.workflowPaths,
|
|
560
|
+
),
|
|
561
|
+
null,
|
|
562
|
+
2,
|
|
563
|
+
)}\n`,
|
|
564
|
+
);
|
|
565
|
+
} else {
|
|
566
|
+
checkV4ReleaseTopology();
|
|
567
|
+
process.stdout.write("v4 release topology: ok\n");
|
|
568
|
+
}
|
|
383
569
|
}
|
|
@@ -35,7 +35,10 @@ function publicSurfaceIds(root) {
|
|
|
35
35
|
return new Set([
|
|
36
36
|
...Object.keys(packageJson.exports || {}).map((id) => `export:${id}`),
|
|
37
37
|
...(cli.commands || []).map((entry) => `cli:${entry.id}`),
|
|
38
|
-
...(workflows.workflows || []).
|
|
38
|
+
...(workflows.workflows || []).flatMap((entry) => [
|
|
39
|
+
`workflow:${entry.id}`,
|
|
40
|
+
...(entry.taxonomy ? [`workflow:${entry.taxonomy.id}`] : []),
|
|
41
|
+
]),
|
|
39
42
|
...(workflows.actions || []).map((entry) => `action:${entry.id}`),
|
|
40
43
|
]);
|
|
41
44
|
}
|