@kungfu-tech/buildchain 4.0.2-alpha.9 → 4.0.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/AGENTS.md +10 -1
- package/CONTRIBUTING.md +24 -0
- package/README.md +2 -2
- package/actions/promote-buildchain-ref/README.md +1 -1
- package/actions/v4-release-candidate-promote/README.md +28 -0
- package/architecture/agent-change-map.md +64 -7
- package/architecture/ci-lane-change-budget.json +366 -0
- package/architecture/internal-capabilities.json +53 -8
- package/architecture/maintainability-debt.json +147 -93
- package/architecture/maintainability-policy.json +32 -28
- package/architecture/release-tail-contract-inventory.json +6 -6
- package/architecture/v4-architecture-constitution.md +23 -20
- package/architecture/v4-cross-platform-adopter-qualification.json +1 -1
- package/architecture/v4-delivery-authority-parity.json +2 -2
- package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.json +1 -1
- package/architecture/v4-floating-consumer-policy.json +2 -2
- package/architecture/v4-next-development-parity.json +5 -5
- package/architecture/v4-partial-mutation-recovery-qualification.json +4 -1
- package/architecture/v4-platform-stage-checkpoints.json +1 -1
- package/architecture/v4-provider-operation-journal-contract.json +5 -2
- package/architecture/v4-publication-facade-patches.json +64 -0
- package/architecture/v4-release-activation-shadow-domain.json +5 -2
- package/architecture/v4-release-topology.json +74 -39
- package/architecture/v4-rust-wasm-production-authority.json +58 -0
- package/architecture/v4-stable-publication-fence.json +5 -2
- package/architecture/v4-stage-capsule-qualification.json +2 -2
- package/architecture/v4-tail-reseal-parity.json +8 -6
- package/architecture/v4-universal-workflow-bootstrap.json +171 -0
- package/architecture/v4-universal-workflow-fault-campaign.json +72 -0
- package/architecture/v4-universal-workflow-train-admission.json +40 -0
- package/architecture/workflow-taxonomy.json +1451 -0
- package/bin/buildchain.mjs +7 -7
- package/contracts/buildchain-v2-residuals-v1.json +57 -48
- package/contracts/dev-delivery-authority-v2.schema.json +16 -2
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/contracts/v4-canonical-contracts-v1.schema.json +1 -0
- package/dist/site/buildchain-contract.json +93 -48
- package/dist/site/buildchain-site.json +222 -69
- package/dist/site/capability-registry.json +4 -4
- package/dist/site/controller-registry.json +50 -13
- package/dist/site/kfd-claims.json +2585 -1000
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +13 -13
- package/dist/site/node-api-registry.json +120 -115
- package/dist/site/page-registry.json +204 -51
- package/dist/site/public-surface-audit.json +3205 -1219
- package/dist/site/publication-authority-registry.json +1124 -362
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +16 -2
- package/dist/site/site-manifest.json +17 -17
- package/dist/site/workflow-registry.json +5061 -1659
- package/docs/MAP.md +1 -0
- package/docs/auditable-demo.md +1 -1
- package/docs/cli.md +21 -8
- package/docs/dev-delivery-warrant.md +1 -1
- package/docs/engineering-housekeeper.md +3 -3
- package/docs/kfd-support.md +26 -2
- package/docs/next-development-transition.md +2 -2
- package/docs/node-api-reference.md +190 -190
- package/docs/publication-authority.md +2 -2
- package/docs/release-candidate.md +3 -3
- package/docs/release-flow.md +67 -19
- package/docs/release-governance.md +8 -8
- package/docs/release-passport.md +27 -2
- package/docs/reusable-build-surface.md +16 -0
- package/docs/v4-adopter-delivery.md +2 -2
- package/docs/v4-production-release.md +71 -8
- package/docs/v4-rust-wasm-production-authority.md +58 -0
- package/docs/v4-stage-capsule.md +2 -2
- package/docs/v4-tail-reseal.md +1 -1
- package/docs/workflow-catalog.md +154 -0
- package/docs/workflow-path-migration.md +109 -0
- package/package.json +10 -6
- package/packages/core/artifact-verification-envelope.js +5 -5
- package/packages/core/buildchain-contract.js +1 -1
- package/packages/core/buildchain-kfd-claims.js +2 -2
- package/packages/core/buildchain-publication-authority.js +42 -26
- package/packages/core/buildchain-v4-domain.wasm +0 -0
- package/packages/core/ci-lane-change-budget.js +14 -1
- package/packages/core/controller-evidence.js +4 -1
- package/packages/core/dev-delivery-candidate-identity.js +40 -6
- package/packages/core/dev-delivery-warrant-state.js +3 -4
- package/packages/core/dev-delivery-warrant.js +12 -2
- package/packages/core/github-governance-authority.js +4 -4
- package/packages/core/kfd-adopter-manifest.js +46 -47
- package/packages/core/next-development-projection.js +2 -2
- package/packages/core/paper-agent-entry.js +30 -4
- package/packages/core/paper-fleet.js +1 -1
- package/packages/core/paper-runtime-channels.js +348 -0
- package/packages/core/paper-scaffold-content.js +10 -0
- package/packages/core/paper.js +37 -71
- package/packages/core/release-line-dry-run.js +1 -1
- package/packages/core/release-passport-contract.js +5 -5
- package/packages/core/release-passport.js +5 -4
- package/packages/core/release-tail-provider-plane.js +68 -1048
- package/packages/core/v4-canonical-contracts.js +10 -75
- package/packages/core/v4-domain-wasm-artifact.js +7 -0
- package/packages/core/v4-domain-wasm.js +216 -0
- package/packages/core/v4-floating-consumer-evidence.js +1 -1
- package/packages/core/v4-floating-consumer-policy.js +1 -1
- package/packages/core/v4-partial-mutation-recovery-qualification.js +9 -386
- package/packages/core/v4-product-publication.js +19 -349
- package/packages/core/v4-protected-publication-source.js +19 -21
- package/packages/core/v4-provider-operation-journal.js +18 -487
- package/packages/core/v4-provider-readback-idempotency.js +12 -97
- package/packages/core/v4-release-activation-shadow.js +13 -500
- package/packages/core/v4-release-invocation.js +28 -389
- package/packages/core/v4-stable-publication-fence.js +13 -342
- package/packages/core/v4-universal-workflow-bootstrap.js +586 -0
- package/scripts/binary-publication-evidence.mjs +135 -0
- package/scripts/build-contract-core.mjs +1 -14
- package/scripts/build-v4-domain-wasm.mjs +189 -0
- package/scripts/check-action-bundles.mjs +12 -2
- package/scripts/check-ci-lane-change-budget.mjs +4 -1
- package/scripts/check-core-mechanism-inventory.mjs +4 -3
- package/scripts/check-inventory.mjs +35 -44
- package/scripts/check-maintainability.mjs +16 -113
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +2 -2
- package/scripts/check-v4-public-dogfood-contract.mjs +22 -9
- package/scripts/check-v4-release-topology.mjs +90 -33
- package/scripts/check-v4-runtime-semantic-closure.mjs +4 -1
- package/scripts/check-v4-universal-workflow-bootstrap.mjs +266 -0
- package/scripts/check-workflow-taxonomy.mjs +6 -0
- package/scripts/copy-v4-domain-wasm.mjs +14 -0
- package/scripts/dev-delivery-authority-command-adapters.mjs +1 -1
- package/scripts/dev-delivery-authority.mjs +8 -8
- package/scripts/dev-delivery-source-proof-reuse.mjs +2 -1
- package/scripts/dev-delivery-warrant-options.mjs +14 -9
- package/scripts/dev-delivery-warrant-store.mjs +73 -7
- package/scripts/dev-delivery-warrant-transition.mjs +109 -0
- package/scripts/dev-delivery-warrant.mjs +76 -99
- package/scripts/dev-pr-auto-merge.mjs +15 -15
- package/scripts/generate-channel-build-workflow.mjs +7 -2
- package/scripts/generate-channel-promotion-workflow.mjs +7 -5
- package/scripts/generate-site-bundle.mjs +11 -11
- package/scripts/generate-v4-universal-workflow-facades.mjs +458 -0
- package/scripts/generate-workflow-taxonomy.mjs +25 -0
- package/scripts/github-output.mjs +31 -0
- package/scripts/maintainability-metrics.mjs +16 -5
- package/scripts/maintainability-public-surface.mjs +115 -0
- package/scripts/next-development-review.mjs +307 -0
- package/scripts/paper-work-fleet-cli.mjs +2 -0
- package/scripts/release-asset-client.mjs +91 -0
- package/scripts/release-candidate-resolver.mjs +9 -9
- package/scripts/source-verification-evidence.mjs +344 -0
- package/scripts/stable-candidate-qualification.mjs +1 -1
- package/scripts/universal-facade-maintainability.mjs +82 -0
- package/scripts/v4-product-publication-intent.mjs +13 -6
- package/scripts/v4-publication-settlement.mjs +211 -0
- package/scripts/v4-universal-workflow-backflow.mjs +212 -0
- package/scripts/v4-universal-workflow-engine.mjs +618 -0
- package/scripts/v4-universal-workflow-self-dogfood.mjs +205 -0
- package/scripts/verify-version-state-delta.mjs +173 -0
- package/scripts/workflow-taxonomy.mjs +507 -0
- package/templates/native-dev-delivery.yml +4 -7
- package/templates/universal-buildchain-bootstrap-recovery.yml +282 -0
- package/templates/universal-buildchain-bootstrap.yml +28 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { verifyVersionStateDelta } from "./verify-version-state-delta.mjs";
|
|
6
|
+
import { readBinaryPublicationEvidence } from "./binary-publication-evidence.mjs";
|
|
7
|
+
import { releaseAssetClient } from "./release-asset-client.mjs";
|
|
8
|
+
import { enqueueNextDevelopmentPullRequest } from "../actions/v4-release-candidate-promote/next-development-queue.js";
|
|
9
|
+
|
|
10
|
+
const REPOSITORY = "kungfu-systems/buildchain";
|
|
11
|
+
const WORKFLOW = ".github/workflows/self-build-verify.yml";
|
|
12
|
+
const REVIEWER = "kungfu-origin";
|
|
13
|
+
const SHA = /^[a-f0-9]{40}$/u;
|
|
14
|
+
const BRANCH =
|
|
15
|
+
/^chore\/next-development\/(4\.\d+\.\d+-alpha\.\d+)-[a-f0-9]{16}$/u;
|
|
16
|
+
|
|
17
|
+
export function assertReviewRun(run, { repository, runId, headSha } = {}) {
|
|
18
|
+
if (
|
|
19
|
+
repository !== REPOSITORY ||
|
|
20
|
+
run.repository?.full_name !== repository ||
|
|
21
|
+
String(run.id) !== String(runId) ||
|
|
22
|
+
run.path !== WORKFLOW ||
|
|
23
|
+
run.name !== "Verify" ||
|
|
24
|
+
run.event !== "pull_request" ||
|
|
25
|
+
run.status !== "completed" ||
|
|
26
|
+
run.conclusion !== "success" ||
|
|
27
|
+
!SHA.test(run.head_sha || "") ||
|
|
28
|
+
!BRANCH.test(run.head_branch || "") ||
|
|
29
|
+
(headSha && run.head_sha !== headSha)
|
|
30
|
+
)
|
|
31
|
+
throw new Error("unqualified next-development verification run");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function assertReviewPull(
|
|
35
|
+
pull,
|
|
36
|
+
{ repository, headSha, baseSha, branch },
|
|
37
|
+
) {
|
|
38
|
+
if (
|
|
39
|
+
repository !== REPOSITORY ||
|
|
40
|
+
pull.head?.repo?.full_name !== repository ||
|
|
41
|
+
pull.base?.repo?.full_name !== repository ||
|
|
42
|
+
pull.head?.sha !== headSha ||
|
|
43
|
+
pull.base?.sha !== baseSha ||
|
|
44
|
+
pull.head?.ref !== branch ||
|
|
45
|
+
!BRANCH.test(branch || "") ||
|
|
46
|
+
!/^dev\/v4\/v4\.\d+$/u.test(pull.base?.ref || "") ||
|
|
47
|
+
pull.state !== "open" ||
|
|
48
|
+
pull.draft ||
|
|
49
|
+
pull.merged_at ||
|
|
50
|
+
!SHA.test(baseSha || "") ||
|
|
51
|
+
!SHA.test(headSha || "") ||
|
|
52
|
+
pull.user?.login === REVIEWER
|
|
53
|
+
)
|
|
54
|
+
throw new Error(
|
|
55
|
+
"next-development review identity or protected base changed",
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function observe(client, repository, runId, expected = {}) {
|
|
60
|
+
const prefix = `repos/${repository}`;
|
|
61
|
+
const run = client.json(`${prefix}/actions/runs/${runId}`);
|
|
62
|
+
assertReviewRun(run, { repository, runId, headSha: expected.headSha });
|
|
63
|
+
const associated = client.pages(`${prefix}/commits/${run.head_sha}/pulls`);
|
|
64
|
+
const matches = associated.filter(
|
|
65
|
+
(pull) =>
|
|
66
|
+
pull.head?.sha === run.head_sha &&
|
|
67
|
+
pull.head?.ref === run.head_branch &&
|
|
68
|
+
pull.head?.repo?.full_name === repository &&
|
|
69
|
+
pull.state === "open",
|
|
70
|
+
);
|
|
71
|
+
if (matches.length !== 1) throw new Error("ambiguous next-development PR");
|
|
72
|
+
const pull = client.json(`${prefix}/pulls/${matches[0].number}`);
|
|
73
|
+
const baseSha = client.json(`${prefix}/git/ref/heads/${pull.base.ref}`).object
|
|
74
|
+
.sha;
|
|
75
|
+
assertReviewPull(pull, {
|
|
76
|
+
repository,
|
|
77
|
+
headSha: run.head_sha,
|
|
78
|
+
baseSha,
|
|
79
|
+
branch: run.head_branch,
|
|
80
|
+
});
|
|
81
|
+
if (
|
|
82
|
+
expected.baseSha &&
|
|
83
|
+
(baseSha !== expected.baseSha || pull.number !== expected.number)
|
|
84
|
+
)
|
|
85
|
+
throw new Error("next-development review plan became stale");
|
|
86
|
+
const checks = client.pages(
|
|
87
|
+
`${prefix}/actions/runs/${runId}/jobs?filter=latest`,
|
|
88
|
+
"jobs",
|
|
89
|
+
);
|
|
90
|
+
const check = checks.filter((job) => job.name === "check");
|
|
91
|
+
if (
|
|
92
|
+
check.length !== 1 ||
|
|
93
|
+
check[0].status !== "completed" ||
|
|
94
|
+
check[0].conclusion !== "success"
|
|
95
|
+
)
|
|
96
|
+
throw new Error("exact Verify check did not succeed");
|
|
97
|
+
return { run, pull, baseSha };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function verifyNextDevelopmentReview({
|
|
101
|
+
client,
|
|
102
|
+
repository,
|
|
103
|
+
runId,
|
|
104
|
+
git,
|
|
105
|
+
verifyDelta,
|
|
106
|
+
publication,
|
|
107
|
+
}) {
|
|
108
|
+
const { run, pull, baseSha } = observe(client, repository, runId);
|
|
109
|
+
if (git("rev-parse", "HEAD") !== baseSha)
|
|
110
|
+
throw new Error(
|
|
111
|
+
"review runtime is not the exact protected development base",
|
|
112
|
+
);
|
|
113
|
+
git("fetch", "--no-tags", "origin", run.head_sha);
|
|
114
|
+
const parents = git("show", "-s", "--format=%P", run.head_sha).split(" ");
|
|
115
|
+
if (parents.length !== 1 || parents[0] !== baseSha)
|
|
116
|
+
throw new Error("next-development must have one exact protected parent");
|
|
117
|
+
const projection = verifyDelta({ baseSha, headSha: run.head_sha });
|
|
118
|
+
if (BRANCH.exec(run.head_branch)[1] !== projection.version)
|
|
119
|
+
throw new Error(
|
|
120
|
+
"next-development branch version differs from regenerated version",
|
|
121
|
+
);
|
|
122
|
+
const version = JSON.parse(git("show", `${baseSha}:package.json`)).version;
|
|
123
|
+
const tag = `v${version}`;
|
|
124
|
+
const sourceSha = client.json(`repos/${repository}/commits/${tag}`).sha;
|
|
125
|
+
const settlement = await publication({ repository, tag, sourceSha });
|
|
126
|
+
const publishedTree = settlement.documents.passport.source.treeHash;
|
|
127
|
+
if (publishedTree !== git("rev-parse", `${baseSha}^{tree}`))
|
|
128
|
+
throw new Error("development base is not the completed alpha source tree");
|
|
129
|
+
observe(client, repository, runId, {
|
|
130
|
+
headSha: run.head_sha,
|
|
131
|
+
baseSha,
|
|
132
|
+
number: pull.number,
|
|
133
|
+
});
|
|
134
|
+
return {
|
|
135
|
+
schema: "buildchain.next-development-review/v1",
|
|
136
|
+
repository,
|
|
137
|
+
runId,
|
|
138
|
+
number: pull.number,
|
|
139
|
+
headSha: run.head_sha,
|
|
140
|
+
baseSha,
|
|
141
|
+
branch: run.head_branch,
|
|
142
|
+
publicationReceiptRoot: settlement.documents.receipt.receiptRoot,
|
|
143
|
+
projection,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function approveNextDevelopment({ client, reviewer, plan }) {
|
|
148
|
+
if (plan.schema !== "buildchain.next-development-review/v1")
|
|
149
|
+
throw new Error("missing verified next-development plan");
|
|
150
|
+
const { pull } = observe(client, plan.repository, plan.runId, plan);
|
|
151
|
+
const identity = reviewer.json("user");
|
|
152
|
+
if (identity.login !== REVIEWER || identity.login === pull.user?.login)
|
|
153
|
+
throw new Error(
|
|
154
|
+
"next-development requires the independent CODEOWNER identity",
|
|
155
|
+
);
|
|
156
|
+
const endpoint = `repos/${plan.repository}/pulls/${plan.number}/reviews`;
|
|
157
|
+
const approved = (review) =>
|
|
158
|
+
review.user?.login === REVIEWER &&
|
|
159
|
+
review.commit_id === plan.headSha &&
|
|
160
|
+
review.state === "APPROVED";
|
|
161
|
+
const reviews = client.pages(endpoint);
|
|
162
|
+
const latest = reviews
|
|
163
|
+
.filter(
|
|
164
|
+
(review) =>
|
|
165
|
+
review.user?.login === REVIEWER &&
|
|
166
|
+
["APPROVED", "CHANGES_REQUESTED", "DISMISSED"].includes(review.state),
|
|
167
|
+
)
|
|
168
|
+
.at(-1);
|
|
169
|
+
if (latest?.state === "CHANGES_REQUESTED")
|
|
170
|
+
throw new Error(
|
|
171
|
+
"independent reviewer requested changes; automation cannot override it",
|
|
172
|
+
);
|
|
173
|
+
if (!latest || !approved(latest)) {
|
|
174
|
+
observe(client, plan.repository, plan.runId, plan);
|
|
175
|
+
await reviewer.post(endpoint, {
|
|
176
|
+
event: "APPROVE",
|
|
177
|
+
commit_id: plan.headSha,
|
|
178
|
+
body: `Verified the exact version-only transition by regenerating all tracked bytes from protected base ${plan.baseSha}. Completed alpha receipt: ${plan.publicationReceiptRoot}. Verification run: https://github.com/${plan.repository}/actions/runs/${plan.runId}.`,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
observe(client, plan.repository, plan.runId, plan);
|
|
182
|
+
const readback = client.pages(endpoint).filter(approved).at(-1);
|
|
183
|
+
if (!readback)
|
|
184
|
+
throw new Error("independent exact-head approval readback failed");
|
|
185
|
+
return { ...plan, reviewId: readback.id, reviewUrl: readback.html_url };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function api(token) {
|
|
189
|
+
const call = (endpoint, args = [], input) =>
|
|
190
|
+
JSON.parse(
|
|
191
|
+
execFileSync("gh", ["api", endpoint, ...args], {
|
|
192
|
+
encoding: "utf8",
|
|
193
|
+
input,
|
|
194
|
+
env: { ...process.env, GH_TOKEN: token },
|
|
195
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
196
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
return {
|
|
200
|
+
json: (endpoint) => call(endpoint),
|
|
201
|
+
pages: (endpoint, field) =>
|
|
202
|
+
call(endpoint, ["--paginate", "--slurp"]).flatMap((page) =>
|
|
203
|
+
field ? page[field] : page,
|
|
204
|
+
),
|
|
205
|
+
post: (endpoint, body) =>
|
|
206
|
+
call(
|
|
207
|
+
endpoint,
|
|
208
|
+
["--method", "POST", "--input", "-"],
|
|
209
|
+
JSON.stringify(body),
|
|
210
|
+
),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function main(mode) {
|
|
215
|
+
const repository = process.env.GITHUB_REPOSITORY;
|
|
216
|
+
const runId = process.env.VERIFY_RUN_ID;
|
|
217
|
+
if (!/^\d+$/u.test(runId || "") || repository !== REPOSITORY)
|
|
218
|
+
throw new Error("exact repository and verification run required");
|
|
219
|
+
const client = api(process.env.GH_TOKEN);
|
|
220
|
+
const file = ".buildchain/next-development-review.json";
|
|
221
|
+
if (mode === "verify") {
|
|
222
|
+
if (process.env.BUILDCHAIN_APPROVAL_TOKEN)
|
|
223
|
+
throw new Error("verification must not receive the approval credential");
|
|
224
|
+
const git = (...args) =>
|
|
225
|
+
execFileSync("git", args, {
|
|
226
|
+
encoding: "utf8",
|
|
227
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
228
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
229
|
+
}).trim();
|
|
230
|
+
const plan = await verifyNextDevelopmentReview({
|
|
231
|
+
client,
|
|
232
|
+
repository,
|
|
233
|
+
runId,
|
|
234
|
+
git,
|
|
235
|
+
verifyDelta: verifyVersionStateDelta,
|
|
236
|
+
publication: (args) =>
|
|
237
|
+
readBinaryPublicationEvidence({
|
|
238
|
+
...args,
|
|
239
|
+
client: releaseAssetClient(repository),
|
|
240
|
+
attempts: 1,
|
|
241
|
+
}),
|
|
242
|
+
});
|
|
243
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
244
|
+
fs.writeFileSync(file, `${JSON.stringify(plan, null, 2)}\n`);
|
|
245
|
+
} else if (mode === "approve") {
|
|
246
|
+
if (!process.env.BUILDCHAIN_APPROVAL_TOKEN)
|
|
247
|
+
throw new Error("independent approval credential missing");
|
|
248
|
+
const plan = JSON.parse(fs.readFileSync(file));
|
|
249
|
+
if (plan.repository !== repository || String(plan.runId) !== runId)
|
|
250
|
+
throw new Error("review plan is from another invocation");
|
|
251
|
+
const result = await approveNextDevelopment({
|
|
252
|
+
client,
|
|
253
|
+
plan,
|
|
254
|
+
reviewer: api(process.env.BUILDCHAIN_APPROVAL_TOKEN),
|
|
255
|
+
});
|
|
256
|
+
fs.writeFileSync(file, `${JSON.stringify(result, null, 2)}\n`);
|
|
257
|
+
console.log(
|
|
258
|
+
JSON.stringify({ reviewId: result.reviewId, headSha: result.headSha }),
|
|
259
|
+
);
|
|
260
|
+
} else if (mode === "enqueue") {
|
|
261
|
+
const plan = JSON.parse(fs.readFileSync(file));
|
|
262
|
+
if (
|
|
263
|
+
plan.repository !== repository ||
|
|
264
|
+
String(plan.runId) !== runId ||
|
|
265
|
+
!plan.reviewId
|
|
266
|
+
)
|
|
267
|
+
throw new Error(
|
|
268
|
+
"exact independent review readback required before enqueue",
|
|
269
|
+
);
|
|
270
|
+
const { pull } = observe(client, repository, runId, plan);
|
|
271
|
+
const reviews = client.pages(
|
|
272
|
+
`repos/${repository}/pulls/${plan.number}/reviews`,
|
|
273
|
+
);
|
|
274
|
+
if (
|
|
275
|
+
!reviews.some(
|
|
276
|
+
(review) =>
|
|
277
|
+
review.id === plan.reviewId &&
|
|
278
|
+
review.user?.login === REVIEWER &&
|
|
279
|
+
review.commit_id === plan.headSha &&
|
|
280
|
+
review.state === "APPROVED",
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
throw new Error("independent approval no longer qualifies enqueue");
|
|
284
|
+
await enqueueNextDevelopmentPullRequest({
|
|
285
|
+
pull,
|
|
286
|
+
headSha: plan.headSha,
|
|
287
|
+
mutationOctokit: {
|
|
288
|
+
graphql: async (query, variables) => {
|
|
289
|
+
const result = client.post("graphql", { query, variables });
|
|
290
|
+
if (result.errors?.length)
|
|
291
|
+
throw new Error(
|
|
292
|
+
result.errors.map((error) => error.message).join("; "),
|
|
293
|
+
);
|
|
294
|
+
return result.data;
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
wait: (delay) => new Promise((resolve) => setTimeout(resolve, delay)),
|
|
298
|
+
maxPolls: 20,
|
|
299
|
+
});
|
|
300
|
+
} else throw new Error("expected verify, approve or enqueue");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
304
|
+
main(process.argv[2]).catch((error) => {
|
|
305
|
+
console.error(error.message);
|
|
306
|
+
process.exitCode = 1;
|
|
307
|
+
});
|
|
@@ -418,6 +418,8 @@ function runMigration(options) {
|
|
|
418
418
|
options.buildchainVersion,
|
|
419
419
|
),
|
|
420
420
|
buildchainSha: options.buildchainSha,
|
|
421
|
+
stableBuildchainRoot: readFlag(options.args, "stable-buildchain-root", ""),
|
|
422
|
+
alphaBuildchainRoot: readFlag(options.args, "alpha-buildchain-root", ""),
|
|
421
423
|
});
|
|
422
424
|
return options.args.some((entry) => ["--write", "--execute"].includes(entry))
|
|
423
425
|
? writePaperMigration(plan)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
reconcileImmutableReleaseAssets,
|
|
6
|
+
uniqueReleaseAssets,
|
|
7
|
+
} from "../actions/promote-buildchain-ref/reuse-complete-release.js";
|
|
8
|
+
|
|
9
|
+
export function releaseAssetClient(
|
|
10
|
+
repository,
|
|
11
|
+
{ execute = execFileSync, token } = {},
|
|
12
|
+
) {
|
|
13
|
+
if (!/^[\w.-]+\/[\w.-]+$/u.test(repository))
|
|
14
|
+
throw new Error("exact repository required");
|
|
15
|
+
const [owner, repo] = repository.split("/");
|
|
16
|
+
const raw = (endpoint, args = []) =>
|
|
17
|
+
execute("gh", ["api", endpoint, ...args], {
|
|
18
|
+
maxBuffer: 128 * 1024 * 1024,
|
|
19
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
20
|
+
...(token ? { env: { ...process.env, GH_TOKEN: token } } : {}),
|
|
21
|
+
});
|
|
22
|
+
const json = (endpoint) => JSON.parse(raw(endpoint));
|
|
23
|
+
const assetBytes = (asset) =>
|
|
24
|
+
raw(`repos/${repository}/releases/assets/${asset.id}`, [
|
|
25
|
+
"-H",
|
|
26
|
+
"Accept: application/octet-stream",
|
|
27
|
+
]);
|
|
28
|
+
return {
|
|
29
|
+
json,
|
|
30
|
+
assetBytes,
|
|
31
|
+
release: (tag) =>
|
|
32
|
+
json(`repos/${repository}/releases/tags/${encodeURIComponent(tag)}`),
|
|
33
|
+
async publish(release, files) {
|
|
34
|
+
const octokit = {
|
|
35
|
+
rest: {
|
|
36
|
+
repos: {
|
|
37
|
+
getReleaseAsset: async ({ asset_id }) => ({
|
|
38
|
+
data: assetBytes({ id: asset_id }),
|
|
39
|
+
}),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
const result = await reconcileImmutableReleaseAssets({
|
|
44
|
+
octokit,
|
|
45
|
+
owner,
|
|
46
|
+
repo,
|
|
47
|
+
releaseId: release.id,
|
|
48
|
+
remoteAssets: uniqueReleaseAssets(release.assets || []),
|
|
49
|
+
assetPaths: files,
|
|
50
|
+
uploadReleaseAsset: async ({ name }) => {
|
|
51
|
+
const file = files.find((file) => path.basename(file) === name);
|
|
52
|
+
return JSON.parse(
|
|
53
|
+
raw(
|
|
54
|
+
`https://uploads.github.com/repos/${repository}/releases/${release.id}/assets?name=${encodeURIComponent(name)}`,
|
|
55
|
+
[
|
|
56
|
+
"--method",
|
|
57
|
+
"POST",
|
|
58
|
+
"--input",
|
|
59
|
+
file,
|
|
60
|
+
"-H",
|
|
61
|
+
"Content-Type: application/octet-stream",
|
|
62
|
+
],
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
// A successful upload response alone is not the provider readback.
|
|
68
|
+
const observed = this.release(release.tag_name);
|
|
69
|
+
await reconcileImmutableReleaseAssets({
|
|
70
|
+
octokit,
|
|
71
|
+
owner,
|
|
72
|
+
repo,
|
|
73
|
+
releaseId: release.id,
|
|
74
|
+
remoteAssets: uniqueReleaseAssets(observed.assets || []),
|
|
75
|
+
assetPaths: files,
|
|
76
|
+
uploadReleaseAsset: async () => {
|
|
77
|
+
throw new Error("uploaded release asset missing on readback");
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
return result.map(({ action, name, digest }) => ({
|
|
81
|
+
action,
|
|
82
|
+
name,
|
|
83
|
+
digest,
|
|
84
|
+
}));
|
|
85
|
+
},
|
|
86
|
+
write(file, value) {
|
|
87
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
88
|
+
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -12,7 +12,7 @@ import { writeGitHubOutputs } from "./build-contract-core.mjs";
|
|
|
12
12
|
import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
|
|
13
13
|
import { v4PublicationQualificationRoot, validateV4PublicationQualificationReceipt } from "../packages/core/v4-publication-qualification.js";
|
|
14
14
|
|
|
15
|
-
const DEFAULT_WORKFLOW_FILE = "build-
|
|
15
|
+
const DEFAULT_WORKFLOW_FILE = "self-build-fixture.yml";
|
|
16
16
|
function env(name, fallback = "") {
|
|
17
17
|
return process.env[name] || fallback;
|
|
18
18
|
}
|
|
@@ -263,17 +263,17 @@ export function selectReleaseAssetPaths({
|
|
|
263
263
|
`release candidate payload patterns matched no files: ${splitPatterns(patterns).join(", ")}`,
|
|
264
264
|
);
|
|
265
265
|
}
|
|
266
|
-
const
|
|
266
|
+
const selectedByBasename = new Map();
|
|
267
|
+
const digest = (filePath) => digestFileSync(filePath, "sha256", "hex");
|
|
267
268
|
for (const filePath of files) {
|
|
268
269
|
const basename = path.basename(filePath);
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
basenames.add(basename);
|
|
270
|
+
const existing = selectedByBasename.get(basename);
|
|
271
|
+
if (!existing) selectedByBasename.set(basename, filePath);
|
|
272
|
+
else if (digest(existing) !== digest(filePath))
|
|
273
|
+
throw new Error(`release candidate public asset basename is not unique: ${basename}`);
|
|
275
274
|
}
|
|
276
|
-
|
|
275
|
+
// Repeated paths are safe only when they name the same immutable bytes.
|
|
276
|
+
return [...selectedByBasename.values()];
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
function packageNameFromArtifactPath(filePath) {
|