@kungfu-tech/buildchain 4.1.3-alpha.1 → 4.1.3-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/architecture/action-taxonomy.json +24 -0
- package/architecture/agent-change-map.md +256 -2
- package/architecture/ci-lane-change-budget.json +374 -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/decisions/0009-pipeline-version-preparation.md +186 -0
- package/architecture/internal-capabilities.json +361 -2
- package/architecture/maintainability-debt.json +6 -4
- package/architecture/maintainability-policy.json +8 -8
- package/architecture/minimal-consumer-migration.json +7567 -0
- package/architecture/product-upstreams.json +20 -0
- package/architecture/release-topology.json +282 -11
- package/architecture/universal-workflow-bootstrap.json +12 -0
- package/architecture/universal-workflow-capability-policy.json +1 -1
- package/architecture/workflow-taxonomy.json +66 -0
- package/dist/readers/business-attempt.cjs +809 -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 +385 -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 +21 -21
- package/dist/site/page-registry.json +13 -8
- package/dist/site/public-surface-audit.json +258 -7
- package/dist/site/publication-authority-registry.json +141 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/dist/site/workflow-registry.json +522 -7
- package/docs/node-api-reference.md +39 -39
- package/docs/runtime-entry.md +45 -5
- package/docs/workflow-catalog.md +6 -0
- package/package.json +4 -3
- 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 +146 -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/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 +6 -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-entry-runs.js +203 -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-release-evidence.js +111 -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-stable-source.js +354 -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/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 +116 -0
- package/packages/core/publication/pipeline/apply.js +188 -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 +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 +124 -0
- package/packages/core/publication/pipeline/documents.js +192 -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 +117 -0
- package/packages/core/publication/pipeline/pack.js +157 -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 +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/stable-entry.js +215 -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/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 +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 +75 -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 +142 -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 +48 -0
- package/scripts/check-release-topology.mjs +7 -1
- package/scripts/generate-minimal-consumer-contract.mjs +155 -0
- package/scripts/generate-site-bundle.mjs +1 -1
- package/scripts/verify-golden-path.mjs +5 -1
- 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,178 @@
|
|
|
1
|
+
import { bindConsumerSource } from "../../consumer/contract/identity.js";
|
|
2
|
+
import { normalInputs } from "../../consumer/contract/entries.js";
|
|
3
|
+
import { channelRoute } from "../../workflow/pipeline/events.js";
|
|
4
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
5
|
+
import { validateGeneration } from "../../workflow/attempt/identity.js";
|
|
6
|
+
|
|
7
|
+
function exactSha(value) {
|
|
8
|
+
if (!/^[0-9a-f]{40}$/u.test(value || ""))
|
|
9
|
+
throw new Error("Pipeline source requires an exact Git commit");
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function pullRequestBinding(pr) {
|
|
14
|
+
return {
|
|
15
|
+
number: pr.number,
|
|
16
|
+
state: pr.state,
|
|
17
|
+
merged: pr.merged,
|
|
18
|
+
mergeCommit: pr.merge_commit_sha,
|
|
19
|
+
head: pr.head,
|
|
20
|
+
base: pr.base,
|
|
21
|
+
draft: pr.draft,
|
|
22
|
+
labels: pr.labels,
|
|
23
|
+
updatedAt: pr.updated_at,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function githubPipelineSource(request, repository) {
|
|
28
|
+
if (!/^[\w.-]+\/[\w.-]+$/u.test(repository || ""))
|
|
29
|
+
throw new Error("Invalid pipeline repository");
|
|
30
|
+
const base = `/repos/${repository}`;
|
|
31
|
+
async function source(commitInput, configPath) {
|
|
32
|
+
const commit = exactSha(commitInput);
|
|
33
|
+
const git = await request(`${base}/git/commits/${commit}`);
|
|
34
|
+
const config = await request(
|
|
35
|
+
`${base}/contents/${configPath}?ref=${commit}`,
|
|
36
|
+
);
|
|
37
|
+
if (
|
|
38
|
+
config.type !== "file" ||
|
|
39
|
+
config.encoding !== "base64" ||
|
|
40
|
+
!Number.isSafeInteger(config.size) ||
|
|
41
|
+
config.size < 1 ||
|
|
42
|
+
config.size > 256_000 ||
|
|
43
|
+
typeof config.content !== "string" ||
|
|
44
|
+
config.content.length > 512_000
|
|
45
|
+
)
|
|
46
|
+
throw new Error("Pipeline TOML is missing or exceeds its byte bound");
|
|
47
|
+
const bytes = Buffer.from(config.content, "base64");
|
|
48
|
+
if (bytes.length !== config.size)
|
|
49
|
+
throw new Error("Pipeline TOML size drift");
|
|
50
|
+
return bindConsumerSource(
|
|
51
|
+
{
|
|
52
|
+
repository,
|
|
53
|
+
commit,
|
|
54
|
+
tree: exactSha(git.tree?.sha),
|
|
55
|
+
configPath,
|
|
56
|
+
configBlob: config.sha,
|
|
57
|
+
},
|
|
58
|
+
bytes,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
async function branchHead(branch) {
|
|
62
|
+
if (
|
|
63
|
+
!/^(?:dev|alpha|release|publish-gate)\/[A-Za-z0-9/._-]+$/u.test(
|
|
64
|
+
branch || "",
|
|
65
|
+
) ||
|
|
66
|
+
branch.includes("..")
|
|
67
|
+
)
|
|
68
|
+
throw new Error("Invalid protected branch");
|
|
69
|
+
const pointer = await request(`${base}/git/ref/heads/${branch}`);
|
|
70
|
+
if (pointer.object?.type !== "commit")
|
|
71
|
+
throw new Error("Protected ref is not a commit");
|
|
72
|
+
return exactSha(pointer.object.sha);
|
|
73
|
+
}
|
|
74
|
+
async function observe(pullRequest, inputs = {}) {
|
|
75
|
+
if (!Number.isSafeInteger(pullRequest) || pullRequest < 1)
|
|
76
|
+
throw new Error("Pipeline requires an exact PR number");
|
|
77
|
+
const { configPath } = normalInputs(inputs);
|
|
78
|
+
const pr = await request(`${base}/pulls/${pullRequest}`);
|
|
79
|
+
const repositoryInfo = await request(base);
|
|
80
|
+
if (pr.number !== pullRequest || !repositoryInfo.node_id)
|
|
81
|
+
throw new Error("Pipeline PR provider identity drift");
|
|
82
|
+
// Read delivery policy from the protected base. A PR may change its own
|
|
83
|
+
// product commands, but cannot lower review or grant itself a channel route.
|
|
84
|
+
const baseCommit = await branchHead(pr.base?.ref);
|
|
85
|
+
const protectedSource = await source(baseCommit, configPath);
|
|
86
|
+
const route = channelRoute(protectedSource.plan, pr, repository);
|
|
87
|
+
if (!route) {
|
|
88
|
+
if (baseCommit !== (await branchHead(pr.base.ref)))
|
|
89
|
+
throw new Error("Protected policy changed during route observation");
|
|
90
|
+
return { eligible: false, reason: "channel-not-enabled" };
|
|
91
|
+
}
|
|
92
|
+
const admitted = await source(pr.head.sha, configPath);
|
|
93
|
+
const again = await request(`${base}/pulls/${pullRequest}`);
|
|
94
|
+
if (
|
|
95
|
+
recordDigest(pullRequestBinding(pr)) !==
|
|
96
|
+
recordDigest(pullRequestBinding(again)) ||
|
|
97
|
+
baseCommit !== (await branchHead(pr.base.ref))
|
|
98
|
+
)
|
|
99
|
+
throw new Error("Pipeline source changed during observation; reobserve");
|
|
100
|
+
return {
|
|
101
|
+
eligible: true,
|
|
102
|
+
repositoryId: repositoryInfo.node_id,
|
|
103
|
+
route,
|
|
104
|
+
plan: admitted.plan,
|
|
105
|
+
protectedPlan: protectedSource.plan,
|
|
106
|
+
protectedSource: protectedSource.identity,
|
|
107
|
+
live: {
|
|
108
|
+
pullRequest,
|
|
109
|
+
observedHead: pr.head.sha,
|
|
110
|
+
targetBranch: pr.base.ref,
|
|
111
|
+
routeEnabled: true,
|
|
112
|
+
source: admitted.identity,
|
|
113
|
+
baseCommit,
|
|
114
|
+
state: pr.state,
|
|
115
|
+
merged: pr.merged === true,
|
|
116
|
+
mergeCommit: pr.merge_commit_sha,
|
|
117
|
+
draft: pr.draft === true,
|
|
118
|
+
ready: pr.labels.some((label) => label.name === "ready"),
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
async function observeIntent(intent, generation, inputs = {}) {
|
|
123
|
+
validateGeneration(generation, intent);
|
|
124
|
+
if (intent.repository !== repository)
|
|
125
|
+
throw new Error("Recorded intent repository drift");
|
|
126
|
+
const number = intent.source.pullRequest;
|
|
127
|
+
const pr = await request(`${base}/pulls/${number}`);
|
|
128
|
+
const repositoryInfo = await request(base);
|
|
129
|
+
if (
|
|
130
|
+
repositoryInfo.node_id !== intent.source.repositoryId ||
|
|
131
|
+
pr.number !== number ||
|
|
132
|
+
pr.base?.repo?.full_name !== repository
|
|
133
|
+
)
|
|
134
|
+
throw new Error("Recorded pipeline intent provider identity drift");
|
|
135
|
+
const sameHead =
|
|
136
|
+
pr.head?.repo?.full_name === repository &&
|
|
137
|
+
pr.head.sha === generation.source.commit;
|
|
138
|
+
const live = {
|
|
139
|
+
pullRequest: number,
|
|
140
|
+
observedHead: exactSha(pr.head?.sha),
|
|
141
|
+
targetBranch: pr.base.ref,
|
|
142
|
+
state: pr.state,
|
|
143
|
+
merged: pr.merged === true,
|
|
144
|
+
mergeCommit: pr.merge_commit_sha,
|
|
145
|
+
source: sameHead ? generation.source : null,
|
|
146
|
+
baseCommit: null,
|
|
147
|
+
ready: pr.labels.some((label) => label.name === "ready"),
|
|
148
|
+
draft: pr.draft === true,
|
|
149
|
+
};
|
|
150
|
+
// Closing, updating or retargeting an admitted PR must remain recoverable
|
|
151
|
+
// even when its new TOML is invalid, its old route disappeared or it is now a fork.
|
|
152
|
+
if (
|
|
153
|
+
pr.state !== "open" ||
|
|
154
|
+
!sameHead ||
|
|
155
|
+
pr.base.ref !== intent.source.targetBranch
|
|
156
|
+
) {
|
|
157
|
+
const again = await request(`${base}/pulls/${number}`);
|
|
158
|
+
if (
|
|
159
|
+
recordDigest(pullRequestBinding(pr)) !==
|
|
160
|
+
recordDigest(pullRequestBinding(again))
|
|
161
|
+
)
|
|
162
|
+
throw new Error("Recorded intent changed during terminal observation");
|
|
163
|
+
return { eligible: true, cleanupOnly: true, live };
|
|
164
|
+
}
|
|
165
|
+
const observed = await observe(number, inputs);
|
|
166
|
+
if (observed.eligible) return observed;
|
|
167
|
+
return {
|
|
168
|
+
eligible: true,
|
|
169
|
+
cleanupOnly: true,
|
|
170
|
+
live: {
|
|
171
|
+
...live,
|
|
172
|
+
baseCommit: await branchHead(intent.source.targetBranch),
|
|
173
|
+
routeEnabled: false,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return { observe, observeIntent, source, branchHead };
|
|
178
|
+
}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
3
|
+
import { verifyPipelinePublicationPlan } from "../../publication/pipeline/plan.js";
|
|
4
|
+
import { compareDevelopmentVersions } from "../../publication/publication-development.js";
|
|
5
|
+
import { githubPipelineSource } from "./pipeline-source.js";
|
|
6
|
+
import { githubPipelineVersion } from "./pipeline-version.js";
|
|
7
|
+
import { normalInputs } from "../../consumer/contract/entries.js";
|
|
8
|
+
|
|
9
|
+
const SHA = /^[0-9a-f]{40}$/u;
|
|
10
|
+
const STABLE = /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u;
|
|
11
|
+
|
|
12
|
+
function publishedRelease(value, repository, tag, prerelease) {
|
|
13
|
+
if (
|
|
14
|
+
!Number.isSafeInteger(value?.id) ||
|
|
15
|
+
value.id < 1 ||
|
|
16
|
+
value.tag_name !== tag ||
|
|
17
|
+
value.draft !== false ||
|
|
18
|
+
value.prerelease !== prerelease ||
|
|
19
|
+
value.html_url !== `https://github.com/${repository}/releases/tag/${tag}` ||
|
|
20
|
+
typeof value.published_at !== "string" ||
|
|
21
|
+
!Number.isFinite(Date.parse(value.published_at))
|
|
22
|
+
)
|
|
23
|
+
throw new Error(
|
|
24
|
+
"Stable eligibility requires the exact public release identity",
|
|
25
|
+
);
|
|
26
|
+
return {
|
|
27
|
+
id: value.id,
|
|
28
|
+
tag,
|
|
29
|
+
publishedAt: new Date(value.published_at).toISOString(),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function exactTag(get, tag) {
|
|
34
|
+
const pointer = await get(`/git/ref/tags/${encodeURIComponent(tag)}`);
|
|
35
|
+
if (
|
|
36
|
+
pointer.ref !== `refs/tags/${tag}` ||
|
|
37
|
+
!SHA.test(pointer.object?.sha || "")
|
|
38
|
+
)
|
|
39
|
+
throw new Error("Stable release tag identity changed");
|
|
40
|
+
let object = pointer.object;
|
|
41
|
+
for (let depth = 0; object.type === "tag" && depth < 4; depth++) {
|
|
42
|
+
const annotated = await get(`/git/tags/${object.sha}`);
|
|
43
|
+
if (annotated.sha !== object.sha || !SHA.test(annotated.object?.sha || ""))
|
|
44
|
+
throw new Error("Stable annotated tag identity changed");
|
|
45
|
+
object = annotated.object;
|
|
46
|
+
}
|
|
47
|
+
if (object.type !== "commit" || !SHA.test(object.sha))
|
|
48
|
+
throw new Error(
|
|
49
|
+
"Stable release tag must resolve to a bounded exact commit",
|
|
50
|
+
);
|
|
51
|
+
return { object: pointer.object.sha, commit: object.sha };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function releases(get) {
|
|
55
|
+
const result = [],
|
|
56
|
+
ids = new Set();
|
|
57
|
+
for (let page = 1; page <= 20; page++) {
|
|
58
|
+
const values = await get(`/releases?per_page=100&page=${page}`);
|
|
59
|
+
if (!Array.isArray(values) || values.length > 100)
|
|
60
|
+
throw new Error("Stable release inventory is incomplete");
|
|
61
|
+
for (const value of values) {
|
|
62
|
+
if (!Number.isSafeInteger(value.id) || value.id < 1 || ids.has(value.id))
|
|
63
|
+
throw new Error(
|
|
64
|
+
"Stable release inventory has duplicate or invalid identities",
|
|
65
|
+
);
|
|
66
|
+
ids.add(value.id);
|
|
67
|
+
result.push(value);
|
|
68
|
+
}
|
|
69
|
+
if (values.length < 100) return result;
|
|
70
|
+
}
|
|
71
|
+
throw new Error("Stable release inventory exceeds its complete-read bound");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function predecessors(values, plan) {
|
|
75
|
+
const older = [],
|
|
76
|
+
tags = new Set(),
|
|
77
|
+
major = plan.version.split(".")[0];
|
|
78
|
+
for (const value of values) {
|
|
79
|
+
const match = STABLE.exec(value.tag_name || "");
|
|
80
|
+
if (!match || match[1] !== major || value.draft === true) continue;
|
|
81
|
+
const release = publishedRelease(
|
|
82
|
+
value,
|
|
83
|
+
plan.source.repository,
|
|
84
|
+
value.tag_name,
|
|
85
|
+
false,
|
|
86
|
+
);
|
|
87
|
+
if (tags.has(release.tag))
|
|
88
|
+
throw new Error("Stable release inventory repeats an exact tag");
|
|
89
|
+
tags.add(release.tag);
|
|
90
|
+
const order = compareDevelopmentVersions(
|
|
91
|
+
release.tag.slice(1),
|
|
92
|
+
plan.version,
|
|
93
|
+
);
|
|
94
|
+
if (order > 0)
|
|
95
|
+
throw new Error("A newer stable product has already been published");
|
|
96
|
+
// The current version can already exist during recovery of the same
|
|
97
|
+
// transaction. The effect journal separately verifies its exact bytes.
|
|
98
|
+
if (order < 0) older.push(release);
|
|
99
|
+
}
|
|
100
|
+
older.sort((a, b) =>
|
|
101
|
+
compareDevelopmentVersions(b.tag.slice(1), a.tag.slice(1)),
|
|
102
|
+
);
|
|
103
|
+
const comparison = older[0] || null;
|
|
104
|
+
const latest =
|
|
105
|
+
[...older].sort((a, b) => b.publishedAt.localeCompare(a.publishedAt))[0] ||
|
|
106
|
+
null;
|
|
107
|
+
return { comparison, latest };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function treeFiles(get, commitSha) {
|
|
111
|
+
const commit = await get(`/git/commits/${commitSha}`);
|
|
112
|
+
if (commit.sha !== commitSha || !SHA.test(commit.tree?.sha || ""))
|
|
113
|
+
throw new Error("Stable source commit or tree identity changed");
|
|
114
|
+
const tree = await get(`/git/trees/${commit.tree.sha}?recursive=1`);
|
|
115
|
+
if (
|
|
116
|
+
tree.sha !== commit.tree.sha ||
|
|
117
|
+
tree.truncated !== false ||
|
|
118
|
+
!Array.isArray(tree.tree) ||
|
|
119
|
+
tree.tree.length > 100_000
|
|
120
|
+
)
|
|
121
|
+
throw new Error("Stable product difference requires a complete Git tree");
|
|
122
|
+
const files = new Map(),
|
|
123
|
+
paths = new Set();
|
|
124
|
+
for (const entry of tree.tree) {
|
|
125
|
+
if (
|
|
126
|
+
typeof entry.path !== "string" ||
|
|
127
|
+
!entry.path ||
|
|
128
|
+
entry.path
|
|
129
|
+
.split("/")
|
|
130
|
+
.some((part) => !part || part === "." || part === "..") ||
|
|
131
|
+
paths.has(entry.path) ||
|
|
132
|
+
!SHA.test(entry.sha || "")
|
|
133
|
+
)
|
|
134
|
+
throw new Error("Stable product tree has an ambiguous or invalid path");
|
|
135
|
+
paths.add(entry.path);
|
|
136
|
+
const modes = {
|
|
137
|
+
tree: ["040000"],
|
|
138
|
+
blob: ["100644", "100755", "120000"],
|
|
139
|
+
commit: ["160000"],
|
|
140
|
+
};
|
|
141
|
+
if (!modes[entry.type]?.includes(entry.mode))
|
|
142
|
+
throw new Error(
|
|
143
|
+
"Stable product tree has an unsupported entry type or mode",
|
|
144
|
+
);
|
|
145
|
+
if (entry.type !== "tree")
|
|
146
|
+
files.set(entry.path, {
|
|
147
|
+
sha: entry.sha,
|
|
148
|
+
mode: entry.mode,
|
|
149
|
+
type: entry.type,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return { tree: commit.tree.sha, files };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function impactDocument(get, files, pathname, version) {
|
|
156
|
+
const entry = files.get(pathname);
|
|
157
|
+
if (entry?.type !== "blob" || !["100644", "100755"].includes(entry.mode))
|
|
158
|
+
throw new Error(
|
|
159
|
+
"Stable impact must be a regular file in the exact Alpha tree",
|
|
160
|
+
);
|
|
161
|
+
const blob = await get(`/git/blobs/${entry.sha}`);
|
|
162
|
+
if (
|
|
163
|
+
blob.sha !== entry.sha ||
|
|
164
|
+
blob.encoding !== "base64" ||
|
|
165
|
+
!Number.isSafeInteger(blob.size) ||
|
|
166
|
+
blob.size < 1 ||
|
|
167
|
+
blob.size > 1024 * 1024 ||
|
|
168
|
+
typeof blob.content !== "string" ||
|
|
169
|
+
blob.content.length > 2 * 1024 * 1024
|
|
170
|
+
)
|
|
171
|
+
throw new Error("Stable impact exceeds its exact Git blob boundary");
|
|
172
|
+
const bytes = Buffer.from(blob.content, "base64");
|
|
173
|
+
const sha = createHash("sha1")
|
|
174
|
+
.update(`blob ${bytes.length}\0`)
|
|
175
|
+
.update(bytes)
|
|
176
|
+
.digest("hex");
|
|
177
|
+
if (bytes.length !== blob.size || sha !== entry.sha)
|
|
178
|
+
throw new Error("Stable impact Git blob bytes changed");
|
|
179
|
+
const value = JSON.parse(
|
|
180
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes),
|
|
181
|
+
);
|
|
182
|
+
if (value?.release?.version !== version)
|
|
183
|
+
throw new Error("Stable impact does not belong to the exact Alpha version");
|
|
184
|
+
return {
|
|
185
|
+
path: pathname,
|
|
186
|
+
blob: sha,
|
|
187
|
+
digest: `sha256:${createHash("sha256").update(bytes).digest("hex")}`,
|
|
188
|
+
value,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function changedPaths(before, after) {
|
|
193
|
+
return [...new Set([...before.keys(), ...after.keys()])]
|
|
194
|
+
.filter(
|
|
195
|
+
(pathname) =>
|
|
196
|
+
recordDigest(before.get(pathname) || null) !==
|
|
197
|
+
recordDigest(after.get(pathname) || null),
|
|
198
|
+
)
|
|
199
|
+
.sort();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function stableSourceReader(plan, host) {
|
|
203
|
+
verifyPipelinePublicationPlan(plan);
|
|
204
|
+
const repository = plan.source.repository;
|
|
205
|
+
if (
|
|
206
|
+
host.repository !== repository ||
|
|
207
|
+
!/^[\w.-]+\/[\w.-]+$/u.test(repository) ||
|
|
208
|
+
plan.channel !== "stable" ||
|
|
209
|
+
!plan.stablePolicy ||
|
|
210
|
+
!/^\d+\.\d+\.\d+-alpha\.\d+$/u.test(plan.candidateVersion) ||
|
|
211
|
+
plan.candidateVersion.split("-alpha.")[0] !== plan.version
|
|
212
|
+
)
|
|
213
|
+
throw new Error(
|
|
214
|
+
"Stable source collection requires its exact repository and Alpha candidate plan",
|
|
215
|
+
);
|
|
216
|
+
const base = `/repos/${repository}`;
|
|
217
|
+
normalInputs({ "config-path": plan.source.configPath });
|
|
218
|
+
const request = (endpoint, options = {}) => {
|
|
219
|
+
if (
|
|
220
|
+
!endpoint.startsWith(`${base}/`) ||
|
|
221
|
+
(options.method && options.method !== "GET")
|
|
222
|
+
)
|
|
223
|
+
throw new Error(
|
|
224
|
+
"Stable source collection cannot mutate or cross repositories",
|
|
225
|
+
);
|
|
226
|
+
return host.request(endpoint, options);
|
|
227
|
+
};
|
|
228
|
+
const get = (pathname) => request(`${base}${pathname}`);
|
|
229
|
+
return { repository, request, get };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function recheckStableSources(
|
|
233
|
+
get,
|
|
234
|
+
{ plan, pointer, release, selected, prior, latest },
|
|
235
|
+
) {
|
|
236
|
+
const repository = plan.source.repository,
|
|
237
|
+
tag = `v${plan.candidateVersion}`;
|
|
238
|
+
const repeatedRelease = publishedRelease(
|
|
239
|
+
await get(`/releases/tags/${tag}`),
|
|
240
|
+
repository,
|
|
241
|
+
tag,
|
|
242
|
+
true,
|
|
243
|
+
);
|
|
244
|
+
if (
|
|
245
|
+
recordDigest(await exactTag(get, tag)) !== recordDigest(pointer) ||
|
|
246
|
+
recordDigest(repeatedRelease) !== recordDigest(release) ||
|
|
247
|
+
recordDigest(predecessors(await releases(get), plan)) !==
|
|
248
|
+
recordDigest(selected) ||
|
|
249
|
+
(prior &&
|
|
250
|
+
recordDigest(await exactTag(get, selected.comparison.tag)) !==
|
|
251
|
+
recordDigest(prior)) ||
|
|
252
|
+
(latest &&
|
|
253
|
+
recordDigest(await exactTag(get, selected.latest.tag)) !==
|
|
254
|
+
recordDigest(latest))
|
|
255
|
+
)
|
|
256
|
+
throw new Error("Stable source facts changed during provider readback");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Read-only provider facts. This result intentionally contains no successful
|
|
260
|
+
// canary or release authorization; those require independent qualification.
|
|
261
|
+
export async function readPipelineStableSource(plan, host) {
|
|
262
|
+
const { repository, request, get } = stableSourceReader(plan, host);
|
|
263
|
+
const tag = `v${plan.candidateVersion}`;
|
|
264
|
+
const pointer = await exactTag(get, tag);
|
|
265
|
+
if (pointer.commit !== plan.intentSource.commit)
|
|
266
|
+
throw new Error("Published Alpha differs from the exact channel PR source");
|
|
267
|
+
const release = publishedRelease(
|
|
268
|
+
await get(`/releases/tags/${tag}`),
|
|
269
|
+
repository,
|
|
270
|
+
tag,
|
|
271
|
+
true,
|
|
272
|
+
);
|
|
273
|
+
const candidate = await githubPipelineSource(request, repository).source(
|
|
274
|
+
pointer.commit,
|
|
275
|
+
plan.source.configPath,
|
|
276
|
+
);
|
|
277
|
+
if (
|
|
278
|
+
recordDigest(candidate.identity) !== recordDigest(plan.intentSource) ||
|
|
279
|
+
recordDigest(candidate.plan) !== plan.contractRoot ||
|
|
280
|
+
recordDigest(candidate.plan.stable) !== recordDigest(plan.stablePolicy) ||
|
|
281
|
+
recordDigest(candidate.plan.version) !== recordDigest(plan.versionPolicy)
|
|
282
|
+
)
|
|
283
|
+
throw new Error(
|
|
284
|
+
"Published Alpha source or product policy differs from the admitted plan",
|
|
285
|
+
);
|
|
286
|
+
const version = await githubPipelineVersion(request, repository).inspect(
|
|
287
|
+
candidate.identity,
|
|
288
|
+
candidate.plan.version,
|
|
289
|
+
);
|
|
290
|
+
if (version.version !== plan.candidateVersion)
|
|
291
|
+
throw new Error(
|
|
292
|
+
"Published Alpha version documents disagree with its exact tag",
|
|
293
|
+
);
|
|
294
|
+
const selected = predecessors(await releases(get), plan);
|
|
295
|
+
const prior = selected.comparison
|
|
296
|
+
? await exactTag(get, selected.comparison.tag)
|
|
297
|
+
: null;
|
|
298
|
+
if ((prior?.commit || null) !== plan.previousChannelCommit)
|
|
299
|
+
throw new Error(
|
|
300
|
+
"Stable comparison differs from the retained published channel",
|
|
301
|
+
);
|
|
302
|
+
const current = await treeFiles(get, pointer.commit);
|
|
303
|
+
if (current.tree !== candidate.identity.tree)
|
|
304
|
+
throw new Error(
|
|
305
|
+
"Alpha tree changed between independent source observations",
|
|
306
|
+
);
|
|
307
|
+
const config = current.files.get(candidate.identity.configPath);
|
|
308
|
+
if (
|
|
309
|
+
config?.type !== "blob" ||
|
|
310
|
+
!["100644", "100755"].includes(config.mode) ||
|
|
311
|
+
config.sha !== candidate.identity.configBlob
|
|
312
|
+
)
|
|
313
|
+
throw new Error(
|
|
314
|
+
"Alpha configuration is not the exact blob in its complete tree",
|
|
315
|
+
);
|
|
316
|
+
const previous = prior
|
|
317
|
+
? await treeFiles(get, prior.commit)
|
|
318
|
+
: { files: new Map() };
|
|
319
|
+
const impact = await impactDocument(
|
|
320
|
+
get,
|
|
321
|
+
current.files,
|
|
322
|
+
plan.stablePolicy.impact_file,
|
|
323
|
+
plan.candidateVersion,
|
|
324
|
+
);
|
|
325
|
+
const latest =
|
|
326
|
+
selected.latest &&
|
|
327
|
+
(selected.latest.tag === selected.comparison.tag
|
|
328
|
+
? prior
|
|
329
|
+
: await exactTag(get, selected.latest.tag));
|
|
330
|
+
await recheckStableSources(get, {
|
|
331
|
+
plan,
|
|
332
|
+
pointer,
|
|
333
|
+
release,
|
|
334
|
+
selected,
|
|
335
|
+
prior,
|
|
336
|
+
latest,
|
|
337
|
+
});
|
|
338
|
+
const body = {
|
|
339
|
+
schema: "buildchain.pipeline-stable-source/v1",
|
|
340
|
+
planRoot: plan.root,
|
|
341
|
+
source: candidate.identity,
|
|
342
|
+
contractRoot: plan.contractRoot,
|
|
343
|
+
candidate: { ...release, sha: pointer.commit, tree: current.tree },
|
|
344
|
+
previousStable: selected.latest
|
|
345
|
+
? { ...selected.latest, sha: latest.commit }
|
|
346
|
+
: null,
|
|
347
|
+
comparisonStable: selected.comparison
|
|
348
|
+
? { ...selected.comparison, sha: prior.commit, tree: previous.tree }
|
|
349
|
+
: null,
|
|
350
|
+
impact,
|
|
351
|
+
changedPaths: changedPaths(previous.files, current.files),
|
|
352
|
+
};
|
|
353
|
+
return { ...body, root: recordDigest(body) };
|
|
354
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import artifact from "@actions/artifact";
|
|
4
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
5
|
+
import {
|
|
6
|
+
publicationPath,
|
|
7
|
+
writeImmutablePublicationFile,
|
|
8
|
+
} from "../../publication/pipeline/files.js";
|
|
9
|
+
import { pipelineVersionRegenerationResult } from "../../publication/pipeline/version-regeneration.js";
|
|
10
|
+
import {
|
|
11
|
+
pipelineVersionArtifactName,
|
|
12
|
+
readPipelineVersionBuild,
|
|
13
|
+
} from "./pipeline-version-readback.js";
|
|
14
|
+
|
|
15
|
+
function verifyResult(context, result, platform) {
|
|
16
|
+
const expected = pipelineVersionRegenerationResult(
|
|
17
|
+
context.preparation,
|
|
18
|
+
platform,
|
|
19
|
+
result.files,
|
|
20
|
+
);
|
|
21
|
+
if (recordDigest(result) !== recordDigest(expected))
|
|
22
|
+
throw new Error(
|
|
23
|
+
"Version artifact changed its exact preparation, platform or bytes",
|
|
24
|
+
);
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function githubPipelineVersionArtifacts(host, client = artifact) {
|
|
29
|
+
async function upload(context, result, directory) {
|
|
30
|
+
verifyResult(context, result, result.platform);
|
|
31
|
+
const file = writeImmutablePublicationFile(
|
|
32
|
+
path.join(directory, "version.json"),
|
|
33
|
+
JSON.stringify(result),
|
|
34
|
+
);
|
|
35
|
+
const uploaded = await client.uploadArtifact(
|
|
36
|
+
pipelineVersionArtifactName(context, result.platform),
|
|
37
|
+
[file],
|
|
38
|
+
directory,
|
|
39
|
+
{
|
|
40
|
+
retentionDays: 30,
|
|
41
|
+
compressionLevel: 0,
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
if (!uploaded.id || !uploaded.digest)
|
|
45
|
+
throw new Error("Version upload omitted immutable provider coordinates");
|
|
46
|
+
return uploaded;
|
|
47
|
+
}
|
|
48
|
+
async function download(context, directory) {
|
|
49
|
+
const { build, assets } = await readPipelineVersionBuild(context, host);
|
|
50
|
+
const [repositoryOwner, repositoryName] = host.repository.split("/");
|
|
51
|
+
const results = [];
|
|
52
|
+
for (const [index, asset] of assets.entries()) {
|
|
53
|
+
const target = path.join(directory, String(asset.id));
|
|
54
|
+
fs.mkdirSync(target, { recursive: true });
|
|
55
|
+
const downloaded = await client.downloadArtifact(asset.id, {
|
|
56
|
+
path: target,
|
|
57
|
+
expectedHash: asset.digest,
|
|
58
|
+
findBy: {
|
|
59
|
+
repositoryOwner,
|
|
60
|
+
repositoryName,
|
|
61
|
+
workflowRunId: context.runId,
|
|
62
|
+
token: host.token,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
if (downloaded.digestMismatch)
|
|
66
|
+
throw new Error(
|
|
67
|
+
"Version download digest differs from its provider archive",
|
|
68
|
+
);
|
|
69
|
+
if (
|
|
70
|
+
recordDigest(fs.readdirSync(target)) !== recordDigest(["version.json"])
|
|
71
|
+
)
|
|
72
|
+
throw new Error("Version artifact contains undeclared files");
|
|
73
|
+
const file = publicationPath(target, "version.json");
|
|
74
|
+
// JSON escaping can expand the bounded UTF-8 document inventory sixfold.
|
|
75
|
+
if (fs.statSync(file).size > 384 * 1024 * 1024)
|
|
76
|
+
throw new Error("Version artifact exceeds its encoded material limit");
|
|
77
|
+
const bytes = fs.readFileSync(file);
|
|
78
|
+
if (!Buffer.from(bytes.toString("utf8")).equals(bytes))
|
|
79
|
+
throw new Error("Version artifact is not canonical UTF-8 text");
|
|
80
|
+
results.push(
|
|
81
|
+
verifyResult(
|
|
82
|
+
context,
|
|
83
|
+
JSON.parse(bytes.toString("utf8")),
|
|
84
|
+
context.preparation.platforms[index],
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return { build, results };
|
|
89
|
+
}
|
|
90
|
+
return { upload, download };
|
|
91
|
+
}
|