@kungfu-tech/buildchain 4.1.3-alpha.0 → 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 +272 -12
- 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 +371 -9
- 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 +331 -74
- package/architecture/stage-capsule-qualification.json +3 -1
- package/architecture/universal-workflow-bootstrap.json +15 -1
- package/architecture/universal-workflow-capability-policy.json +9 -9
- package/architecture/universal-workflow-fault-campaign.json +3 -1
- package/architecture/workflow-taxonomy.json +77 -328
- package/contracts/release-discussion-v1.schema.json +3 -0
- package/dist/readers/business-attempt.cjs +809 -0
- package/dist/readers/release-discussion.cjs +4 -0
- package/dist/site/buildchain-contract.json +16 -68
- package/dist/site/buildchain-site.json +47 -27
- 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 +56 -56
- package/dist/site/page-registry.json +40 -20
- 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 +103 -103
- package/docs/release-discussions.md +44 -5
- 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 +35 -11
- package/packages/core/providers/github/discussions/transport.js +16 -5
- 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/discussion/binary.js +11 -0
- package/packages/core/release/discussion/checkpoints.js +45 -4
- package/packages/core/release/discussion/envelope.js +1 -0
- package/packages/core/release/discussion/evidence.js +51 -0
- package/packages/core/release/discussion/presentation.js +92 -0
- package/packages/core/release/discussion/publication.js +3 -1
- package/packages/core/release/discussion/qualification.js +1 -0
- package/packages/core/release/discussion/reader.js +9 -0
- package/packages/core/release/discussion/session.js +2 -0
- package/packages/core/release/discussion/store.js +28 -31
- package/packages/core/release/discussion/threads.js +83 -0
- 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,73 @@
|
|
|
1
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
2
|
+
import { retainedRecoveryEvidence } from "../../workflow/pipeline/recovery-session.js";
|
|
3
|
+
import { recoveryPublicationMaterial } from "./recovery-materials.js";
|
|
4
|
+
|
|
5
|
+
import { importRecoveredPublication } from "./recovery-import.js";
|
|
6
|
+
|
|
7
|
+
export async function prepareRecoveredPublication(
|
|
8
|
+
session,
|
|
9
|
+
publisherSha,
|
|
10
|
+
host,
|
|
11
|
+
journal,
|
|
12
|
+
phase,
|
|
13
|
+
) {
|
|
14
|
+
if (
|
|
15
|
+
!session.observed.history.at(-1).identity.requestKey.startsWith("recover:")
|
|
16
|
+
)
|
|
17
|
+
return null;
|
|
18
|
+
const { plan: recoveryPlan, evidence } = await retainedRecoveryEvidence(
|
|
19
|
+
session,
|
|
20
|
+
host,
|
|
21
|
+
);
|
|
22
|
+
const publication = evidence.publication;
|
|
23
|
+
if (!publication || publication.mode === "unprepared") return null;
|
|
24
|
+
const plan = recoveryPublicationMaterial(
|
|
25
|
+
publication.materials,
|
|
26
|
+
"publication/plan/",
|
|
27
|
+
true,
|
|
28
|
+
);
|
|
29
|
+
if (recordDigest(host.runtime) !== recordDigest(recoveryPlan.runtime))
|
|
30
|
+
throw new Error(
|
|
31
|
+
"Recovered publication executor differs from the admitted repair",
|
|
32
|
+
);
|
|
33
|
+
const runtime = await host.request(
|
|
34
|
+
`/repos/${host.runtime.repository}/git/commits/${host.runtime.sha}`,
|
|
35
|
+
);
|
|
36
|
+
if (
|
|
37
|
+
runtime.sha !== host.runtime.sha ||
|
|
38
|
+
!/^[0-9a-f]{40}$/u.test(runtime.tree?.sha || "")
|
|
39
|
+
)
|
|
40
|
+
throw new Error("Recovery publisher runtime has no exact admitted tree");
|
|
41
|
+
const execution = {
|
|
42
|
+
attempt: session.observed.attempt,
|
|
43
|
+
runId: host.runId,
|
|
44
|
+
runAttempt: host.runAttempt,
|
|
45
|
+
publisher: {
|
|
46
|
+
repository: "kungfu-systems/buildchain",
|
|
47
|
+
workflow: ".github/workflows/.release-pipeline-products.yml",
|
|
48
|
+
workflowSha: publisherSha,
|
|
49
|
+
job: "apply",
|
|
50
|
+
},
|
|
51
|
+
runtime: {
|
|
52
|
+
repository: host.runtime.repository,
|
|
53
|
+
commit: runtime.sha,
|
|
54
|
+
tree: runtime.tree.sha,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
await importRecoveredPublication(
|
|
58
|
+
publication,
|
|
59
|
+
execution,
|
|
60
|
+
recoveryPlan.root,
|
|
61
|
+
journal,
|
|
62
|
+
phase,
|
|
63
|
+
);
|
|
64
|
+
return {
|
|
65
|
+
planRoot: recoveryPlan.root,
|
|
66
|
+
predecessor: recoveryPlan.predecessor,
|
|
67
|
+
originalPlanRoot: plan.root,
|
|
68
|
+
mode: publication.mode,
|
|
69
|
+
build: publication.build || null,
|
|
70
|
+
preserveTransaction: publication.mode === "qualified",
|
|
71
|
+
execution,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
3
|
+
import { createDomainPublicationQualificationReceipt } from "../publication-qualification.js";
|
|
4
|
+
import { restorePipelineProducts } from "./sealed-products.js";
|
|
5
|
+
import { inspectPipelinePublicationArtifacts } from "./qualification.js";
|
|
6
|
+
import { verifyPipelineQualification } from "./documents.js";
|
|
7
|
+
import { reobservePublicationBuild } from "./recovery-build-readback.js";
|
|
8
|
+
import { requalifyPublicationCapsules } from "./recovery-capsules.js";
|
|
9
|
+
import { readPipelineCaller } from "../../providers/github/pipeline-run-entry.js";
|
|
10
|
+
|
|
11
|
+
function originalManifests(qualified) {
|
|
12
|
+
return [...new Set(qualified.artifacts.map((item) => item.manifestRoot))].map(
|
|
13
|
+
(root) => {
|
|
14
|
+
const artifacts = qualified.artifacts
|
|
15
|
+
.filter((item) => item.manifestRoot === root)
|
|
16
|
+
.map(({ manifestRoot, providerArtifactId, ...item }) => item);
|
|
17
|
+
const body = {
|
|
18
|
+
schema: "buildchain.pipeline-publication-products/v1",
|
|
19
|
+
planRoot: qualified.planRoot,
|
|
20
|
+
source: qualified.source,
|
|
21
|
+
platform: artifacts[0].platform,
|
|
22
|
+
artifacts,
|
|
23
|
+
};
|
|
24
|
+
if (recordDigest(body) !== root)
|
|
25
|
+
throw new Error(
|
|
26
|
+
"Recovered artifact descriptors cannot reconstruct their original manifest bytes",
|
|
27
|
+
);
|
|
28
|
+
return { ...body, root };
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function requalifySealedPublication({
|
|
34
|
+
context,
|
|
35
|
+
prepared,
|
|
36
|
+
originalPlan,
|
|
37
|
+
originalMaterialization,
|
|
38
|
+
archive,
|
|
39
|
+
host,
|
|
40
|
+
directory,
|
|
41
|
+
now = new Date(),
|
|
42
|
+
}) {
|
|
43
|
+
const previous = prepared.qualified;
|
|
44
|
+
verifyPipelineQualification({
|
|
45
|
+
plan: originalPlan,
|
|
46
|
+
materialization: originalMaterialization,
|
|
47
|
+
qualified: previous,
|
|
48
|
+
evaluatedAt: previous.qualification.issuedAt,
|
|
49
|
+
});
|
|
50
|
+
const { plan, materialization } = context;
|
|
51
|
+
if (
|
|
52
|
+
recordDigest(plan.outputs) !== recordDigest(originalPlan.outputs) ||
|
|
53
|
+
plan.version !== originalPlan.version ||
|
|
54
|
+
plan.contractRoot !== originalPlan.contractRoot ||
|
|
55
|
+
recordDigest(materialization.source) !== recordDigest(previous.source)
|
|
56
|
+
)
|
|
57
|
+
throw new Error(
|
|
58
|
+
"Signing recovery cannot change the already sealed product contract or source",
|
|
59
|
+
);
|
|
60
|
+
await reobservePublicationBuild(previous.build, previous.source, host);
|
|
61
|
+
const products = await restorePipelineProducts(
|
|
62
|
+
archive,
|
|
63
|
+
previous,
|
|
64
|
+
prepared.sealed,
|
|
65
|
+
path.join(directory, "requalified-products"),
|
|
66
|
+
);
|
|
67
|
+
for (const manifest of originalManifests(previous))
|
|
68
|
+
inspectPipelinePublicationArtifacts(products, manifest, plan);
|
|
69
|
+
const { run } = await host.runs.read(context.runId, context.runAttempt);
|
|
70
|
+
await readPipelineCaller(
|
|
71
|
+
run,
|
|
72
|
+
previous.source.configPath,
|
|
73
|
+
host.request,
|
|
74
|
+
host.repository,
|
|
75
|
+
);
|
|
76
|
+
const buildBody = {
|
|
77
|
+
schema: "buildchain.pipeline-publication-requalification/v1",
|
|
78
|
+
operation: "requalify-sealed-bytes",
|
|
79
|
+
outcome: "success",
|
|
80
|
+
planRoot: plan.root,
|
|
81
|
+
source: previous.source,
|
|
82
|
+
runId: context.runId,
|
|
83
|
+
runAttempt: context.runAttempt,
|
|
84
|
+
providerSource: run.head_sha,
|
|
85
|
+
predecessorBuild: previous.build,
|
|
86
|
+
sealedRoot: prepared.sealed.root,
|
|
87
|
+
};
|
|
88
|
+
const build = { ...buildBody, root: recordDigest(buildBody) };
|
|
89
|
+
const qualification = createDomainPublicationQualificationReceipt({
|
|
90
|
+
...previous.qualification,
|
|
91
|
+
candidateRoot: recordDigest({
|
|
92
|
+
planRoot: plan.root,
|
|
93
|
+
source: previous.source,
|
|
94
|
+
artifacts: previous.artifacts,
|
|
95
|
+
}),
|
|
96
|
+
issuedAt: now.toISOString(),
|
|
97
|
+
expiresAt: new Date(now.getTime() + 3600000).toISOString(),
|
|
98
|
+
});
|
|
99
|
+
const body = {
|
|
100
|
+
schema: "buildchain.pipeline-publication-qualification/v1",
|
|
101
|
+
planRoot: plan.root,
|
|
102
|
+
source: previous.source,
|
|
103
|
+
artifacts: previous.artifacts,
|
|
104
|
+
build,
|
|
105
|
+
qualification,
|
|
106
|
+
predecessorRoot: previous.root,
|
|
107
|
+
};
|
|
108
|
+
const qualified = { ...body, root: recordDigest(body) };
|
|
109
|
+
const capsules = requalifyPublicationCapsules(
|
|
110
|
+
prepared.capsules,
|
|
111
|
+
qualified,
|
|
112
|
+
plan,
|
|
113
|
+
);
|
|
114
|
+
const { root, ...sealedBody } = prepared.sealed;
|
|
115
|
+
const sealedValue = {
|
|
116
|
+
...sealedBody,
|
|
117
|
+
qualificationRoot: qualified.root,
|
|
118
|
+
predecessorRoot: root,
|
|
119
|
+
};
|
|
120
|
+
const sealed = { ...sealedValue, root: recordDigest(sealedValue) };
|
|
121
|
+
return {
|
|
122
|
+
schema: "buildchain.pipeline-qualification-preparation/v1",
|
|
123
|
+
contextRoot: recordDigest(context),
|
|
124
|
+
qualified,
|
|
125
|
+
capsules,
|
|
126
|
+
sealed,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
2
|
+
|
|
3
|
+
export async function observeRecoveryPublicationEffects({
|
|
4
|
+
effects,
|
|
5
|
+
receipts,
|
|
6
|
+
retained,
|
|
7
|
+
provider,
|
|
8
|
+
fence,
|
|
9
|
+
}) {
|
|
10
|
+
const transactionRoot = retained.documents.transaction.transactionRoot;
|
|
11
|
+
const observations = [];
|
|
12
|
+
for (const effect of effects) {
|
|
13
|
+
const { root, ...body } = effect;
|
|
14
|
+
if (root !== recordDigest(body))
|
|
15
|
+
throw new Error(
|
|
16
|
+
"Recovery publication effect changed its immutable declaration",
|
|
17
|
+
);
|
|
18
|
+
const prior = receipts.filter((receipt) => receipt.effectId === effect.id);
|
|
19
|
+
for (const receipt of prior) {
|
|
20
|
+
const { root: receiptRoot, ...value } = receipt;
|
|
21
|
+
if (
|
|
22
|
+
receiptRoot !== recordDigest(value) ||
|
|
23
|
+
value.schema !== "buildchain.pipeline-publication-effect/v1" ||
|
|
24
|
+
receipt.transactionRoot !== transactionRoot ||
|
|
25
|
+
receipt.effectRoot !== effect.root
|
|
26
|
+
)
|
|
27
|
+
throw new Error(
|
|
28
|
+
"Recovery cannot rewrite a prior publication receipt or transaction",
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
await fence();
|
|
32
|
+
const observed = await provider.observe(effect);
|
|
33
|
+
const matched = provider.matches(effect, observed);
|
|
34
|
+
if (
|
|
35
|
+
(!matched && prior.some((item) => item.state === "success")) ||
|
|
36
|
+
(observed.state !== "absent" &&
|
|
37
|
+
!matched &&
|
|
38
|
+
!provider.canApply?.(effect, observed))
|
|
39
|
+
)
|
|
40
|
+
throw new Error(
|
|
41
|
+
"Recovery publication destination conflicts with retained bytes",
|
|
42
|
+
);
|
|
43
|
+
observations.push({ effect, observed, matched });
|
|
44
|
+
}
|
|
45
|
+
const body = {
|
|
46
|
+
schema: "buildchain.pipeline-recovery-publication-readback/v1",
|
|
47
|
+
transactionRoot,
|
|
48
|
+
qualificationRoot: retained.qualified.root,
|
|
49
|
+
signingRoot: recordDigest(retained.signing),
|
|
50
|
+
sealedRoot: retained.sealed.root,
|
|
51
|
+
effectsRoot: recordDigest(effects),
|
|
52
|
+
receiptRoots: receipts.map((receipt) => receipt.root),
|
|
53
|
+
observations,
|
|
54
|
+
};
|
|
55
|
+
return { ...body, root: recordDigest(body) };
|
|
56
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { uniquePublicationMaterial } from "./context.js";
|
|
3
|
+
import { requalifySealedPublication } from "./recovery-qualification.js";
|
|
4
|
+
import { preparePipelineSigning } from "./signing.js";
|
|
5
|
+
|
|
6
|
+
async function originalMaterial(journal, prefix, accepts) {
|
|
7
|
+
const matches = (await journal.materials(prefix)).filter(accepts);
|
|
8
|
+
if (matches.length !== 1)
|
|
9
|
+
throw new Error(
|
|
10
|
+
`Signing recovery has no unique original material: ${prefix}`,
|
|
11
|
+
);
|
|
12
|
+
return matches[0];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function prepareRecoveredSigning(
|
|
16
|
+
context,
|
|
17
|
+
host,
|
|
18
|
+
journal,
|
|
19
|
+
archive,
|
|
20
|
+
directory,
|
|
21
|
+
) {
|
|
22
|
+
const prepared = await uniquePublicationMaterial(
|
|
23
|
+
journal,
|
|
24
|
+
"publication/predecessor-prepared/",
|
|
25
|
+
);
|
|
26
|
+
const originalPlan = await originalMaterial(
|
|
27
|
+
journal,
|
|
28
|
+
"publication/predecessor-plan/",
|
|
29
|
+
(plan) => plan.root === prepared.qualified.planRoot,
|
|
30
|
+
);
|
|
31
|
+
const originalMaterialization = await originalMaterial(
|
|
32
|
+
journal,
|
|
33
|
+
"publication/predecessor-materialization/",
|
|
34
|
+
(value) =>
|
|
35
|
+
value.planRoot === originalPlan.root &&
|
|
36
|
+
value.source.commit === prepared.qualified.source.commit,
|
|
37
|
+
);
|
|
38
|
+
const result = await requalifySealedPublication({
|
|
39
|
+
context,
|
|
40
|
+
prepared,
|
|
41
|
+
originalPlan,
|
|
42
|
+
originalMaterialization,
|
|
43
|
+
archive,
|
|
44
|
+
host,
|
|
45
|
+
directory,
|
|
46
|
+
});
|
|
47
|
+
await journal.fence();
|
|
48
|
+
await journal.record(
|
|
49
|
+
`publication/qualification-prepared/${context.runId}-${context.runAttempt}`,
|
|
50
|
+
result,
|
|
51
|
+
);
|
|
52
|
+
return preparePipelineSigning({
|
|
53
|
+
plan: context.plan,
|
|
54
|
+
materialization: context.materialization,
|
|
55
|
+
qualified: result.qualified,
|
|
56
|
+
directory: path.join(directory, "signing"),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
5
|
+
import {
|
|
6
|
+
publicationFile,
|
|
7
|
+
publicationPath,
|
|
8
|
+
writeImmutablePublicationFile,
|
|
9
|
+
} from "./files.js";
|
|
10
|
+
|
|
11
|
+
export async function retainPipelineProducts(archive, qualified, bundles) {
|
|
12
|
+
const retained = [];
|
|
13
|
+
for (const artifact of qualified.artifacts) {
|
|
14
|
+
const bundle = bundles.find(
|
|
15
|
+
({ manifest }) => manifest.root === artifact.manifestRoot,
|
|
16
|
+
);
|
|
17
|
+
if (!bundle)
|
|
18
|
+
throw new Error("Qualified product has no exact source manifest");
|
|
19
|
+
const file = publicationFile(
|
|
20
|
+
publicationPath(bundle.directory, artifact.file),
|
|
21
|
+
);
|
|
22
|
+
if (file.digest !== artifact.digest || file.size !== artifact.size)
|
|
23
|
+
throw new Error("Qualified product changed before durable retention");
|
|
24
|
+
const handle = await archive.put(file.bytes, {
|
|
25
|
+
name: `${artifact.digest.slice(7)}-${path.basename(artifact.file)}`,
|
|
26
|
+
mediaType: "application/octet-stream",
|
|
27
|
+
});
|
|
28
|
+
await verifiedBytes(archive, handle, artifact);
|
|
29
|
+
retained.push({ id: artifact.id, handle });
|
|
30
|
+
}
|
|
31
|
+
const body = {
|
|
32
|
+
schema: "buildchain.pipeline-sealed-products/v1",
|
|
33
|
+
qualificationRoot: qualified.root,
|
|
34
|
+
products: retained,
|
|
35
|
+
};
|
|
36
|
+
return { ...body, root: recordDigest(body) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function verifiedBytes(archive, handle, artifact) {
|
|
40
|
+
if (handle.digest !== artifact.digest || handle.size !== artifact.size)
|
|
41
|
+
throw new Error("Retained product handle does not bind qualified bytes");
|
|
42
|
+
const bytes = Buffer.from(await archive.read(handle));
|
|
43
|
+
if (
|
|
44
|
+
bytes.length !== artifact.size ||
|
|
45
|
+
`sha256:${createHash("sha256").update(bytes).digest("hex")}` !==
|
|
46
|
+
artifact.digest
|
|
47
|
+
)
|
|
48
|
+
throw new Error("Retained product readback differs from qualified bytes");
|
|
49
|
+
return bytes;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function restorePipelineProducts(
|
|
53
|
+
archive,
|
|
54
|
+
qualified,
|
|
55
|
+
sealed,
|
|
56
|
+
directory,
|
|
57
|
+
) {
|
|
58
|
+
const { root, ...body } = sealed;
|
|
59
|
+
if (
|
|
60
|
+
root !== recordDigest(body) ||
|
|
61
|
+
sealed.qualificationRoot !== qualified.root ||
|
|
62
|
+
recordDigest(sealed.products.map(({ id }) => id)) !==
|
|
63
|
+
recordDigest(qualified.artifacts.map(({ id }) => id))
|
|
64
|
+
)
|
|
65
|
+
throw new Error(
|
|
66
|
+
"Retained products must cover the exact qualification inventory",
|
|
67
|
+
);
|
|
68
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
69
|
+
for (const artifact of qualified.artifacts) {
|
|
70
|
+
const { handle } = sealed.products.find(({ id }) => id === artifact.id);
|
|
71
|
+
const bytes = await verifiedBytes(archive, handle, artifact);
|
|
72
|
+
const target = path.resolve(directory, artifact.file);
|
|
73
|
+
if (!target.startsWith(`${path.resolve(directory)}${path.sep}`))
|
|
74
|
+
throw new Error("Retained product path escapes its directory");
|
|
75
|
+
writeImmutablePublicationFile(target, bytes);
|
|
76
|
+
publicationPath(directory, artifact.file);
|
|
77
|
+
}
|
|
78
|
+
return directory;
|
|
79
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { publicationContext, uniquePublicationMaterial } from "./context.js";
|
|
3
|
+
import { distributePipelineProducts } from "./distribution.js";
|
|
4
|
+
import { nextPipelineDevelopment } from "./next-development.js";
|
|
5
|
+
|
|
6
|
+
export async function settlePipelinePublication(
|
|
7
|
+
context,
|
|
8
|
+
host,
|
|
9
|
+
directory,
|
|
10
|
+
environment,
|
|
11
|
+
) {
|
|
12
|
+
const { session, journal } = await publicationContext(context, host);
|
|
13
|
+
if (session.observed.phases.publish?.payload.state !== "success")
|
|
14
|
+
throw new Error(
|
|
15
|
+
"Publication must retain success before distribution and next-development",
|
|
16
|
+
);
|
|
17
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
18
|
+
const retained = await uniquePublicationMaterial(
|
|
19
|
+
journal,
|
|
20
|
+
"publication/qualified/",
|
|
21
|
+
);
|
|
22
|
+
if (session.observed.phases.distribution?.payload.state !== "success") {
|
|
23
|
+
const receipt = await distributePipelineProducts(
|
|
24
|
+
context,
|
|
25
|
+
host,
|
|
26
|
+
journal,
|
|
27
|
+
retained,
|
|
28
|
+
environment,
|
|
29
|
+
);
|
|
30
|
+
await journal.record("publication/distribution-complete", receipt, {
|
|
31
|
+
phase: "distribution",
|
|
32
|
+
state: "success",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const next = await nextPipelineDevelopment(context, host, journal);
|
|
36
|
+
await journal.record("publication/next-development-result", next, {
|
|
37
|
+
phase: "next-development",
|
|
38
|
+
state: next.state,
|
|
39
|
+
});
|
|
40
|
+
await host.project(session);
|
|
41
|
+
return next;
|
|
42
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import {
|
|
6
|
+
canonicalJson,
|
|
7
|
+
recordDigest,
|
|
8
|
+
} from "../../release/discussion/envelope.js";
|
|
9
|
+
import { verifyPipelineQualification } from "./documents.js";
|
|
10
|
+
import { writeImmutablePublicationFile } from "./files.js";
|
|
11
|
+
|
|
12
|
+
export const PIPELINE_PRODUCT_PREDICATE =
|
|
13
|
+
"https://buildchain.libkungfu.dev/attestations/pipeline-products/v1";
|
|
14
|
+
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
15
|
+
|
|
16
|
+
export function preparePipelineSigning({
|
|
17
|
+
plan,
|
|
18
|
+
materialization,
|
|
19
|
+
qualified,
|
|
20
|
+
directory,
|
|
21
|
+
evaluatedAt,
|
|
22
|
+
}) {
|
|
23
|
+
verifyPipelineQualification({
|
|
24
|
+
plan,
|
|
25
|
+
materialization,
|
|
26
|
+
qualified,
|
|
27
|
+
evaluatedAt,
|
|
28
|
+
});
|
|
29
|
+
const predicate = {
|
|
30
|
+
schema: "buildchain.pipeline-product-attestation/v1",
|
|
31
|
+
planRoot: plan.root,
|
|
32
|
+
materializationRoot: materialization.root,
|
|
33
|
+
qualificationRoot: qualified.root,
|
|
34
|
+
source: qualified.source,
|
|
35
|
+
protectedSource: plan.source,
|
|
36
|
+
publisher: plan.publisher,
|
|
37
|
+
runtime: plan.runtime,
|
|
38
|
+
provider: {
|
|
39
|
+
runId: qualified.build.runId,
|
|
40
|
+
runAttempt: qualified.build.runAttempt,
|
|
41
|
+
source: qualified.build.providerSource,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
const subjectBytes = Buffer.from(`${canonicalJson(qualified)}\n`);
|
|
45
|
+
const subjectPath = writeImmutablePublicationFile(
|
|
46
|
+
path.join(directory, "qualified-products.json"),
|
|
47
|
+
subjectBytes,
|
|
48
|
+
);
|
|
49
|
+
const predicatePath = writeImmutablePublicationFile(
|
|
50
|
+
path.join(directory, "predicate.json"),
|
|
51
|
+
`${canonicalJson(predicate)}\n`,
|
|
52
|
+
);
|
|
53
|
+
return {
|
|
54
|
+
subjectPath,
|
|
55
|
+
predicatePath,
|
|
56
|
+
predicate,
|
|
57
|
+
subjectDigest: digest(subjectBytes),
|
|
58
|
+
predicateType: PIPELINE_PRODUCT_PREDICATE,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function verifyPipelineSigning({
|
|
63
|
+
plan,
|
|
64
|
+
materialization,
|
|
65
|
+
qualified,
|
|
66
|
+
bundlePath,
|
|
67
|
+
directory,
|
|
68
|
+
token,
|
|
69
|
+
execute = execFileSync,
|
|
70
|
+
evaluatedAt,
|
|
71
|
+
}) {
|
|
72
|
+
const prepared = preparePipelineSigning({
|
|
73
|
+
plan,
|
|
74
|
+
materialization,
|
|
75
|
+
qualified,
|
|
76
|
+
directory,
|
|
77
|
+
evaluatedAt,
|
|
78
|
+
});
|
|
79
|
+
const output = execute(
|
|
80
|
+
"gh",
|
|
81
|
+
[
|
|
82
|
+
"attestation",
|
|
83
|
+
"verify",
|
|
84
|
+
prepared.subjectPath,
|
|
85
|
+
"--repo",
|
|
86
|
+
qualified.source.repository,
|
|
87
|
+
"--signer-workflow",
|
|
88
|
+
`${plan.publisher.repository}/${plan.publisher.workflow}`,
|
|
89
|
+
"--signer-digest",
|
|
90
|
+
plan.publisher.workflowSha,
|
|
91
|
+
"--source-digest",
|
|
92
|
+
qualified.build.providerSource,
|
|
93
|
+
"--predicate-type",
|
|
94
|
+
PIPELINE_PRODUCT_PREDICATE,
|
|
95
|
+
"--bundle",
|
|
96
|
+
bundlePath,
|
|
97
|
+
"--deny-self-hosted-runners",
|
|
98
|
+
"--format",
|
|
99
|
+
"json",
|
|
100
|
+
],
|
|
101
|
+
{
|
|
102
|
+
encoding: "utf8",
|
|
103
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
104
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
105
|
+
env: { ...process.env, GH_TOKEN: token },
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
const results = JSON.parse(output);
|
|
109
|
+
const statements = Array.isArray(results)
|
|
110
|
+
? results
|
|
111
|
+
.map((result) => result.verificationResult?.statement)
|
|
112
|
+
.filter(Boolean)
|
|
113
|
+
: [];
|
|
114
|
+
if (
|
|
115
|
+
statements.length !== 1 ||
|
|
116
|
+
statements[0].predicateType !== prepared.predicateType ||
|
|
117
|
+
recordDigest(statements[0].predicate) !==
|
|
118
|
+
recordDigest(prepared.predicate) ||
|
|
119
|
+
statements[0].subject?.length !== 1 ||
|
|
120
|
+
statements[0].subject[0].digest?.sha256 !== prepared.subjectDigest
|
|
121
|
+
)
|
|
122
|
+
throw new Error(
|
|
123
|
+
"Cryptographically verified attestation does not bind the exact publication predicate and subject",
|
|
124
|
+
);
|
|
125
|
+
if (digest(fs.readFileSync(prepared.subjectPath)) !== prepared.subjectDigest)
|
|
126
|
+
throw new Error("Signed publication subject changed during verification");
|
|
127
|
+
const bundleBytes = fs.readFileSync(bundlePath);
|
|
128
|
+
return {
|
|
129
|
+
schema: "buildchain.pipeline-product-signing/v1",
|
|
130
|
+
verified: true,
|
|
131
|
+
planRoot: plan.root,
|
|
132
|
+
materializationRoot: materialization.root,
|
|
133
|
+
qualificationRoot: qualified.root,
|
|
134
|
+
subjectDigest: `sha256:${prepared.subjectDigest}`,
|
|
135
|
+
predicateRoot: recordDigest(prepared.predicate),
|
|
136
|
+
bundleDigest: `sha256:${digest(bundleBytes)}`,
|
|
137
|
+
signer: plan.publisher,
|
|
138
|
+
providerSource: qualified.build.providerSource,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { planPipelinePublication } from "./plan.js";
|
|
2
|
+
|
|
3
|
+
export async function createPipelinePublicationPlan(
|
|
4
|
+
session,
|
|
5
|
+
publisherSha,
|
|
6
|
+
host,
|
|
7
|
+
version,
|
|
8
|
+
) {
|
|
9
|
+
const observed = session.observed;
|
|
10
|
+
const attempt = observed.attempt;
|
|
11
|
+
const integration = await host.integration.observe({
|
|
12
|
+
...observed.history.at(-1),
|
|
13
|
+
intent: session.intent,
|
|
14
|
+
});
|
|
15
|
+
const source = await host.source.source(
|
|
16
|
+
integration.mergeCommit,
|
|
17
|
+
observed.history.at(-1).generation.source.configPath,
|
|
18
|
+
);
|
|
19
|
+
const route = source.plan.channels.find(
|
|
20
|
+
(entry) =>
|
|
21
|
+
entry.to === session.intent.source.targetBranch &&
|
|
22
|
+
entry.operation !== "develop",
|
|
23
|
+
);
|
|
24
|
+
if (!route)
|
|
25
|
+
throw new Error(
|
|
26
|
+
"Protected source does not declare this publication channel",
|
|
27
|
+
);
|
|
28
|
+
const input = await version.inspect(source.identity, source.plan.version);
|
|
29
|
+
const runtimeCommit = await host.request(
|
|
30
|
+
`/repos/${host.runtime.repository}/git/commits/${host.runtime.sha}`,
|
|
31
|
+
);
|
|
32
|
+
if (
|
|
33
|
+
runtimeCommit.sha !== host.runtime.sha ||
|
|
34
|
+
!/^[0-9a-f]{40}$/u.test(runtimeCommit.tree?.sha || "")
|
|
35
|
+
)
|
|
36
|
+
throw new Error("Publication runtime has no exact source tree");
|
|
37
|
+
const definition = {
|
|
38
|
+
repository: "kungfu-systems/buildchain",
|
|
39
|
+
workflow: ".github/workflows/.release-pipeline-products.yml",
|
|
40
|
+
workflowSha: publisherSha,
|
|
41
|
+
job: "apply",
|
|
42
|
+
};
|
|
43
|
+
const parameters = {
|
|
44
|
+
attempt,
|
|
45
|
+
generation: observed.generation,
|
|
46
|
+
source: source.identity,
|
|
47
|
+
intentSource: observed.history.at(-1).generation.source,
|
|
48
|
+
runtime: {
|
|
49
|
+
repository: host.runtime.repository,
|
|
50
|
+
commit: runtimeCommit.sha,
|
|
51
|
+
tree: runtimeCommit.tree.sha,
|
|
52
|
+
},
|
|
53
|
+
publisher: definition,
|
|
54
|
+
contract: source.plan,
|
|
55
|
+
route,
|
|
56
|
+
version: input.version,
|
|
57
|
+
sourceTimestamp: input.sourceTimestamp,
|
|
58
|
+
};
|
|
59
|
+
let plan = planPipelinePublication(parameters);
|
|
60
|
+
const tag = await host.request(
|
|
61
|
+
`/repos/${host.repository}/git/ref/tags/${encodeURIComponent(plan.tag)}`,
|
|
62
|
+
{ allow404: true },
|
|
63
|
+
);
|
|
64
|
+
const channel = `v${plan.version.split(".")[0]}${plan.channel === "alpha" ? "-alpha" : ""}`;
|
|
65
|
+
const previous = await host.request(
|
|
66
|
+
`/repos/${host.repository}/git/ref/tags/${channel}`,
|
|
67
|
+
{ allow404: true },
|
|
68
|
+
);
|
|
69
|
+
if (
|
|
70
|
+
previous &&
|
|
71
|
+
(previous.object?.type !== "commit" ||
|
|
72
|
+
!/^[0-9a-f]{40}$/u.test(previous.object.sha))
|
|
73
|
+
)
|
|
74
|
+
throw new Error(
|
|
75
|
+
"Publication channel requires an exact lightweight commit ref",
|
|
76
|
+
);
|
|
77
|
+
plan = planPipelinePublication({
|
|
78
|
+
...parameters,
|
|
79
|
+
expectedTagSha: tag?.object?.sha || null,
|
|
80
|
+
previousChannelCommit: previous?.object?.sha || null,
|
|
81
|
+
});
|
|
82
|
+
return plan;
|
|
83
|
+
}
|