@kungfu-tech/buildchain 4.0.2 → 4.0.3
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 +4 -4
- package/CONTRIBUTING.md +34 -0
- package/README.md +1 -1
- package/architecture/agent-change-map.md +154 -147
- package/architecture/{v4-architecture-constitution.md → architecture-constitution.md} +11 -11
- package/architecture/{v3-core-mechanism-inventory.md → baseline-core-mechanism-inventory.md} +1 -1
- package/architecture/{v4-bootstrap-authority.json → bootstrap-authority.json} +3 -3
- package/architecture/{v3-v4-capability-closure.md → capability-parity-closure.md} +3 -3
- package/architecture/{v3-v4-live-capability-inventory.json → capability-parity-live-capability-inventory.json} +4 -4
- package/architecture/{v4-capability-state-machine-manifest.json → capability-state-machine-manifest.json} +2 -2
- package/architecture/{v4-capability-state-machine-manifest.schema.json → capability-state-machine-manifest.schema.json} +2 -2
- package/architecture/ci-lane-change-budget.json +18 -0
- package/architecture/{v4-cross-platform-adopter-qualification.json → cross-platform-adopter-qualification.json} +3 -3
- package/architecture/{v4-delivery-authority-parity.json → delivery-authority-parity.json} +16 -16
- package/architecture/{v4-delivery-warrant-shadow-bootstrap-plan.json → delivery-warrant-shadow-bootstrap-plan.json} +3 -3
- package/architecture/{v4-delivery-warrant-shadow-bootstrap-plan.md → delivery-warrant-shadow-bootstrap-plan.md} +7 -7
- package/architecture/{v4-floating-consumer-policy.md → floating-consumer-policy.md} +1 -1
- package/architecture/implementation-naming.json +1273 -0
- package/architecture/internal-capabilities.json +171 -169
- package/architecture/maintainability-debt.json +87 -82
- package/architecture/maintainability-policy.json +15 -15
- package/architecture/{v4-next-development-parity.json → next-development-parity.json} +8 -8
- package/architecture/{v4-partial-mutation-recovery-qualification.json → partial-mutation-recovery-qualification.json} +8 -8
- package/architecture/{v4-platform-stage-checkpoints.json → platform-stage-checkpoints.json} +2 -2
- package/architecture/{v4-provider-operation-journal-contract.json → provider-operation-journal-contract.json} +9 -9
- package/architecture/{v4-publication-rehearsal-parity.json → publication-rehearsal-parity.json} +4 -4
- package/architecture/{v4-release-activation-shadow-domain.json → release-activation-shadow-domain.json} +8 -8
- package/architecture/{v4-release-topology.json → release-topology.json} +112 -56
- package/architecture/{v4-release-train-parity.json → release-train-parity.json} +2 -2
- package/architecture/{v4-runtime-ref-resume-authority.json → runtime-ref-resume-authority.json} +1 -1
- package/architecture/{v4-runtime-semantic-closure.json → runtime-semantic-closure.json} +2 -2
- package/architecture/{v4-rust-libnode-bridge-evaluation.json → rust-libnode-bridge-evaluation.json} +3 -3
- package/architecture/{v4-rust-libnode-bridge-spike.md → rust-libnode-bridge-spike.md} +6 -6
- package/architecture/{v4-rust-wasm-production-authority.json → rust-wasm-production-authority.json} +11 -11
- package/architecture/{v4-stable-publication-fence.json → stable-publication-fence.json} +6 -6
- package/architecture/{v4-stage-capsule-contract.json → stage-capsule-contract.json} +1 -1
- package/architecture/{v4-stage-capsule-qualification.json → stage-capsule-qualification.json} +6 -6
- package/architecture/{v4-stage-capsule-resume-planner.json → stage-capsule-resume-planner.json} +2 -2
- package/architecture/{v4-stage-capsule-store-contract.json → stage-capsule-store-contract.json} +4 -4
- package/architecture/{v4-tail-reseal-parity.json → tail-reseal-parity.json} +28 -28
- package/architecture/{v4-universal-workflow-bootstrap.json → universal-workflow-bootstrap.json} +9 -4
- package/architecture/{v4-universal-workflow-fault-campaign.json → universal-workflow-fault-campaign.json} +8 -8
- package/architecture/{v4-universal-workflow-train-admission.json → universal-workflow-train-admission.json} +8 -8
- package/architecture/workflow-taxonomy.json +11 -0
- package/bin/buildchain.mjs +3 -3
- package/bin/internal/adopter-delivery-cli.mjs +9 -9
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/crates/{buildchain-v4-bridge → buildchain-host-bridge}/Cargo.lock +1 -1
- package/crates/{buildchain-v4-bridge → buildchain-host-bridge}/Cargo.toml +1 -1
- package/crates/{buildchain-v4-bridge → buildchain-host-bridge}/src/main.rs +3 -3
- package/dist/site/buildchain-contract.json +18 -18
- package/dist/site/buildchain-site.json +167 -71
- package/dist/site/capability-registry.json +5 -5
- package/dist/site/kfd-claims.json +226 -90
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +7 -7
- package/dist/site/node-api-registry.json +12929 -10491
- package/dist/site/page-registry.json +155 -59
- package/dist/site/public-surface-audit.json +65 -46
- package/dist/site/publication-authority-registry.json +27 -17
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +18 -9
- package/dist/site/site-manifest.json +11 -11
- package/dist/site/workflow-registry.json +86 -52
- package/docs/MAP.md +4 -3
- package/docs/next-development-transition.md +24 -1
- package/docs/node-api-reference.md +435 -175
- package/docs/oci-publication.md +173 -0
- package/docs/publish-transaction.md +36 -3
- package/docs/release-flow.md +1 -1
- package/docs/reusable-build-surface.md +1 -1
- package/docs/v4-adopter-delivery.md +2 -2
- package/docs/v4-canonical-contracts.md +7 -7
- package/docs/v4-delivery-warrant-read-candidate.md +3 -3
- package/docs/v4-delivery-warrant-semantic-diff.md +3 -3
- package/docs/v4-production-release.md +8 -2
- package/docs/v4-publication-rehearsal.md +3 -3
- package/docs/v4-runtime-ref-resume-authority.md +2 -2
- package/docs/v4-rust-wasm-production-authority.md +6 -6
- package/docs/v4-stage-capsule.md +7 -7
- package/docs/v4-tail-reseal.md +1 -1
- package/docs/workflow-catalog.md +1 -0
- package/package.json +37 -26
- package/packages/compatibility/adopter-delivery.js +16 -0
- package/packages/compatibility/cross-platform-adopter-qualification.js +12 -0
- package/packages/compatibility/floating-consumer-policy.js +14 -0
- package/packages/compatibility/publication-qualification.js +11 -0
- package/packages/compatibility/publication-rehearsal.js +18 -0
- package/packages/compatibility/runtime-ref-resume-authority.js +13 -0
- package/packages/compatibility/tail-reseal-receipt.js +7 -0
- package/packages/compatibility/tail-reseal.js +11 -0
- package/packages/core/{v4-adopter-delivery-parity.js → adopter-delivery-parity.js} +12 -12
- package/packages/core/{v4-adopter-delivery.js → adopter-delivery.js} +24 -24
- package/packages/core/buildchain-contract.js +5 -5
- package/packages/core/buildchain-domain.wasm +0 -0
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/{v4-canonical-contracts.js → canonical-contracts.js} +32 -31
- package/packages/core/{v4-cross-platform-adopter-qualification.js → cross-platform-adopter-qualification.js} +19 -19
- package/packages/core/{v4-delivery-warrant-fixture-runner.js → delivery-warrant-fixture-runner.js} +36 -32
- package/packages/core/{v4-delivery-warrant-read-candidate.js → delivery-warrant-read-candidate.js} +40 -40
- package/packages/core/{v4-delivery-warrant-semantic-diff-gate.js → delivery-warrant-semantic-diff-gate.js} +58 -59
- package/packages/core/{v4-delivery-warrant-shadow-adapter.js → delivery-warrant-shadow-adapter.js} +15 -15
- package/packages/core/dev-delivery-execution-transfer.js +6 -6
- package/packages/core/dev-delivery-native-proof.js +12 -8
- package/packages/core/dev-delivery-warrant-legacy-recovery.js +1 -1
- package/packages/core/dev-delivery-warrant-native-compatibility.js +4 -4
- package/packages/core/dev-delivery-warrant-state.js +11 -11
- package/packages/core/dev-delivery-warrant.js +3 -3
- package/packages/core/domain-wasm-artifact.js +7 -0
- package/packages/core/{v4-domain-wasm.js → domain-wasm.js} +31 -31
- package/packages/core/{v4-floating-consumer-evidence.js → floating-consumer-evidence.js} +13 -13
- package/packages/core/{v4-floating-consumer-policy.js → floating-consumer-policy.js} +27 -29
- package/packages/core/{v4-floating-consumer-release-passport.js → floating-consumer-release-passport.js} +25 -25
- package/packages/core/index.js +4 -8
- package/packages/core/next-development-projection.js +24 -1
- package/packages/core/next-development-transition.js +5 -6
- package/packages/core/oci-compose-qualification.js +143 -0
- package/packages/core/oci-publication-bundle.js +281 -0
- package/packages/core/oci-publication-graph.js +206 -0
- package/packages/core/paper-runtime-channels.js +2 -2
- package/packages/core/paper.js +3 -3
- package/packages/core/partial-mutation-recovery-qualification.js +18 -0
- package/packages/core/{v4-platform-stage-checkpoints.js → platform-stage-checkpoints.js} +37 -37
- package/packages/core/{v4-product-publication.js → product-publication.js} +11 -9
- package/packages/core/{v4-protected-publication-source.js → protected-publication-source.js} +3 -3
- package/packages/core/provider-operation-journal.js +62 -0
- package/packages/core/{v4-provider-readback-idempotency.js → provider-readback-idempotency.js} +33 -33
- package/packages/core/public-export-compatibility.js +33 -0
- package/packages/core/publication-development.js +72 -0
- package/packages/core/{v4-publication-qualification.js → publication-qualification.js} +17 -17
- package/packages/core/{v4-publication-rehearsal-capsule.js → publication-rehearsal-capsule.js} +23 -23
- package/packages/core/publication-rehearsal-projection.js +2 -2
- package/packages/core/{v4-publication-rehearsal-provider-bindings.js → publication-rehearsal-provider-bindings.js} +4 -4
- package/packages/core/publication-rehearsal-runtime.js +21 -21
- package/packages/core/{v4-publication-rehearsal.js → publication-rehearsal.js} +33 -33
- package/packages/core/publication-sealed-bundle.js +78 -13
- package/packages/core/release-activation-shadow.js +32 -0
- package/packages/core/release-candidate.js +6 -6
- package/packages/core/{v4-release-invocation.js → release-invocation.js} +15 -15
- package/packages/core/release-passport.js +36 -36
- package/packages/core/release-tail-product-capabilities.js +9 -0
- package/packages/core/release-tail-provider-plane.js +10 -10
- package/packages/core/{v4-runtime-ref-resume-authority.js → runtime-ref-resume-authority.js} +36 -40
- package/packages/core/{v4-runtime-selector-persistence.js → runtime-selector-persistence.js} +7 -7
- package/packages/core/stable-publication-fence.js +28 -0
- package/packages/core/{v4-stage-capsule-local-store.js → stage-capsule-local-store.js} +56 -54
- package/packages/core/{v4-stage-capsule-qualification-campaign.js → stage-capsule-qualification-campaign.js} +37 -38
- package/packages/core/{v4-stage-capsule-qualification.js → stage-capsule-qualification.js} +28 -29
- package/packages/core/{v4-stage-capsule-resume-planner.js → stage-capsule-resume-planner.js} +29 -29
- package/packages/core/{v4-stage-capsule-store.js → stage-capsule-store.js} +59 -56
- package/packages/core/{v4-stage-capsule.js → stage-capsule.js} +45 -45
- package/packages/core/{v4-tail-reseal-contract.js → tail-reseal-contract.js} +6 -6
- package/packages/core/{v4-tail-reseal-github.js → tail-reseal-github.js} +12 -12
- package/packages/core/{v4-tail-reseal-receipt.js → tail-reseal-receipt.js} +22 -22
- package/packages/core/{v4-tail-reseal.js → tail-reseal.js} +29 -29
- package/packages/core/{v4-universal-workflow-bootstrap.js → universal-workflow-bootstrap.js} +23 -23
- package/scripts/{v4-architecture.mjs → architecture.mjs} +6 -6
- package/scripts/binary-publication-evidence.mjs +1 -1
- package/scripts/{v4-bridge-bootstrap.mjs → bridge-bootstrap.mjs} +14 -18
- package/scripts/{v4-bridge-evidence.mjs → bridge-evidence.mjs} +8 -8
- package/scripts/{build-v4-domain-wasm.mjs → build-domain-wasm.mjs} +6 -18
- package/scripts/{v3-v4-capability-catalog.mjs → capability-parity-catalog.mjs} +19 -19
- package/scripts/check-action-bundles.mjs +2 -2
- package/scripts/{check-v3-v4-capability-inventory.mjs → check-capability-parity-inventory.mjs} +42 -42
- package/scripts/check-core-mechanism-inventory.mjs +4 -1
- package/scripts/{check-v4-floating-consumer-policy-contract.mjs → check-floating-consumer-policy-contract.mjs} +8 -8
- package/scripts/check-implementation-naming.mjs +135 -0
- package/scripts/check-inventory.mjs +6 -5
- package/scripts/check-maintainability.mjs +9 -2
- package/scripts/{check-v4-public-dogfood-contract.mjs → check-public-dogfood-contract.mjs} +15 -15
- package/scripts/{check-v4-release-topology.mjs → check-release-topology.mjs} +53 -31
- package/scripts/{check-v4-runtime-semantic-closure.mjs → check-runtime-semantic-closure.mjs} +19 -16
- package/scripts/{check-v4-universal-workflow-bootstrap.mjs → check-universal-workflow-bootstrap.mjs} +14 -14
- package/scripts/{v4-consumer-policy.mjs → consumer-policy.mjs} +15 -15
- package/scripts/{copy-v4-domain-wasm.mjs → copy-domain-wasm.mjs} +3 -3
- package/scripts/{v4-cross-platform-adopter-qualification.mjs → cross-platform-adopter-qualification.mjs} +9 -9
- package/scripts/{v4-declarative-promotion-admission.mjs → declarative-promotion-admission.mjs} +4 -4
- package/scripts/dev-delivery-warrant-options.mjs +1 -1
- package/scripts/dev-delivery-warrant-store.mjs +29 -10
- package/scripts/dev-delivery-warrant.mjs +4 -4
- package/scripts/dev-pr-delivery-warrant.mjs +1 -1
- package/scripts/generate-channel-build-workflow.mjs +2 -2
- package/scripts/generate-channel-promotion-workflow.mjs +14 -31
- package/scripts/generate-release-candidate-passport.mjs +23 -23
- package/scripts/generate-site-bundle.mjs +17 -17
- package/scripts/{generate-v4-universal-workflow-facades.mjs → generate-universal-workflow-facades.mjs} +61 -21
- package/scripts/{v4-host-adapter.mjs → host-adapter.mjs} +1 -1
- package/scripts/implementation-references.mjs +25 -0
- package/scripts/init-repo.mjs +1 -1
- package/scripts/next-development-review.mjs +17 -11
- package/scripts/oci-compose-preview.mjs +261 -0
- package/scripts/{v4-platform-stage-checkpoint-rehearsal.mjs → platform-stage-checkpoint-rehearsal.mjs} +17 -17
- package/scripts/{v4-product-publication-intent.mjs → product-publication-intent.mjs} +16 -8
- package/scripts/publication-candidate-kind.mjs +93 -0
- package/scripts/publication-candidate-sealer.mjs +16 -1
- package/scripts/{v4-publication-rehearsal-fixture.mjs → publication-rehearsal-fixture.mjs} +5 -5
- package/scripts/{v4-publication-settlement.mjs → publication-settlement.mjs} +8 -8
- package/scripts/{v4-release-candidate-adapter.mjs → release-candidate-adapter.mjs} +4 -4
- package/scripts/release-candidate-resolver.mjs +15 -26
- package/scripts/release-tail.mjs +5 -5
- package/scripts/resume-from-candidate-run.mjs +35 -42
- package/scripts/site-capability-metadata.mjs +9 -0
- package/scripts/source-verification-evidence.mjs +7 -7
- package/scripts/{v4-stage-capsule-qualification.mjs → stage-capsule-qualification.mjs} +62 -62
- package/scripts/{v4-stage-capsule-resume-rehearsal.mjs → stage-capsule-resume-rehearsal.mjs} +6 -6
- package/scripts/{v4-tail-reseal-macos-rehearsal.mjs → tail-reseal-macos-rehearsal.mjs} +9 -9
- package/scripts/{v4-tail-reseal.mjs → tail-reseal.mjs} +19 -19
- package/scripts/universal-facade-maintainability.mjs +12 -3
- package/scripts/{v4-universal-workflow-backflow.mjs → universal-workflow-backflow.mjs} +10 -10
- package/scripts/{v4-universal-workflow-engine.mjs → universal-workflow-engine.mjs} +40 -38
- package/scripts/{v4-universal-workflow-self-dogfood.mjs → universal-workflow-self-dogfood.mjs} +12 -12
- package/scripts/verify-version-state-delta.mjs +4 -2
- package/scripts/{v4-warrant-shadow-plan.mjs → warrant-shadow-plan.mjs} +6 -6
- package/templates/universal-buildchain-bootstrap-recovery.yml +4 -4
- package/packages/core/buildchain-v4-domain.wasm +0 -0
- package/packages/core/v4-domain-wasm-artifact.js +0 -7
- package/packages/core/v4-partial-mutation-recovery-qualification.js +0 -18
- package/packages/core/v4-provider-operation-journal.js +0 -62
- package/packages/core/v4-release-activation-shadow.js +0 -32
- package/packages/core/v4-stable-publication-fence.js +0 -28
- /package/actions/{v4-release-candidate-promote → release-candidate-promote}/README.md +0 -0
- /package/architecture/{v4-adopter-delivery-parity.json → adopter-delivery-parity.json} +0 -0
- /package/architecture/{v3-core-mechanism-inventory.json → baseline-core-mechanism-inventory.json} +0 -0
- /package/architecture/{v4-canonical-contract-fixtures.json → canonical-contract-fixtures.json} +0 -0
- /package/architecture/{v4-compatibility-facts-parity.json → compatibility-facts-parity.json} +0 -0
- /package/architecture/{v4-delivery-warrant-shadow-bootstrap-plan.schema.json → delivery-warrant-shadow-bootstrap-plan.schema.json} +0 -0
- /package/architecture/{v4-delivery-warrant-shadow-fixtures.json → delivery-warrant-shadow-fixtures.json} +0 -0
- /package/architecture/{v4-delivery-warrant-shadow-fixtures.schema.json → delivery-warrant-shadow-fixtures.schema.json} +0 -0
- /package/architecture/{v4-exception-ledger.json → exception-ledger.json} +0 -0
- /package/architecture/{v4-exception-ledger.schema.json → exception-ledger.schema.json} +0 -0
- /package/architecture/{v4-floating-consumer-policy.json → floating-consumer-policy.json} +0 -0
- /package/architecture/{v4-publication-facade-patches.json → publication-facade-patches.json} +0 -0
- /package/architecture/{v4-release-invocation-fixtures.json → release-invocation-fixtures.json} +0 -0
package/scripts/init-repo.mjs
CHANGED
|
@@ -366,7 +366,7 @@ jobs:
|
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
function workflowYaml({ type, runnerPreset, artifactName }) {
|
|
369
|
-
return `${nextDevelopmentWorkflowHeader()}# Buildchain v4 lifecycle checkpoints are governed by architecture/
|
|
369
|
+
return `${nextDevelopmentWorkflowHeader()}# Buildchain v4 lifecycle checkpoints are governed by architecture/platform-stage-checkpoints.json.
|
|
370
370
|
# Do not add undeclared runner-only inputs, outputs, environment, or provider effects.
|
|
371
371
|
# Commit both .buildchain/contract-lock.json and .buildchain/alpha-contract-lock.json.
|
|
372
372
|
# Persist only @v4 or @v4-alpha; pass temporary train/SHA runtimes through workflow_dispatch.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nextPatchDevelopmentVersion } from "../packages/core/publication-development.js";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { execFileSync } from "node:child_process";
|
|
@@ -5,14 +6,14 @@ import { pathToFileURL } from "node:url";
|
|
|
5
6
|
import { verifyVersionStateDelta } from "./verify-version-state-delta.mjs";
|
|
6
7
|
import { readBinaryPublicationEvidence } from "./binary-publication-evidence.mjs";
|
|
7
8
|
import { releaseAssetClient } from "./release-asset-client.mjs";
|
|
8
|
-
import { enqueueNextDevelopmentPullRequest } from "../actions/
|
|
9
|
+
import { enqueueNextDevelopmentPullRequest } from "../actions/release-candidate-promote/next-development-queue.js";
|
|
9
10
|
|
|
10
11
|
const REPOSITORY = "kungfu-systems/buildchain";
|
|
11
12
|
const WORKFLOW = ".github/workflows/self-build-verify.yml";
|
|
12
13
|
const REVIEWER = "kungfu-origin";
|
|
13
14
|
const SHA = /^[a-f0-9]{40}$/u;
|
|
14
15
|
const BRANCH =
|
|
15
|
-
/^chore\/next-development\/(
|
|
16
|
+
/^chore\/next-development\/((?:0|[1-9]\d*)\.\d+\.\d+-alpha\.\d+)-[a-f0-9]{16}$/u;
|
|
16
17
|
|
|
17
18
|
export function assertReviewRun(run, { repository, runId, headSha } = {}) {
|
|
18
19
|
if (
|
|
@@ -43,7 +44,7 @@ export function assertReviewPull(
|
|
|
43
44
|
pull.base?.sha !== baseSha ||
|
|
44
45
|
pull.head?.ref !== branch ||
|
|
45
46
|
!BRANCH.test(branch || "") ||
|
|
46
|
-
|
|
47
|
+
pull.base?.ref !== `dev/v${BRANCH.exec(branch)?.[1].split(".")[0]}/v${BRANCH.exec(branch)?.[1].split(".").slice(0, 2).join(".")}` ||
|
|
47
48
|
pull.state !== "open" ||
|
|
48
49
|
pull.draft ||
|
|
49
50
|
pull.merged_at ||
|
|
@@ -114,18 +115,23 @@ export async function verifyNextDevelopmentReview({
|
|
|
114
115
|
const parents = git("show", "-s", "--format=%P", run.head_sha).split(" ");
|
|
115
116
|
if (parents.length !== 1 || parents[0] !== baseSha)
|
|
116
117
|
throw new Error("next-development must have one exact protected parent");
|
|
117
|
-
const projection = verifyDelta({ baseSha, headSha: run.head_sha });
|
|
118
|
-
if (BRANCH.exec(run.head_branch)[1] !== projection.version)
|
|
119
|
-
throw new Error(
|
|
120
|
-
"next-development branch version differs from regenerated version",
|
|
121
|
-
);
|
|
122
118
|
const version = JSON.parse(git("show", `${baseSha}:package.json`)).version;
|
|
123
|
-
const
|
|
119
|
+
const targetVersion = BRANCH.exec(run.head_branch)[1];
|
|
120
|
+
const stable = targetVersion.endsWith("-alpha.0");
|
|
121
|
+
const completedStableVersion = version.split("-alpha.")[0];
|
|
122
|
+
if (stable && nextPatchDevelopmentVersion(completedStableVersion) !== targetVersion)
|
|
123
|
+
throw new Error("development target is not the completed stable patch successor");
|
|
124
|
+
const tag = `v${stable ? completedStableVersion : version}`;
|
|
124
125
|
const sourceSha = client.json(`repos/${repository}/commits/${tag}`).sha;
|
|
125
126
|
const settlement = await publication({ repository, tag, sourceSha });
|
|
126
127
|
const publishedTree = settlement.documents.passport.source.treeHash;
|
|
127
|
-
if (publishedTree !== git("rev-parse", `${baseSha}^{tree}`))
|
|
128
|
+
if (!stable && publishedTree !== git("rev-parse", `${baseSha}^{tree}`))
|
|
128
129
|
throw new Error("development base is not the completed alpha source tree");
|
|
130
|
+
if (stable && (settlement.documents.passport.release?.version !== completedStableVersion || settlement.documents.invocation?.target?.channel !== "stable"))
|
|
131
|
+
throw new Error("stable publication evidence differs from the completed version");
|
|
132
|
+
const projection = verifyDelta({ baseSha, headSha: run.head_sha, ...(stable ? { completedStableVersion } : {}) });
|
|
133
|
+
if (targetVersion !== projection.version)
|
|
134
|
+
throw new Error("next-development branch version differs from regenerated version");
|
|
129
135
|
observe(client, repository, runId, {
|
|
130
136
|
headSha: run.head_sha,
|
|
131
137
|
baseSha,
|
|
@@ -175,7 +181,7 @@ export async function approveNextDevelopment({ client, reviewer, plan }) {
|
|
|
175
181
|
await reviewer.post(endpoint, {
|
|
176
182
|
event: "APPROVE",
|
|
177
183
|
commit_id: plan.headSha,
|
|
178
|
-
body: `Verified the exact version-only transition by regenerating all tracked bytes from protected base ${plan.baseSha}. Completed
|
|
184
|
+
body: `Verified the exact version-only transition by regenerating all tracked bytes from protected base ${plan.baseSha}. Completed publication receipt: ${plan.publicationReceiptRoot}. Verification run: https://github.com/${plan.repository}/actions/runs/${plan.runId}.`,
|
|
179
185
|
});
|
|
180
186
|
}
|
|
181
187
|
observe(client, plan.repository, plan.runId, plan);
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { verifyPublicationSettlement } from "./publication-settlement.mjs";
|
|
7
|
+
import {
|
|
8
|
+
verifyComposePublication,
|
|
9
|
+
verifyComposeQualification,
|
|
10
|
+
} from "../packages/core/oci-compose-qualification.js";
|
|
11
|
+
import { releaseTailRoot } from "../packages/core/release-tail-provider-plane.js";
|
|
12
|
+
import { createRegistryClient } from "../actions/release-candidate-promote/oci-registry-client.js";
|
|
13
|
+
import { ociPublicationTag } from "../packages/core/oci-publication-graph.js";
|
|
14
|
+
|
|
15
|
+
const digest = (bytes) =>
|
|
16
|
+
`sha256:${crypto.createHash("sha256").update(bytes).digest("hex")}`;
|
|
17
|
+
function check(condition, message) {
|
|
18
|
+
if (!condition) throw new Error(`Compose preview: ${message}`);
|
|
19
|
+
}
|
|
20
|
+
function gh(args) {
|
|
21
|
+
return execFileSync("gh", args, {
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const api = (route) => JSON.parse(gh(["api", route]));
|
|
27
|
+
const read = (file) => JSON.parse(fs.readFileSync(file));
|
|
28
|
+
export function createComposePreviewRegistry(environment, fetchImpl = fetch) {
|
|
29
|
+
const token = environment.BUILDCHAIN_REGISTRY_TOKEN;
|
|
30
|
+
const actor = environment.GITHUB_ACTOR;
|
|
31
|
+
check(
|
|
32
|
+
typeof token === "string" &&
|
|
33
|
+
token.length > 0 &&
|
|
34
|
+
typeof actor === "string" &&
|
|
35
|
+
actor.length > 0,
|
|
36
|
+
"explicit scoped registry identity required",
|
|
37
|
+
);
|
|
38
|
+
return createRegistryClient(token, fetchImpl, actor);
|
|
39
|
+
}
|
|
40
|
+
function output(name, value) {
|
|
41
|
+
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function promoteComposePreview({
|
|
45
|
+
context,
|
|
46
|
+
registry,
|
|
47
|
+
receipt,
|
|
48
|
+
fetchManifest,
|
|
49
|
+
}) {
|
|
50
|
+
const { application, policy } = context;
|
|
51
|
+
const exact = await fetchManifest(
|
|
52
|
+
ociPublicationTag(application, context.tag.slice(1)),
|
|
53
|
+
);
|
|
54
|
+
check(
|
|
55
|
+
exact.digest === application.digest,
|
|
56
|
+
"immutable application digest changed",
|
|
57
|
+
);
|
|
58
|
+
const current = await fetchManifest(policy.alias, true);
|
|
59
|
+
check(
|
|
60
|
+
current.digest === policy.previousDigest ||
|
|
61
|
+
current.digest === application.digest,
|
|
62
|
+
"preview changed since qualification",
|
|
63
|
+
);
|
|
64
|
+
const plan = {
|
|
65
|
+
schema: "kungfu-buildchain-compose-preview-plan/v1",
|
|
66
|
+
...context,
|
|
67
|
+
qualificationRoot: releaseTailRoot(receipt),
|
|
68
|
+
expectedOld: policy.previousDigest,
|
|
69
|
+
targetDigest: application.digest,
|
|
70
|
+
operation: "digest-preserving-preview-promotion",
|
|
71
|
+
};
|
|
72
|
+
if (current.digest !== application.digest) {
|
|
73
|
+
const result = await registry(application, `manifests/${policy.alias}`, {
|
|
74
|
+
write: true,
|
|
75
|
+
method: "PUT",
|
|
76
|
+
body: exact.bytes,
|
|
77
|
+
headers: {
|
|
78
|
+
"content-type": exact.mediaType,
|
|
79
|
+
"content-length": String(exact.bytes.length),
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
check(
|
|
83
|
+
result.status === 201,
|
|
84
|
+
`preview write uncertain (HTTP ${result.status}); retain the original qualification for readback`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const observed = await fetchManifest(policy.alias);
|
|
88
|
+
check(observed.digest === application.digest, "preview readback mismatch");
|
|
89
|
+
const body = {
|
|
90
|
+
schema: "kungfu-buildchain-compose-preview-receipt/v1",
|
|
91
|
+
repository: context.repository,
|
|
92
|
+
tag: context.tag,
|
|
93
|
+
sourceSha: context.sourceSha,
|
|
94
|
+
publicationReceiptRoot: context.publicationReceiptRoot,
|
|
95
|
+
planRoot: releaseTailRoot(plan),
|
|
96
|
+
qualificationRoot: plan.qualificationRoot,
|
|
97
|
+
previousDigest: policy.previousDigest,
|
|
98
|
+
application: `${application.repository}@${application.digest}`,
|
|
99
|
+
alias: `${application.repository}:${policy.alias}`,
|
|
100
|
+
observedDigest: observed.digest,
|
|
101
|
+
outcome: "complete",
|
|
102
|
+
};
|
|
103
|
+
return { ...body, receiptRoot: releaseTailRoot(body) };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function run(mode) {
|
|
107
|
+
const repository = process.env.GITHUB_REPOSITORY,
|
|
108
|
+
event = read(process.env.GITHUB_EVENT_PATH);
|
|
109
|
+
check(
|
|
110
|
+
process.env.GITHUB_EVENT_NAME === "workflow_run" && event.workflow_run?.id,
|
|
111
|
+
"requires a completed qualification workflow event",
|
|
112
|
+
);
|
|
113
|
+
check(
|
|
114
|
+
/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/u.test(repository),
|
|
115
|
+
"invalid repository",
|
|
116
|
+
);
|
|
117
|
+
const id = String(event.workflow_run.id);
|
|
118
|
+
check(/^\d+$/u.test(id), "invalid qualification run");
|
|
119
|
+
const run = api(`repos/${repository}/actions/runs/${id}`),
|
|
120
|
+
tag = run.head_branch;
|
|
121
|
+
check(
|
|
122
|
+
/^v\d+\.\d+\.\d+-alpha\.\d+$/u.test(tag),
|
|
123
|
+
"qualification must run on an exact alpha tag",
|
|
124
|
+
);
|
|
125
|
+
const release = api(`repos/${repository}/releases/tags/${tag}`);
|
|
126
|
+
check(release.prerelease && !release.draft, "public alpha release required");
|
|
127
|
+
const root = path.resolve(".buildchain/compose-preview");
|
|
128
|
+
fs.mkdirSync(path.join(root, "public"), { recursive: true });
|
|
129
|
+
const names = [
|
|
130
|
+
"oci-family.json",
|
|
131
|
+
"oci-publication-readback.json",
|
|
132
|
+
"buildchain-publication-settlement.json",
|
|
133
|
+
"buildchain.release.json",
|
|
134
|
+
];
|
|
135
|
+
for (const name of names) {
|
|
136
|
+
check(
|
|
137
|
+
release.assets.filter((asset) => asset.name === name).length === 1,
|
|
138
|
+
`missing exact public asset ${name}`,
|
|
139
|
+
);
|
|
140
|
+
gh([
|
|
141
|
+
"release",
|
|
142
|
+
"download",
|
|
143
|
+
tag,
|
|
144
|
+
"--repo",
|
|
145
|
+
repository,
|
|
146
|
+
"--pattern",
|
|
147
|
+
name,
|
|
148
|
+
"--dir",
|
|
149
|
+
path.join(root, "public"),
|
|
150
|
+
"--clobber",
|
|
151
|
+
]);
|
|
152
|
+
}
|
|
153
|
+
const [family, readback, settlement, publicPassport] = names.map((name) =>
|
|
154
|
+
read(path.join(root, "public", name)),
|
|
155
|
+
);
|
|
156
|
+
const documents = settlement.documents;
|
|
157
|
+
const tagged = api(`repos/${repository}/commits/${tag}`).sha;
|
|
158
|
+
verifyPublicationSettlement(documents, {
|
|
159
|
+
repository,
|
|
160
|
+
tag,
|
|
161
|
+
sourceSha: tagged,
|
|
162
|
+
publicPassport,
|
|
163
|
+
});
|
|
164
|
+
const context = verifyComposePublication({
|
|
165
|
+
family,
|
|
166
|
+
readback,
|
|
167
|
+
documents,
|
|
168
|
+
repository,
|
|
169
|
+
tag,
|
|
170
|
+
});
|
|
171
|
+
verifyComposeQualification(context, run);
|
|
172
|
+
const artifactName = `oci-compose-qualification-${id}-${run.run_attempt}`;
|
|
173
|
+
const artifacts = api(
|
|
174
|
+
`repos/${repository}/actions/runs/${id}/artifacts?per_page=100`,
|
|
175
|
+
).artifacts.filter(
|
|
176
|
+
(artifact) => artifact.name === artifactName && !artifact.expired,
|
|
177
|
+
);
|
|
178
|
+
check(
|
|
179
|
+
artifacts.length === 1,
|
|
180
|
+
"qualification artifact is missing or ambiguous",
|
|
181
|
+
);
|
|
182
|
+
if (mode === "prepare") {
|
|
183
|
+
output("artifact-id", artifacts[0].id);
|
|
184
|
+
output("run-id", id);
|
|
185
|
+
output("source-sha", tagged);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
check(mode === "apply", "unsupported mode");
|
|
189
|
+
const evidenceRoot = path.join(root, "qualification");
|
|
190
|
+
const receipt = read(path.join(evidenceRoot, "qualification.json"));
|
|
191
|
+
verifyComposeQualification(context, run, receipt);
|
|
192
|
+
for (const item of receipt.evidence) {
|
|
193
|
+
const file = path.join(evidenceRoot, item.path);
|
|
194
|
+
check(
|
|
195
|
+
!fs.lstatSync(file).isSymbolicLink() &&
|
|
196
|
+
fs.statSync(file).size < 2_000_000 &&
|
|
197
|
+
digest(fs.readFileSync(file)) === item.sha256,
|
|
198
|
+
"qualification evidence bytes mismatch",
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
const { registry } = createComposePreviewRegistry(process.env);
|
|
202
|
+
async function fetchManifest(ref, allowAbsent = false) {
|
|
203
|
+
const response = await registry(context.application, `manifests/${ref}`, {
|
|
204
|
+
headers: {
|
|
205
|
+
accept:
|
|
206
|
+
"application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json",
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
if (allowAbsent && response.status === 404) return { digest: "none" };
|
|
210
|
+
check(response.ok, "public registry readback unavailable");
|
|
211
|
+
const bytes = Buffer.from(await response.arrayBuffer()),
|
|
212
|
+
value = digest(bytes);
|
|
213
|
+
check(
|
|
214
|
+
!response.headers.get("docker-content-digest") ||
|
|
215
|
+
response.headers.get("docker-content-digest") === value,
|
|
216
|
+
"registry digest mismatch",
|
|
217
|
+
);
|
|
218
|
+
return { bytes, digest: value, mediaType: JSON.parse(bytes).mediaType };
|
|
219
|
+
}
|
|
220
|
+
const result = await promoteComposePreview({
|
|
221
|
+
context,
|
|
222
|
+
registry,
|
|
223
|
+
receipt,
|
|
224
|
+
fetchManifest,
|
|
225
|
+
});
|
|
226
|
+
const file = path.join(
|
|
227
|
+
root,
|
|
228
|
+
`buildchain-compose-preview-${id}-${run.run_attempt}.json`,
|
|
229
|
+
);
|
|
230
|
+
fs.writeFileSync(file, JSON.stringify(result, null, 2) + "\n");
|
|
231
|
+
const existing = release.assets.find(
|
|
232
|
+
(asset) => asset.name === path.basename(file),
|
|
233
|
+
);
|
|
234
|
+
if (existing) {
|
|
235
|
+
const previous = path.join(root, "previous");
|
|
236
|
+
fs.mkdirSync(previous, { recursive: true });
|
|
237
|
+
gh([
|
|
238
|
+
"release",
|
|
239
|
+
"download",
|
|
240
|
+
tag,
|
|
241
|
+
"--repo",
|
|
242
|
+
repository,
|
|
243
|
+
"--pattern",
|
|
244
|
+
path.basename(file),
|
|
245
|
+
"--dir",
|
|
246
|
+
previous,
|
|
247
|
+
"--clobber",
|
|
248
|
+
]);
|
|
249
|
+
check(
|
|
250
|
+
fs.readFileSync(path.join(previous, path.basename(file)), "utf8") ===
|
|
251
|
+
fs.readFileSync(file, "utf8"),
|
|
252
|
+
"existing preview receipt conflict",
|
|
253
|
+
);
|
|
254
|
+
} else gh(["release", "upload", tag, file, "--repo", repository]);
|
|
255
|
+
process.stdout.write(`Verified ${result.alias}@${result.observedDigest}\n`);
|
|
256
|
+
}
|
|
257
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
258
|
+
run(process.argv[2]).catch((error) => {
|
|
259
|
+
process.stderr.write(`${error.message}\n`);
|
|
260
|
+
process.exitCode = 1;
|
|
261
|
+
});
|
|
@@ -5,14 +5,14 @@ import path from "node:path";
|
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { domainCanonicalBytes } from "../packages/core/canonical-contracts.js";
|
|
9
|
+
import { StageCapsuleLocalStore } from "../packages/core/stage-capsule-local-store.js";
|
|
10
|
+
import { stageCapsuleBlobRoot } from "../packages/core/stage-capsule-store.js";
|
|
11
11
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from "../packages/core/
|
|
12
|
+
emitPlatformStageCheckpoint,
|
|
13
|
+
restorePlatformStageCheckpoint,
|
|
14
|
+
validatePlatformStageCheckpointDeclaration,
|
|
15
|
+
} from "../packages/core/platform-stage-checkpoints.js";
|
|
16
16
|
|
|
17
17
|
const repoRoot = path.resolve(
|
|
18
18
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
@@ -32,10 +32,10 @@ function required(name) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
function readDeclaration() {
|
|
35
|
-
return
|
|
35
|
+
return validatePlatformStageCheckpointDeclaration(
|
|
36
36
|
JSON.parse(
|
|
37
37
|
fs.readFileSync(
|
|
38
|
-
path.join(repoRoot, "architecture/
|
|
38
|
+
path.join(repoRoot, "architecture/platform-stage-checkpoints.json"),
|
|
39
39
|
"utf8",
|
|
40
40
|
),
|
|
41
41
|
),
|
|
@@ -49,12 +49,12 @@ function stageFor(declaration, stageId) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function rooted(label) {
|
|
52
|
-
return
|
|
52
|
+
return stageCapsuleBlobRoot(Buffer.from(`${label}\n`, "utf8"));
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
function writeJson(file, value) {
|
|
56
56
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
57
|
-
fs.writeFileSync(file,
|
|
57
|
+
fs.writeFileSync(file, domainCanonicalBytes(value), { flag: "wx" });
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function emit({ workRoot, platformId, stageId, recordedAt }) {
|
|
@@ -87,11 +87,11 @@ function emit({ workRoot, platformId, stageId, recordedAt }) {
|
|
|
87
87
|
const productionOutputRoots = Object.fromEntries(
|
|
88
88
|
Object.entries(outputs).map(([name, bytes]) => [
|
|
89
89
|
name,
|
|
90
|
-
|
|
90
|
+
stageCapsuleBlobRoot(bytes),
|
|
91
91
|
]),
|
|
92
92
|
);
|
|
93
|
-
const store = new
|
|
94
|
-
const result =
|
|
93
|
+
const store = new StageCapsuleLocalStore(path.join(workRoot, "store"));
|
|
94
|
+
const result = emitPlatformStageCheckpoint({
|
|
95
95
|
declaration,
|
|
96
96
|
platformId,
|
|
97
97
|
stageId,
|
|
@@ -131,8 +131,8 @@ function restore({ workRoot, platformId, stageId, recordedAt }) {
|
|
|
131
131
|
);
|
|
132
132
|
if (emission.platform !== platformId || emission.stage !== stageId)
|
|
133
133
|
throw new Error("emission identity does not match restore request");
|
|
134
|
-
const store = new
|
|
135
|
-
const result =
|
|
134
|
+
const store = new StageCapsuleLocalStore(path.join(workRoot, "store"));
|
|
135
|
+
const result = restorePlatformStageCheckpoint({
|
|
136
136
|
declaration,
|
|
137
137
|
expectedPlatform: platformId,
|
|
138
138
|
expectedStage: stageId,
|
|
@@ -201,7 +201,7 @@ else if (action === "rehearse") {
|
|
|
201
201
|
providerCredentialsUsed: restored.providerCredentialsUsed,
|
|
202
202
|
};
|
|
203
203
|
writeJson(path.join(common.workRoot, "evidence.json"), evidence);
|
|
204
|
-
process.stdout.write(
|
|
204
|
+
process.stdout.write(domainCanonicalBytes(evidence));
|
|
205
205
|
} else {
|
|
206
206
|
throw new Error("action must be emit, restore, or rehearse");
|
|
207
207
|
}
|
|
@@ -5,8 +5,8 @@ import path from "node:path";
|
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
|
|
7
7
|
import { spawnSyncCommand } from "../packages/core/spawn-command.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { selectProductPublicationIntent } from "../packages/core/product-publication.js";
|
|
9
|
+
import { domainContentRoot } from "../packages/core/canonical-contracts.js";
|
|
10
10
|
|
|
11
11
|
function env(name, required = false) {
|
|
12
12
|
const value = String(process.env[name] || "").trim();
|
|
@@ -59,20 +59,20 @@ function writeOutput(name, value) {
|
|
|
59
59
|
fs.appendFileSync(output, `${name}=${String(value)}\n`);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
export function
|
|
62
|
+
export function resolveProductPublicationIntent() {
|
|
63
63
|
const artifactKind = env("BUILDCHAIN_PUBLISH_ARTIFACT_KIND") || "npm";
|
|
64
64
|
const name = packageName(artifactKind);
|
|
65
65
|
const sourceSha = env("BUILDCHAIN_SOURCE_SHA", true);
|
|
66
66
|
const manifestPath = env(
|
|
67
67
|
"BUILDCHAIN_SEALED_BUNDLE_MANIFEST",
|
|
68
|
-
artifactKind
|
|
68
|
+
artifactKind !== "custom",
|
|
69
69
|
);
|
|
70
70
|
const manifest = manifestPath ? readJson(manifestPath) : null;
|
|
71
71
|
const requiredArtifacts = readJson(
|
|
72
72
|
env("BUILDCHAIN_REQUIRED_ARTIFACTS_PATH", true),
|
|
73
73
|
);
|
|
74
74
|
const channel = env("BUILDCHAIN_CHANNEL", true);
|
|
75
|
-
const intent =
|
|
75
|
+
const intent = selectProductPublicationIntent({
|
|
76
76
|
channel,
|
|
77
77
|
targetRef: env("BUILDCHAIN_TARGET_REF", true),
|
|
78
78
|
sourceSha,
|
|
@@ -80,11 +80,19 @@ export function resolveV4ProductPublicationIntent() {
|
|
|
80
80
|
repository: env("BUILDCHAIN_REPOSITORY", true),
|
|
81
81
|
artifactKind,
|
|
82
82
|
packageName: name,
|
|
83
|
+
...(manifest?.npmPackages
|
|
84
|
+
? {
|
|
85
|
+
npmPackages: manifest.npmPackages.map((entry) => ({
|
|
86
|
+
...entry,
|
|
87
|
+
sha256: `sha256:${entry.sha256}`,
|
|
88
|
+
})),
|
|
89
|
+
}
|
|
90
|
+
: {}),
|
|
83
91
|
distTag:
|
|
84
92
|
env("BUILDCHAIN_PUBLISH_DIST_TAG") ||
|
|
85
93
|
(channel === "alpha" ? "alpha" : "latest"),
|
|
86
94
|
sealedBundleRoot: manifest?.root,
|
|
87
|
-
requiredArtifactsRoot:
|
|
95
|
+
requiredArtifactsRoot: domainContentRoot(
|
|
88
96
|
"v4-product-required-artifacts",
|
|
89
97
|
requiredArtifacts,
|
|
90
98
|
),
|
|
@@ -94,7 +102,7 @@ export function resolveV4ProductPublicationIntent() {
|
|
|
94
102
|
});
|
|
95
103
|
const outputPath = path.resolve(
|
|
96
104
|
env("BUILDCHAIN_PRODUCT_PUBLICATION_INTENT_PATH") ||
|
|
97
|
-
".buildchain/release-candidate/
|
|
105
|
+
".buildchain/release-candidate/product-publication-intent.json",
|
|
98
106
|
);
|
|
99
107
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
100
108
|
fs.writeFileSync(outputPath, `${JSON.stringify(intent, null, 2)}\n`);
|
|
@@ -110,7 +118,7 @@ if (
|
|
|
110
118
|
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
111
119
|
) {
|
|
112
120
|
try {
|
|
113
|
-
const result =
|
|
121
|
+
const result = resolveProductPublicationIntent();
|
|
114
122
|
process.stdout.write(
|
|
115
123
|
`v4 product publication intent: ${result.name}@${result.intent.version} (${result.intent.mode})\n`,
|
|
116
124
|
);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ociPublicationTag } from "../packages/core/oci-publication-graph.js";
|
|
4
|
+
import { verifyOciPublicationBundle } from "../packages/core/oci-publication-bundle.js";
|
|
5
|
+
|
|
6
|
+
export function resolveOciCandidate({ payloadRoot, passport }) {
|
|
7
|
+
const matches = [];
|
|
8
|
+
function visit(directory) {
|
|
9
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
10
|
+
if (entry.isSymbolicLink())
|
|
11
|
+
throw new Error("OCI candidate cannot contain symlinks");
|
|
12
|
+
const file = path.join(directory, entry.name);
|
|
13
|
+
if (entry.isDirectory()) visit(file);
|
|
14
|
+
else if (entry.name === "oci-family.json") matches.push(file);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
visit(payloadRoot);
|
|
18
|
+
if (matches.length !== 1)
|
|
19
|
+
throw new Error("OCI candidate requires exactly one sealed image family");
|
|
20
|
+
const manifest = JSON.parse(fs.readFileSync(matches[0]));
|
|
21
|
+
const bundleRoot = path.dirname(matches[0]);
|
|
22
|
+
const verified = verifyOciPublicationBundle({
|
|
23
|
+
bundleRoot,
|
|
24
|
+
manifest,
|
|
25
|
+
repository: passport.repository,
|
|
26
|
+
sourceSha: passport.source.headSha,
|
|
27
|
+
version: passport.target.version,
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
root: bundleRoot,
|
|
31
|
+
manifest: verified.manifest,
|
|
32
|
+
requiredArtifacts: manifest.images.map((image) => ({
|
|
33
|
+
kind: "oci",
|
|
34
|
+
name: image.repository,
|
|
35
|
+
ref: ociPublicationTag(image, manifest.version),
|
|
36
|
+
digest: image.digest,
|
|
37
|
+
platform: image.platform,
|
|
38
|
+
required: true,
|
|
39
|
+
})),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function resolveRecoveredPublicationVersion({
|
|
44
|
+
artifactVersion,
|
|
45
|
+
channel,
|
|
46
|
+
rematerializeOnResume = false,
|
|
47
|
+
} = {}) {
|
|
48
|
+
const version = String(artifactVersion || "").trim(),
|
|
49
|
+
match = version.match(
|
|
50
|
+
/^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u,
|
|
51
|
+
);
|
|
52
|
+
if (!match)
|
|
53
|
+
throw new Error(
|
|
54
|
+
`recovered npm artifact has invalid publication version: ${version || "<empty>"}`,
|
|
55
|
+
);
|
|
56
|
+
return channel === "release" && rematerializeOnResume ? match[1] : version;
|
|
57
|
+
}
|
|
58
|
+
export function resolveRecoveredCandidateVersion({
|
|
59
|
+
artifactVersion,
|
|
60
|
+
publicationVersion,
|
|
61
|
+
channel,
|
|
62
|
+
rematerializeOnResume = false,
|
|
63
|
+
targetRef = "",
|
|
64
|
+
candidateRef = "",
|
|
65
|
+
} = {}) {
|
|
66
|
+
const version = String(artifactVersion || "").trim(),
|
|
67
|
+
target = String(targetRef || "").replace(/^refs\/heads\//u, ""),
|
|
68
|
+
candidate = String(candidateRef || "").replace(/^refs\/heads\//u, "");
|
|
69
|
+
if (channel !== "release" || !rematerializeOnResume) return version;
|
|
70
|
+
const prefix = `publish-gate/${target}/`;
|
|
71
|
+
if (!target || !candidate.startsWith(prefix))
|
|
72
|
+
throw new Error(
|
|
73
|
+
`stable recovery candidate ref must descend from ${prefix || "publish-gate/<target>/"}`,
|
|
74
|
+
);
|
|
75
|
+
const candidateVersion = candidate.slice(prefix.length);
|
|
76
|
+
if (!/^\d+\.\d+\.\d+-alpha\.\d+$/u.test(candidateVersion))
|
|
77
|
+
throw new Error(
|
|
78
|
+
`stable recovery candidate ref must bind an exact alpha version, got ${candidateVersion || "<empty>"}`,
|
|
79
|
+
);
|
|
80
|
+
if (candidateVersion.replace(/-alpha\.\d+$/u, "") !== publicationVersion)
|
|
81
|
+
throw new Error(
|
|
82
|
+
`stable recovery candidate ${candidateVersion} does not match publication ${publicationVersion || "<empty>"}`,
|
|
83
|
+
);
|
|
84
|
+
if (
|
|
85
|
+
(version.match(
|
|
86
|
+
/^(\d+\.\d+\.\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u,
|
|
87
|
+
)?.[1] || "") !== publicationVersion
|
|
88
|
+
)
|
|
89
|
+
throw new Error(
|
|
90
|
+
`recovered npm artifact version ${version || "<empty>"} does not match publication ${publicationVersion}`,
|
|
91
|
+
);
|
|
92
|
+
return candidateVersion;
|
|
93
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveOciCandidate } from "./publication-candidate-kind.mjs";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
@@ -69,7 +70,7 @@ export function createResolvedPublicationSealedBundle({
|
|
|
69
70
|
});
|
|
70
71
|
const main = resolvedNpmArtifacts.find((entry) => entry.metadata.role === "main")
|
|
71
72
|
|| (resolvedNpmArtifacts.length === 1 ? resolvedNpmArtifacts[0] : undefined);
|
|
72
|
-
if (!main) throw new Error("candidate npm payload set has no unique main package tarball");
|
|
73
|
+
if (!main || resolvedNpmArtifacts.filter((entry) => entry.metadata.role === "main").length > 1) throw new Error("candidate npm payload set has no unique main package tarball");
|
|
73
74
|
const selectedReleaseAssets = (releaseAssetPaths.length > 0
|
|
74
75
|
? releaseAssetPaths
|
|
75
76
|
: resolvedNpmArtifacts.map((entry) => entry.file.absolutePath))
|
|
@@ -88,6 +89,12 @@ export function createResolvedPublicationSealedBundle({
|
|
|
88
89
|
releaseCandidateRoot: `sha256:${normalizeSha256(releaseCandidateRoot, "releaseCandidateRoot")}`,
|
|
89
90
|
files: files.map(({ path: filePath, size, sha256 }) => ({ path: filePath, size, sha256 })),
|
|
90
91
|
};
|
|
92
|
+
if (resolvedNpmArtifacts.length > 1) {
|
|
93
|
+
candidatePayload.npmPackages = resolvedNpmArtifacts.map(({file, metadata}) => ({
|
|
94
|
+
name: metadata.name, version: metadata.ref, role: metadata.role,
|
|
95
|
+
path: file.path, size: file.size, sha256: file.sha256, integrity: metadata.integrity,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
91
98
|
const candidate = {
|
|
92
99
|
...candidatePayload,
|
|
93
100
|
candidateDigest: publicationArtifactCandidateDigest(candidatePayload),
|
|
@@ -102,3 +109,11 @@ export function createResolvedPublicationSealedBundle({
|
|
|
102
109
|
});
|
|
103
110
|
return { root: resolvedRoot, manifest, npmArtifacts: resolvedNpmArtifacts, files };
|
|
104
111
|
}
|
|
112
|
+
|
|
113
|
+
export function resolveCandidatePublicationBundle({ kind, payloadDir, passport, runtimeSha, npmArtifacts, releaseAssetPaths }) {
|
|
114
|
+
if (kind === "oci") return resolveOciCandidate({ payloadRoot: payloadDir, passport });
|
|
115
|
+
if (kind !== "npm") return undefined;
|
|
116
|
+
return createResolvedPublicationSealedBundle({ bundleRoot: payloadDir, repository: passport.repository,
|
|
117
|
+
sourceSha: passport.source?.headSha, sourceTreeSha: passport.source?.treeHash,
|
|
118
|
+
runtimeSha, releaseCandidateRoot: passport.candidateHash, npmArtifacts, releaseAssetPaths });
|
|
119
|
+
}
|
|
@@ -6,9 +6,9 @@ import path from "node:path";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "../packages/core/
|
|
9
|
+
createDomainPublicationRehearsalCapsule,
|
|
10
|
+
executeDomainPublicationRehearsal,
|
|
11
|
+
} from "../packages/core/publication-rehearsal.js";
|
|
12
12
|
|
|
13
13
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
14
14
|
const fixtureRoot = path.join(
|
|
@@ -61,7 +61,7 @@ async function generated() {
|
|
|
61
61
|
path: relative,
|
|
62
62
|
root: files.find((entry) => entry.path === relative).root,
|
|
63
63
|
});
|
|
64
|
-
const capsule =
|
|
64
|
+
const capsule = createDomainPublicationRehearsalCapsule({
|
|
65
65
|
source: {
|
|
66
66
|
repository: "kungfu-systems/buildchain",
|
|
67
67
|
revision: "bc12cd0ff18a7e1d918777ea093c169349952d34",
|
|
@@ -100,7 +100,7 @@ async function generated() {
|
|
|
100
100
|
});
|
|
101
101
|
const vectors = [];
|
|
102
102
|
for (const platform of ["linux", "macos", "windows"]) {
|
|
103
|
-
const result = await
|
|
103
|
+
const result = await executeDomainPublicationRehearsal({
|
|
104
104
|
capsule,
|
|
105
105
|
candidateRoot,
|
|
106
106
|
mode: "simulate",
|