@kungfu-tech/buildchain 4.0.2-alpha.4 → 4.0.2-alpha.41

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.
Files changed (153) hide show
  1. package/AGENTS.md +10 -1
  2. package/CONTRIBUTING.md +24 -0
  3. package/README.md +2 -2
  4. package/actions/promote-buildchain-ref/README.md +1 -1
  5. package/actions/v4-release-candidate-promote/README.md +28 -0
  6. package/architecture/agent-change-map.md +66 -7
  7. package/architecture/ci-lane-change-budget.json +366 -0
  8. package/architecture/internal-capabilities.json +52 -8
  9. package/architecture/maintainability-debt.json +148 -92
  10. package/architecture/maintainability-policy.json +34 -30
  11. package/architecture/release-tail-contract-inventory.json +6 -6
  12. package/architecture/v4-architecture-constitution.md +23 -20
  13. package/architecture/v4-cross-platform-adopter-qualification.json +1 -1
  14. package/architecture/v4-delivery-authority-parity.json +2 -2
  15. package/architecture/v4-delivery-warrant-shadow-bootstrap-plan.json +1 -1
  16. package/architecture/v4-floating-consumer-policy.json +2 -2
  17. package/architecture/v4-next-development-parity.json +5 -5
  18. package/architecture/v4-partial-mutation-recovery-qualification.json +4 -1
  19. package/architecture/v4-platform-stage-checkpoints.json +1 -1
  20. package/architecture/v4-provider-operation-journal-contract.json +5 -2
  21. package/architecture/v4-publication-facade-patches.json +64 -0
  22. package/architecture/v4-release-activation-shadow-domain.json +5 -2
  23. package/architecture/v4-release-topology.json +143 -35
  24. package/architecture/v4-rust-wasm-production-authority.json +58 -0
  25. package/architecture/v4-stable-publication-fence.json +5 -2
  26. package/architecture/v4-stage-capsule-qualification.json +2 -2
  27. package/architecture/v4-tail-reseal-parity.json +8 -6
  28. package/architecture/v4-universal-workflow-bootstrap.json +171 -0
  29. package/architecture/v4-universal-workflow-fault-campaign.json +72 -0
  30. package/architecture/v4-universal-workflow-train-admission.json +40 -0
  31. package/architecture/workflow-taxonomy.json +1451 -0
  32. package/bin/buildchain.mjs +7 -7
  33. package/contracts/buildchain-v2-residuals-v1.json +57 -48
  34. package/contracts/dev-delivery-authority-v2.schema.json +16 -2
  35. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  36. package/contracts/v4-canonical-contracts-v1.schema.json +1 -0
  37. package/dist/site/buildchain-contract.json +93 -48
  38. package/dist/site/buildchain-site.json +222 -69
  39. package/dist/site/capability-registry.json +4 -4
  40. package/dist/site/controller-registry.json +50 -13
  41. package/dist/site/kfd-claims.json +2569 -982
  42. package/dist/site/kfd-upstream-aggregate.json +1 -1
  43. package/dist/site/manual-registry.json +13 -13
  44. package/dist/site/node-api-registry.json +88 -83
  45. package/dist/site/page-registry.json +204 -51
  46. package/dist/site/public-surface-audit.json +3207 -1219
  47. package/dist/site/publication-authority-registry.json +1124 -362
  48. package/dist/site/publication-registry.json +4 -4
  49. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +16 -2
  50. package/dist/site/site-manifest.json +17 -17
  51. package/dist/site/workflow-registry.json +5063 -1659
  52. package/docs/MAP.md +1 -0
  53. package/docs/auditable-demo.md +1 -1
  54. package/docs/cli.md +3 -3
  55. package/docs/dev-delivery-warrant.md +1 -1
  56. package/docs/engineering-housekeeper.md +3 -3
  57. package/docs/kfd-support.md +26 -2
  58. package/docs/next-development-transition.md +2 -2
  59. package/docs/node-api-reference.md +129 -129
  60. package/docs/publication-authority.md +2 -2
  61. package/docs/release-candidate.md +3 -3
  62. package/docs/release-flow.md +67 -19
  63. package/docs/release-governance.md +8 -8
  64. package/docs/release-passport.md +27 -2
  65. package/docs/reusable-build-surface.md +16 -0
  66. package/docs/v4-adopter-delivery.md +2 -2
  67. package/docs/v4-production-release.md +71 -8
  68. package/docs/v4-rust-wasm-production-authority.md +58 -0
  69. package/docs/v4-stage-capsule.md +2 -2
  70. package/docs/v4-tail-reseal.md +1 -1
  71. package/docs/workflow-catalog.md +154 -0
  72. package/docs/workflow-path-migration.md +109 -0
  73. package/package.json +10 -6
  74. package/packages/core/artifact-verification-envelope.js +5 -5
  75. package/packages/core/buildchain-contract.js +1 -1
  76. package/packages/core/buildchain-kfd-claims.js +2 -2
  77. package/packages/core/buildchain-publication-authority.js +42 -26
  78. package/packages/core/buildchain-v4-domain.wasm +0 -0
  79. package/packages/core/ci-lane-change-budget.js +14 -1
  80. package/packages/core/controller-evidence.js +4 -1
  81. package/packages/core/dev-delivery-candidate-identity.js +40 -6
  82. package/packages/core/dev-delivery-warrant-state.js +3 -4
  83. package/packages/core/dev-delivery-warrant.js +12 -2
  84. package/packages/core/github-governance-authority.js +4 -4
  85. package/packages/core/kfd-adopter-manifest.js +46 -47
  86. package/packages/core/next-development-projection.js +2 -2
  87. package/packages/core/release-line-dry-run.js +1 -1
  88. package/packages/core/release-passport-contract.js +5 -5
  89. package/packages/core/release-passport.js +5 -4
  90. package/packages/core/release-tail-product-capabilities.js +29 -0
  91. package/packages/core/release-tail-provider-plane.js +68 -1048
  92. package/packages/core/v4-canonical-contracts.js +14 -75
  93. package/packages/core/v4-domain-wasm-artifact.js +7 -0
  94. package/packages/core/v4-domain-wasm.js +216 -0
  95. package/packages/core/v4-floating-consumer-evidence.js +1 -1
  96. package/packages/core/v4-floating-consumer-policy.js +1 -1
  97. package/packages/core/v4-partial-mutation-recovery-qualification.js +9 -386
  98. package/packages/core/v4-product-publication.js +57 -0
  99. package/packages/core/v4-protected-publication-source.js +19 -21
  100. package/packages/core/v4-provider-operation-journal.js +18 -487
  101. package/packages/core/v4-provider-readback-idempotency.js +12 -97
  102. package/packages/core/v4-release-activation-shadow.js +13 -500
  103. package/packages/core/v4-release-invocation.js +28 -389
  104. package/packages/core/v4-stable-publication-fence.js +13 -342
  105. package/packages/core/v4-universal-workflow-bootstrap.js +586 -0
  106. package/scripts/binary-publication-evidence.mjs +135 -0
  107. package/scripts/build-contract-core.mjs +1 -14
  108. package/scripts/build-v4-domain-wasm.mjs +189 -0
  109. package/scripts/check-action-bundles.mjs +12 -2
  110. package/scripts/check-ci-lane-change-budget.mjs +4 -1
  111. package/scripts/check-core-mechanism-inventory.mjs +4 -3
  112. package/scripts/check-inventory.mjs +35 -44
  113. package/scripts/check-maintainability.mjs +16 -113
  114. package/scripts/check-v4-floating-consumer-policy-contract.mjs +2 -2
  115. package/scripts/check-v4-public-dogfood-contract.mjs +22 -9
  116. package/scripts/check-v4-release-topology.mjs +214 -50
  117. package/scripts/check-v4-runtime-semantic-closure.mjs +4 -1
  118. package/scripts/check-v4-universal-workflow-bootstrap.mjs +266 -0
  119. package/scripts/check-workflow-taxonomy.mjs +6 -0
  120. package/scripts/copy-v4-domain-wasm.mjs +14 -0
  121. package/scripts/dev-delivery-authority-command-adapters.mjs +1 -1
  122. package/scripts/dev-delivery-authority.mjs +8 -8
  123. package/scripts/dev-delivery-source-proof-reuse.mjs +2 -1
  124. package/scripts/dev-delivery-warrant-options.mjs +14 -9
  125. package/scripts/dev-delivery-warrant-store.mjs +73 -7
  126. package/scripts/dev-delivery-warrant-transition.mjs +109 -0
  127. package/scripts/dev-delivery-warrant.mjs +76 -99
  128. package/scripts/dev-pr-auto-merge.mjs +15 -15
  129. package/scripts/generate-channel-build-workflow.mjs +7 -2
  130. package/scripts/generate-channel-promotion-workflow.mjs +7 -5
  131. package/scripts/generate-site-bundle.mjs +11 -11
  132. package/scripts/generate-v4-universal-workflow-facades.mjs +458 -0
  133. package/scripts/generate-workflow-taxonomy.mjs +25 -0
  134. package/scripts/github-output.mjs +31 -0
  135. package/scripts/maintainability-metrics.mjs +16 -5
  136. package/scripts/maintainability-public-surface.mjs +115 -0
  137. package/scripts/next-development-review.mjs +307 -0
  138. package/scripts/release-asset-client.mjs +91 -0
  139. package/scripts/release-candidate-resolver.mjs +17 -18
  140. package/scripts/resume-from-candidate-run.mjs +1 -1
  141. package/scripts/source-verification-evidence.mjs +344 -0
  142. package/scripts/stable-candidate-qualification.mjs +1 -1
  143. package/scripts/universal-facade-maintainability.mjs +82 -0
  144. package/scripts/v4-product-publication-intent.mjs +121 -0
  145. package/scripts/v4-publication-settlement.mjs +211 -0
  146. package/scripts/v4-universal-workflow-backflow.mjs +212 -0
  147. package/scripts/v4-universal-workflow-engine.mjs +618 -0
  148. package/scripts/v4-universal-workflow-self-dogfood.mjs +205 -0
  149. package/scripts/verify-version-state-delta.mjs +173 -0
  150. package/scripts/workflow-taxonomy.mjs +507 -0
  151. package/templates/native-dev-delivery.yml +4 -7
  152. package/templates/universal-buildchain-bootstrap-recovery.yml +282 -0
  153. 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
