@kungfu-tech/buildchain 4.0.2-alpha.4 → 4.0.2-alpha.41
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 +66 -7
- package/architecture/ci-lane-change-budget.json +366 -0
- package/architecture/internal-capabilities.json +52 -8
- package/architecture/maintainability-debt.json +148 -92
- 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 +143 -35
- 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 +171 -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 +1451 -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 +222 -69
- package/dist/site/capability-registry.json +4 -4
- package/dist/site/controller-registry.json +50 -13
- package/dist/site/kfd-claims.json +2569 -982
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +13 -13
- package/dist/site/node-api-registry.json +88 -83
- package/dist/site/page-registry.json +204 -51
- package/dist/site/public-surface-audit.json +3207 -1219
- package/dist/site/publication-authority-registry.json +1124 -362
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +16 -2
- package/dist/site/site-manifest.json +17 -17
- package/dist/site/workflow-registry.json +5063 -1659
- package/docs/MAP.md +1 -0
- package/docs/auditable-demo.md +1 -1
- package/docs/cli.md +3 -3
- 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 +129 -129
- package/docs/publication-authority.md +2 -2
- 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 +154 -0
- package/docs/workflow-path-migration.md +109 -0
- package/package.json +10 -6
- 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 +42 -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/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 +29 -0
- package/packages/core/release-tail-provider-plane.js +68 -1048
- package/packages/core/v4-canonical-contracts.js +14 -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 +57 -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 +214 -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 +7 -5
- package/scripts/generate-site-bundle.mjs +11 -11
- package/scripts/generate-v4-universal-workflow-facades.mjs +458 -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/release-asset-client.mjs +91 -0
- package/scripts/release-candidate-resolver.mjs +17 -18
- package/scripts/resume-from-candidate-run.mjs +1 -1
- package/scripts/source-verification-evidence.mjs +344 -0
- package/scripts/stable-candidate-qualification.mjs +1 -1
- package/scripts/universal-facade-maintainability.mjs +82 -0
- package/scripts/v4-product-publication-intent.mjs +121 -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 +618 -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,100 @@ 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/v4-publication-settlement.mjs",
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export function localModuleSpecifiers(source) {
|
|
32
|
+
const specifiers = [];
|
|
33
|
+
const visit = (node) => {
|
|
34
|
+
let value;
|
|
35
|
+
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node))
|
|
36
|
+
value = node.moduleSpecifier;
|
|
37
|
+
else if (
|
|
38
|
+
ts.isCallExpression(node) &&
|
|
39
|
+
node.expression.kind === ts.SyntaxKind.ImportKeyword
|
|
40
|
+
)
|
|
41
|
+
value = node.arguments[0];
|
|
42
|
+
if (value && ts.isStringLiteral(value) && value.text.startsWith("."))
|
|
43
|
+
specifiers.push(value.text);
|
|
44
|
+
ts.forEachChild(node, visit);
|
|
45
|
+
};
|
|
46
|
+
visit(
|
|
47
|
+
ts.createSourceFile(
|
|
48
|
+
"module.mjs",
|
|
49
|
+
source,
|
|
50
|
+
ts.ScriptTarget.Latest,
|
|
51
|
+
true,
|
|
52
|
+
ts.ScriptKind.JS,
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
return [...new Set(specifiers)].sort();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveLocalModule(importer, specifier) {
|
|
59
|
+
const unresolved = path.posix.normalize(
|
|
60
|
+
path.posix.join(path.posix.dirname(importer), specifier),
|
|
61
|
+
);
|
|
62
|
+
assert.ok(
|
|
63
|
+
!unresolved.startsWith("../") && !path.posix.isAbsolute(unresolved),
|
|
64
|
+
`local module import escapes the repository: ${importer} -> ${specifier}`,
|
|
65
|
+
);
|
|
66
|
+
const candidates = [
|
|
67
|
+
...LOCAL_MODULE_EXTENSIONS.map((extension) => `${unresolved}${extension}`),
|
|
68
|
+
...LOCAL_MODULE_EXTENSIONS.slice(1).map((extension) =>
|
|
69
|
+
path.posix.join(unresolved, `index${extension}`),
|
|
70
|
+
),
|
|
71
|
+
];
|
|
72
|
+
const matches = candidates.filter((relative) => {
|
|
73
|
+
const absolute = path.join(root, relative);
|
|
74
|
+
return fs.existsSync(absolute) && fs.statSync(absolute).isFile();
|
|
75
|
+
});
|
|
76
|
+
assert.equal(
|
|
77
|
+
matches.length,
|
|
78
|
+
1,
|
|
79
|
+
`local module import must resolve exactly once: ${importer} -> ${specifier}`,
|
|
80
|
+
);
|
|
81
|
+
return matches[0];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function discoverStaticModuleClosure(entrypoints) {
|
|
85
|
+
const pending = [...entrypoints];
|
|
86
|
+
const visited = new Set();
|
|
87
|
+
while (pending.length > 0) {
|
|
88
|
+
const relative = pending.pop();
|
|
89
|
+
if (visited.has(relative)) continue;
|
|
90
|
+
assert.ok(
|
|
91
|
+
fs.statSync(path.join(root, relative)).isFile(),
|
|
92
|
+
`privileged entry is not a file: ${relative}`,
|
|
93
|
+
);
|
|
94
|
+
visited.add(relative);
|
|
95
|
+
for (const specifier of localModuleSpecifiers(read(relative))) {
|
|
96
|
+
const dependency = resolveLocalModule(relative, specifier);
|
|
97
|
+
if (!visited.has(dependency)) pending.push(dependency);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return [...visited].sort();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function rootedPathSet(paths) {
|
|
104
|
+
return `sha256:${crypto
|
|
105
|
+
.createHash("sha256")
|
|
106
|
+
.update(JSON.stringify([...paths].sort()))
|
|
107
|
+
.digest("hex")}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function fileSha256(relative) {
|
|
111
|
+
return `sha256:${crypto
|
|
112
|
+
.createHash("sha256")
|
|
113
|
+
.update(fs.readFileSync(path.join(root, relative)))
|
|
114
|
+
.digest("hex")}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
20
117
|
function productionFiles(relative, extensions) {
|
|
21
118
|
const absolute = path.join(root, relative);
|
|
22
119
|
if (!fs.existsSync(absolute)) return [];
|
|
@@ -61,6 +158,23 @@ export function discoverV4ReleaseAuthorityClosure() {
|
|
|
61
158
|
[".yml", ".yaml", ".js", ".mjs", ".cjs"],
|
|
62
159
|
/(?:createV4ReleaseReceipt|release-receipt\.json|V4_RELEASE_RECEIPT_CONTRACT)/u,
|
|
63
160
|
);
|
|
161
|
+
const privilegedModules = discoverStaticModuleClosure(PRIVILEGED_ENTRYPOINTS);
|
|
162
|
+
const rustWasmArtifact = "packages/core/buildchain-v4-domain.wasm";
|
|
163
|
+
const rustWasmDistributions = [
|
|
164
|
+
"actions/promote-buildchain-ref/dist/buildchain-v4-domain.wasm",
|
|
165
|
+
"actions/release-tail/dist/buildchain-v4-domain.wasm",
|
|
166
|
+
"actions/v4-release-candidate-promote/dist/buildchain-v4-domain.wasm",
|
|
167
|
+
];
|
|
168
|
+
const legacyEngineModules = productionFiles(
|
|
169
|
+
"actions/promote-buildchain-ref",
|
|
170
|
+
[".js", ".mjs", ".cjs"],
|
|
171
|
+
)
|
|
172
|
+
.filter((relative) =>
|
|
173
|
+
/(?:^|\/)(?:lib|promote-(?:alpha|release|major)-channel|durable-transaction-operations)\.js$/u.test(
|
|
174
|
+
relative,
|
|
175
|
+
),
|
|
176
|
+
)
|
|
177
|
+
.sort();
|
|
64
178
|
return {
|
|
65
179
|
providerAdapters: [
|
|
66
180
|
...new Set([
|
|
@@ -71,6 +185,23 @@ export function discoverV4ReleaseAuthorityClosure() {
|
|
|
71
185
|
runtimeSelectors,
|
|
72
186
|
runtimeEngines: ["actions/v4-release-candidate-promote/index.js"],
|
|
73
187
|
terminalProjections,
|
|
188
|
+
privilegedExecutableClosure: {
|
|
189
|
+
entrypoints: PRIVILEGED_ENTRYPOINTS,
|
|
190
|
+
modules: privilegedModules,
|
|
191
|
+
root: rootedPathSet(privilegedModules),
|
|
192
|
+
},
|
|
193
|
+
rustWasmAuthority: {
|
|
194
|
+
loader: "packages/core/v4-domain-wasm.js",
|
|
195
|
+
metadata: "packages/core/v4-domain-wasm-artifact.js",
|
|
196
|
+
artifact: rustWasmArtifact,
|
|
197
|
+
artifactRoot: fileSha256(rustWasmArtifact),
|
|
198
|
+
distributedArtifacts: rustWasmDistributions,
|
|
199
|
+
byteIdenticalDistributions: rustWasmDistributions.every(
|
|
200
|
+
(relative) => fileSha256(relative) === fileSha256(rustWasmArtifact),
|
|
201
|
+
),
|
|
202
|
+
fallbackWriterCount: 0,
|
|
203
|
+
},
|
|
204
|
+
legacyEngineModules,
|
|
74
205
|
};
|
|
75
206
|
}
|
|
76
207
|
|
|
@@ -97,33 +228,36 @@ function permission(block, name) {
|
|
|
97
228
|
function workflowSnapshot(relative) {
|
|
98
229
|
const source = read(relative);
|
|
99
230
|
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
|
-
block,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
"
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
231
|
+
const jobs = document.jobs
|
|
232
|
+
.filter((job) => job.id !== "universal-bootstrap")
|
|
233
|
+
.map((job) => {
|
|
234
|
+
const block = jobBlock(source, job.id);
|
|
235
|
+
const uses = job.uses || null;
|
|
236
|
+
return {
|
|
237
|
+
id: job.id,
|
|
238
|
+
kind: uses ? "reusable-call" : "runner",
|
|
239
|
+
uses,
|
|
240
|
+
permissions: {
|
|
241
|
+
contents: permission(block, "contents"),
|
|
242
|
+
idToken: permission(block, "id-token"),
|
|
243
|
+
},
|
|
244
|
+
carriers: {
|
|
245
|
+
artifactDownload: /uses:\s+actions\/download-artifact@/u.test(block),
|
|
246
|
+
artifactUpload: /uses:\s+actions\/upload-artifact@/u.test(block),
|
|
247
|
+
jobOutput: /GITHUB_OUTPUT/u.test(block),
|
|
248
|
+
},
|
|
249
|
+
mutationSignals: [
|
|
250
|
+
/contents:\s*write/u.test(block) && "contents-write",
|
|
251
|
+
/id-token:\s*write/u.test(block) && "oidc-write",
|
|
252
|
+
/(?:promote-buildchain-ref|v4-release-candidate-promote)/u.test(
|
|
253
|
+
block,
|
|
254
|
+
) && "promotion-runtime",
|
|
255
|
+
/(?:git push|npm publish|gh release (?:create|upload))/u.test(
|
|
256
|
+
block,
|
|
257
|
+
) && "direct-publication-command",
|
|
258
|
+
].filter(Boolean),
|
|
259
|
+
};
|
|
260
|
+
});
|
|
127
261
|
return {
|
|
128
262
|
path: relative,
|
|
129
263
|
triggers: document.triggers,
|
|
@@ -131,6 +265,7 @@ function workflowSnapshot(relative) {
|
|
|
131
265
|
reusableEdges: parseYamlUses(source)
|
|
132
266
|
.map(({ value }) => value)
|
|
133
267
|
.filter((value) => /\.github\/workflows\//u.test(value))
|
|
268
|
+
.filter((value) => !/\/bootstrap\.yml(?:@v4)?$/u.test(value))
|
|
134
269
|
.sort(),
|
|
135
270
|
};
|
|
136
271
|
}
|
|
@@ -230,7 +365,12 @@ export function findUnknownV4ReleaseTopology(
|
|
|
230
365
|
) {
|
|
231
366
|
const declared = new Set(workflowPaths);
|
|
232
367
|
return allWorkflowPaths
|
|
233
|
-
.filter(
|
|
368
|
+
.filter(
|
|
369
|
+
(relative) =>
|
|
370
|
+
!declared.has(
|
|
371
|
+
workflowCompatibilityIdentity(root, relative, readWorkflow(relative)),
|
|
372
|
+
),
|
|
373
|
+
)
|
|
234
374
|
.filter((relative) => {
|
|
235
375
|
const source = readWorkflow(relative);
|
|
236
376
|
const usesReleaseAuthority = parseYamlUses(source).some(({ value }) =>
|
|
@@ -274,6 +414,7 @@ function assertAuthorityClosure(ledger) {
|
|
|
274
414
|
"runtimeSelectors",
|
|
275
415
|
"runtimeEngines",
|
|
276
416
|
"terminalProjections",
|
|
417
|
+
"legacyEngineModules",
|
|
277
418
|
]) {
|
|
278
419
|
assert.ok(
|
|
279
420
|
Array.isArray(closure[className]) && closure[className].length > 0,
|
|
@@ -292,6 +433,24 @@ function assertAuthorityClosure(ledger) {
|
|
|
292
433
|
discovered[className],
|
|
293
434
|
`authority closure class drifted: ${className}`,
|
|
294
435
|
);
|
|
436
|
+
assert.deepEqual(
|
|
437
|
+
closure.privilegedExecutableClosure,
|
|
438
|
+
discovered.privilegedExecutableClosure,
|
|
439
|
+
"privileged executable transitive closure drifted",
|
|
440
|
+
);
|
|
441
|
+
assert.match(
|
|
442
|
+
closure.privilegedExecutableClosure.root,
|
|
443
|
+
/^sha256:[0-9a-f]{64}$/u,
|
|
444
|
+
);
|
|
445
|
+
const reachableLegacyEngines = closure.legacyEngineModules.filter(
|
|
446
|
+
(relative) =>
|
|
447
|
+
closure.privilegedExecutableClosure.modules.includes(relative),
|
|
448
|
+
);
|
|
449
|
+
assert.deepEqual(
|
|
450
|
+
reachableLegacyEngines,
|
|
451
|
+
[],
|
|
452
|
+
`legacy release engines remain reachable from APPLY: ${reachableLegacyEngines.join(", ")}`,
|
|
453
|
+
);
|
|
295
454
|
assert.deepEqual(closure.runtimeEngines, [
|
|
296
455
|
"actions/v4-release-candidate-promote/index.js",
|
|
297
456
|
]);
|
|
@@ -301,11 +460,11 @@ function assertAuthorityClosure(ledger) {
|
|
|
301
460
|
);
|
|
302
461
|
assert.equal(
|
|
303
462
|
closure.recoveryEntry,
|
|
304
|
-
".github/workflows/
|
|
463
|
+
".github/workflows/self-ops-promotion-recovery.yml",
|
|
305
464
|
);
|
|
306
465
|
const engineSurface = [
|
|
307
466
|
".github/workflows/.release-candidate-promote.yml",
|
|
308
|
-
...closure.
|
|
467
|
+
...closure.privilegedExecutableClosure.modules,
|
|
309
468
|
]
|
|
310
469
|
.map(read)
|
|
311
470
|
.join("\n");
|
|
@@ -361,23 +520,28 @@ export function checkV4ReleaseTopology() {
|
|
|
361
520
|
return actual;
|
|
362
521
|
}
|
|
363
522
|
|
|
364
|
-
if (
|
|
365
|
-
process.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
523
|
+
if (
|
|
524
|
+
process.argv[1] &&
|
|
525
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
526
|
+
) {
|
|
527
|
+
if (process.argv.includes("--print-closure")) {
|
|
528
|
+
process.stdout.write(
|
|
529
|
+
`${JSON.stringify(discoverV4ReleaseAuthorityClosure(), null, 2)}\n`,
|
|
530
|
+
);
|
|
531
|
+
} else if (process.argv.includes("--print")) {
|
|
532
|
+
const ledger = JSON.parse(fs.readFileSync(ledgerPath, "utf8"));
|
|
533
|
+
process.stdout.write(
|
|
534
|
+
`${JSON.stringify(
|
|
535
|
+
discoverV4ReleaseTopology(
|
|
536
|
+
ledger.closedWorld.workflowPaths,
|
|
537
|
+
ledger.semanticScope.workflowPaths,
|
|
538
|
+
),
|
|
539
|
+
null,
|
|
540
|
+
2,
|
|
541
|
+
)}\n`,
|
|
542
|
+
);
|
|
543
|
+
} else {
|
|
544
|
+
checkV4ReleaseTopology();
|
|
545
|
+
process.stdout.write("v4 release topology: ok\n");
|
|
546
|
+
}
|
|
383
547
|
}
|
|
@@ -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
|
}
|