@kungfu-tech/buildchain 4.1.3-alpha.1 → 4.1.3-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/architecture/action-taxonomy.json +16 -0
- package/architecture/agent-change-map.md +239 -2
- package/architecture/ci-lane-change-budget.json +289 -0
- 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 +99 -0
- package/architecture/internal-capabilities.json +331 -2
- package/architecture/maintainability-debt.json +3 -1
- package/architecture/maintainability-policy.json +8 -8
- package/architecture/minimal-consumer-migration.json +7558 -0
- package/architecture/release-topology.json +245 -11
- package/architecture/universal-workflow-bootstrap.json +10 -0
- package/architecture/universal-workflow-capability-policy.json +1 -1
- package/architecture/workflow-taxonomy.json +55 -0
- package/dist/readers/business-attempt.cjs +805 -0
- package/dist/site/buildchain-contract.json +7 -5
- package/dist/site/buildchain-site.json +18 -13
- package/dist/site/capability-registry.json +3 -3
- package/dist/site/kfd-claims.json +311 -10
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/node-api-registry.json +7 -7
- package/dist/site/page-registry.json +13 -8
- package/dist/site/public-surface-audit.json +201 -7
- package/dist/site/publication-authority-registry.json +121 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/dist/site/workflow-registry.json +416 -7
- package/docs/node-api-reference.md +11 -11
- package/docs/runtime-entry.md +45 -5
- package/docs/workflow-catalog.md +5 -0
- package/package.json +4 -3
- 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 +99 -0
- package/packages/core/consumer/contract/products.js +101 -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/native/actions.js +7 -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/queue/landing-action.js +5 -0
- package/packages/core/governance/buildchain-publication-authority.js +5 -0
- 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 +7 -2
- package/packages/core/providers/github/pipeline-checkout.js +49 -0
- package/packages/core/providers/github/pipeline-events.js +52 -0
- package/packages/core/providers/github/pipeline-integration.js +95 -0
- package/packages/core/providers/github/pipeline-policy.js +213 -0
- package/packages/core/providers/github/pipeline-product-payload.js +50 -0
- package/packages/core/providers/github/pipeline-product-release.js +191 -0
- package/packages/core/providers/github/pipeline-publication-artifacts.js +153 -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 +178 -0
- package/packages/core/providers/github/pipeline-version.js +201 -0
- package/packages/core/providers/github/pipeline-worker.js +108 -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 +87 -0
- package/packages/core/publication/npm/pipeline-provider.js +109 -0
- package/packages/core/publication/npm/registry.js +12 -9
- package/packages/core/publication/pipeline/actions.js +100 -0
- package/packages/core/publication/pipeline/apply.js +182 -0
- package/packages/core/publication/pipeline/build-segments.js +107 -0
- package/packages/core/publication/pipeline/capsules.js +89 -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 +85 -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 +124 -0
- package/packages/core/publication/pipeline/documents.js +156 -0
- package/packages/core/publication/pipeline/effects.js +162 -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 +110 -0
- package/packages/core/publication/pipeline/next-development.js +116 -0
- package/packages/core/publication/pipeline/pack.js +151 -0
- package/packages/core/publication/pipeline/package-policy.js +22 -0
- package/packages/core/publication/pipeline/plan.js +114 -0
- package/packages/core/publication/pipeline/prepare.js +151 -0
- package/packages/core/publication/pipeline/qualification.js +170 -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 +43 -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/version.js +81 -0
- package/packages/core/publication/publication-reproducibility.js +2 -2
- 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/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 +165 -0
- package/packages/core/workflow/attempt/journal.js +87 -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 +62 -0
- package/packages/core/workflow/pipeline/build-control.js +140 -0
- package/packages/core/workflow/pipeline/build-evidence.js +92 -0
- package/packages/core/workflow/pipeline/build-qualification.js +145 -0
- package/packages/core/workflow/pipeline/build-result.js +69 -0
- package/packages/core/workflow/pipeline/build.js +95 -0
- package/packages/core/workflow/pipeline/cancellation.js +169 -0
- package/packages/core/workflow/pipeline/channel-control.js +127 -0
- package/packages/core/workflow/pipeline/controller.js +133 -0
- package/packages/core/workflow/pipeline/delivery-control.js +204 -0
- package/packages/core/workflow/pipeline/delivery-observation.js +118 -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 +56 -0
- package/packages/core/workflow/pipeline/guard-build.js +37 -0
- package/packages/core/workflow/pipeline/guard.js +156 -0
- package/packages/core/workflow/pipeline/host.js +114 -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/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 +66 -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 +106 -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/scripts/build-release-discussion-reader.mjs +11 -7
- package/scripts/check-pipeline-publication-topology.mjs +36 -0
- package/scripts/check-release-topology.mjs +6 -1
- package/scripts/generate-minimal-consumer-contract.mjs +155 -0
- package/scripts/verify-golden-path.mjs +5 -1
- 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,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,43 @@
|
|
|
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
|
+
directory,
|
|
30
|
+
);
|
|
31
|
+
await journal.record("publication/distribution-complete", receipt, {
|
|
32
|
+
phase: "distribution",
|
|
33
|
+
state: "success",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const next = await nextPipelineDevelopment(context, host, journal);
|
|
37
|
+
await journal.record("publication/next-development-result", next, {
|
|
38
|
+
phase: "next-development",
|
|
39
|
+
state: next.state,
|
|
40
|
+
});
|
|
41
|
+
await host.project(session);
|
|
42
|
+
return next;
|
|
43
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { parse, stringify } from "smol-toml";
|
|
2
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
3
|
+
|
|
4
|
+
function document(file, bytes) {
|
|
5
|
+
const text = Buffer.isBuffer(bytes) ? bytes.toString("utf8") : bytes;
|
|
6
|
+
return file.format === "json" ? JSON.parse(text) : parse(text);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function field(value, key) {
|
|
10
|
+
const names = key.split(".");
|
|
11
|
+
let parent = value;
|
|
12
|
+
for (const name of names.slice(0, -1)) {
|
|
13
|
+
if (
|
|
14
|
+
["__proto__", "prototype", "constructor"].includes(name) ||
|
|
15
|
+
!Object.hasOwn(parent, name) ||
|
|
16
|
+
!parent[name] ||
|
|
17
|
+
typeof parent[name] !== "object"
|
|
18
|
+
)
|
|
19
|
+
throw new Error(
|
|
20
|
+
"Version field does not resolve to an owned document key",
|
|
21
|
+
);
|
|
22
|
+
parent = parent[name];
|
|
23
|
+
}
|
|
24
|
+
const name = names.at(-1);
|
|
25
|
+
if (
|
|
26
|
+
["__proto__", "prototype", "constructor"].includes(name) ||
|
|
27
|
+
!Object.hasOwn(parent, name) ||
|
|
28
|
+
typeof parent[name] !== "string"
|
|
29
|
+
)
|
|
30
|
+
throw new Error("Version field must be an existing string");
|
|
31
|
+
return { parent, name };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function readPipelineVersion(policy, files) {
|
|
35
|
+
const versions = policy.files.map((file) => {
|
|
36
|
+
if (!Object.hasOwn(files, file.path))
|
|
37
|
+
throw new Error(`Missing declared version file: ${file.path}`);
|
|
38
|
+
const { parent, name } = field(document(file, files[file.path]), file.key);
|
|
39
|
+
return parent[name];
|
|
40
|
+
});
|
|
41
|
+
if (!versions.length || new Set(versions).size !== 1)
|
|
42
|
+
throw new Error(
|
|
43
|
+
"All declared version fields must identify the same candidate version",
|
|
44
|
+
);
|
|
45
|
+
return versions[0];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function materializePipelineVersion(policy, files, version) {
|
|
49
|
+
const before = readPipelineVersion(policy, files);
|
|
50
|
+
if (policy.strategy === "anchored" && before !== version)
|
|
51
|
+
throw new Error("Buildchain cannot rewrite anchored version authority");
|
|
52
|
+
const documents = new Map();
|
|
53
|
+
for (const file of policy.files) {
|
|
54
|
+
if (!documents.has(file.path))
|
|
55
|
+
documents.set(file.path, {
|
|
56
|
+
file,
|
|
57
|
+
value: document(file, files[file.path]),
|
|
58
|
+
});
|
|
59
|
+
const entry = documents.get(file.path);
|
|
60
|
+
if (entry.file.format !== file.format)
|
|
61
|
+
throw new Error("One version document cannot have conflicting formats");
|
|
62
|
+
const { parent, name } = field(entry.value, file.key);
|
|
63
|
+
parent[name] = version;
|
|
64
|
+
}
|
|
65
|
+
const changes = [...documents].flatMap(([filePath, { file, value }]) => {
|
|
66
|
+
if (before === version) return [];
|
|
67
|
+
const content =
|
|
68
|
+
file.format === "json"
|
|
69
|
+
? `${JSON.stringify(value, null, 2)}\n`
|
|
70
|
+
: stringify(value);
|
|
71
|
+
return [
|
|
72
|
+
{
|
|
73
|
+
path: filePath,
|
|
74
|
+
before: recordDigest(String(files[filePath])),
|
|
75
|
+
content,
|
|
76
|
+
after: recordDigest(content),
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
});
|
|
80
|
+
return { before, version, changes };
|
|
81
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readNpmPackResult } from "./npm/pack-result.js";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import os from "node:os";
|
|
@@ -564,8 +565,7 @@ function packPublication({ buildRoot, packageDir, source, toolchain }) {
|
|
|
564
565
|
label: "npm pack reproducibility candidate",
|
|
565
566
|
},
|
|
566
567
|
);
|
|
567
|
-
const
|
|
568
|
-
const result = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
568
|
+
const result = readNpmPackResult(output);
|
|
569
569
|
if (!result?.filename || !result?.integrity || !result?.shasum) {
|
|
570
570
|
throw new Error(
|
|
571
571
|
"npm pack reproducibility candidate did not report filename, integrity, and shasum",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { parseNpmView } from "../../publication/npm/registry.js";
|
|
2
|
+
import { readNpmPackResult } from "../../publication/npm/pack-result.js";
|
|
1
3
|
import { createOciPublicationAdapter } from "./oci-provider.js";
|
|
2
4
|
import crypto from "node:crypto";
|
|
3
5
|
import fs from "node:fs";
|
|
@@ -306,8 +308,7 @@ function createPackedPackage(context) {
|
|
|
306
308
|
{ cwd: context.cwd, encoding: "utf8" },
|
|
307
309
|
"npm pack",
|
|
308
310
|
);
|
|
309
|
-
const
|
|
310
|
-
const pack = Array.isArray(payload) ? payload[0] : payload;
|
|
311
|
+
const pack = readNpmPackResult(String(result.stdout || "[]"));
|
|
311
312
|
if (
|
|
312
313
|
pack?.name !== context.intent.packageName ||
|
|
313
314
|
pack?.version !== context.intent.version ||
|
|
@@ -363,7 +364,7 @@ async function npmReadback(context, packedPackage, effect) {
|
|
|
363
364
|
"npm-readback-failed",
|
|
364
365
|
);
|
|
365
366
|
}
|
|
366
|
-
const integrity =
|
|
367
|
+
const { integrity } = parseNpmView(result.stdout);
|
|
367
368
|
if (integrity !== expected.integrity) return conflict("npm-integrity-conflict");
|
|
368
369
|
return observed(effect, {
|
|
369
370
|
kind: "npm-package",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseNpmView } from "../../../publication/npm/registry.js";
|
|
1
2
|
import { npmPackageSpec } from "./publish-contract.js";
|
|
2
3
|
import { execNpmSync } from "./npm-distribution.js";
|
|
3
4
|
import fs from "node:fs";
|
|
@@ -14,7 +15,7 @@ export function readExistingNpmIntegrity({ cwd, artifact }) {
|
|
|
14
15
|
if (!output) {
|
|
15
16
|
throw new Error("empty dist.integrity");
|
|
16
17
|
}
|
|
17
|
-
return
|
|
18
|
+
return parseNpmView(output).integrity;
|
|
18
19
|
} catch (error) {
|
|
19
20
|
const message = error.stderr?.toString?.().trim() || error.message;
|
|
20
21
|
throw new Error(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readNpmPackResult } from "../../../publication/npm/pack-result.js";
|
|
1
2
|
import { npmPublishTransaction } from "../../../publication/npm/transaction.js";
|
|
2
3
|
import { npmPublicationEnvironment } from "../../../publication/npm/environment.js";
|
|
3
4
|
import {
|
|
@@ -15,10 +16,21 @@ import {
|
|
|
15
16
|
updateVersionStateContents,
|
|
16
17
|
} from "../../version-state.js";
|
|
17
18
|
import { publishTransactionEnvironment } from "./transaction-context.js";
|
|
18
|
-
export function runPublishCommand({
|
|
19
|
+
export function runPublishCommand({
|
|
20
|
+
cwd,
|
|
21
|
+
command,
|
|
22
|
+
provider,
|
|
23
|
+
loadedConfig,
|
|
24
|
+
env,
|
|
25
|
+
}) {
|
|
19
26
|
if (provider) {
|
|
20
|
-
if (provider.kind !== "npm" || !provider.directory || command)
|
|
21
|
-
|
|
27
|
+
if (provider.kind !== "npm" || !provider.directory || command)
|
|
28
|
+
throw new Error("Invalid or ambiguous declarative publication provider");
|
|
29
|
+
npmPublishTransaction({
|
|
30
|
+
cwd: path.resolve(cwd, provider.directory),
|
|
31
|
+
publication: npmPublicationEnvironment(env),
|
|
32
|
+
env: { ...process.env, ...env },
|
|
33
|
+
});
|
|
22
34
|
return "provider:npm";
|
|
23
35
|
}
|
|
24
36
|
const lifecyclePublish = getLifecycleStage(loadedConfig, "publish");
|
|
@@ -64,7 +76,7 @@ export function rematerializedNpmPackEnvironment({
|
|
|
64
76
|
path.join(os.tmpdir(), "buildchain-rematerialized-npm-"),
|
|
65
77
|
);
|
|
66
78
|
try {
|
|
67
|
-
const
|
|
79
|
+
const result = readNpmPackResult(
|
|
68
80
|
execNpmSync(
|
|
69
81
|
[
|
|
70
82
|
"pack",
|
|
@@ -82,7 +94,6 @@ export function rematerializedNpmPackEnvironment({
|
|
|
82
94
|
},
|
|
83
95
|
),
|
|
84
96
|
);
|
|
85
|
-
const result = Array.isArray(packed) ? packed[0] : packed;
|
|
86
97
|
if (!result?.filename)
|
|
87
98
|
throw new Error("rematerialized npm pack did not return a filename");
|
|
88
99
|
if (result.name !== pkg.name || result.version !== version) {
|
|
Binary file
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
export const DOMAIN_WASM_ABI_VERSION = 1;
|
|
3
3
|
export const DOMAIN_WASM_RUSTC = "rustc 1.96.0 (ac68faa20 2026-05-25)";
|
|
4
4
|
export const DOMAIN_WASM_SOURCE_SHA256 =
|
|
5
|
-
"
|
|
5
|
+
"9275a02bbc3daac66cac3021384bcaaf43bd52ed9af56b4f1ac67f1cbc69fb71";
|
|
6
6
|
export const DOMAIN_WASM_SHA256 =
|
|
7
|
-
"
|
|
7
|
+
"d42ab5ae8d42253d40ab49057e460285a848925cece176c4d68e63e90ca86c72";
|
|
@@ -1,45 +1,38 @@
|
|
|
1
1
|
import { resolveRecoverySource } from "./recovery.js";
|
|
2
2
|
import { getOctokit } from "@actions/github";
|
|
3
3
|
import { runtimeEntryProvider } from "./github.js";
|
|
4
|
+
import { resolveRuntimeActionSource } from "./source.js";
|
|
4
5
|
import {
|
|
5
6
|
preparedRuntimeSelection,
|
|
6
7
|
selectExecutionRuntime,
|
|
7
8
|
} from "./selection.js";
|
|
9
|
+
import { selectAttemptRecoveryRuntime } from "./attempt.js";
|
|
8
10
|
|
|
9
11
|
export async function selectExecutionRuntimeAction(
|
|
10
12
|
core,
|
|
11
13
|
env,
|
|
12
|
-
{
|
|
14
|
+
{
|
|
15
|
+
providerFactory = runtimeEntryProvider,
|
|
16
|
+
githubFactory = getOctokit,
|
|
17
|
+
pipelineLookup,
|
|
18
|
+
} = {},
|
|
13
19
|
) {
|
|
14
20
|
const retained = core.getInput("selection");
|
|
15
21
|
const transport = retained
|
|
16
22
|
? preparedRuntimeSelection(JSON.parse(retained))
|
|
17
23
|
: undefined;
|
|
18
24
|
const github = githubFactory(core.getInput("token", { required: true }));
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
eventName: env.GITHUB_EVENT_NAME,
|
|
31
|
-
});
|
|
32
|
-
await reader.authorize({ origin: "runtime-parameter" });
|
|
33
|
-
source = await resolveRecoverySource(
|
|
34
|
-
{
|
|
35
|
-
repository: source.repository,
|
|
36
|
-
runId: resumeRunId,
|
|
37
|
-
currentRunId: env.GITHUB_RUN_ID,
|
|
38
|
-
workflow: env.GITHUB_WORKFLOW_REF?.slice(source.repository.length + 1),
|
|
39
|
-
},
|
|
40
|
-
reader.readRun,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
25
|
+
const {
|
|
26
|
+
source: resolvedSource,
|
|
27
|
+
continuation,
|
|
28
|
+
resumeRunId,
|
|
29
|
+
pipelineAttempt,
|
|
30
|
+
} = await resolveRuntimeActionSource(core, env, {
|
|
31
|
+
providerFactory,
|
|
32
|
+
github,
|
|
33
|
+
pipelineLookup,
|
|
34
|
+
});
|
|
35
|
+
let source = resolvedSource;
|
|
43
36
|
const provider = providerFactory(github, {
|
|
44
37
|
sourceRepository: source.repository,
|
|
45
38
|
sourceSha: source.sha,
|
|
@@ -91,6 +84,15 @@ export async function selectExecutionRuntimeAction(
|
|
|
91
84
|
selection.source?.sha !== source.sha
|
|
92
85
|
)
|
|
93
86
|
throw new Error("Runtime transport changed the admitted consumer source");
|
|
87
|
+
} else if (continuation?.recovery && (!runtimeRef || pipelineAttempt)) {
|
|
88
|
+
if (runtimeRef)
|
|
89
|
+
throw new Error(
|
|
90
|
+
"An admitted recovery runtime can change only through a new recovery attempt",
|
|
91
|
+
);
|
|
92
|
+
selection = await selectAttemptRecoveryRuntime(
|
|
93
|
+
continuation.recovery,
|
|
94
|
+
provider,
|
|
95
|
+
);
|
|
94
96
|
} else {
|
|
95
97
|
selection = await selectExecutionRuntime(
|
|
96
98
|
{
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { validateRuntime } from "../../release/discussion/envelope.js";
|
|
2
|
+
import { selectExecutionRuntime } from "./selection.js";
|
|
3
|
+
|
|
4
|
+
// This input is read from the canonical current attempt, never from workflow
|
|
5
|
+
// inputs. Continuation retains its admitted repair without reopening an override.
|
|
6
|
+
export async function selectAttemptRecoveryRuntime(recovery, provider) {
|
|
7
|
+
validateRuntime(recovery.runtime);
|
|
8
|
+
if (
|
|
9
|
+
!/^attempt-[0-9a-f]{64}$/u.test(recovery.attempt || "") ||
|
|
10
|
+
!/^sha256:[0-9a-f]{64}$/u.test(recovery.recordRoot || "") ||
|
|
11
|
+
recovery.runtime.repository !== "kungfu-systems/buildchain"
|
|
12
|
+
)
|
|
13
|
+
throw new Error(
|
|
14
|
+
"Attempt runtime continuation requires its canonical recovery admission",
|
|
15
|
+
);
|
|
16
|
+
const selected = await selectExecutionRuntime(
|
|
17
|
+
{ runtimeRef: recovery.runtime.sha },
|
|
18
|
+
{
|
|
19
|
+
...provider,
|
|
20
|
+
authorize: (input) =>
|
|
21
|
+
provider.authorize({ ...input, origin: "attempt-recovery" }),
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
return { ...selected, origin: "attempt-recovery", recovery };
|
|
25
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { consumerContractLock } from "../../consumer/contract/identity.js";
|
|
2
|
+
import { canonicalJson } from "../../release/discussion/envelope.js";
|
|
3
|
+
|
|
1
4
|
const exactSha = /^[a-f0-9]{40}$/iu;
|
|
2
5
|
const repository = "kungfu-systems/buildchain";
|
|
3
6
|
const trainRef =
|
|
@@ -28,6 +31,14 @@ export function runtimeSelector({ runtimeRef = "", lock, workflowSha }) {
|
|
|
28
31
|
return { ref: runtimeRef, origin: "runtime-parameter" };
|
|
29
32
|
}
|
|
30
33
|
if (lock) {
|
|
34
|
+
if (lock.schema === "buildchain.consumer-contract-lock/v2") {
|
|
35
|
+
const validated = consumerContractLock(lock);
|
|
36
|
+
if (canonicalJson(validated) !== canonicalJson(lock))
|
|
37
|
+
throw new Error(
|
|
38
|
+
"Minimal consumer contract lock contains unsupported fields",
|
|
39
|
+
);
|
|
40
|
+
return { ref: validated.runtime.sha, origin: "consumer-contract-lock" };
|
|
41
|
+
}
|
|
31
42
|
if (lock.contract !== "kungfu-buildchain-contract-lock")
|
|
32
43
|
throw new Error("Unsupported Buildchain contract lock");
|
|
33
44
|
const sha = commit(lock.buildchain?.resolvedSha, "Contract lock runtime");
|
|
@@ -92,6 +103,9 @@ export async function selectExecutionRuntime(
|
|
|
92
103
|
...(selected.origin === "contract-lock"
|
|
93
104
|
? { contract: { digest: request.lock.buildchain.contractDigest } }
|
|
94
105
|
: {}),
|
|
106
|
+
...(selected.origin === "consumer-contract-lock"
|
|
107
|
+
? { contract: { consumerConfigDigest: request.lock.configDigest } }
|
|
108
|
+
: {}),
|
|
95
109
|
class:
|
|
96
110
|
selected.origin === "contract-lock" &&
|
|
97
111
|
request.lock.buildchain.ref === "v4-alpha"
|