@kungfu-tech/buildchain 4.0.2-alpha.0 → 4.0.2-alpha.11
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/architecture/agent-change-map.md +52 -5
- package/architecture/ci-lane-change-budget.json +408 -8
- package/architecture/internal-capabilities.json +44 -1
- package/architecture/maintainability-debt.json +65 -133
- package/architecture/maintainability-policy.json +20 -15
- package/architecture/release-tail-contract-inventory.json +4 -4
- package/architecture/v3-core-mechanism-inventory.json +1 -0
- package/architecture/v3-v4-live-capability-inventory.json +17 -17
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +5 -5
- package/architecture/v4-release-invocation-fixtures.json +139 -0
- package/architecture/v4-release-topology.json +317 -0
- package/architecture/v4-runtime-semantic-closure.json +4 -1
- package/architecture/v4-universal-workflow-bootstrap.json +181 -0
- package/architecture/v4-universal-workflow-fault-campaign.json +72 -0
- package/architecture/v4-universal-workflow-train-admission.json +40 -0
- package/contracts/buildchain-v2-residuals-v1.json +0 -9
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/contracts/v4-release-invocation-v1.schema.json +134 -0
- package/dist/site/buildchain-contract.json +87 -42
- package/dist/site/buildchain-site.json +15 -10
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/controller-registry.json +46 -10
- package/dist/site/kfd-claims.json +176 -69
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +2 -2
- package/dist/site/node-api-registry.json +97 -22
- package/dist/site/page-registry.json +9 -4
- package/dist/site/public-surface-audit.json +190 -71
- package/dist/site/publication-authority-registry.json +68 -3
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +6 -6
- package/dist/site/workflow-registry.json +221 -72
- package/docs/node-api-reference.md +28 -25
- package/docs/reusable-build-surface.md +16 -0
- package/package.json +2 -2
- package/packages/core/buildchain-publication-authority.js +3 -0
- package/packages/core/ci-lane-change-budget.js +14 -1
- package/packages/core/dev-delivery-candidate-identity.js +45 -17
- package/packages/core/dev-delivery-execution-transfer.js +6 -7
- package/packages/core/dev-delivery-provider-heartbeat.js +22 -6
- package/packages/core/dev-delivery-warrant-legacy-recovery.js +4 -2
- package/packages/core/dev-delivery-warrant-native-compatibility.js +33 -0
- package/packages/core/dev-delivery-warrant-state.js +54 -54
- package/packages/core/dev-delivery-warrant.js +8 -0
- package/packages/core/dev-delivery-writer-protocol-transition.js +72 -0
- package/packages/core/release-tail-product-capabilities.js +29 -0
- package/packages/core/release-tail-provider-plane.js +3 -3
- package/packages/core/v4-canonical-contracts.js +14 -0
- package/packages/core/v4-floating-consumer-policy.js +4 -1
- package/packages/core/v4-product-publication.js +387 -0
- package/packages/core/v4-protected-publication-source.js +93 -0
- package/packages/core/v4-publication-qualification.js +1 -0
- package/packages/core/v4-release-invocation.js +425 -0
- package/packages/core/v4-universal-workflow-bootstrap.js +596 -0
- package/scripts/audit-publication-control-plane.mjs +0 -1
- package/scripts/check-inventory.mjs +27 -59
- package/scripts/check-maintainability.mjs +29 -118
- package/scripts/check-v3-v4-capability-inventory.mjs +3 -61
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +10 -20
- package/scripts/check-v4-release-topology.mjs +493 -0
- package/scripts/check-v4-universal-workflow-bootstrap.mjs +257 -0
- package/scripts/dev-delivery-warrant.mjs +26 -5
- package/scripts/generate-channel-build-workflow.mjs +5 -1
- package/scripts/generate-channel-promotion-workflow.mjs +17 -57
- package/scripts/generate-v4-universal-workflow-facades.mjs +415 -0
- package/scripts/maintainability-public-surface.mjs +115 -0
- package/scripts/release-candidate-resolver.mjs +23 -24
- package/scripts/resume-from-candidate-run.mjs +16 -17
- package/scripts/universal-facade-maintainability.mjs +82 -0
- package/scripts/v3-v4-capability-catalog.mjs +107 -0
- package/scripts/v4-declarative-promotion-admission.mjs +4 -1
- package/scripts/v4-product-publication-intent.mjs +114 -0
- package/scripts/v4-release-candidate-adapter.mjs +41 -0
- package/scripts/v4-universal-workflow-backflow.mjs +212 -0
- package/scripts/v4-universal-workflow-engine.mjs +586 -0
- package/scripts/v4-universal-workflow-self-dogfood.mjs +205 -0
- package/templates/universal-buildchain-bootstrap-recovery.yml +282 -0
- package/templates/universal-buildchain-bootstrap.yml +28 -0
- package/scripts/capture-package-release-propagation.mjs +0 -263
- package/scripts/publication-commit-evidence.mjs +0 -444
- package/scripts/publish-github-artifact-attestation-evidence.mjs +0 -201
- package/scripts/stage-github-artifact-attestation-inputs.mjs +0 -65
|
@@ -127,7 +127,6 @@ const requiredPaths = [
|
|
|
127
127
|
"scripts/npm-publish-transaction.mjs",
|
|
128
128
|
"scripts/paper.mjs",
|
|
129
129
|
"scripts/publication-package.mjs",
|
|
130
|
-
"scripts/publication-commit-evidence.mjs",
|
|
131
130
|
"scripts/publication-reproducibility.mjs",
|
|
132
131
|
"scripts/release-candidate-resolver.mjs",
|
|
133
132
|
"scripts/resume-from-candidate-run.mjs",
|
|
@@ -300,8 +299,8 @@ if (channelPromotionWorkflow !== generateChannelPromotionWorkflow(advancedPromot
|
|
|
300
299
|
throw new Error("generated channel promotion workflow is stale");
|
|
301
300
|
}
|
|
302
301
|
for (const requiredSnippet of [
|
|
303
|
-
"
|
|
304
|
-
"
|
|
302
|
+
"name: Adapt recovery inputs into the canonical v4 publisher",
|
|
303
|
+
"uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@v4-alpha",
|
|
305
304
|
"declarative-release-tail: true",
|
|
306
305
|
]) {
|
|
307
306
|
if (!boundedAlphaRecoveryWorkflow.includes(requiredSnippet)) {
|
|
@@ -316,8 +315,8 @@ for (const workflowSource of [boundedAlphaRecoveryWorkflow, channelPromotionWork
|
|
|
316
315
|
for (const requiredSnippet of [
|
|
317
316
|
"buildchain-channel:",
|
|
318
317
|
`/${promotionShellRouting.alpha.workflowPath}@${promotionShellRouting.alpha.callRef}`,
|
|
319
|
-
|
|
320
|
-
|
|
318
|
+
`SELECTED_SHELL_REF: v${selfDogfoodMajor}-alpha`,
|
|
319
|
+
"name: Invoke the single v4 publisher adapter",
|
|
321
320
|
"promotion-contract-lock-digest:",
|
|
322
321
|
"authorize-promotion-runtime-override.cjs",
|
|
323
322
|
"BUILDCHAIN_ROUTER_REPOSITORY: ${{ inputs.buildchain-repository }}",
|
|
@@ -329,6 +328,9 @@ for (const requiredSnippet of [
|
|
|
329
328
|
throw new Error(`channel promotion workflow missing routing contract: ${requiredSnippet}`);
|
|
330
329
|
}
|
|
331
330
|
}
|
|
331
|
+
if (channelPromotionWorkflow.includes("Promote with stable workflow shell")) {
|
|
332
|
+
throw new Error("channel promotion workflow retains a parallel stable publisher");
|
|
333
|
+
}
|
|
332
334
|
for (const forbiddenSnippet of ["job.workflow_repository", "job.workflow_sha"]) {
|
|
333
335
|
if (channelPromotionWorkflow.includes(forbiddenSnippet)) {
|
|
334
336
|
throw new Error(`channel promotion workflow uses unsupported GitHub context: ${forbiddenSnippet}`);
|
|
@@ -1046,7 +1048,6 @@ for (const requiredSnippet of [
|
|
|
1046
1048
|
"resume-candidate-run-id:",
|
|
1047
1049
|
"resume-expected-source-tree:",
|
|
1048
1050
|
"resume-buildchain-runtime-ref:",
|
|
1049
|
-
"uses: kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@v4",
|
|
1050
1051
|
"release-passport-buildchain-self-kfd: true",
|
|
1051
1052
|
"declarative-release-tail: true",
|
|
1052
1053
|
"release-passport-impact-json: .buildchain/release-impact.json",
|
|
@@ -1057,61 +1058,28 @@ for (const requiredSnippet of [
|
|
|
1057
1058
|
}
|
|
1058
1059
|
const releaseCandidatePromoteWorkflow = fs.readFileSync(path.join(root, ".github/workflows/.release-candidate-promote.yml"), "utf8");
|
|
1059
1060
|
for (const requiredSnippet of [
|
|
1060
|
-
"release
|
|
1061
|
-
"
|
|
1062
|
-
"
|
|
1063
|
-
"
|
|
1064
|
-
"
|
|
1065
|
-
"
|
|
1066
|
-
"
|
|
1067
|
-
"release-
|
|
1068
|
-
"release-
|
|
1069
|
-
"release-
|
|
1070
|
-
"release-passport-kfd-support-matrix-json:", "release-passport-kfd-support-matrix-json: ${{ inputs.release-passport-kfd-support-matrix-json }}",
|
|
1071
|
-
"release-passport-kfd-product-gate-jsons:",
|
|
1072
|
-
"release-passport-kfd-product-gate-jsons: ${{ inputs.release-passport-kfd-product-gate-jsons }}",
|
|
1073
|
-
"release-passport-invariant-passport-jsons:",
|
|
1074
|
-
"release-passport-invariant-passport-jsons: ${{ inputs.release-passport-invariant-passport-jsons }}",
|
|
1075
|
-
"release-passport-invariant-passport-command:",
|
|
1076
|
-
"release-passport-invariant-passport-command: ${{ inputs.release-passport-invariant-passport-command }}",
|
|
1077
|
-
"release-passport-buildchain-self-kfd:",
|
|
1078
|
-
"release-passport-buildchain-self-kfd: ${{ inputs.release-passport-buildchain-self-kfd }}",
|
|
1079
|
-
"github-release:",
|
|
1080
|
-
"default: true",
|
|
1081
|
-
"github-release: ${{ inputs.github-release }}",
|
|
1082
|
-
"github-release-payload-patterns:",
|
|
1083
|
-
"github-release-artifact-paths: ${{ steps.rc.outputs.release-candidate-github-release-artifact-paths }}",
|
|
1084
|
-
"github-release-title: ${{ inputs.github-release-title }}",
|
|
1085
|
-
"github-release-notes: ${{ inputs.github-release-notes }}",
|
|
1086
|
-
"publication-commit-command:",
|
|
1087
|
-
"BUILDCHAIN_PUBLICATION_COMMIT_SIGNING_KEY:",
|
|
1088
|
-
"KUNGFU_GOVERNANCE_AUDITOR_APP_PRIVATE_KEY:",
|
|
1089
|
-
"Commit consumer publication authority last",
|
|
1090
|
-
"node .buildchain/runtime/scripts/publication-commit-evidence.mjs",
|
|
1091
|
-
"require-publish-source-lock: \"true\"",
|
|
1092
|
-
"publish-source-ref: ${{ steps.publish-gate.outputs.ref }}",
|
|
1093
|
-
"publish-source-sha: ${{ steps.publish-gate.outputs.sha }}",
|
|
1094
|
-
"publish-source-locked: ${{ steps.publish-gate.outputs.locked }}",
|
|
1095
|
-
"expected-publication-version: ${{ needs.publication-plan.outputs.version }}",
|
|
1096
|
-
"publication-version: ${{ needs.publication-plan.outputs.version }}",
|
|
1097
|
-
"name: Plan exact publication version",
|
|
1098
|
-
"name: Install exact publication planning dependencies",
|
|
1099
|
-
"DRY_RUN: ${{ inputs.dry-run }}",
|
|
1100
|
-
"if (dryRun) {",
|
|
1101
|
-
"Enforce Buildchain stable release canary gate",
|
|
1102
|
-
"BUILDCHAIN_STABLE_RELEASE_POLICY: .buildchain/stable-release-policy.json",
|
|
1103
|
-
"Consumer has no binary-distribution.yml; standalone binary dispatch is not applicable.",
|
|
1104
|
-
"gh workflow view binary-distribution.yml",
|
|
1105
|
-
"resume-candidate-run-id:",
|
|
1106
|
-
"node .buildchain/runtime/scripts/resume-from-candidate-run.mjs",
|
|
1107
|
-
"publish-sealed-bundle-root: ${{ steps.rc.outputs.publish-sealed-bundle-root }}",
|
|
1108
|
-
"BUILDCHAIN_EXPECTED_TRANSACTION_ID: ${{ inputs.resume-transaction-id }}",
|
|
1109
|
-
"if: ${{ inputs.resume-candidate-run-id == '' }}",
|
|
1110
|
-
"Bridge Buildchain self-runtime dependencies",
|
|
1111
|
-
"ln -s .buildchain/runtime/node_modules node_modules",
|
|
1061
|
+
"name: QUALIFY canonical v4 release invocation inputs",
|
|
1062
|
+
"name: APPLY one rooted provider transaction",
|
|
1063
|
+
"name: SETTLE terminal ReleaseReceipt projection",
|
|
1064
|
+
"uses: ./.buildchain/runtime/actions/v4-release-candidate-promote",
|
|
1065
|
+
"publisher-workflow-sha:",
|
|
1066
|
+
"runtime-commit:",
|
|
1067
|
+
"runtime-tree:",
|
|
1068
|
+
"release-invocation-root:",
|
|
1069
|
+
"release-transaction-root:",
|
|
1070
|
+
"release-receipt-root:",
|
|
1112
1071
|
]) {
|
|
1113
1072
|
if (!releaseCandidatePromoteWorkflow.includes(requiredSnippet)) {
|
|
1114
|
-
throw new Error(`release candidate promote workflow missing
|
|
1073
|
+
throw new Error(`release candidate promote workflow missing canonical v4 boundary: ${requiredSnippet}`);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
for (const retiredSnippet of [
|
|
1077
|
+
"Commit consumer publication authority last",
|
|
1078
|
+
"publish-github-artifact-attestation-evidence.mjs",
|
|
1079
|
+
"capture-package-release-propagation.mjs",
|
|
1080
|
+
]) {
|
|
1081
|
+
if (releaseCandidatePromoteWorkflow.includes(retiredSnippet)) {
|
|
1082
|
+
throw new Error(`release candidate promote workflow retains parallel publication authority: ${retiredSnippet}`);
|
|
1115
1083
|
}
|
|
1116
1084
|
}
|
|
1117
1085
|
const workflowDir = path.join(root, ".github/workflows");
|
|
@@ -19,18 +19,28 @@ 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
|
-
function collectHotspots(
|
|
25
|
+
function collectHotspots(
|
|
26
|
+
root,
|
|
27
|
+
current,
|
|
28
|
+
limit = 20,
|
|
29
|
+
shallowFallback = [],
|
|
30
|
+
{
|
|
31
|
+
baseRef = process.env.GITHUB_BASE_REF || process.env.GITHUB_REF_NAME || "",
|
|
32
|
+
} = {},
|
|
33
|
+
) {
|
|
24
34
|
const maintained = new Set([
|
|
25
35
|
...Object.keys(current.files || {}),
|
|
26
36
|
...Object.keys(current.tests || {}),
|
|
27
37
|
...Object.keys(current.workflows || {}),
|
|
28
38
|
]);
|
|
29
|
-
//
|
|
30
|
-
// Reuse the audited routes; full clones still re-rank them below.
|
|
31
|
-
// This keeps consumer verification deterministic without inventing history.
|
|
39
|
+
// Release-line reconciliation reuses audited routes across equivalent-tree DAG shapes.
|
|
32
40
|
if (
|
|
33
|
-
gitOutput(root, ["rev-parse", "--is-shallow-repository"]).trim() ===
|
|
41
|
+
gitOutput(root, ["rev-parse", "--is-shallow-repository"]).trim() ===
|
|
42
|
+
"true" ||
|
|
43
|
+
/^(?:alpha|release|publish-gate)\//u.test(baseRef)
|
|
34
44
|
) {
|
|
35
45
|
return shallowFallback.slice(0, limit);
|
|
36
46
|
}
|
|
@@ -154,116 +164,6 @@ function selectedFunction(metrics, file, name) {
|
|
|
154
164
|
return metrics.files[file]?.functions.find((entry) => entry.name === name);
|
|
155
165
|
}
|
|
156
166
|
|
|
157
|
-
function readJsonAtRevision(root, revision, file) {
|
|
158
|
-
return JSON.parse(gitOutput(root, ["show", `${revision}:${file}`]));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function publicSurfaceContract(entry, kind) {
|
|
162
|
-
if (kind === "cli") return { id: entry.id, usage: entry.usage };
|
|
163
|
-
if (kind === "node")
|
|
164
|
-
return {
|
|
165
|
-
export: entry.export,
|
|
166
|
-
specifier: entry.specifier,
|
|
167
|
-
target: entry.target,
|
|
168
|
-
};
|
|
169
|
-
return {
|
|
170
|
-
id: entry.id,
|
|
171
|
-
path: entry.path,
|
|
172
|
-
reusable: entry.reusable,
|
|
173
|
-
inputs: entry.inputs || [],
|
|
174
|
-
secrets: entry.secrets || [],
|
|
175
|
-
outputs: entry.outputs || [],
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function evaluatePublicSurface({ root, revision, policy }) {
|
|
180
|
-
const issues = [];
|
|
181
|
-
const lifecycleFields = policy.publicSurfacePolicy.requiredLifecycleFields;
|
|
182
|
-
const capabilityGroups = new Set(
|
|
183
|
-
readJson(root, "dist/site/capability-registry.json").groups.map(
|
|
184
|
-
(entry) => entry.id,
|
|
185
|
-
),
|
|
186
|
-
);
|
|
187
|
-
const definitions = [
|
|
188
|
-
{
|
|
189
|
-
file: "dist/site/cli-registry.json",
|
|
190
|
-
collection: "commands",
|
|
191
|
-
kind: "cli",
|
|
192
|
-
key: "id",
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
file: "dist/site/node-api-registry.json",
|
|
196
|
-
collection: "exports",
|
|
197
|
-
kind: "node",
|
|
198
|
-
key: "export",
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
file: "dist/site/workflow-registry.json",
|
|
202
|
-
collection: "workflows",
|
|
203
|
-
kind: "workflow",
|
|
204
|
-
key: "id",
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
file: "dist/site/workflow-registry.json",
|
|
208
|
-
collection: "actions",
|
|
209
|
-
kind: "action",
|
|
210
|
-
key: "id",
|
|
211
|
-
},
|
|
212
|
-
];
|
|
213
|
-
for (const definition of definitions) {
|
|
214
|
-
const current =
|
|
215
|
-
readJson(root, definition.file)[definition.collection] || [];
|
|
216
|
-
const baseline =
|
|
217
|
-
readJsonAtRevision(root, revision, definition.file)[
|
|
218
|
-
definition.collection
|
|
219
|
-
] || [];
|
|
220
|
-
const baselineByKey = new Map(
|
|
221
|
-
baseline.map((entry) => [entry[definition.key], entry]),
|
|
222
|
-
);
|
|
223
|
-
for (const entry of current) {
|
|
224
|
-
const label = `${definition.kind}:${entry[definition.key]}`;
|
|
225
|
-
for (const field of lifecycleFields) {
|
|
226
|
-
if (
|
|
227
|
-
!Object.prototype.hasOwnProperty.call(entry, field) ||
|
|
228
|
-
typeof entry[field] !== "string"
|
|
229
|
-
) {
|
|
230
|
-
issues.push(`${label}: lifecycle field ${field} is missing`);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
if (!capabilityGroups.has(entry.capabilityGroup)) {
|
|
234
|
-
issues.push(
|
|
235
|
-
`${label}: capability group ${entry.capabilityGroup || "<empty>"} is not registered`,
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
const previous = baselineByKey.get(entry[definition.key]);
|
|
239
|
-
const currentContract = publicSurfaceContract(entry, definition.kind);
|
|
240
|
-
if (
|
|
241
|
-
previous &&
|
|
242
|
-
JSON.stringify(currentContract) !==
|
|
243
|
-
JSON.stringify(publicSurfaceContract(previous, definition.kind))
|
|
244
|
-
) {
|
|
245
|
-
const approval = policy.approvedPublicSurfaceTransitions?.[label];
|
|
246
|
-
const approved =
|
|
247
|
-
approval?.fromRevision === revision &&
|
|
248
|
-
String(approval?.rationale || "").trim() &&
|
|
249
|
-
JSON.stringify(approval?.contract) ===
|
|
250
|
-
JSON.stringify(currentContract);
|
|
251
|
-
if (!approved) {
|
|
252
|
-
issues.push(
|
|
253
|
-
`${label}: existing public contract drifted from ${revision}`,
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
if (!previous && !entry.nonDuplicationRationale) {
|
|
258
|
-
issues.push(
|
|
259
|
-
`${label}: new public surface requires a non-duplication rationale`,
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return issues;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
167
|
function evaluateAddedFunctionBudgets({
|
|
268
168
|
file,
|
|
269
169
|
metrics,
|
|
@@ -489,12 +389,18 @@ function checkMaintainability({ root = process.cwd() } = {}) {
|
|
|
489
389
|
root,
|
|
490
390
|
extendedCoverageRevision,
|
|
491
391
|
);
|
|
392
|
+
const { governed: governedCurrent, migration: facadeMigration } =
|
|
393
|
+
governUniversalFacadeWorkflowMetrics({
|
|
394
|
+
root,
|
|
395
|
+
current,
|
|
396
|
+
workflowMetricsAtRevision,
|
|
397
|
+
});
|
|
492
398
|
const issues = evaluateExceptionGovernance({ policy });
|
|
493
399
|
issues.push(...evaluateExceptionBudget({ policy }));
|
|
494
400
|
const hotspots = collectHotspots(root, current, 20, debt.hotspots || []);
|
|
495
401
|
issues.push(
|
|
496
402
|
...evaluateDebtAuthority({
|
|
497
|
-
current,
|
|
403
|
+
current: governedCurrent,
|
|
498
404
|
policy,
|
|
499
405
|
debt,
|
|
500
406
|
capabilityIds: new Set(
|
|
@@ -510,14 +416,19 @@ function checkMaintainability({ root = process.cwd() } = {}) {
|
|
|
510
416
|
);
|
|
511
417
|
issues.push(
|
|
512
418
|
...evaluateWorkflowBudgets({
|
|
513
|
-
current,
|
|
419
|
+
current: governedCurrent,
|
|
514
420
|
baselineFiles: baselineWorkflows,
|
|
515
421
|
policy,
|
|
516
422
|
}),
|
|
517
423
|
);
|
|
518
424
|
issues.push(...evaluateRepositoryBudgets({ current, policy }));
|
|
519
425
|
issues.push(
|
|
520
|
-
...evaluatePublicSurface({
|
|
426
|
+
...evaluatePublicSurface({
|
|
427
|
+
root,
|
|
428
|
+
revision: enforcementRevision,
|
|
429
|
+
policy,
|
|
430
|
+
migration: facadeMigration,
|
|
431
|
+
}),
|
|
521
432
|
);
|
|
522
433
|
if (issues.length > 0) {
|
|
523
434
|
throw new Error(`maintainability check failed:\n- ${issues.join("\n- ")}`);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
collectHistoryRows,
|
|
9
9
|
collectRevisionCatalog,
|
|
10
10
|
countsBy,
|
|
11
|
+
ensureCapabilityCutLineage,
|
|
11
12
|
git,
|
|
12
13
|
sha256,
|
|
13
14
|
} from "./v3-v4-capability-catalog.mjs";
|
|
@@ -17,9 +18,8 @@ export const V3_V4_CAPABILITY_INVENTORY_CONTRACT =
|
|
|
17
18
|
export const V3_V4_CAPABILITY_CUTS = Object.freeze({
|
|
18
19
|
priorFamilyV3: "6b96bdad8d9f8ccf9275f27d9370a226a9c78465",
|
|
19
20
|
liveV3: "8493bf140a7f567e76aff3119f3d39ff026afc84",
|
|
20
|
-
liveV4: "
|
|
21
|
+
liveV4: "97edd5b93eb67e5bf36ad55726e08231b47b70c0",
|
|
21
22
|
});
|
|
22
|
-
|
|
23
23
|
const INVENTORY_PATH = "architecture/v3-v4-live-capability-inventory.json";
|
|
24
24
|
const ALLOWED_DISPOSITIONS = new Set([
|
|
25
25
|
"v4-native",
|
|
@@ -92,64 +92,6 @@ function capabilityCutAvailable(root, revision) {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
export function assertCapabilityCutAncestor({
|
|
96
|
-
root = process.cwd(),
|
|
97
|
-
revision,
|
|
98
|
-
descendant = "HEAD",
|
|
99
|
-
label = "capability cut",
|
|
100
|
-
} = {}) {
|
|
101
|
-
try {
|
|
102
|
-
execFileSync("git", ["merge-base", "--is-ancestor", revision, descendant], {
|
|
103
|
-
cwd: root,
|
|
104
|
-
stdio: "ignore",
|
|
105
|
-
});
|
|
106
|
-
} catch {
|
|
107
|
-
throw new Error(
|
|
108
|
-
`${label} ${revision} must be an ancestor of ${descendant}; regenerate the cut after rebasing instead of relying on a retained local object`,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function repositoryIsShallow(root) {
|
|
114
|
-
return (
|
|
115
|
-
execFileSync("git", ["rev-parse", "--is-shallow-repository"], {
|
|
116
|
-
cwd: root,
|
|
117
|
-
encoding: "utf8",
|
|
118
|
-
}).trim() === "true"
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function ensureCapabilityCutAncestor({
|
|
123
|
-
root = process.cwd(),
|
|
124
|
-
revision,
|
|
125
|
-
descendant = "HEAD",
|
|
126
|
-
label = "capability cut",
|
|
127
|
-
} = {}) {
|
|
128
|
-
try {
|
|
129
|
-
assertCapabilityCutAncestor({ root, revision, descendant, label });
|
|
130
|
-
return;
|
|
131
|
-
} catch (error) {
|
|
132
|
-
if (!repositoryIsShallow(root)) throw error;
|
|
133
|
-
}
|
|
134
|
-
const descendantCommit = execFileSync(
|
|
135
|
-
"git",
|
|
136
|
-
["rev-parse", `${descendant}^{commit}`],
|
|
137
|
-
{ cwd: root, encoding: "utf8" },
|
|
138
|
-
).trim();
|
|
139
|
-
try {
|
|
140
|
-
execFileSync(
|
|
141
|
-
"git",
|
|
142
|
-
["fetch", "--no-tags", "--depth=128", "origin", descendantCommit],
|
|
143
|
-
{ cwd: root, stdio: "ignore" },
|
|
144
|
-
);
|
|
145
|
-
} catch {
|
|
146
|
-
throw new Error(
|
|
147
|
-
`${label} ${revision} ancestry could not be hydrated from ${descendantCommit} through a bounded origin fetch`,
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
assertCapabilityCutAncestor({ root, revision, descendant, label });
|
|
151
|
-
}
|
|
152
|
-
|
|
153
95
|
function ensureCapabilityCutsAvailable(root) {
|
|
154
96
|
const revisions = Object.values(V3_V4_CAPABILITY_CUTS);
|
|
155
97
|
const missing = revisions.filter(
|
|
@@ -170,7 +112,7 @@ function ensureCapabilityCutsAvailable(root) {
|
|
|
170
112
|
`v3/v4 capability cuts are unavailable after a bounded origin fetch: ${unavailable.join(", ")}`,
|
|
171
113
|
);
|
|
172
114
|
}
|
|
173
|
-
|
|
115
|
+
ensureCapabilityCutLineage({
|
|
174
116
|
root,
|
|
175
117
|
revision: V3_V4_CAPABILITY_CUTS.liveV4,
|
|
176
118
|
label: "live v4 capability cut",
|
|
@@ -52,16 +52,11 @@ function assertOrdered(relative, markers) {
|
|
|
52
52
|
|
|
53
53
|
export function assertPromotionCertificationWiring(source) {
|
|
54
54
|
for (const marker of [
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
"
|
|
60
|
-
"path: .buildchain/runtime/policy-caller",
|
|
61
|
-
'--caller-root "${{ github.workspace }}/.buildchain/runtime/policy-caller"',
|
|
62
|
-
'--stable-lock "${{ inputs.buildchain-stable-contract-lock-path }}"',
|
|
63
|
-
'--alpha-lock "${{ inputs.buildchain-alpha-contract-lock-path }}"',
|
|
64
|
-
"release-passport-v4-consumer-policy-certification-root: ${{ steps.v4-policy-certification.outputs.v4-consumer-policy-certification-root }}",
|
|
55
|
+
"Translate and admit legacy-compatible inputs",
|
|
56
|
+
"BUILDCHAIN_PROMOTION_INPUTS_JSON: ${{ toJSON(inputs) }}",
|
|
57
|
+
"Resolve and qualify the sealed release candidate",
|
|
58
|
+
"BUILDCHAIN_RUNTIME_AUTHORIZATION_JSON",
|
|
59
|
+
"QUALIFY canonical v4 release invocation inputs",
|
|
65
60
|
]) {
|
|
66
61
|
if (!source.includes(marker)) {
|
|
67
62
|
fail(`promotion certification is missing ${marker}`);
|
|
@@ -165,8 +160,7 @@ export function checkV4FloatingConsumerPolicyContract() {
|
|
|
165
160
|
const promotion = read(".github/workflows/release-candidate-promote.yml");
|
|
166
161
|
if (
|
|
167
162
|
!promotion.includes("consumer-admission:") ||
|
|
168
|
-
!jobDependsOn(promotion, "
|
|
169
|
-
!jobDependsOn(promotion, "stable", "consumer-admission")
|
|
163
|
+
!jobDependsOn(promotion, "invoke", "consumer-admission")
|
|
170
164
|
) {
|
|
171
165
|
fail(
|
|
172
166
|
"release candidate promotion is not transitively gated by consumer admission",
|
|
@@ -179,16 +173,12 @@ export function checkV4FloatingConsumerPolicyContract() {
|
|
|
179
173
|
["packages/core/release-candidate.js", "consumerPolicy"],
|
|
180
174
|
["packages/core/release-passport.js", "v4ConsumerPolicy"],
|
|
181
175
|
[
|
|
182
|
-
"actions/
|
|
183
|
-
"
|
|
176
|
+
"actions/v4-release-candidate-promote/index.js",
|
|
177
|
+
"candidate.consumerPolicy?.receiptRoot",
|
|
184
178
|
],
|
|
185
179
|
[
|
|
186
|
-
"actions/
|
|
187
|
-
"
|
|
188
|
-
],
|
|
189
|
-
[
|
|
190
|
-
".github/workflows/.release-candidate-promote.yml",
|
|
191
|
-
"v4-policy-certification",
|
|
180
|
+
"actions/v4-release-candidate-promote/index.js",
|
|
181
|
+
"createV4ReleaseInvocation",
|
|
192
182
|
],
|
|
193
183
|
]) {
|
|
194
184
|
if (!read(relative).includes(marker)) {
|