+ });
@@ -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,8 +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-surface-fixture.yml";
16
-
15
+ const DEFAULT_WORKFLOW_FILE = "self-build-fixture.yml";
17
16
  function env(name, fallback = "") {
18
17
  return process.env[name] || fallback;
19
18
  }
@@ -21,7 +20,6 @@ function env(name, fallback = "") {
21
20
  export function releaseCandidateDownloadEnabled(value = "true") {
22
21
  return String(value || "true").trim().toLowerCase() !== "false";
23
22
  }
24
-
25
23
  function splitRepository(repository) {
26
24
  const match = String(repository || "").trim().match(/^([^/\s]+)\/([^/\s]+)$/);
27
25
  if (!match) {
@@ -44,7 +42,9 @@ function assertSha(value, label = "sha") {
44
42
 
45
43
  export const releaseCandidateRuntimeSha = (passport) =>
46
44
  assertSha(passport?.buildchain?.sha, "release candidate Passport Buildchain runtime SHA").toLowerCase();
47
-
45
+ export const resolveFreshPublicationVersion = ({ sealedBundle, candidateVersion = "" } = {}) =>
46
+ String(sealedBundle?.manifest?.npm?.version || candidateVersion || "").trim();
47
+ const optionalText = (value) => String(value || "");
48
48
  function githubHeaders(token) {
49
49
  const headers = {
50
50
  accept: "application/vnd.github+json",
@@ -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 basenames = new Set();
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
- if (basenames.has(basename)) {
270
- throw new Error(
271
- `release candidate public asset basename is not unique: ${basename}`,
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
- return files;
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) {
@@ -740,19 +740,17 @@ export async function resolveReleaseCandidateArtifacts({
740
740
  releaseAssetPaths,
741
741
  })
742
742
  : undefined;
743
- const manifests = platformManifestPaths.map((manifestPath) => JSON.parse(fs.readFileSync(manifestPath, "utf8")));
743
+ const manifests = platformManifestPaths.map((manifestPath) => JSON.parse(fs.readFileSync(manifestPath, "utf8"))), publicationVersion = resolveFreshPublicationVersion({ sealedBundle, candidateVersion: passport.target?.version });
744
744
  const generatedRequiredArtifacts = generatePublishRequiredArtifacts({
745
745
  manifests,
746
- version: [sealedBundle?.manifest?.npm?.version, passport.target?.version, ""].find(Boolean),
746
+ version: publicationVersion,
747
747
  kind: publishArtifactKind,
748
748
  tarballPaths: npmTarballPaths,
749
749
  mainPackage: publishPackageMain,
750
750
  });
751
751
  const requiredArtifactsPath = path.join(resolvedOutput, "publish-required-artifacts.json");
752
752
  fs.writeFileSync(requiredArtifactsPath, `${JSON.stringify(generatedRequiredArtifacts, null, 2)}\n`);
753
- const sealedBundleManifestPath = sealedBundle
754
- ? path.join(resolvedOutput, "sealed-bundle.json")
755
- : "";
753
+ const sealedBundleManifestPath = sealedBundle ? path.join(resolvedOutput, "sealed-bundle.json") : "";
756
754
  if (sealedBundleManifestPath) {
757
755
  fs.writeFileSync(sealedBundleManifestPath, `${JSON.stringify(sealedBundle.manifest, null, 2)}\n`);
758
756
  }
@@ -772,6 +770,7 @@ export async function resolveReleaseCandidateArtifacts({
772
770
  sealedBundleManifest: sealedBundleManifestPath ? outputPath(sealedBundleManifestPath) : "",
773
771
  },
774
772
  version: passport.target?.version || "",
773
+ publicationVersion,
775
774
  candidateHash: passport.candidateHash || "",
776
775
  payloadCount: payloadArtifacts.length,
777
776
  platformManifestCount: platformManifestPaths.length,
@@ -811,6 +810,7 @@ export async function resolveReleaseCandidateArtifactsCli() {
811
810
  "release-candidate-publication-qualification-path": result.paths?.publicationQualification || "",
812
811
  "release-candidate-publication-qualification-root": result.publicationQualificationRoot || "",
813
812
  "release-candidate-version": result.version || "",
813
+ "release-candidate-publication-version": optionalText(result.publicationVersion),
814
814
  "release-candidate-source-sha": result.artifacts?.sourceSha || "",
815
815
  "release-candidate-artifact": result.artifacts?.passport || "",
816
816
  "release-candidate-build-summary-artifact": result.artifacts?.summary || "",
@@ -843,7 +843,6 @@ export async function resolveReleaseCandidateArtifactsCli() {
843
843
  console.log(JSON.stringify(result, null, 2));
844
844
  return result;
845
845
  }
846
-
847
846
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
848
847
  try {
849
848
  await resolveReleaseCandidateArtifactsCli();
@@ -821,7 +821,7 @@ export async function resolveRecoveryTransaction({
821
821
  transactionId,
822
822
  publicationVersion,
823
823
  }) {
824
- let transaction = publicationVersion
824
+ let transaction = transactionId && publicationVersion
825
825
  ? await readExistingTransaction({
826
826
  repoInfo,
827
827
  apiUrl,