@kungfu-tech/buildchain 4.1.0-alpha.0 → 4.1.0-alpha.2
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 +30 -41
- package/CONTRIBUTING.md +6 -7
- package/architecture/action-taxonomy.json +5 -21
- package/architecture/agent-change-map.md +74 -56
- package/architecture/build-orchestration.json +8 -1
- package/architecture/ci-lane-change-budget.json +179 -69
- package/architecture/floating-consumer-policy.json +7 -17
- package/architecture/internal-capabilities.json +58 -72
- package/architecture/maintainability-debt.json +138 -52
- package/architecture/maintainability-policy.json +6 -15
- package/architecture/promotion-job-topology.json +39 -20
- package/architecture/release-topology.json +26 -37
- package/architecture/runtime-entry.json +26 -0
- package/architecture/runtime-resume-lineage.json +23 -0
- package/architecture/stage-capsule-qualification.json +2 -2
- package/architecture/universal-workflow-bootstrap.json +10 -25
- package/architecture/universal-workflow-capability-policy.json +61 -0
- package/architecture/universal-workflow-fault-campaign.json +18 -24
- package/architecture/workflow-taxonomy.json +0 -11
- package/contracts/fixtures/{v4-runtime-ref-resume-authority-v1 → runtime-resume-lineage-v2}/scenario.json +2 -3
- package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +2 -2
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/contracts/promotion-invocation-v1.schema.json +0 -44
- package/contracts/promotion-request-v1.schema.json +0 -39
- package/contracts/runtime-resume-lineage-v2.schema.json +182 -0
- package/dist/site/buildchain-contract.json +472 -361
- package/dist/site/buildchain-site.json +99 -99
- package/dist/site/capability-registry.json +3 -3
- package/dist/site/cli-registry.json +4 -4
- package/dist/site/controller-registry.json +94 -78
- package/dist/site/kfd-claims.json +224 -343
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +15 -15
- package/dist/site/node-api-registry.json +239 -354
- package/dist/site/page-registry.json +80 -80
- package/dist/site/public-surface-audit.json +203 -254
- package/dist/site/publication-authority-registry.json +1 -24
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +1 -1
- package/dist/site/site-manifest.json +19 -19
- package/dist/site/workflow-registry.json +197 -357
- package/docs/MAP.md +3 -3
- package/docs/auditable-demo.md +4 -4
- package/docs/cli-reference.md +2 -2
- package/docs/code-organization.md +5 -4
- package/docs/controller-evidence.md +2 -2
- package/docs/dev-alpha-candidate-patrol.md +5 -5
- package/docs/dev-delivery-warrant.md +14 -24
- package/docs/engineering-housekeeper.md +3 -3
- package/docs/github-artifact-attestation.md +9 -12
- package/docs/github-governance-authority.md +4 -4
- package/docs/node-api-reference.md +153 -167
- package/docs/publication-artifacts.md +3 -4
- package/docs/publication-authority.md +2 -2
- package/docs/release-candidate.md +21 -27
- package/docs/release-governance.md +10 -10
- package/docs/release-promotion-request.md +11 -18
- package/docs/release-propagation.md +5 -6
- package/docs/release-tail-provider-plane.md +2 -3
- package/docs/reusable-build-surface.md +13 -7
- package/docs/runtime-entry.md +122 -0
- package/docs/runtime-train-validation.md +30 -41
- package/docs/v4-publication-rehearsal.md +5 -5
- package/docs/v4-stage-capsule.md +9 -10
- package/docs/web-surface-deployments.md +16 -20
- package/docs/workflow-catalog.md +0 -1
- package/package.json +6 -6
- package/packages/core/adoption/commands/init-repo.mjs +2 -2
- package/packages/core/adoption/cross-platform-adopter-qualification.js +2 -13
- package/packages/core/adoption/qualification/actions.js +3 -5
- package/packages/core/adoption/qualification/admission.js +2 -3
- package/packages/core/build/artifact/attestation.js +0 -1
- package/packages/core/build/artifact/contracts.js +1 -4
- package/packages/core/build/artifact/store.js +6 -5
- package/packages/core/build/artifact-signing.js +4 -10
- package/packages/core/build/binary/actions.js +15 -9
- package/packages/core/build/binary-publication-dispatch.js +2 -6
- package/packages/core/build/demo/action-context.js +2 -11
- package/packages/core/build/demo/binary-action.js +8 -10
- package/packages/core/build/gate/controller.js +0 -2
- package/packages/core/build/github-attestation/action.js +2 -7
- package/packages/core/build/github-attestation/admission.js +0 -8
- package/packages/core/build/github-attestation/transaction.js +1 -2
- package/packages/core/build/lifecycle/stage-action.js +2 -0
- package/packages/core/build/lifecycle/stage.js +6 -3
- package/packages/core/build/macos-credential-island/dmg-assembly.js +0 -2
- package/packages/core/build/macos-credential-island/lib.js +2 -6
- package/packages/core/build/plan/action-context.js +0 -7
- package/packages/core/build/plan/action.js +7 -11
- package/packages/core/build/plan/admission.js +6 -73
- package/packages/core/build/plan/configuration.js +1 -1
- package/packages/core/build/plan/environment.js +1 -1
- package/packages/core/build/plan/identity.js +1 -5
- package/packages/core/build/plan/resolve.js +7 -1
- package/packages/core/build/recovery/checkpoint.js +158 -0
- package/packages/core/build/recovery/transaction.js +51 -0
- package/packages/core/build/signing/authority-actions.js +2 -11
- package/packages/core/build/signing/control.js +0 -5
- package/packages/core/build/signing/controller-transaction.js +0 -1
- package/packages/core/build/signing/delegation.js +2 -4
- package/packages/core/build/signing/dispatch.js +10 -56
- package/packages/core/build/signing/intake.js +0 -4
- package/packages/core/build/signing/prepare.js +1 -1
- package/packages/core/build/signing/request.js +3 -8
- package/packages/core/build/source/checkout-action.js +6 -10
- package/packages/core/build/source/controller.js +1 -4
- package/packages/core/build/source/lifecycle.js +3 -13
- package/packages/core/build/stage-capsule/actions.js +3 -13
- package/packages/core/build/stage-capsule/campaign/resume.js +10 -5
- package/packages/core/build/stage-capsule/rehearsal/verify.js +3 -4
- package/packages/core/build/stage-capsule-resume-planner.js +0 -1
- package/packages/core/build/summary/aggregation.js +5 -5
- package/packages/core/build/summary/finalization.js +1 -1
- package/packages/core/build/verification/actions.js +3 -12
- package/packages/core/build/verification/runner.js +22 -20
- package/packages/core/consumer/floating-consumer-evidence.js +3 -21
- package/packages/core/consumer/floating-consumer-policy.js +2 -93
- package/packages/core/consumer/floating-consumer-release-passport.js +1 -34
- package/packages/core/consumer/policy-admission.js +1 -3
- package/packages/core/contracts/buildchain-contract.js +12 -146
- package/packages/core/contracts/workflow-yaml-contract.js +11 -1
- package/packages/core/dev-delivery/candidate/admission.js +3 -4
- package/packages/core/dev-delivery/candidate/coordinates.js +0 -8
- package/packages/core/dev-delivery/commands/dev-delivery-request.sh +5 -13
- package/packages/core/dev-delivery/commands/dev-delivery-source-proof-reuse.mjs +0 -3
- package/packages/core/dev-delivery/dev-delivery-contract-surface.js +4 -52
- package/packages/core/dev-delivery/dev-delivery-execution-transfer.js +2 -15
- package/packages/core/dev-delivery/native/action-context.js +4 -32
- package/packages/core/dev-delivery/native/provider-boundary.js +0 -2
- package/packages/core/dev-delivery/native/transactions.js +1 -2
- package/packages/core/dev-delivery/queue/reconciliation-action.js +6 -10
- package/packages/core/dev-delivery/source-proof/predicates.js +0 -6
- package/packages/core/dev-delivery/source-proof/reuse.js +0 -2
- package/packages/core/dev-delivery/source-proof/seal.js +0 -2
- package/packages/core/governance/alpha-candidate/action.js +1 -10
- package/packages/core/governance/audit/actions.js +1 -3
- package/packages/core/governance/audit/collection.js +1 -3
- package/packages/core/governance/audit/identity.js +3 -31
- package/packages/core/governance/audit/transactions.js +2 -4
- package/packages/core/governance/buildchain-publication-authority.js +0 -1
- package/packages/core/governance/commands/audit-github-governance.mjs +3 -2
- package/packages/core/governance/github-governance-authority.js +0 -5
- package/packages/core/governance/receipt-admission.js +0 -15
- package/packages/core/index.js +1 -1
- package/packages/core/observability/controller/identity.js +4 -5
- package/packages/core/observability/controller-evidence.js +5 -10
- package/packages/core/paper/commands/paper.mjs +6 -6
- package/packages/core/paper/operations/plans.js +2 -2
- package/packages/core/paper/operations/provisioning-validation.js +1 -1
- package/packages/core/paper/operations/scaffold-workflows.js +29 -12
- package/packages/core/paper/operations/scaffold.js +97 -69
- package/packages/core/paper/paper-agent-entry.js +0 -11
- package/packages/core/paper/paper-runtime-channels.js +21 -66
- package/packages/core/paper/paper-scaffold-content.js +1 -1
- package/packages/core/paper/paper.js +9 -5
- package/packages/core/paper/publication/candidate.js +0 -9
- package/packages/core/paper/publication-controller.js +2 -4
- package/packages/core/providers/github/artifact-attestation.js +1 -1
- package/packages/core/publication/authority/actions.js +9 -8
- package/packages/core/publication/authority/admission.js +0 -11
- package/packages/core/publication/authority/assembly.js +2 -7
- package/packages/core/publication/authority/control-plane.js +2 -2
- package/packages/core/publication/authority/governance.js +2 -9
- package/packages/core/publication/authority/request.js +7 -2
- package/packages/core/publication/authority/verification.js +3 -18
- package/packages/core/publication/binary/action.js +5 -7
- package/packages/core/publication/candidate/qualification-action.js +1 -6
- package/packages/core/publication/candidate/stage-capsules.js +1 -1
- package/packages/core/publication/npm/preview-action.js +9 -15
- package/packages/core/publication/oci/preview-actions.js +1 -10
- package/packages/core/publication/publication-artifact-candidate.js +2 -2
- package/packages/core/publication/publication-authority.js +2 -2
- package/packages/core/publication/publication-rehearsal-projection.js +3 -3
- package/packages/core/publication/settlement/actions.js +2 -11
- package/packages/core/release/binary/coordinates-action.js +0 -13
- package/packages/core/release/binary/coordinates.js +2 -19
- package/packages/core/release/commands/promotion-channel-router.mjs +12 -56
- package/packages/core/release/commands/resume-from-candidate-run.mjs +2 -3
- package/packages/core/release/dev-alpha-active-release-train.js +0 -5
- package/packages/core/release/line/action.js +2 -13
- package/packages/core/release/next-development/actions.js +2 -5
- package/packages/core/release/passport/consumer-validation.js +1 -28
- package/packages/core/release/promote-candidate/development-source.js +7 -8
- package/packages/core/release/promotion/actions.js +7 -38
- package/packages/core/release/promotion/admission.js +3 -50
- package/packages/core/release/promotion/candidate.js +2 -4
- package/packages/core/release/promotion/channel.js +13 -101
- package/packages/core/release/promotion/qualification-action.js +2 -13
- package/packages/core/release/promotion/routing.js +16 -81
- package/packages/core/release/promotion/selection.js +1 -9
- package/packages/core/release/promotion-request.js +1 -40
- package/packages/core/release/qualification/actions.js +0 -8
- package/packages/core/release/qualification/public-build.js +1 -1
- package/packages/core/release/recovery/candidate.js +15 -21
- package/packages/core/release/recovery/capsules.js +2 -10
- package/packages/core/release/recovery/lineage.js +305 -0
- package/packages/core/release/recovery/provenance.js +1 -2
- package/packages/core/release/recovery/publication.js +4 -14
- package/packages/core/release/recovery/readback.js +19 -32
- package/packages/core/release/recovery/runtime.js +4 -95
- package/packages/core/release/release-candidate-recovery.js +4 -9
- package/packages/core/release/release-candidate.js +2 -5
- package/packages/core/release/reseal/actions.js +3 -22
- package/packages/core/release/reseal/admission.js +0 -2
- package/packages/core/release/reseal/finalize-platform.js +0 -2
- package/packages/core/release/tail-reseal-github.js +1 -2
- package/packages/core/release/tail-reseal-receipt.js +0 -1
- package/packages/core/release/tail-reseal.js +2 -20
- package/packages/core/release/version-state.js +16 -13
- package/packages/core/runtime/buildchain-domain.wasm +0 -0
- package/packages/core/runtime/checkout-identity.js +0 -13
- package/packages/core/runtime/domain-wasm-artifact.js +2 -2
- package/packages/core/runtime/entry/actions.js +121 -0
- package/packages/core/runtime/entry/activation.js +86 -0
- package/packages/core/runtime/entry/github.js +55 -0
- package/packages/core/runtime/entry/recovery.js +37 -0
- package/packages/core/runtime/entry/selection.js +110 -0
- package/packages/core/web/controller.js +1 -1
- package/packages/core/web/publication-admission-action.js +1 -8
- package/packages/core/web/web-surface-publication-candidate.js +2 -5
- package/packages/core/workflow/admission/actions.js +6 -26
- package/packages/core/workflow/admission/recovery.js +3 -43
- package/packages/core/workflow/admission/transactions.js +8 -24
- package/packages/core/workflow/commands/buildchain-cli-help.mjs +1 -1
- package/packages/core/workflow/commands/universal-workflow-engine.mjs +2 -7
- package/packages/core/workflow/dogfood/actions.js +0 -17
- package/packages/core/workflow/engine/action.js +0 -5
- package/packages/core/workflow/engine/conformance.js +1 -1
- package/packages/core/workflow/engine/execution.js +2 -6
- package/packages/core/workflow/engine/identity.js +1 -6
- package/packages/core/workflow/engine/release-environment.js +11 -35
- package/packages/core/workflow/engine/release-promotion.js +2 -2
- package/packages/core/workflow/settlement/actions.js +0 -12
- package/packages/core/workflow/universal-self-dogfood.js +2 -16
- package/packages/core/workflow/universal-workflow-bootstrap.js +16 -241
- package/packages/core/workflow/workflow-taxonomy.mjs +2 -2
- package/scripts/check-dev-delivery-actions.mjs +17 -8
- package/scripts/check-floating-consumer-policy-contract.mjs +6 -6
- package/scripts/check-inventory.mjs +1 -10
- package/scripts/check-public-dogfood-contract.mjs +34 -21
- package/scripts/check-release-topology.mjs +2 -6
- package/scripts/check-runtime-entry.mjs +133 -0
- package/scripts/check-universal-workflow-bootstrap.mjs +45 -281
- package/scripts/check-workflows.sh +0 -7
- package/scripts/generate-bootstrap-admission.mjs +1 -1
- package/scripts/generate-channel-promotion-workflow.mjs +4 -4
- package/scripts/inventory/binary.mjs +4 -4
- package/scripts/inventory/promotion.mjs +5 -3
- package/scripts/runtime-source-boundaries.mjs +70 -0
- package/scripts/site-capability-metadata.mjs +2 -2
- package/scripts/workflow-action-graph.mjs +5 -18
- package/scripts/workflow-self-reference.mjs +20 -0
- package/templates/native-dev-delivery.yml +4 -4
- package/templates/universal-buildchain-bootstrap.yml +12 -2
- package/architecture/runtime-ref-resume-authority.json +0 -64
- package/architecture/universal-workflow-train-admission.json +0 -76
- package/contracts/v4-runtime-ref-resume-authority-v1.schema.json +0 -235
- package/docs/v4-runtime-ref-resume-authority.md +0 -69
- package/packages/core/consumer/channel-selection.js +0 -211
- package/packages/core/consumer/commands/buildchain-channel-router.mjs +0 -81
- package/packages/core/consumer/invocation-selector.js +0 -4
- package/packages/core/consumer/runtime-ref-resume-authority.js +0 -621
- package/packages/core/consumer/workflow-definition-authority.js +0 -64
- package/packages/core/contracts/runtime-contract-action.js +0 -48
- package/packages/core/release/commands/promotion-identity-resolver.mjs +0 -77
- package/packages/core/release/commands/verify-promotion-router-binding.sh +0 -36
- package/packages/core/release/promotion/identities.js +0 -56
- package/packages/core/release/runtime-override-authorization.js +0 -242
- package/packages/core/runtime/commands/runtime-ref-core.mjs +0 -171
- package/packages/core/runtime/provider-closure.js +0 -60
- package/packages/core/runtime/runtime-selection-action.js +0 -34
- package/packages/core/runtime/runtime-selection.js +0 -136
- package/packages/core/runtime/workflow-runtime.js +0 -11
- package/packages/core/workflow/admission/review.js +0 -188
- package/packages/core/workflow/commands/universal-workflow-backflow.mjs +0 -26
- package/packages/core/workflow/installation.js +0 -33
- package/packages/core/workflow/settlement/backflow.js +0 -187
- package/scripts/generate-bootstrap-recovery.mjs +0 -94
- package/templates/bootstrap-recovery/actions/build/source/admit/action.yml +0 -17
- package/templates/bootstrap-recovery/actions/build/source/admit/dist/index.js +0 -63
- package/templates/bootstrap-recovery/actions/runtime/dependencies/install/action.yml +0 -18
- package/templates/bootstrap-recovery/actions/runtime/dependencies/install/dist/index.js +0 -63
- package/templates/bootstrap-recovery/actions/runtime/environment/prepare/action.yml +0 -21
- package/templates/bootstrap-recovery/actions/workflow/admission/inspect/action.yml +0 -27
- package/templates/bootstrap-recovery/actions/workflow/admission/inspect/dist/index.js +0 -64
- package/templates/bootstrap-recovery/actions/workflow/admission/review/action.yml +0 -15
- package/templates/bootstrap-recovery/actions/workflow/admission/review/dist/index.js +0 -65
- package/templates/bootstrap-recovery/actions/workflow/recovery/admit/action.yml +0 -47
- package/templates/bootstrap-recovery/actions/workflow/recovery/execute/action.yml +0 -48
- package/templates/bootstrap-recovery/actions/workflow/recovery/settle/action.yml +0 -54
- package/templates/bootstrap-recovery/actions/workflow/settlement/seal/action.yml +0 -17
- package/templates/bootstrap-recovery/actions/workflow/settlement/seal/dist/index.js +0 -65
- package/templates/bootstrap-recovery/manifest.json +0 -68
- package/templates/bootstrap-recovery/package.json +0 -9
- package/templates/universal-buildchain-bootstrap-recovery.yml +0 -115
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
export function exactRuntime(admission) {
|
|
3
3
|
const runtime = admission?.runtime;
|
|
4
|
-
if (
|
|
5
|
-
admission?.status !== "admitted" ||
|
|
6
|
-
runtime?.repository !== "kungfu-systems/buildchain" ||
|
|
7
|
-
!/^[0-9a-f]{40}$/u.test(runtime?.sha || "")
|
|
8
|
-
)
|
|
9
|
-
fail("an exact admitted Buildchain runtime is required");
|
|
4
|
+
if (admission?.status !== "admitted") fail("Capability execution requires an admitted request");
|
|
10
5
|
return runtime;
|
|
11
6
|
}
|
|
12
7
|
export function contentRoot(domain, value) {
|
|
@@ -9,7 +9,7 @@ export function prepareTrustedPublishingNpm() {
|
|
|
9
9
|
!process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
|
10
10
|
)
|
|
11
11
|
fail("trusted publishing requires GitHub Actions OIDC authority");
|
|
12
|
-
const binDirectory = path.resolve(".buildchain/
|
|
12
|
+
const binDirectory = path.resolve(".buildchain/tooling/trusted-publishing/bin");
|
|
13
13
|
const npmShim = path.join(binDirectory, "npm");
|
|
14
14
|
fs.mkdirSync(binDirectory, { recursive: true });
|
|
15
15
|
fs.writeFileSync(
|
|
@@ -20,38 +20,14 @@ export function prepareTrustedPublishingNpm() {
|
|
|
20
20
|
fs.chmodSync(npmShim, 0o755);
|
|
21
21
|
process.env.PATH = `${binDirectory}${path.delimiter}${process.env.PATH || ""}`;
|
|
22
22
|
}
|
|
23
|
-
export function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
if (!fs.existsSync(consumerModules) && fs.existsSync(runtimeModules))
|
|
34
|
-
fs.symlinkSync(
|
|
35
|
-
path.relative(path.dirname(consumerModules), runtimeModules),
|
|
36
|
-
consumerModules,
|
|
37
|
-
"dir",
|
|
38
|
-
);
|
|
39
|
-
if (!fs.existsSync("node_modules/@kungfu-tech/kfd/package.json"))
|
|
40
|
-
execFileSync(
|
|
41
|
-
"corepack",
|
|
42
|
-
"pnpm@11.7.0 install --frozen-lockfile --ignore-scripts".split(" "),
|
|
43
|
-
{ stdio: ["ignore", 2, 2] },
|
|
44
|
-
);
|
|
45
|
-
if (fs.existsSync(runtimeModules)) {
|
|
46
|
-
if (fs.realpathSync(consumerModules) !== fs.realpathSync(runtimeModules))
|
|
47
|
-
fail("Buildchain release consumer runtime bridge conflicts");
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
fs.mkdirSync(path.dirname(runtimeModules), { recursive: true });
|
|
51
|
-
fs.renameSync(consumerModules, runtimeModules);
|
|
52
|
-
fs.symlinkSync(
|
|
53
|
-
path.relative(path.dirname(consumerModules), runtimeModules),
|
|
54
|
-
consumerModules,
|
|
55
|
-
"dir",
|
|
56
|
-
);
|
|
23
|
+
export function prepareReleaseConsumerDependencies(directory = process.cwd(), execute = execFileSync) {
|
|
24
|
+
const file = path.join(directory, "package.json");
|
|
25
|
+
if (!fs.existsSync(file)) return;
|
|
26
|
+
const manifest = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
27
|
+
const manager = String(manifest.packageManager || "");
|
|
28
|
+
if (!/^(?:npm|pnpm|yarn)@\d+\.\d+\.\d+(?:\+sha\d+\.[a-zA-Z0-9]+)?$/u.test(manager))
|
|
29
|
+
fail("Release consumer dependencies require a pinned packageManager");
|
|
30
|
+
const kind = manager.split("@")[0];
|
|
31
|
+
const args = kind === "npm" ? ["ci", "--ignore-scripts"] : kind === "pnpm" ? ["install", "--frozen-lockfile", "--ignore-scripts"] : /^yarn@1\./u.test(manager) ? ["install", "--frozen-lockfile", "--ignore-scripts"] : ["install", "--immutable", "--mode=skip-builds"];
|
|
32
|
+
execute("corepack", [manager, ...args], { cwd: directory, stdio: ["ignore", 2, 2] });
|
|
57
33
|
}
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "./release-observation.js";
|
|
17
17
|
import {
|
|
18
18
|
prepareTrustedPublishingNpm,
|
|
19
|
-
|
|
19
|
+
prepareReleaseConsumerDependencies,
|
|
20
20
|
} from "./release-environment.js";
|
|
21
21
|
import { verifiedReleaseDocuments } from "./release-evidence.js";
|
|
22
22
|
async function materializeProductPublicationIntent(
|
|
@@ -125,7 +125,7 @@ export async function executeReleasePromotion(request, admission, context) {
|
|
|
125
125
|
fail(`release route blocked: ${route.reason}`);
|
|
126
126
|
if (route.decision === "NoOp" || payload.inputs["dry-run"] === true)
|
|
127
127
|
return { route, dryRun: payload.inputs["dry-run"] === true };
|
|
128
|
-
|
|
128
|
+
prepareReleaseConsumerDependencies();
|
|
129
129
|
if (payload.inputs["trusted-publishing"] === true)
|
|
130
130
|
prepareTrustedPublishingNpm();
|
|
131
131
|
const candidate = await resolveReleaseCandidateArtifacts({
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { recoveryTerminalReceipt } from "../admission/recovery.js";
|
|
4
|
-
import { upsertUniversalBackflow } from "./backflow.js";
|
|
5
4
|
export function sealUniversalReceiptAction(core, env) {
|
|
6
5
|
const admission = JSON.parse(
|
|
7
6
|
core.getInput("admission-json", { required: true }),
|
|
@@ -21,14 +20,3 @@ export function sealUniversalReceiptAction(core, env) {
|
|
|
21
20
|
"Candidate workflow failed after sealing its terminal receipt",
|
|
22
21
|
);
|
|
23
22
|
}
|
|
24
|
-
export async function reconcileUniversalBackflowAction(core, env) {
|
|
25
|
-
const result = await upsertUniversalBackflow({
|
|
26
|
-
request: JSON.parse(core.getInput("request-json", { required: true })),
|
|
27
|
-
receipt: JSON.parse(core.getInput("receipt-json", { required: true })),
|
|
28
|
-
token: core.getInput("token", { required: true }),
|
|
29
|
-
});
|
|
30
|
-
const file = path.join(env.GITHUB_WORKSPACE, ".buildchain/backflow.json");
|
|
31
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
32
|
-
fs.writeFileSync(file, JSON.stringify(result, null, 2) + "\n");
|
|
33
|
-
core.setOutput("backflow-root", result.backflowRoot || "");
|
|
34
|
-
}
|
|
@@ -33,7 +33,7 @@ function contentRoot(domain, value) {
|
|
|
33
33
|
return `sha256:${hash.digest("hex")}`;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function defaultPolicy() { return JSON.parse(fs.readFileSync(path.join(installationRoot(import.meta.url), "architecture/universal-workflow-
|
|
36
|
+
function defaultPolicy() { return JSON.parse(fs.readFileSync(path.join(installationRoot(import.meta.url), "architecture/universal-workflow-capability-policy.json"), "utf8")); }
|
|
37
37
|
|
|
38
38
|
function exactSha(value, label) {
|
|
39
39
|
const normalized = String(value || "").toLowerCase();
|
|
@@ -42,16 +42,11 @@ function exactSha(value, label) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export function createUniversalSelfDogfoodRequest({
|
|
45
|
-
candidateSha,
|
|
46
45
|
consumerSha,
|
|
47
|
-
pullRequest,
|
|
48
46
|
channel,
|
|
49
47
|
policy = defaultPolicy(),
|
|
50
48
|
}) {
|
|
51
|
-
const expectedSha = exactSha(candidateSha, "candidateSha");
|
|
52
49
|
const sourceSha = exactSha(consumerSha, "consumerSha");
|
|
53
|
-
if (!Number.isSafeInteger(pullRequest) || pullRequest < 1)
|
|
54
|
-
fail("pullRequest must be a positive integer");
|
|
55
50
|
if (!["alpha", "stable", "conformance"].includes(channel))
|
|
56
51
|
fail("channel must be alpha, stable, or conformance");
|
|
57
52
|
const capability =
|
|
@@ -85,14 +80,6 @@ export function createUniversalSelfDogfoodRequest({
|
|
|
85
80
|
};
|
|
86
81
|
return validateUniversalWorkflowRequest({
|
|
87
82
|
schema: UNIVERSAL_WORKFLOW_REQUEST,
|
|
88
|
-
mode: "exact",
|
|
89
|
-
candidate: {
|
|
90
|
-
repository: "kungfu-systems/buildchain",
|
|
91
|
-
discoveryRef: expectedSha,
|
|
92
|
-
expectedSha,
|
|
93
|
-
admissionRoot: universalWorkflowAdmissionRoot(policy),
|
|
94
|
-
reviewPullRequest: pullRequest,
|
|
95
|
-
},
|
|
96
83
|
consumer: {
|
|
97
84
|
repository: "kungfu-systems/buildchain",
|
|
98
85
|
workflow: ".github/workflows/self-ops-bootstrap-dogfood.yml",
|
|
@@ -111,8 +98,7 @@ function exactResult(value, label, expectedSha, expectedChannel) {
|
|
|
111
98
|
if (
|
|
112
99
|
value?.schema !== "kungfu-buildchain-v4-universal-workflow-result/v1" ||
|
|
113
100
|
value.status !== "succeeded" ||
|
|
114
|
-
value.runtime?.repository !== "kungfu-systems/buildchain"
|
|
115
|
-
value.runtime?.sha !== expectedSha
|
|
101
|
+
value.runtime?.repository !== "kungfu-systems/buildchain"
|
|
116
102
|
)
|
|
117
103
|
fail(`${label} is not a successful exact-candidate result`);
|
|
118
104
|
if (expectedChannel === "conformance") {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
|
|
3
3
|
export const UNIVERSAL_WORKFLOW_REQUEST =
|
|
4
|
-
"
|
|
4
|
+
"buildchain.universal-workflow-request/v2";
|
|
5
5
|
export const UNIVERSAL_WORKFLOW_ADMISSION_POLICY =
|
|
6
|
-
"
|
|
6
|
+
"buildchain.universal-workflow-admission-policy/v2";
|
|
7
7
|
export const UNIVERSAL_WORKFLOW_ADMISSION =
|
|
8
|
-
"
|
|
8
|
+
"buildchain.universal-workflow-admission/v2";
|
|
9
9
|
export const UNIVERSAL_WORKFLOW_TERMINAL_RECEIPT =
|
|
10
|
-
"
|
|
10
|
+
"buildchain.universal-workflow-terminal-receipt/v2";
|
|
11
11
|
const EXACT_SHA = /^[0-9a-f]{40}$/u;
|
|
12
12
|
const ROOT = /^sha256:[0-9a-f]{64}$/u;
|
|
13
13
|
const REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
|
|
@@ -169,45 +169,11 @@ function timestamp(value, label) {
|
|
|
169
169
|
export function validateUniversalWorkflowRequest(value) {
|
|
170
170
|
exactKeys(
|
|
171
171
|
value,
|
|
172
|
-
["schema", "
|
|
172
|
+
["schema", "consumer", "capability", "payload"],
|
|
173
173
|
"request",
|
|
174
174
|
);
|
|
175
175
|
if (value.schema !== UNIVERSAL_WORKFLOW_REQUEST)
|
|
176
176
|
fail("unsupported-request-schema", "request schema is unsupported");
|
|
177
|
-
if (!MODES.has(value.mode))
|
|
178
|
-
fail("unsupported-mode", "request mode is unsupported");
|
|
179
|
-
exactKeys(
|
|
180
|
-
value.candidate,
|
|
181
|
-
[
|
|
182
|
-
"repository",
|
|
183
|
-
"discoveryRef",
|
|
184
|
-
"expectedSha",
|
|
185
|
-
"admissionRoot",
|
|
186
|
-
"reviewPullRequest",
|
|
187
|
-
],
|
|
188
|
-
"request.candidate",
|
|
189
|
-
);
|
|
190
|
-
const candidateRepository = repository(
|
|
191
|
-
value.candidate.repository,
|
|
192
|
-
"request.candidate.repository",
|
|
193
|
-
);
|
|
194
|
-
if (candidateRepository !== "kungfu-systems/buildchain")
|
|
195
|
-
fail(
|
|
196
|
-
"untrusted-candidate-repository",
|
|
197
|
-
"candidate repository must be kungfu-systems/buildchain",
|
|
198
|
-
);
|
|
199
|
-
const discoveryRef = nonEmpty(
|
|
200
|
-
value.candidate.discoveryRef,
|
|
201
|
-
"request.candidate.discoveryRef",
|
|
202
|
-
).replace(/^refs\/(?:heads|tags)\//u, "");
|
|
203
|
-
if (value.mode === "train" && !TRAIN.test(discoveryRef))
|
|
204
|
-
fail("invalid-train-ref", "train mode requires a governed v4 Train ref");
|
|
205
|
-
if (value.mode === "exact" && !EXACT_SHA.test(discoveryRef.toLowerCase()))
|
|
206
|
-
fail("invalid-exact-ref", "exact mode requires an exact Git SHA selector");
|
|
207
|
-
if (value.mode === "alpha" && discoveryRef !== "v4-alpha")
|
|
208
|
-
fail("invalid-alpha-ref", "alpha mode requires v4-alpha discovery");
|
|
209
|
-
if (value.mode === "stable" && discoveryRef !== "v4")
|
|
210
|
-
fail("invalid-stable-ref", "stable mode requires v4 discovery");
|
|
211
177
|
exactKeys(
|
|
212
178
|
value.consumer,
|
|
213
179
|
["repository", "workflow", "sourceSha"],
|
|
@@ -224,23 +190,6 @@ export function validateUniversalWorkflowRequest(value) {
|
|
|
224
190
|
canonical(value.payload);
|
|
225
191
|
return {
|
|
226
192
|
schema: value.schema,
|
|
227
|
-
mode: value.mode,
|
|
228
|
-
candidate: {
|
|
229
|
-
repository: candidateRepository,
|
|
230
|
-
discoveryRef,
|
|
231
|
-
expectedSha: exactSha(
|
|
232
|
-
value.candidate.expectedSha,
|
|
233
|
-
"request.candidate.expectedSha",
|
|
234
|
-
),
|
|
235
|
-
admissionRoot: root(
|
|
236
|
-
value.candidate.admissionRoot,
|
|
237
|
-
"request.candidate.admissionRoot",
|
|
238
|
-
),
|
|
239
|
-
reviewPullRequest: positiveInteger(
|
|
240
|
-
value.candidate.reviewPullRequest,
|
|
241
|
-
"request.candidate.reviewPullRequest",
|
|
242
|
-
),
|
|
243
|
-
},
|
|
244
193
|
consumer: {
|
|
245
194
|
repository: repository(
|
|
246
195
|
value.consumer.repository,
|
|
@@ -290,10 +239,6 @@ function validatePolicy(value) {
|
|
|
290
239
|
"allowedCapabilities",
|
|
291
240
|
"permissionCeiling",
|
|
292
241
|
"contractRoots",
|
|
293
|
-
"targetRef",
|
|
294
|
-
"allowedReviewers",
|
|
295
|
-
"minimumApprovals",
|
|
296
|
-
"requiredChecks",
|
|
297
242
|
"validFrom",
|
|
298
243
|
"expiresAt",
|
|
299
244
|
],
|
|
@@ -323,19 +268,6 @@ function validatePolicy(value) {
|
|
|
323
268
|
"policy.permissionCeiling",
|
|
324
269
|
),
|
|
325
270
|
contractRoots: sortedRoots(value.contractRoots, "policy.contractRoots"),
|
|
326
|
-
targetRef: nonEmpty(value.targetRef, "policy.targetRef"),
|
|
327
|
-
allowedReviewers: sortedTokens(
|
|
328
|
-
value.allowedReviewers,
|
|
329
|
-
"policy.allowedReviewers",
|
|
330
|
-
),
|
|
331
|
-
minimumApprovals: positiveInteger(
|
|
332
|
-
value.minimumApprovals,
|
|
333
|
-
"policy.minimumApprovals",
|
|
334
|
-
),
|
|
335
|
-
requiredChecks: sortedStrings(
|
|
336
|
-
value.requiredChecks,
|
|
337
|
-
"policy.requiredChecks",
|
|
338
|
-
),
|
|
339
271
|
validFrom: timestamp(value.validFrom, "policy.validFrom"),
|
|
340
272
|
expiresAt: timestamp(value.expiresAt, "policy.expiresAt"),
|
|
341
273
|
};
|
|
@@ -344,56 +276,6 @@ function validatePolicy(value) {
|
|
|
344
276
|
return policy;
|
|
345
277
|
}
|
|
346
278
|
|
|
347
|
-
function validateReviewEvidence(value) {
|
|
348
|
-
const hasBinding = Object.hasOwn(value || {}, "runtimeBinding");
|
|
349
|
-
exactKeys(value, ["repository", "pullRequest", "headSha", "baseRef", "approvals", "checks", "observedAt", ...(hasBinding ? ["runtimeBinding"] : [])], "reviewEvidence");
|
|
350
|
-
const approvals = value.approvals.map((approval, index) => {
|
|
351
|
-
exactKeys(approval, ["reviewer", "commitSha", "submittedAt"], `reviewEvidence.approvals[${index}]`);
|
|
352
|
-
return {
|
|
353
|
-
reviewer: nonEmpty(approval.reviewer, `reviewEvidence.approvals[${index}].reviewer`),
|
|
354
|
-
commitSha: exactSha(approval.commitSha, `reviewEvidence.approvals[${index}].commitSha`),
|
|
355
|
-
submittedAt: timestamp(approval.submittedAt, `reviewEvidence.approvals[${index}].submittedAt`),
|
|
356
|
-
};
|
|
357
|
-
});
|
|
358
|
-
const checks = value.checks.map((check, index) => {
|
|
359
|
-
const hasCommit = Object.hasOwn(check || {}, "commitSha");
|
|
360
|
-
exactKeys(check, ["name", "status", "conclusion", ...(hasCommit ? ["commitSha"] : [])], `reviewEvidence.checks[${index}]`);
|
|
361
|
-
return {
|
|
362
|
-
name: nonEmpty(check.name, `reviewEvidence.checks[${index}].name`),
|
|
363
|
-
status: nonEmpty(check.status, `reviewEvidence.checks[${index}].status`),
|
|
364
|
-
conclusion: nonEmpty(check.conclusion, `reviewEvidence.checks[${index}].conclusion`),
|
|
365
|
-
...(hasCommit ? { commitSha: exactSha(check.commitSha, `reviewEvidence.checks[${index}].commitSha`) } : {}),
|
|
366
|
-
};
|
|
367
|
-
});
|
|
368
|
-
let runtimeBinding;
|
|
369
|
-
if (hasBinding) {
|
|
370
|
-
exactKeys(value.runtimeBinding, ["kind", "runtimeSha", "parentShas", "mergedAt"], "reviewEvidence.runtimeBinding");
|
|
371
|
-
const kind = nonEmpty(value.runtimeBinding.kind, "reviewEvidence.runtimeBinding.kind");
|
|
372
|
-
if (!new Set(["reviewed-head", "protected-alpha-merge"]).has(kind)) fail("invalid-runtime-binding", "review evidence runtime binding kind is unsupported");
|
|
373
|
-
const parentShas = value.runtimeBinding.parentShas.map((parent, index) => exactSha(parent, `reviewEvidence.runtimeBinding.parentShas[${index}]`));
|
|
374
|
-
if (new Set(parentShas).size !== parentShas.length) fail("invalid-runtime-binding", "review evidence runtime parents must be duplicate-free");
|
|
375
|
-
runtimeBinding = { kind, runtimeSha: exactSha(value.runtimeBinding.runtimeSha, "reviewEvidence.runtimeBinding.runtimeSha"), parentShas, mergedAt: value.runtimeBinding.mergedAt === null ? null : timestamp(value.runtimeBinding.mergedAt, "reviewEvidence.runtimeBinding.mergedAt") };
|
|
376
|
-
}
|
|
377
|
-
return {
|
|
378
|
-
repository: repository(value.repository, "reviewEvidence.repository"),
|
|
379
|
-
pullRequest: positiveInteger(value.pullRequest, "reviewEvidence.pullRequest"),
|
|
380
|
-
headSha: exactSha(value.headSha, "reviewEvidence.headSha"),
|
|
381
|
-
baseRef: nonEmpty(value.baseRef, "reviewEvidence.baseRef"),
|
|
382
|
-
approvals, checks,
|
|
383
|
-
observedAt: timestamp(value.observedAt, "reviewEvidence.observedAt"),
|
|
384
|
-
...(runtimeBinding ? { runtimeBinding } : {}),
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function reviewedRuntimeSha(request, policy, evidence, runtimeSha) {
|
|
389
|
-
const binding = evidence.runtimeBinding || { kind: "reviewed-head", runtimeSha: evidence.headSha, parentShas: [], mergedAt: null };
|
|
390
|
-
if (binding.runtimeSha !== runtimeSha) fail("review-runtime-mismatch", "review evidence runtime binding does not match the candidate");
|
|
391
|
-
const direct = binding.kind === "reviewed-head" && evidence.headSha === runtimeSha && evidence.baseRef === policy.targetRef && binding.mergedAt === null;
|
|
392
|
-
const alphaMerge = binding.kind === "protected-alpha-merge" && request.mode === "alpha" && evidence.baseRef === policy.targetRef.replace(/^dev\//u, "alpha/") && binding.parentShas.length === 2 && binding.parentShas.includes(evidence.headSha) && binding.mergedAt !== null && Date.parse(binding.mergedAt) <= Date.parse(evidence.observedAt);
|
|
393
|
-
if (!direct && !alphaMerge) fail("review-identity-mismatch", "review evidence does not bind the candidate runtime");
|
|
394
|
-
return evidence.headSha;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
279
|
export function universalWorkflowAdmissionRoot(value) {
|
|
398
280
|
return documentRoot("universal-workflow-admission", validatePolicy(value));
|
|
399
281
|
}
|
|
@@ -409,128 +291,25 @@ function assertPermissionCeiling(requested, ceiling) {
|
|
|
409
291
|
}
|
|
410
292
|
}
|
|
411
293
|
|
|
412
|
-
export function admitUniversalWorkflow({
|
|
413
|
-
request: requestValue,
|
|
414
|
-
policy: policyValue,
|
|
415
|
-
observedRefSha,
|
|
416
|
-
observedConsumerRepository,
|
|
417
|
-
observedConsumerSha,
|
|
418
|
-
observedConsumerWorkflowRef,
|
|
419
|
-
reviewEvidence: reviewEvidenceValue,
|
|
420
|
-
now,
|
|
421
|
-
} = {}) {
|
|
294
|
+
export function admitUniversalWorkflow({ request: requestValue, policy: policyValue, runtime, observedConsumerRepository, observedConsumerSha, observedConsumerWorkflowRef, now } = {}) {
|
|
422
295
|
const request = validateUniversalWorkflowRequest(requestValue);
|
|
423
296
|
const policy = validatePolicy(policyValue);
|
|
424
|
-
const admissionRoot = universalWorkflowAdmissionRoot(policy);
|
|
425
|
-
if (request.candidate.admissionRoot !== admissionRoot)
|
|
426
|
-
fail("stale-admission", "request is not bound to the current admission");
|
|
427
|
-
if (policy.sourceRepository !== request.candidate.repository)
|
|
428
|
-
fail("source-repository-mismatch", "admission source repository mismatch");
|
|
429
297
|
const workflowRef = nonEmpty(observedConsumerWorkflowRef, "workflow ref");
|
|
430
|
-
const
|
|
431
|
-
if (
|
|
432
|
-
request.consumer.repository !== observedConsumerRepository ||
|
|
433
|
-
request.consumer.sourceSha !==
|
|
434
|
-
exactSha(observedConsumerSha, "observedConsumerSha") ||
|
|
435
|
-
!workflowRef.startsWith(expectedWorkflowPrefix) ||
|
|
436
|
-
workflowRef.length === expectedWorkflowPrefix.length
|
|
437
|
-
)
|
|
298
|
+
const prefix = `${request.consumer.repository}/${request.consumer.workflow}@`;
|
|
299
|
+
if (request.consumer.repository !== observedConsumerRepository || request.consumer.sourceSha !== exactSha(observedConsumerSha, "observedConsumerSha") || !workflowRef.startsWith(prefix) || workflowRef.length === prefix.length)
|
|
438
300
|
fail("consumer-identity-mismatch", "caller identity mismatch");
|
|
439
|
-
if (!policy.allowedCapabilities.includes(request.capability.id))
|
|
440
|
-
|
|
441
|
-
if (
|
|
442
|
-
request.capability.contractRoots.length !== policy.contractRoots.length ||
|
|
443
|
-
request.capability.contractRoots.some(
|
|
444
|
-
(value, index) => value !== policy.contractRoots[index],
|
|
445
|
-
)
|
|
446
|
-
)
|
|
447
|
-
fail("contract-root-mismatch", "candidate contract roots are not admitted");
|
|
448
|
-
assertPermissionCeiling(
|
|
449
|
-
request.capability.permissions,
|
|
450
|
-
policy.permissionCeiling,
|
|
451
|
-
);
|
|
301
|
+
if (!policy.allowedCapabilities.includes(request.capability.id)) fail("capability-not-admitted", "capability is not admitted");
|
|
302
|
+
assertPermissionCeiling(request.capability.permissions, policy.permissionCeiling);
|
|
452
303
|
const observedAt = timestamp(now, "now");
|
|
453
|
-
if (
|
|
454
|
-
Date.parse(observedAt) < Date.parse(policy.validFrom) ||
|
|
455
|
-
Date.parse(observedAt) >= Date.parse(policy.expiresAt)
|
|
456
|
-
)
|
|
457
|
-
fail("stale-admission", "admission is outside its validity window");
|
|
458
|
-
const runtimeSha = exactSha(observedRefSha, "observedRefSha");
|
|
459
|
-
if (runtimeSha !== request.candidate.expectedSha)
|
|
460
|
-
fail(
|
|
461
|
-
"candidate-ref-moved",
|
|
462
|
-
"candidate ref readback does not match expected SHA",
|
|
463
|
-
);
|
|
464
|
-
const reviewEvidence = validateReviewEvidence(reviewEvidenceValue);
|
|
465
|
-
if (
|
|
466
|
-
reviewEvidence.repository !== request.candidate.repository ||
|
|
467
|
-
reviewEvidence.pullRequest !== request.candidate.reviewPullRequest
|
|
468
|
-
)
|
|
469
|
-
fail(
|
|
470
|
-
"review-identity-mismatch",
|
|
471
|
-
"review evidence does not bind the candidate",
|
|
472
|
-
);
|
|
473
|
-
const reviewedSha = reviewedRuntimeSha(request, policy, reviewEvidence, runtimeSha);
|
|
474
|
-
const approvedReviewers = new Set(
|
|
475
|
-
reviewEvidence.approvals
|
|
476
|
-
.filter((approval) => approval.commitSha === reviewedSha)
|
|
477
|
-
.map((approval) => approval.reviewer),
|
|
478
|
-
);
|
|
479
|
-
const admittedApprovalCount = policy.allowedReviewers.filter((reviewer) =>
|
|
480
|
-
approvedReviewers.has(reviewer),
|
|
481
|
-
).length;
|
|
482
|
-
if (admittedApprovalCount < policy.minimumApprovals)
|
|
483
|
-
fail(
|
|
484
|
-
"independent-review-missing",
|
|
485
|
-
"candidate lacks exact-head independent review",
|
|
486
|
-
);
|
|
487
|
-
const checksByName = new Map();
|
|
488
|
-
for (const check of reviewEvidence.checks) {
|
|
489
|
-
const entries = checksByName.get(check.name) || [];
|
|
490
|
-
entries.push(check);
|
|
491
|
-
checksByName.set(check.name, entries);
|
|
492
|
-
}
|
|
493
|
-
if (
|
|
494
|
-
policy.requiredChecks.some((name) => {
|
|
495
|
-
const checks = checksByName.get(name) || [];
|
|
496
|
-
return (
|
|
497
|
-
checks.length === 0 ||
|
|
498
|
-
checks.some(
|
|
499
|
-
(check) =>
|
|
500
|
-
check.status !== "completed" || check.conclusion !== "success" ||
|
|
501
|
-
(check.commitSha !== undefined && check.commitSha !== runtimeSha),
|
|
502
|
-
)
|
|
503
|
-
);
|
|
504
|
-
})
|
|
505
|
-
)
|
|
506
|
-
fail(
|
|
507
|
-
"exact-head-checks-incomplete",
|
|
508
|
-
"candidate exact-head checks are not successful",
|
|
509
|
-
);
|
|
510
|
-
const requestRoot = universalWorkflowRequestRoot(request);
|
|
511
|
-
const discovery = {
|
|
512
|
-
repository: request.candidate.repository,
|
|
513
|
-
ref: request.candidate.discoveryRef,
|
|
514
|
-
expectedSha: request.candidate.expectedSha,
|
|
515
|
-
observedSha: runtimeSha,
|
|
516
|
-
observedAt,
|
|
517
|
-
};
|
|
304
|
+
if (Date.parse(observedAt) < Date.parse(policy.validFrom) || Date.parse(observedAt) >= Date.parse(policy.expiresAt)) fail("stale-admission", "capability policy is outside its validity window");
|
|
518
305
|
return {
|
|
519
306
|
schema: UNIVERSAL_WORKFLOW_ADMISSION,
|
|
520
307
|
status: "admitted",
|
|
521
|
-
requestRoot,
|
|
522
|
-
admissionRoot,
|
|
523
|
-
discoveryRoot: documentRoot("universal-workflow-discovery", discovery),
|
|
524
|
-
reviewRoot: documentRoot("universal-workflow-review", reviewEvidence),
|
|
308
|
+
requestRoot: universalWorkflowRequestRoot(request),
|
|
309
|
+
admissionRoot: universalWorkflowAdmissionRoot(policy),
|
|
525
310
|
consumerRoot: documentRoot("universal-workflow-consumer", request.consumer),
|
|
526
|
-
capabilityRoot: documentRoot(
|
|
527
|
-
|
|
528
|
-
request.capability,
|
|
529
|
-
),
|
|
530
|
-
runtime: {
|
|
531
|
-
repository: request.candidate.repository,
|
|
532
|
-
sha: runtimeSha,
|
|
533
|
-
},
|
|
311
|
+
capabilityRoot: documentRoot("universal-workflow-capability", request.capability),
|
|
312
|
+
runtime,
|
|
534
313
|
permissions: request.capability.permissions,
|
|
535
314
|
contractRoots: request.capability.contractRoots,
|
|
536
315
|
};
|
|
@@ -544,8 +323,6 @@ export function completeUniversalWorkflow({ admission, resultRoot, status }) {
|
|
|
544
323
|
"status",
|
|
545
324
|
"requestRoot",
|
|
546
325
|
"admissionRoot",
|
|
547
|
-
"discoveryRoot",
|
|
548
|
-
"reviewRoot",
|
|
549
326
|
"consumerRoot",
|
|
550
327
|
"capabilityRoot",
|
|
551
328
|
"runtime",
|
|
@@ -566,8 +343,6 @@ export function completeUniversalWorkflow({ admission, resultRoot, status }) {
|
|
|
566
343
|
status,
|
|
567
344
|
requestRoot: root(admission.requestRoot, "admission.requestRoot"),
|
|
568
345
|
admissionRoot: root(admission.admissionRoot, "admission.admissionRoot"),
|
|
569
|
-
discoveryRoot: root(admission.discoveryRoot, "admission.discoveryRoot"),
|
|
570
|
-
reviewRoot: root(admission.reviewRoot, "admission.reviewRoot"),
|
|
571
346
|
consumerRoot: root(admission.consumerRoot, "admission.consumerRoot"),
|
|
572
347
|
capabilityRoot: root(admission.capabilityRoot, "admission.capabilityRoot"),
|
|
573
348
|
runtime: {
|
|
@@ -575,7 +350,7 @@ export function completeUniversalWorkflow({ admission, resultRoot, status }) {
|
|
|
575
350
|
admission.runtime?.repository,
|
|
576
351
|
"admission.runtime.repository",
|
|
577
352
|
),
|
|
578
|
-
sha:
|
|
353
|
+
sha: admission.runtime?.sha,
|
|
579
354
|
},
|
|
580
355
|
resultRoot: root(resultRoot, "resultRoot"),
|
|
581
356
|
};
|
|
@@ -208,13 +208,13 @@ function validateGateIntegration(root, errors) {
|
|
|
208
208
|
errors.push(`required Verify workflow lacks ${trigger}`);
|
|
209
209
|
}
|
|
210
210
|
const checkJob = verify.match(/^ check:\n([\s\S]*?)(?=^ [\w-]+:|(?![\s\S]))/mu)?.[1] || "";
|
|
211
|
-
const verifyNode = "./.buildchain/
|
|
211
|
+
const verifyNode = "./.buildchain/runtime/actions/build/verification/repository";
|
|
212
212
|
const verifyAction = read("actions/build/verification/repository/action.yml");
|
|
213
213
|
const sourceAction = read("actions/build/verification/qualify-source/action.yml");
|
|
214
214
|
const sourceVerification = read("packages/core/build/verification/source.js");
|
|
215
215
|
const sourceLifecycle = read("packages/core/build/source/lifecycle.js");
|
|
216
216
|
if (!parseYamlUses(checkJob).some(call => call.value === verifyNode) ||
|
|
217
|
-
!parseYamlUses(verifyAction).some(call => call.value === "
|
|
217
|
+
!parseYamlUses(verifyAction).some(call => call.value === "./.buildchain/runtime/actions/build/verification/qualify-source") ||
|
|
218
218
|
!/using: node24/u.test(sourceAction) ||
|
|
219
219
|
!/qualify = qualifySourceLifecycle/u.test(sourceVerification) ||
|
|
220
220
|
!/mode: "verify"/u.test(sourceVerification) ||
|
|
@@ -5,11 +5,17 @@ import path from "node:path";
|
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import YAML from "yaml";
|
|
8
|
+
import { lowerSelfReferencesForLint } from "./workflow-self-reference.mjs";
|
|
8
9
|
const root = process.cwd();
|
|
9
10
|
const contract = JSON.parse(fs.readFileSync(path.join(root, "architecture/dev-delivery-orchestration.json"), "utf8"));
|
|
10
|
-
const temp = fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-delivery-actionlint-"));
|
|
11
|
+
const temp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "buildchain-delivery-actionlint-")));
|
|
11
12
|
try {
|
|
12
|
-
const
|
|
13
|
+
const workflows = path.join(temp, ".github/workflows");
|
|
14
|
+
fs.cpSync(path.join(root, ".github"), path.join(temp, ".github"), { recursive: true });
|
|
15
|
+
fs.mkdirSync(path.join(temp, ".git")); // actionlint's isolated project boundary, never a runtime checkout.
|
|
16
|
+
fs.mkdirSync(path.join(temp, ".buildchain"));
|
|
17
|
+
fs.symlinkSync(path.join(root, "actions"), path.join(temp, "actions"), "junction");
|
|
18
|
+
const files = fs.readdirSync(workflows).filter(file => /\.ya?ml$/u.test(file)).map(file => path.join(workflows, file));
|
|
13
19
|
for (const node of contract.nodes) for (const implementation of node.implementations) {
|
|
14
20
|
const action = YAML.parse(fs.readFileSync(path.join(root, implementation.action), "utf8"));
|
|
15
21
|
assert.deepEqual(Object.keys(action.inputs || {}), implementation.inputs, `${implementation.action}: input contract drift`);
|
|
@@ -21,15 +27,18 @@ try {
|
|
|
21
27
|
}
|
|
22
28
|
const inputs = Object.fromEntries(Object.entries(action.inputs || {}).map(([name, input]) => [name, { ...input, type: "string" }]));
|
|
23
29
|
const outputs = Object.fromEntries(Object.entries(action.outputs || {}).map(([name, output]) => [name, output.value]));
|
|
24
|
-
const file = path.join(
|
|
30
|
+
const file = path.join(workflows, `lint-composite-${node.id}-${files.length}.yml`);
|
|
25
31
|
fs.writeFileSync(file, YAML.stringify({ name: `Validate ${node.id} composite`, on: { workflow_call: { inputs } }, jobs: { node: { "runs-on": "ubuntu-24.04", ...(Object.keys(outputs).length ? { outputs } : {}), steps: action.runs.steps } } }));
|
|
26
32
|
files.push(file);
|
|
27
33
|
}
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
34
|
+
const aliases = new Set(files.flatMap(file => [...fs.readFileSync(file, "utf8").matchAll(/\.\/\.buildchain\/([a-z0-9][a-z0-9-]*)\/actions\//gu)].map(match => match[1])));
|
|
35
|
+
for (const alias of aliases) fs.symlinkSync(root, path.join(temp, ".buildchain", alias), "junction");
|
|
36
|
+
for (const file of files) fs.writeFileSync(file, lowerSelfReferencesForLint(fs.readFileSync(file, "utf8")));
|
|
37
|
+
const version = "1.7.12", probe = spawnSync("actionlint", ["-version"], { encoding: "utf8" });
|
|
38
|
+
const command = probe.status === 0 && probe.stdout.trim().split(/\s/u)[0].replace(/^v/u, "") === version ? "actionlint" : "go";
|
|
39
|
+
const prefix = command === "go" ? ["run", `github.com/rhysd/actionlint/cmd/actionlint@v${version}`] : [];
|
|
40
|
+
const result = spawnSync(command, [...prefix, "-color=false", ...files], { cwd: temp, stdio: "inherit" });
|
|
32
41
|
if (result.error) throw result.error;
|
|
33
42
|
if (result.signal || result.status !== 0) process.exitCode = result.status || 1;
|
|
34
|
-
else console.log(`Validated ${files.length} delivery composite
|
|
43
|
+
else console.log(`Validated ${files.length} source workflows and delivery composite interfaces.`);
|
|
35
44
|
} finally { fs.rmSync(temp, { recursive: true, force: true }); }
|
|
@@ -52,7 +52,7 @@ function assertOrdered(relative, markers) {
|
|
|
52
52
|
export function assertPromotionCertificationWiring(source) {
|
|
53
53
|
const workflow = YAML.parse(source);
|
|
54
54
|
const node = workflow?.jobs?.qualify?.steps?.find((step) => step.id === "node");
|
|
55
|
-
if (node?.uses !== "./.buildchain/
|
|
55
|
+
if (node?.uses !== "./.buildchain/runtime/actions/release/promotion/qualify" ||
|
|
56
56
|
node.with?.["request-json"] !== "${{ inputs.request-json }}" ||
|
|
57
57
|
node.with?.["job-workflow-sha"] !== "${{ toJSON(job.workflow_sha) }}")
|
|
58
58
|
fail("promotion certification is missing its exact owned node and typed request binding");
|
|
@@ -101,12 +101,12 @@ export function checkFloatingConsumerPolicyContract() {
|
|
|
101
101
|
fail("architecture policy contract is missing");
|
|
102
102
|
}
|
|
103
103
|
if (
|
|
104
|
-
policy.contractLocks?.
|
|
104
|
+
policy.contractLocks?.runtimeSelectionOwner !== "actions/runtime/selection/resolve"
|
|
105
105
|
) {
|
|
106
|
-
fail("contract lock must
|
|
106
|
+
fail("contract lock selection must belong to the unified entry");
|
|
107
107
|
}
|
|
108
108
|
assertPersistedSelectors();
|
|
109
|
-
assertOrdered("packages/core/build/plan/admission.js", ["const policy = scanConsumerPolicy(", "validatePackageManagerContract({"
|
|
109
|
+
assertOrdered("packages/core/build/plan/admission.js", ["const policy = scanConsumerPolicy(", "validatePackageManagerContract({"]);
|
|
110
110
|
const buildWorkflow = read(".github/workflows/.build.yml");
|
|
111
111
|
const planner = read("packages/core/build/plan/admission.js");
|
|
112
112
|
if (!planner.includes("expectedInvocationChannel: plan.identity.channel") ||
|
|
@@ -114,7 +114,7 @@ export function checkFloatingConsumerPolicyContract() {
|
|
|
114
114
|
!buildWorkflow.includes("actions/build/lifecycle/plan")) fail("build planning must bind called workflow and channel admission");
|
|
115
115
|
assertTrustGatedJobs(buildWorkflow, ["build-native", "build-container", "sign", "attest", "deliver"]);
|
|
116
116
|
assertOrdered("actions/publication/candidate/plan/action.yml", [
|
|
117
|
-
"
|
|
117
|
+
"Admit source checkout", "Enforce v4 floating consumer policy", "Resolve controller identities",
|
|
118
118
|
]);
|
|
119
119
|
const publication = YAML.parse(read(".github/workflows/public-build-publication.yml"));
|
|
120
120
|
if (!Object.values(publication.jobs).some((job) => job.steps?.some((step) => step.uses?.endsWith("/actions/publication/candidate/plan"))))
|
|
@@ -122,7 +122,7 @@ export function checkFloatingConsumerPolicyContract() {
|
|
|
122
122
|
const stageCanary = read(".github/workflows/public-build-stage-capsule-canary.yml");
|
|
123
123
|
if (
|
|
124
124
|
!stageCanary.includes("consumer-admission:") ||
|
|
125
|
-
!stageCanary
|
|
125
|
+
!jobDependsOn(stageCanary, "qualify", "consumer-admission")
|
|
126
126
|
) {
|
|
127
127
|
fail(
|
|
128
128
|
"Stage Capsule qualification is not transitively gated by consumer admission",
|
|
@@ -106,9 +106,7 @@ const requiredPaths = [
|
|
|
106
106
|
"scripts/next-development-self-dogfood-harness.mjs",
|
|
107
107
|
"scripts/generate-next-development-guidance.mjs",
|
|
108
108
|
"packages/core/governance/commands/reconcile-release-governance.mjs",
|
|
109
|
-
"packages/core/consumer/commands/buildchain-channel-router.mjs",
|
|
110
109
|
"packages/core/release/commands/promotion-channel-router.mjs",
|
|
111
|
-
"packages/core/release/commands/promotion-identity-resolver.mjs",
|
|
112
110
|
"scripts/generate-channel-promotion-workflow.mjs",
|
|
113
111
|
"scripts/build-standalone-binary.mjs",
|
|
114
112
|
"packages/core/publication/commands/create-release-bundle.mjs",
|
|
@@ -317,17 +315,10 @@ const boundedAlphaRecoveryWorkflow = fs.readFileSync(
|
|
|
317
315
|
"utf8",
|
|
318
316
|
);
|
|
319
317
|
assertPromotionInventory(root);
|
|
320
|
-
const promotionOverrideAuthorization = fs.readFileSync(
|
|
321
|
-
path.join(root, "packages/core/release/runtime-override-authorization.js"),
|
|
322
|
-
"utf8",
|
|
323
|
-
);
|
|
324
|
-
if (!promotionOverrideAuthorization.includes("promotion runtime override is only allowed for trusted workflow_dispatch runs")) {
|
|
325
|
-
throw new Error("promotion runtime override authorization must remain fail closed");
|
|
326
|
-
}
|
|
327
318
|
for (const workflow of [channelBuildWorkflow, reusableBuildWorkflow]) {
|
|
328
319
|
const block = workflow.split(" inputs:\n")[1].split(" secrets:\n")[0];
|
|
329
320
|
const names = [...block.matchAll(/^ ([a-z0-9-]+):$/gm)].map((match) => match[1]);
|
|
330
|
-
if (JSON.stringify(names) !== '
|
|
321
|
+
if (JSON.stringify(names.sort()) !== JSON.stringify(['config-path','contract-lock','resume-run-id','runtime-ref','runtime-selection'])) throw new Error('build API must expose only the locator and unified runtime entry inputs');
|
|
331
322
|
}
|
|
332
323
|
if ((channelBuildWorkflow.match(/uses: \.\/\.github\/workflows\/\.build\.yml/g) || []).length !== 1) {
|
|
333
324
|
throw new Error("public build facade must invoke its exact backbone once");
|