@kungfu-tech/buildchain 4.1.3-alpha.1 → 4.1.3-alpha.10
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 +41 -75
- package/CONTRIBUTING.md +19 -26
- package/architecture/action-taxonomy.json +24 -0
- package/architecture/agent-change-map.md +266 -10
- package/architecture/capability-state-machine-manifest.json +1 -4
- package/architecture/ci-lane-change-budget.json +190 -807
- package/architecture/core-mechanisms.json +3 -6
- package/architecture/decisions/0005-minimal-consumer-contract.md +122 -0
- package/architecture/decisions/0006-business-attempt-journal.md +89 -0
- package/architecture/decisions/0007-hosted-pipeline-controller.md +106 -0
- package/architecture/decisions/0008-pipeline-product-publication.md +120 -0
- package/architecture/decisions/0009-pipeline-version-preparation.md +186 -0
- package/architecture/internal-capabilities.json +362 -7
- package/architecture/maintainability-debt.json +50 -34
- package/architecture/maintainability-policy.json +8 -8
- package/architecture/minimal-consumer-migration.json +5670 -0
- package/architecture/platform-stage-checkpoints.json +31 -7
- package/architecture/product-upstreams.json +20 -0
- package/architecture/release-topology.json +328 -74
- package/architecture/stage-capsule-qualification.json +3 -1
- package/architecture/universal-workflow-bootstrap.json +15 -1
- package/architecture/universal-workflow-capability-policy.json +4 -4
- package/architecture/universal-workflow-fault-campaign.json +3 -1
- package/architecture/workflow-taxonomy.json +77 -328
- package/dist/readers/business-attempt.cjs +809 -0
- package/dist/site/buildchain-contract.json +16 -68
- package/dist/site/buildchain-site.json +40 -25
- package/dist/site/capability-registry.json +4 -4
- package/dist/site/controller-registry.json +1 -47
- package/dist/site/kfd-claims.json +499 -494
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +3 -3
- package/dist/site/node-api-registry.json +54 -54
- package/dist/site/page-registry.json +33 -18
- package/dist/site/public-surface-audit.json +286 -349
- package/dist/site/publication-authority-registry.json +203 -523
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +7 -7
- package/dist/site/workflow-registry.json +590 -940
- package/docs/code-organization.md +9 -6
- package/docs/node-api-reference.md +102 -102
- package/docs/release-governance.md +10 -12
- package/docs/runtime-entry.md +73 -5
- package/docs/runtime-train-validation.md +39 -11
- package/docs/stable-candidate-patrol.md +6 -5
- package/docs/workflow-catalog.md +9 -32
- package/package.json +4 -3
- package/packages/core/adoption/buildchain-kfd-claims.js +7 -7
- package/packages/core/build/standalone/build.js +7 -1
- package/packages/core/consumer/buildchain-config.js +2 -21
- package/packages/core/consumer/contract/entries.js +85 -0
- package/packages/core/consumer/contract/examples.js +96 -0
- package/packages/core/consumer/contract/identity.js +65 -0
- package/packages/core/consumer/contract/inspection.js +45 -0
- package/packages/core/consumer/contract/plan.js +149 -0
- package/packages/core/consumer/contract/products.js +119 -0
- package/packages/core/consumer/contract/reader.js +47 -0
- package/packages/core/consumer/contract/shape.js +46 -0
- package/packages/core/dev-delivery/candidate/admission.js +9 -5
- package/packages/core/dev-delivery/candidate/reservation.js +11 -0
- package/packages/core/dev-delivery/candidate/source-paths.js +12 -4
- package/packages/core/dev-delivery/commands/project-cut-merge-queue-admission.mjs +47 -20
- package/packages/core/dev-delivery/dev-delivery-provider-heartbeat.js +18 -15
- package/packages/core/dev-delivery/native/actions.js +9 -1
- package/packages/core/dev-delivery/native/heartbeat-action.js +16 -0
- package/packages/core/dev-delivery/native/heartbeat.js +6 -4
- package/packages/core/dev-delivery/native/process-entry.js +19 -0
- package/packages/core/dev-delivery/queue/landing-action.js +53 -0
- package/packages/core/dev-delivery/warrant/failure-settlement.js +8 -6
- package/packages/core/dev-delivery/warrant/service.js +7 -0
- package/packages/core/governance/buildchain-publication-authority.js +8 -23
- package/packages/core/governance/github-governance-authority.js +1 -0
- package/packages/core/observability/controller-evidence.js +0 -8
- package/packages/core/paper/operations/bootstrap.js +2 -0
- package/packages/core/paper/paper-npm-bootstrap.js +2 -2
- package/packages/core/providers/github/attempt-index.js +60 -0
- package/packages/core/providers/github/attempt-journal.js +154 -0
- package/packages/core/providers/github/discussions/materials.js +19 -6
- package/packages/core/providers/github/pipeline-checkout.js +49 -0
- package/packages/core/providers/github/pipeline-entry-runs.js +210 -0
- package/packages/core/providers/github/pipeline-events.js +131 -0
- package/packages/core/providers/github/pipeline-integration.js +95 -0
- package/packages/core/providers/github/pipeline-policy.js +240 -0
- package/packages/core/providers/github/pipeline-product-payload.js +50 -0
- package/packages/core/providers/github/pipeline-product-release.js +198 -0
- package/packages/core/providers/github/pipeline-publication-artifacts.js +153 -0
- package/packages/core/providers/github/pipeline-release-evidence.js +30 -0
- package/packages/core/providers/github/pipeline-run-entry.js +86 -0
- package/packages/core/providers/github/pipeline-runs.js +132 -0
- package/packages/core/providers/github/pipeline-source.js +195 -0
- package/packages/core/providers/github/pipeline-stable-baseline.js +86 -0
- package/packages/core/providers/github/pipeline-stable-source.js +372 -0
- package/packages/core/providers/github/pipeline-version-artifacts.js +91 -0
- package/packages/core/providers/github/pipeline-version-readback.js +117 -0
- package/packages/core/providers/github/pipeline-version.js +199 -0
- package/packages/core/providers/github/pipeline-worker.js +108 -0
- package/packages/core/providers/github/release-evidence-assets.js +97 -0
- package/packages/core/publication/candidate/registry-hydration.js +2 -2
- package/packages/core/publication/npm/pack-preview.js +2 -2
- package/packages/core/publication/npm/pack-result.js +31 -0
- package/packages/core/publication/npm/package.js +3 -6
- package/packages/core/publication/npm/pipeline-channel.js +36 -0
- package/packages/core/publication/npm/pipeline-provider.js +137 -0
- package/packages/core/publication/npm/registry.js +12 -9
- package/packages/core/publication/pipeline/actions.js +116 -0
- package/packages/core/publication/pipeline/apply.js +189 -0
- package/packages/core/publication/pipeline/build-segments.js +107 -0
- package/packages/core/publication/pipeline/capsules.js +92 -0
- package/packages/core/publication/pipeline/context.js +39 -0
- package/packages/core/publication/pipeline/development-anchor.js +91 -0
- package/packages/core/publication/pipeline/development-current.js +63 -0
- package/packages/core/publication/pipeline/development-pr.js +112 -0
- package/packages/core/publication/pipeline/development-proof.js +76 -0
- package/packages/core/publication/pipeline/development-transition.js +112 -0
- package/packages/core/publication/pipeline/distribution.js +130 -0
- package/packages/core/publication/pipeline/documents.js +192 -0
- package/packages/core/publication/pipeline/effects.js +192 -0
- package/packages/core/publication/pipeline/files.js +66 -0
- package/packages/core/publication/pipeline/imported-materials.js +32 -0
- package/packages/core/publication/pipeline/journal.js +122 -0
- package/packages/core/publication/pipeline/next-development.js +117 -0
- package/packages/core/publication/pipeline/pack.js +180 -0
- package/packages/core/publication/pipeline/package-policy.js +22 -0
- package/packages/core/publication/pipeline/plan.js +136 -0
- package/packages/core/publication/pipeline/prepare.js +139 -0
- package/packages/core/publication/pipeline/qualification.js +175 -0
- package/packages/core/publication/pipeline/qualify.js +104 -0
- package/packages/core/publication/pipeline/recovery-admission.js +122 -0
- package/packages/core/publication/pipeline/recovery-build-download.js +80 -0
- package/packages/core/publication/pipeline/recovery-build-plan.js +122 -0
- package/packages/core/publication/pipeline/recovery-build-readback.js +68 -0
- package/packages/core/publication/pipeline/recovery-capsules.js +54 -0
- package/packages/core/publication/pipeline/recovery-import.js +80 -0
- package/packages/core/publication/pipeline/recovery-inspection.js +218 -0
- package/packages/core/publication/pipeline/recovery-materials.js +104 -0
- package/packages/core/publication/pipeline/recovery-plan.js +46 -0
- package/packages/core/publication/pipeline/recovery-prepare.js +73 -0
- package/packages/core/publication/pipeline/recovery-qualification.js +128 -0
- package/packages/core/publication/pipeline/recovery-readback.js +56 -0
- package/packages/core/publication/pipeline/recovery-signing.js +58 -0
- package/packages/core/publication/pipeline/sealed-products.js +79 -0
- package/packages/core/publication/pipeline/settle.js +42 -0
- package/packages/core/publication/pipeline/signing.js +140 -0
- package/packages/core/publication/pipeline/source-plan.js +83 -0
- package/packages/core/publication/pipeline/stable-entry.js +219 -0
- package/packages/core/publication/pipeline/stable-products.js +317 -0
- package/packages/core/publication/pipeline/stable-wait.js +170 -0
- package/packages/core/publication/pipeline/stable.js +82 -0
- package/packages/core/publication/pipeline/version-actions.js +93 -0
- package/packages/core/publication/pipeline/version-build.js +130 -0
- package/packages/core/publication/pipeline/version-context.js +143 -0
- package/packages/core/publication/pipeline/version-material.js +36 -0
- package/packages/core/publication/pipeline/version-preparation.js +69 -0
- package/packages/core/publication/pipeline/version-regeneration.js +162 -0
- package/packages/core/publication/pipeline/version-source-guard.js +69 -0
- package/packages/core/publication/pipeline/version.js +81 -0
- package/packages/core/publication/pipeline/worker.js +72 -0
- package/packages/core/publication/publication-reproducibility.js +2 -2
- package/packages/core/release/next-development-transition.js +15 -15
- package/packages/core/release/promote-candidate/product-provider-adapters.js +4 -3
- package/packages/core/release/promote-ref/internal/npm-existing-evidence.js +2 -1
- package/packages/core/release/promote-ref/internal/publish-command.js +16 -5
- package/packages/core/release/stable-release-gate.js +11 -0
- package/packages/core/runtime/buildchain-domain.wasm +0 -0
- package/packages/core/runtime/domain-wasm-artifact.js +2 -2
- package/packages/core/runtime/entry/actions.js +27 -25
- package/packages/core/runtime/entry/attempt.js +25 -0
- package/packages/core/runtime/entry/selection.js +14 -0
- package/packages/core/runtime/entry/source.js +62 -0
- package/packages/core/workflow/attempt/identity.js +170 -0
- package/packages/core/workflow/attempt/journal.js +89 -0
- package/packages/core/workflow/attempt/materials.js +72 -0
- package/packages/core/workflow/attempt/reader-entry.js +12 -0
- package/packages/core/workflow/attempt/reader.js +149 -0
- package/packages/core/workflow/attempt/records.js +153 -0
- package/packages/core/workflow/attempt/store.js +75 -0
- package/packages/core/workflow/commands/pipeline-build.mjs +23 -0
- package/packages/core/workflow/pipeline/action-output.js +11 -0
- package/packages/core/workflow/pipeline/actions.js +52 -0
- package/packages/core/workflow/pipeline/build-control.js +140 -0
- package/packages/core/workflow/pipeline/build-evidence.js +210 -0
- package/packages/core/workflow/pipeline/build-qualification.js +145 -0
- package/packages/core/workflow/pipeline/build-result.js +78 -0
- package/packages/core/workflow/pipeline/build.js +142 -0
- package/packages/core/workflow/pipeline/cancellation.js +169 -0
- package/packages/core/workflow/pipeline/channel-control.js +157 -0
- package/packages/core/workflow/pipeline/channel-source.js +53 -0
- package/packages/core/workflow/pipeline/controller.js +141 -0
- package/packages/core/workflow/pipeline/delivery-control.js +249 -0
- package/packages/core/workflow/pipeline/delivery-observation.js +124 -0
- package/packages/core/workflow/pipeline/delivery-request.js +198 -0
- package/packages/core/workflow/pipeline/events.js +84 -0
- package/packages/core/workflow/pipeline/fence.js +42 -0
- package/packages/core/workflow/pipeline/group-control.js +59 -0
- package/packages/core/workflow/pipeline/guard-build.js +74 -0
- package/packages/core/workflow/pipeline/guard.js +158 -0
- package/packages/core/workflow/pipeline/host.js +144 -0
- package/packages/core/workflow/pipeline/integration-status.js +113 -0
- package/packages/core/workflow/pipeline/materials.js +58 -0
- package/packages/core/workflow/pipeline/notifications.js +36 -0
- package/packages/core/workflow/pipeline/parent-notification.js +27 -0
- package/packages/core/workflow/pipeline/platforms.js +21 -0
- package/packages/core/workflow/pipeline/progress.js +108 -0
- package/packages/core/workflow/pipeline/projection.js +20 -0
- package/packages/core/workflow/pipeline/reconcile.js +194 -0
- package/packages/core/workflow/pipeline/record-action.js +27 -0
- package/packages/core/workflow/pipeline/recorder-status.js +69 -0
- package/packages/core/workflow/pipeline/recovery-action.js +30 -0
- package/packages/core/workflow/pipeline/recovery-admission.js +122 -0
- package/packages/core/workflow/pipeline/recovery-build-control.js +95 -0
- package/packages/core/workflow/pipeline/recovery-build-evidence.js +71 -0
- package/packages/core/workflow/pipeline/recovery-build.js +121 -0
- package/packages/core/workflow/pipeline/recovery-controller.js +91 -0
- package/packages/core/workflow/pipeline/recovery-integration.js +79 -0
- package/packages/core/workflow/pipeline/recovery-merge-proof.js +90 -0
- package/packages/core/workflow/pipeline/recovery-ownership-settlement.js +150 -0
- package/packages/core/workflow/pipeline/recovery-ownership.js +91 -0
- package/packages/core/workflow/pipeline/recovery-plan.js +126 -0
- package/packages/core/workflow/pipeline/recovery-runtime.js +68 -0
- package/packages/core/workflow/pipeline/recovery-session.js +129 -0
- package/packages/core/workflow/pipeline/recovery-transition.js +135 -0
- package/packages/core/workflow/pipeline/recovery-unrecorded-build.js +74 -0
- package/packages/core/workflow/pipeline/runtime-source.js +111 -0
- package/packages/core/workflow/pipeline/selection.js +111 -0
- package/packages/core/workflow/pipeline/session.js +72 -0
- package/packages/core/workflow/pipeline/settlement.js +137 -0
- package/packages/core/workflow/pipeline/wake.js +38 -0
- package/packages/core/workflow/pipeline/web-status.js +108 -0
- package/packages/core/workflow/workflow-taxonomy.mjs +67 -47
- package/scripts/build-release-discussion-reader.mjs +11 -7
- package/scripts/check-inventory.mjs +7 -70
- package/scripts/check-pipeline-publication-topology.mjs +50 -0
- package/scripts/check-public-dogfood-contract.mjs +38 -51
- package/scripts/check-release-topology.mjs +15 -8
- package/scripts/check-self-consumer-contract.mjs +133 -0
- package/scripts/check-universal-workflow-bootstrap.mjs +4 -6
- package/scripts/generate-minimal-consumer-contract.mjs +155 -0
- package/scripts/generate-site-bundle.mjs +1 -1
- package/scripts/inventory/binary.mjs +9 -19
- package/scripts/inventory/promotion.mjs +2 -13
- package/scripts/prepare-product-toolchain.mjs +10 -0
- package/scripts/verify-golden-path.mjs +5 -1
- package/scripts/verify-product-platform.mjs +65 -0
- package/scripts/verify-standalone-product.mjs +42 -0
- package/templates/minimal-consumer/binary/.buildchain/buildchain.toml +45 -0
- package/templates/minimal-consumer/binary/.github/workflows/buildchain-recover.yml +30 -0
- package/templates/minimal-consumer/binary/.github/workflows/buildchain.yml +28 -0
- package/templates/minimal-consumer/binary/package.json +6 -0
- package/templates/minimal-consumer/binary/src/build.mjs +5 -0
- package/templates/minimal-consumer/binary/src/hello.c +2 -0
- package/templates/minimal-consumer/binary/src/verify.mjs +3 -0
- package/templates/minimal-consumer/npm/.buildchain/buildchain.toml +46 -0
- package/templates/minimal-consumer/npm/.github/workflows/buildchain-recover.yml +30 -0
- package/templates/minimal-consumer/npm/.github/workflows/buildchain.yml +28 -0
- package/templates/minimal-consumer/npm/package.json +6 -0
- package/templates/minimal-consumer/npm/src/build.mjs +5 -0
- package/templates/minimal-consumer/npm/src/verify.mjs +3 -0
- package/templates/minimal-consumer/paper/.buildchain/buildchain.toml +45 -0
- package/templates/minimal-consumer/paper/.github/workflows/buildchain-recover.yml +30 -0
- package/templates/minimal-consumer/paper/.github/workflows/buildchain.yml +28 -0
- package/templates/minimal-consumer/paper/package.json +6 -0
- package/templates/minimal-consumer/paper/src/build.mjs +12 -0
- package/templates/minimal-consumer/paper/src/verify.mjs +3 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { compareDevelopmentVersions } from "../publication-development.js";
|
|
2
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
3
|
+
|
|
4
|
+
export async function observeAdvancedPipelineDevelopment(
|
|
5
|
+
context,
|
|
6
|
+
host,
|
|
7
|
+
transition,
|
|
8
|
+
adapter,
|
|
9
|
+
) {
|
|
10
|
+
const { plan } = context;
|
|
11
|
+
const commit = await host.source.branchHead(plan.developmentBranch);
|
|
12
|
+
const source = await host.source.source(commit, plan.source.configPath);
|
|
13
|
+
if (recordDigest(source.plan) !== plan.contractRoot)
|
|
14
|
+
throw new Error(
|
|
15
|
+
"Protected development contract changed before next-version readback",
|
|
16
|
+
);
|
|
17
|
+
const observed = await adapter.inspect(source.identity, plan.versionPolicy);
|
|
18
|
+
const order = compareDevelopmentVersions(
|
|
19
|
+
observed.version,
|
|
20
|
+
transition.target.version,
|
|
21
|
+
);
|
|
22
|
+
if (order < 0) return null;
|
|
23
|
+
const pulls = await host.request(
|
|
24
|
+
`/repos/${host.repository}/commits/${commit}/pulls?per_page=100`,
|
|
25
|
+
);
|
|
26
|
+
const matches = pulls.filter(
|
|
27
|
+
(pr) =>
|
|
28
|
+
pr.base?.ref === plan.developmentBranch && pr.merge_commit_sha === commit,
|
|
29
|
+
);
|
|
30
|
+
if (matches.length !== 1)
|
|
31
|
+
throw new Error(
|
|
32
|
+
"Advanced development requires one exact protected merged PR",
|
|
33
|
+
);
|
|
34
|
+
const pr = await host.request(
|
|
35
|
+
`/repos/${host.repository}/pulls/${matches[0].number}`,
|
|
36
|
+
);
|
|
37
|
+
const integration = await host.integration.observe({
|
|
38
|
+
intent: {
|
|
39
|
+
source: { pullRequest: pr.number, targetBranch: plan.developmentBranch },
|
|
40
|
+
},
|
|
41
|
+
generation: {
|
|
42
|
+
source: { commit: pr.head.sha, configPath: plan.source.configPath },
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
if (integration.mergeCommit !== commit)
|
|
46
|
+
throw new Error(
|
|
47
|
+
"Advanced development proof differs from the observed protected commit",
|
|
48
|
+
);
|
|
49
|
+
return {
|
|
50
|
+
state: "success",
|
|
51
|
+
reason:
|
|
52
|
+
order === 0
|
|
53
|
+
? "protected-development-already-current"
|
|
54
|
+
: "protected-development-already-advanced",
|
|
55
|
+
transitionRoot: transition.idempotencyKey,
|
|
56
|
+
targetVersion: transition.target.version,
|
|
57
|
+
observedVersion: observed.version,
|
|
58
|
+
source: source.identity,
|
|
59
|
+
integration,
|
|
60
|
+
filesRoot: recordDigest(observed.files),
|
|
61
|
+
publicationOutcome: "preserved-success",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
2
|
+
import {
|
|
3
|
+
preparePipelineVersionContext,
|
|
4
|
+
retainedPipelineVersionRegeneration,
|
|
5
|
+
} from "./version-context.js";
|
|
6
|
+
|
|
7
|
+
export async function prepareNextDevelopmentPullRequest(
|
|
8
|
+
context,
|
|
9
|
+
host,
|
|
10
|
+
journal,
|
|
11
|
+
transition,
|
|
12
|
+
adapter,
|
|
13
|
+
) {
|
|
14
|
+
const { plan } = context;
|
|
15
|
+
const retained = await journal.materials(
|
|
16
|
+
"publication/next-development-source/",
|
|
17
|
+
);
|
|
18
|
+
if (retained.length > 1)
|
|
19
|
+
throw new Error("Next-development source reservation is ambiguous");
|
|
20
|
+
let request = retained[0];
|
|
21
|
+
if (!request) {
|
|
22
|
+
const baseCommit = await host.source.branchHead(plan.developmentBranch);
|
|
23
|
+
const source = await host.source.source(baseCommit, plan.source.configPath);
|
|
24
|
+
if (recordDigest(source.plan) !== plan.contractRoot)
|
|
25
|
+
throw new Error(
|
|
26
|
+
"Protected development contract changed before version preparation",
|
|
27
|
+
);
|
|
28
|
+
const input = await adapter.inspect(source.identity, plan.versionPolicy);
|
|
29
|
+
if (
|
|
30
|
+
plan.channel === "stable"
|
|
31
|
+
? input.version.split("-alpha.")[0] !== plan.version
|
|
32
|
+
: input.version !== plan.version
|
|
33
|
+
)
|
|
34
|
+
throw new Error(
|
|
35
|
+
"Development version already changed; reconcile its protected provenance before preparing another PR",
|
|
36
|
+
);
|
|
37
|
+
request = {
|
|
38
|
+
source: source.identity,
|
|
39
|
+
versionPolicy: plan.versionPolicy,
|
|
40
|
+
version: transition.target.version,
|
|
41
|
+
root: recordDigest({
|
|
42
|
+
transitionRoot: transition.idempotencyKey,
|
|
43
|
+
source: source.identity,
|
|
44
|
+
}),
|
|
45
|
+
sourceTimestamp: input.sourceTimestamp,
|
|
46
|
+
};
|
|
47
|
+
await journal.record("publication/next-development-source", request, {
|
|
48
|
+
phase: "next-development",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const regeneration = await retainedPipelineVersionRegeneration(
|
|
52
|
+
journal,
|
|
53
|
+
request,
|
|
54
|
+
"development",
|
|
55
|
+
host.runtime,
|
|
56
|
+
);
|
|
57
|
+
if (request.versionPolicy.derived_files?.length && !regeneration)
|
|
58
|
+
return {
|
|
59
|
+
state: "waiting",
|
|
60
|
+
reason: "next-development-version-regeneration-required",
|
|
61
|
+
versionContext: await preparePipelineVersionContext(
|
|
62
|
+
request,
|
|
63
|
+
"development",
|
|
64
|
+
context,
|
|
65
|
+
host,
|
|
66
|
+
journal,
|
|
67
|
+
context.recovery?.execution.publisher.workflowSha ||
|
|
68
|
+
plan.publisher.workflowSha,
|
|
69
|
+
),
|
|
70
|
+
};
|
|
71
|
+
await journal.fence();
|
|
72
|
+
const materialization = await adapter.materializeDevelopment(
|
|
73
|
+
request,
|
|
74
|
+
regeneration,
|
|
75
|
+
);
|
|
76
|
+
const branch = materialization.branch;
|
|
77
|
+
const pulls = await host.request(
|
|
78
|
+
`/repos/${host.repository}/pulls?state=all&head=${encodeURIComponent(`${host.repository.split("/")[0]}:${branch}`)}&base=${encodeURIComponent(plan.developmentBranch)}&per_page=100`,
|
|
79
|
+
);
|
|
80
|
+
if (!Array.isArray(pulls) || pulls.length > 1)
|
|
81
|
+
throw new Error("Next-development PR identity is ambiguous");
|
|
82
|
+
let pr = pulls[0];
|
|
83
|
+
if (!pr) {
|
|
84
|
+
await journal.fence();
|
|
85
|
+
if (!host.pullRequests)
|
|
86
|
+
throw new Error(
|
|
87
|
+
"Next-development requires the repository-authorized automation App or token to trigger ordinary PR checks",
|
|
88
|
+
);
|
|
89
|
+
pr = await host.pullRequests(`/repos/${host.repository}/pulls`, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
body: {
|
|
92
|
+
title: `chore(version): prepare ${transition.target.version}`,
|
|
93
|
+
head: branch,
|
|
94
|
+
base: plan.developmentBranch,
|
|
95
|
+
body: `Prepare the declared next development version after ${plan.tag}.\n\nBuildchain-Parent-Attempt: ${context.attempt}\nBuildchain-Transition: ${transition.idempotencyKey}\n\nThe normal protected pipeline verifies the declared products before merge.`,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const selected = {
|
|
100
|
+
schema: "buildchain.pipeline-next-development-pr/v1",
|
|
101
|
+
number: pr.number,
|
|
102
|
+
branch,
|
|
103
|
+
base: plan.developmentBranch,
|
|
104
|
+
source: materialization.source,
|
|
105
|
+
materialization,
|
|
106
|
+
transitionRoot: transition.idempotencyKey,
|
|
107
|
+
};
|
|
108
|
+
await journal.record("publication/next-development-pr", selected, {
|
|
109
|
+
phase: "next-development",
|
|
110
|
+
});
|
|
111
|
+
return selected;
|
|
112
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { stablePipelineDevelopmentReadback } from "./development-transition.js";
|
|
3
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
4
|
+
import {
|
|
5
|
+
nextDevelopmentRoot,
|
|
6
|
+
recordNextDevelopmentMaterialization,
|
|
7
|
+
advanceNextDevelopmentTransition,
|
|
8
|
+
} from "../../release/next-development-transition.js";
|
|
9
|
+
|
|
10
|
+
export function developmentTransitionReadback(
|
|
11
|
+
transition,
|
|
12
|
+
{ before, after, evidence, createdAt, mergedAt },
|
|
13
|
+
) {
|
|
14
|
+
const hash = (bytes) =>
|
|
15
|
+
`sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
16
|
+
const paths = [
|
|
17
|
+
...transition.adapter.sourcePaths,
|
|
18
|
+
...transition.adapter.derivedPaths,
|
|
19
|
+
]
|
|
20
|
+
.sort()
|
|
21
|
+
.map((file) => {
|
|
22
|
+
if (
|
|
23
|
+
typeof before.files[file] !== "string" ||
|
|
24
|
+
typeof after.files[file] !== "string"
|
|
25
|
+
)
|
|
26
|
+
throw new Error("Next-development lacks exact declared file bytes");
|
|
27
|
+
return {
|
|
28
|
+
path: file,
|
|
29
|
+
beforeRoot: hash(before.files[file]),
|
|
30
|
+
afterRoot: hash(after.files[file]),
|
|
31
|
+
changed: before.files[file] !== after.files[file],
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
if (after.version !== transition.target.version)
|
|
35
|
+
throw new Error(
|
|
36
|
+
"Next-development materialization does not match its authorized target",
|
|
37
|
+
);
|
|
38
|
+
const body = {
|
|
39
|
+
targetVersion: transition.target.version,
|
|
40
|
+
anchorRoot: transition.target.anchor?.manifestRoot || null,
|
|
41
|
+
paths,
|
|
42
|
+
};
|
|
43
|
+
if (!transition.completedAlpha)
|
|
44
|
+
return stablePipelineDevelopmentReadback(
|
|
45
|
+
transition,
|
|
46
|
+
body,
|
|
47
|
+
evidence,
|
|
48
|
+
createdAt,
|
|
49
|
+
mergedAt,
|
|
50
|
+
);
|
|
51
|
+
let state = recordNextDevelopmentMaterialization(transition, {
|
|
52
|
+
materialization: {
|
|
53
|
+
...body,
|
|
54
|
+
materializationRoot: nextDevelopmentRoot(body),
|
|
55
|
+
},
|
|
56
|
+
evidenceRoot: recordDigest(evidence),
|
|
57
|
+
recordedAt: new Date(createdAt).toISOString(),
|
|
58
|
+
});
|
|
59
|
+
state = advanceNextDevelopmentTransition(state, {
|
|
60
|
+
to: "pr-pending",
|
|
61
|
+
event: "protected-version-pr-observed",
|
|
62
|
+
evidenceRoot: recordDigest(evidence),
|
|
63
|
+
expectedStateRoot: state.state.stateRoot,
|
|
64
|
+
recordedAt: new Date(createdAt).toISOString(),
|
|
65
|
+
});
|
|
66
|
+
if (mergedAt)
|
|
67
|
+
for (const phase of ["merged", "verified"])
|
|
68
|
+
state = advanceNextDevelopmentTransition(state, {
|
|
69
|
+
to: phase,
|
|
70
|
+
event: `protected-version-${phase}`,
|
|
71
|
+
evidenceRoot: recordDigest(evidence),
|
|
72
|
+
expectedStateRoot: state.state.stateRoot,
|
|
73
|
+
recordedAt: new Date(mergedAt).toISOString(),
|
|
74
|
+
});
|
|
75
|
+
return state;
|
|
76
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createNextDevelopmentTransition,
|
|
3
|
+
bindNextDevelopmentAnchor,
|
|
4
|
+
nextDevelopmentRoot,
|
|
5
|
+
} from "../../release/next-development-transition.js";
|
|
6
|
+
import { createStableDevelopmentTransition } from "../publication-development.js";
|
|
7
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
8
|
+
|
|
9
|
+
export function createPipelineDevelopmentTransition(context, publication) {
|
|
10
|
+
const { plan, materialization } = context;
|
|
11
|
+
const input = {
|
|
12
|
+
repository: plan.source.repository,
|
|
13
|
+
model: {
|
|
14
|
+
strategy: plan.versionPolicy.strategy,
|
|
15
|
+
next: plan.versionPolicy.strategy === "semver" ? "auto" : "manual",
|
|
16
|
+
},
|
|
17
|
+
sourcePaths: [
|
|
18
|
+
...new Set(plan.versionPolicy.files.map(({ path }) => path)),
|
|
19
|
+
].sort(),
|
|
20
|
+
derivedPaths: [...(plan.versionPolicy.derived_files || [])].sort(),
|
|
21
|
+
};
|
|
22
|
+
const completed = {
|
|
23
|
+
outcome: "succeeded",
|
|
24
|
+
version: plan.version,
|
|
25
|
+
exactTag: plan.tag,
|
|
26
|
+
releaseSha: materialization.source.commit,
|
|
27
|
+
treeSha: materialization.source.tree,
|
|
28
|
+
publicationRoot: publication.release.receiptRoot,
|
|
29
|
+
completedAt: publication.completedAt,
|
|
30
|
+
};
|
|
31
|
+
if (plan.channel === "alpha")
|
|
32
|
+
return createNextDevelopmentTransition({
|
|
33
|
+
...input,
|
|
34
|
+
completedAlpha: completed,
|
|
35
|
+
});
|
|
36
|
+
const adapter = {
|
|
37
|
+
sourcePaths: input.sourcePaths,
|
|
38
|
+
derivedPaths: input.derivedPaths,
|
|
39
|
+
};
|
|
40
|
+
if (input.model.strategy === "semver")
|
|
41
|
+
return {
|
|
42
|
+
...createStableDevelopmentTransition({
|
|
43
|
+
...input,
|
|
44
|
+
completedStable: completed,
|
|
45
|
+
}),
|
|
46
|
+
adapter,
|
|
47
|
+
};
|
|
48
|
+
const identity = {
|
|
49
|
+
contract: "buildchain.pipeline-stable-anchor-transition/v1",
|
|
50
|
+
...input,
|
|
51
|
+
completedStable: completed,
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
...identity,
|
|
55
|
+
idempotencyKey: nextDevelopmentRoot(identity),
|
|
56
|
+
adapter,
|
|
57
|
+
target: { version: null, anchor: null },
|
|
58
|
+
state: { status: "waiting-anchor" },
|
|
59
|
+
publicationOutcome: "preserved-success",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function bindPipelineDevelopmentAnchor(transition, input) {
|
|
64
|
+
if (transition.completedAlpha)
|
|
65
|
+
return bindNextDevelopmentAnchor(transition, input);
|
|
66
|
+
if (
|
|
67
|
+
transition.contract !== "buildchain.pipeline-stable-anchor-transition/v1" ||
|
|
68
|
+
transition.state.status !== "waiting-anchor" ||
|
|
69
|
+
!input.targetVersion ||
|
|
70
|
+
!input.anchor?.manifestRoot
|
|
71
|
+
)
|
|
72
|
+
throw new Error(
|
|
73
|
+
"Stable manual development requires its explicit protected anchor",
|
|
74
|
+
);
|
|
75
|
+
return {
|
|
76
|
+
...transition,
|
|
77
|
+
target: { version: input.targetVersion, anchor: input.anchor },
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function stablePipelineDevelopmentReadback(
|
|
82
|
+
transition,
|
|
83
|
+
materialization,
|
|
84
|
+
evidence,
|
|
85
|
+
createdAt,
|
|
86
|
+
mergedAt,
|
|
87
|
+
) {
|
|
88
|
+
if (
|
|
89
|
+
![
|
|
90
|
+
"buildchain.stable-development-transition/v1",
|
|
91
|
+
"buildchain.pipeline-stable-anchor-transition/v1",
|
|
92
|
+
].includes(transition.contract)
|
|
93
|
+
)
|
|
94
|
+
throw new Error("Unknown stable development transition");
|
|
95
|
+
const state = {
|
|
96
|
+
status: mergedAt ? "verified" : "pr-pending",
|
|
97
|
+
evidenceRoot: recordDigest(evidence),
|
|
98
|
+
recordedAt: new Date(mergedAt || createdAt).toISOString(),
|
|
99
|
+
materializationRoot: nextDevelopmentRoot(materialization),
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
...transition,
|
|
103
|
+
materialization,
|
|
104
|
+
state: {
|
|
105
|
+
...state,
|
|
106
|
+
stateRoot: nextDevelopmentRoot({
|
|
107
|
+
transitionRoot: transition.idempotencyKey,
|
|
108
|
+
...state,
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
2
|
+
import { pipelineNpmChannel } from "../npm/pipeline-channel.js";
|
|
3
|
+
import { applyPipelineEffects, pipelinePublicationEffects } from "./effects.js";
|
|
4
|
+
|
|
5
|
+
export async function distributePipelineProducts(
|
|
6
|
+
context,
|
|
7
|
+
host,
|
|
8
|
+
journal,
|
|
9
|
+
retained,
|
|
10
|
+
environment,
|
|
11
|
+
) {
|
|
12
|
+
const { plan, materialization } = context;
|
|
13
|
+
const channel = `v${plan.version.split(".")[0]}${plan.channel === "alpha" ? "-alpha" : ""}`;
|
|
14
|
+
const refPath = `/repos/${host.repository}/git/ref/tags/${channel}`;
|
|
15
|
+
const npm = pipelineNpmChannel({ environment });
|
|
16
|
+
async function observe(effect) {
|
|
17
|
+
if (effect.kind === "git-channel") {
|
|
18
|
+
const value = await host.request(refPath, { allow404: true });
|
|
19
|
+
if (value && value.object?.type !== "commit")
|
|
20
|
+
throw new Error("Floating channel requires a lightweight commit ref");
|
|
21
|
+
return value
|
|
22
|
+
? { state: "present", commit: value.object.sha }
|
|
23
|
+
: { state: "absent", commit: null };
|
|
24
|
+
}
|
|
25
|
+
// Only retained historical plans contain npm channel effects. New releases
|
|
26
|
+
// set their channel in npm publish and never enter a separate npm operation.
|
|
27
|
+
const value = await npm.observe(effect);
|
|
28
|
+
if (value.version !== effect.version)
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Historical npm channel ${effect.name}@${effect.tag} requires a one-time authenticated correction to ${effect.version}; recovery never republishes or rewrites npm tags`,
|
|
31
|
+
);
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
const plans = await journal.materials("publication/distribution-plan/");
|
|
35
|
+
if (plans.length > 1)
|
|
36
|
+
throw new Error("Distribution has conflicting retained plans");
|
|
37
|
+
let effects = plans[0]?.effects;
|
|
38
|
+
if (!effects) {
|
|
39
|
+
const raw = [
|
|
40
|
+
{
|
|
41
|
+
id: "git-channel",
|
|
42
|
+
kind: "git-channel",
|
|
43
|
+
channel,
|
|
44
|
+
commit: materialization.source.commit,
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
const receipts = await journal.materials("publication/effect/");
|
|
48
|
+
if (receipts.length) {
|
|
49
|
+
const published = pipelinePublicationEffects({
|
|
50
|
+
plan,
|
|
51
|
+
...retained,
|
|
52
|
+
receipts,
|
|
53
|
+
});
|
|
54
|
+
for (const effect of published.filter(
|
|
55
|
+
(item) =>
|
|
56
|
+
item.kind === "npm-package" && item.tag.startsWith("buildchain-"),
|
|
57
|
+
))
|
|
58
|
+
raw.push({
|
|
59
|
+
id: `npm-channel:${effect.product}`,
|
|
60
|
+
kind: "npm-channel",
|
|
61
|
+
name: effect.name,
|
|
62
|
+
access: effect.access,
|
|
63
|
+
tag: plan.channel === "alpha" ? "alpha" : "latest",
|
|
64
|
+
version: effect.version,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
effects = [];
|
|
68
|
+
for (const effect of raw) {
|
|
69
|
+
const before = await observe(effect);
|
|
70
|
+
const body = { ...effect, expected: before };
|
|
71
|
+
effects.push({ ...body, root: recordDigest(body) });
|
|
72
|
+
}
|
|
73
|
+
await journal.record(
|
|
74
|
+
"publication/distribution-plan",
|
|
75
|
+
{ planRoot: plan.root, effects },
|
|
76
|
+
{ phase: "distribution" },
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const matches = (effect, value) =>
|
|
80
|
+
value.state === "present" &&
|
|
81
|
+
(effect.kind === "git-channel"
|
|
82
|
+
? value.commit === effect.commit
|
|
83
|
+
: value.version === effect.version);
|
|
84
|
+
const provider = {
|
|
85
|
+
observe,
|
|
86
|
+
matches,
|
|
87
|
+
canApply: (effect, value) =>
|
|
88
|
+
recordDigest(value) === recordDigest(effect.expected),
|
|
89
|
+
async apply(effect) {
|
|
90
|
+
const current = await observe(effect);
|
|
91
|
+
if (matches(effect, current)) return;
|
|
92
|
+
if (recordDigest(current) !== recordDigest(effect.expected))
|
|
93
|
+
throw new Error(
|
|
94
|
+
"Floating channel changed since retained distribution admission",
|
|
95
|
+
);
|
|
96
|
+
await journal.fence();
|
|
97
|
+
if (effect.kind === "git-channel") {
|
|
98
|
+
if (current.state === "absent")
|
|
99
|
+
return host.request(`/repos/${host.repository}/git/refs`, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
body: { ref: `refs/tags/${channel}`, sha: effect.commit },
|
|
102
|
+
});
|
|
103
|
+
// Provider-enforced fast-forward preserves published history. A
|
|
104
|
+
// divergent channel requires a separately proved source reconciliation.
|
|
105
|
+
return host.request(
|
|
106
|
+
`/repos/${host.repository}/git/refs/tags/${channel}`,
|
|
107
|
+
{ method: "PATCH", body: { sha: effect.commit, force: false } },
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
throw new Error("Distribution cannot write npm channels");
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
const receipts = await applyPipelineEffects({
|
|
114
|
+
effects,
|
|
115
|
+
transactionRoot: retained.documents.transaction.transactionRoot,
|
|
116
|
+
receipts: await journal.materials("publication/distribution-effect/"),
|
|
117
|
+
provider,
|
|
118
|
+
fence: journal.fence,
|
|
119
|
+
retain: (receipt) =>
|
|
120
|
+
journal.record("publication/distribution-effect", receipt, {
|
|
121
|
+
phase: "distribution",
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
return {
|
|
125
|
+
schema: "buildchain.pipeline-distribution-complete/v1",
|
|
126
|
+
planRoot: plan.root,
|
|
127
|
+
effects,
|
|
128
|
+
receipts,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
canonicalJson,
|
|
4
|
+
recordDigest,
|
|
5
|
+
} from "../../release/discussion/envelope.js";
|
|
6
|
+
import { releaseTailRoot } from "../../release/release-tail-provider-plane.js";
|
|
7
|
+
import {
|
|
8
|
+
RELEASE_INVOCATION_CONTRACT,
|
|
9
|
+
RELEASE_PROVIDER_CONTRACT,
|
|
10
|
+
createReleaseInvocation,
|
|
11
|
+
createDomainReleaseTransaction,
|
|
12
|
+
} from "../../release/release-invocation.js";
|
|
13
|
+
import {
|
|
14
|
+
validatePublicationQualificationReceipt,
|
|
15
|
+
domainPublicationQualificationRoot,
|
|
16
|
+
} from "../publication-qualification.js";
|
|
17
|
+
import { verifyPipelinePublicationPlan } from "./plan.js";
|
|
18
|
+
|
|
19
|
+
export function pipelineReleaseEvidence({
|
|
20
|
+
plan,
|
|
21
|
+
qualified,
|
|
22
|
+
capsules,
|
|
23
|
+
documents,
|
|
24
|
+
bundle,
|
|
25
|
+
}) {
|
|
26
|
+
verifyPipelinePublicationPlan(plan);
|
|
27
|
+
if (plan.evidenceVersion !== undefined && plan.evidenceVersion !== 1)
|
|
28
|
+
throw new Error("Unsupported retained publication evidence version");
|
|
29
|
+
// An older retained transaction keeps its original evidence inventory.
|
|
30
|
+
// Adding a disclosure during recovery would change its provider receipt.
|
|
31
|
+
const values = {
|
|
32
|
+
...(plan.evidenceVersion === 1 ? { plan } : {}),
|
|
33
|
+
qualification: qualified,
|
|
34
|
+
capsules,
|
|
35
|
+
invocation: documents.invocation.invocation,
|
|
36
|
+
};
|
|
37
|
+
const bytes = Object.entries(values).map(([id, value]) => [
|
|
38
|
+
id,
|
|
39
|
+
Buffer.from(`${canonicalJson(value)}\n`),
|
|
40
|
+
]);
|
|
41
|
+
bytes.push(["attestation", Buffer.from(bundle)]);
|
|
42
|
+
return bytes.map(([id, value]) => ({
|
|
43
|
+
id,
|
|
44
|
+
name: `buildchain.${id}.json`,
|
|
45
|
+
bytes: value,
|
|
46
|
+
size: value.length,
|
|
47
|
+
digest: `sha256:${createHash("sha256").update(value).digest("hex")}`,
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function verifyRootedPublication(value, schema) {
|
|
52
|
+
const { root, ...body } = value || {};
|
|
53
|
+
if (body.schema !== schema || root !== recordDigest(body))
|
|
54
|
+
throw new Error(`Invalid retained ${schema} bytes`);
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function verifyPipelineQualification({
|
|
59
|
+
plan,
|
|
60
|
+
materialization,
|
|
61
|
+
qualified,
|
|
62
|
+
evaluatedAt,
|
|
63
|
+
}) {
|
|
64
|
+
verifyPipelinePublicationPlan(plan);
|
|
65
|
+
verifyRootedPublication(
|
|
66
|
+
materialization,
|
|
67
|
+
"buildchain.pipeline-version-materialization/v1",
|
|
68
|
+
);
|
|
69
|
+
verifyRootedPublication(
|
|
70
|
+
qualified,
|
|
71
|
+
"buildchain.pipeline-publication-qualification/v1",
|
|
72
|
+
);
|
|
73
|
+
if (
|
|
74
|
+
qualified.planRoot !== plan.root ||
|
|
75
|
+
materialization.planRoot !== plan.root ||
|
|
76
|
+
recordDigest(materialization.protectedSource) !==
|
|
77
|
+
recordDigest(plan.source) ||
|
|
78
|
+
recordDigest(qualified.source) !== recordDigest(materialization.source) ||
|
|
79
|
+
materialization.material.version !== plan.version
|
|
80
|
+
)
|
|
81
|
+
throw new Error(
|
|
82
|
+
"Publication plan, materialized source and qualification differ",
|
|
83
|
+
);
|
|
84
|
+
const artifacts = qualified.artifacts
|
|
85
|
+
.map((artifact) => ({
|
|
86
|
+
role: `${artifact.product}/${artifact.artifact}`,
|
|
87
|
+
platform: artifact.platform,
|
|
88
|
+
artifactRoot: artifact.digest,
|
|
89
|
+
manifestRoot: artifact.manifestRoot,
|
|
90
|
+
}))
|
|
91
|
+
.sort((a, b) =>
|
|
92
|
+
`${a.role}\0${a.platform}`.localeCompare(`${b.role}\0${b.platform}`),
|
|
93
|
+
);
|
|
94
|
+
validatePublicationQualificationReceipt(qualified.qualification, {
|
|
95
|
+
repository: plan.source.repository,
|
|
96
|
+
sourceSha: qualified.source.commit,
|
|
97
|
+
sourceRoot: recordDigest(qualified.source),
|
|
98
|
+
candidateRoot: recordDigest({
|
|
99
|
+
planRoot: plan.root,
|
|
100
|
+
source: qualified.source,
|
|
101
|
+
artifacts: qualified.artifacts,
|
|
102
|
+
}),
|
|
103
|
+
artifactRoot: domainPublicationQualificationRoot(artifacts),
|
|
104
|
+
policyDigest: plan.contractRoot,
|
|
105
|
+
...(evaluatedAt ? { evaluatedAt } : {}),
|
|
106
|
+
});
|
|
107
|
+
return qualified;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function pipelineReleaseDocuments({
|
|
111
|
+
plan,
|
|
112
|
+
materialization,
|
|
113
|
+
qualified,
|
|
114
|
+
capsules,
|
|
115
|
+
signing,
|
|
116
|
+
evaluatedAt,
|
|
117
|
+
}) {
|
|
118
|
+
verifyPipelineQualification({
|
|
119
|
+
plan,
|
|
120
|
+
materialization,
|
|
121
|
+
qualified,
|
|
122
|
+
evaluatedAt,
|
|
123
|
+
});
|
|
124
|
+
verifyRootedPublication(capsules, "buildchain.pipeline-product-capsules/v1");
|
|
125
|
+
if (
|
|
126
|
+
capsules.qualificationRoot !== qualified.root ||
|
|
127
|
+
signing.planRoot !== plan.root ||
|
|
128
|
+
signing.qualificationRoot !== qualified.root ||
|
|
129
|
+
signing.materializationRoot !== materialization.root ||
|
|
130
|
+
signing.verified !== true
|
|
131
|
+
)
|
|
132
|
+
throw new Error(
|
|
133
|
+
"Release requires independently verified signing and capsules for this qualification",
|
|
134
|
+
);
|
|
135
|
+
// The Rust domain still owns publisher admission and QUALIFY/APPLY/SETTLE.
|
|
136
|
+
const invocation = createReleaseInvocation({
|
|
137
|
+
schema: RELEASE_INVOCATION_CONTRACT,
|
|
138
|
+
publisher: plan.publisher,
|
|
139
|
+
runtime: plan.runtime,
|
|
140
|
+
candidate: {
|
|
141
|
+
repository: qualified.source.repository,
|
|
142
|
+
commit: qualified.source.commit,
|
|
143
|
+
tree: qualified.source.tree,
|
|
144
|
+
version: plan.version,
|
|
145
|
+
},
|
|
146
|
+
target: {
|
|
147
|
+
channel: plan.channel,
|
|
148
|
+
tag: plan.tag,
|
|
149
|
+
expectedOldSha: plan.expectedTagSha,
|
|
150
|
+
},
|
|
151
|
+
authority: {
|
|
152
|
+
policyRoot: plan.contractRoot,
|
|
153
|
+
qualificationRoot: qualified.qualification.receiptRoot,
|
|
154
|
+
warrantRoot: qualified.qualification.receiptRoot,
|
|
155
|
+
},
|
|
156
|
+
provider: {
|
|
157
|
+
adapter: "built-in-provider-plane",
|
|
158
|
+
contract: RELEASE_PROVIDER_CONTRACT,
|
|
159
|
+
repository: qualified.source.repository,
|
|
160
|
+
},
|
|
161
|
+
parent: { invocationRoot: null, transactionRoot: null, receiptRoot: null },
|
|
162
|
+
});
|
|
163
|
+
const transaction = createDomainReleaseTransaction({
|
|
164
|
+
invocationRoot: invocation.roots.invocationRoot,
|
|
165
|
+
publisherRoot: invocation.roots.publisherRoot,
|
|
166
|
+
runtimeRoot: invocation.roots.runtimeRoot,
|
|
167
|
+
providerRoot: invocation.roots.providerRoot,
|
|
168
|
+
parentRoot: invocation.roots.parentRoot,
|
|
169
|
+
});
|
|
170
|
+
const body = {
|
|
171
|
+
schema: "kungfu.buildchain.release-passport/v4",
|
|
172
|
+
repository: qualified.source.repository,
|
|
173
|
+
source: qualified.source,
|
|
174
|
+
// This binding explicitly retains the declared version overlay. It never
|
|
175
|
+
// claims tree equivalence with the protected commit.
|
|
176
|
+
versionMaterialization: materialization,
|
|
177
|
+
release: { version: plan.version, tag: plan.tag, channel: plan.channel },
|
|
178
|
+
candidateRoot: qualified.qualification.candidateRoot,
|
|
179
|
+
policyDigest: qualified.qualification.policyDigest,
|
|
180
|
+
artifactRoot: qualified.qualification.artifactRoot,
|
|
181
|
+
publicationQualificationRoot: qualified.qualification.receiptRoot,
|
|
182
|
+
stageCapsuleAggregateRoot: capsules.root,
|
|
183
|
+
stageCapsuleRoots: capsules.capsules
|
|
184
|
+
.map(({ capsule }) => capsule.capsuleRoot)
|
|
185
|
+
.sort(),
|
|
186
|
+
signingRoot: recordDigest(signing),
|
|
187
|
+
invocationRoot: invocation.roots.invocationRoot,
|
|
188
|
+
transactionRoot: transaction.transactionRoot,
|
|
189
|
+
};
|
|
190
|
+
const passport = { ...body, passportRoot: releaseTailRoot(body) };
|
|
191
|
+
return { invocation, transaction, passport };
|
|
192
|
+
}
|