@kungfu-tech/buildchain 4.1.3-alpha.0 → 4.1.3-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/AGENTS.md +41 -75
  2. package/CONTRIBUTING.md +19 -26
  3. package/architecture/action-taxonomy.json +24 -0
  4. package/architecture/agent-change-map.md +272 -12
  5. package/architecture/capability-state-machine-manifest.json +1 -4
  6. package/architecture/ci-lane-change-budget.json +190 -807
  7. package/architecture/core-mechanisms.json +3 -6
  8. package/architecture/decisions/0005-minimal-consumer-contract.md +122 -0
  9. package/architecture/decisions/0006-business-attempt-journal.md +89 -0
  10. package/architecture/decisions/0007-hosted-pipeline-controller.md +106 -0
  11. package/architecture/decisions/0008-pipeline-product-publication.md +120 -0
  12. package/architecture/decisions/0009-pipeline-version-preparation.md +186 -0
  13. package/architecture/internal-capabilities.json +371 -9
  14. package/architecture/maintainability-debt.json +50 -34
  15. package/architecture/maintainability-policy.json +8 -8
  16. package/architecture/minimal-consumer-migration.json +5670 -0
  17. package/architecture/platform-stage-checkpoints.json +31 -7
  18. package/architecture/product-upstreams.json +20 -0
  19. package/architecture/release-topology.json +331 -74
  20. package/architecture/stage-capsule-qualification.json +3 -1
  21. package/architecture/universal-workflow-bootstrap.json +15 -1
  22. package/architecture/universal-workflow-capability-policy.json +9 -9
  23. package/architecture/universal-workflow-fault-campaign.json +3 -1
  24. package/architecture/workflow-taxonomy.json +77 -328
  25. package/contracts/release-discussion-v1.schema.json +3 -0
  26. package/dist/readers/business-attempt.cjs +809 -0
  27. package/dist/readers/release-discussion.cjs +4 -0
  28. package/dist/site/buildchain-contract.json +16 -68
  29. package/dist/site/buildchain-site.json +47 -27
  30. package/dist/site/capability-registry.json +4 -4
  31. package/dist/site/controller-registry.json +1 -47
  32. package/dist/site/kfd-claims.json +499 -494
  33. package/dist/site/kfd-upstream-aggregate.json +1 -1
  34. package/dist/site/manual-registry.json +3 -3
  35. package/dist/site/node-api-registry.json +56 -56
  36. package/dist/site/page-registry.json +40 -20
  37. package/dist/site/public-surface-audit.json +286 -349
  38. package/dist/site/publication-authority-registry.json +203 -523
  39. package/dist/site/publication-registry.json +4 -4
  40. package/dist/site/site-manifest.json +7 -7
  41. package/dist/site/workflow-registry.json +590 -940
  42. package/docs/code-organization.md +9 -6
  43. package/docs/node-api-reference.md +103 -103
  44. package/docs/release-discussions.md +44 -5
  45. package/docs/release-governance.md +10 -12
  46. package/docs/runtime-entry.md +73 -5
  47. package/docs/runtime-train-validation.md +39 -11
  48. package/docs/stable-candidate-patrol.md +6 -5
  49. package/docs/workflow-catalog.md +9 -32
  50. package/package.json +4 -3
  51. package/packages/core/adoption/buildchain-kfd-claims.js +7 -7
  52. package/packages/core/build/standalone/build.js +7 -1
  53. package/packages/core/consumer/buildchain-config.js +2 -21
  54. package/packages/core/consumer/contract/entries.js +85 -0
  55. package/packages/core/consumer/contract/examples.js +96 -0
  56. package/packages/core/consumer/contract/identity.js +65 -0
  57. package/packages/core/consumer/contract/inspection.js +45 -0
  58. package/packages/core/consumer/contract/plan.js +149 -0
  59. package/packages/core/consumer/contract/products.js +119 -0
  60. package/packages/core/consumer/contract/reader.js +47 -0
  61. package/packages/core/consumer/contract/shape.js +46 -0
  62. package/packages/core/dev-delivery/candidate/admission.js +9 -5
  63. package/packages/core/dev-delivery/candidate/reservation.js +11 -0
  64. package/packages/core/dev-delivery/candidate/source-paths.js +12 -4
  65. package/packages/core/dev-delivery/commands/project-cut-merge-queue-admission.mjs +47 -20
  66. package/packages/core/dev-delivery/dev-delivery-provider-heartbeat.js +18 -15
  67. package/packages/core/dev-delivery/native/actions.js +9 -1
  68. package/packages/core/dev-delivery/native/heartbeat-action.js +16 -0
  69. package/packages/core/dev-delivery/native/heartbeat.js +6 -4
  70. package/packages/core/dev-delivery/native/process-entry.js +19 -0
  71. package/packages/core/dev-delivery/queue/landing-action.js +53 -0
  72. package/packages/core/dev-delivery/warrant/failure-settlement.js +8 -6
  73. package/packages/core/dev-delivery/warrant/service.js +7 -0
  74. package/packages/core/governance/buildchain-publication-authority.js +8 -23
  75. package/packages/core/governance/github-governance-authority.js +1 -0
  76. package/packages/core/observability/controller-evidence.js +0 -8
  77. package/packages/core/paper/operations/bootstrap.js +2 -0
  78. package/packages/core/paper/paper-npm-bootstrap.js +2 -2
  79. package/packages/core/providers/github/attempt-index.js +60 -0
  80. package/packages/core/providers/github/attempt-journal.js +154 -0
  81. package/packages/core/providers/github/discussions/materials.js +35 -11
  82. package/packages/core/providers/github/discussions/transport.js +16 -5
  83. package/packages/core/providers/github/pipeline-checkout.js +49 -0
  84. package/packages/core/providers/github/pipeline-entry-runs.js +210 -0
  85. package/packages/core/providers/github/pipeline-events.js +131 -0
  86. package/packages/core/providers/github/pipeline-integration.js +95 -0
  87. package/packages/core/providers/github/pipeline-policy.js +240 -0
  88. package/packages/core/providers/github/pipeline-product-payload.js +50 -0
  89. package/packages/core/providers/github/pipeline-product-release.js +198 -0
  90. package/packages/core/providers/github/pipeline-publication-artifacts.js +153 -0
  91. package/packages/core/providers/github/pipeline-release-evidence.js +30 -0
  92. package/packages/core/providers/github/pipeline-run-entry.js +86 -0
  93. package/packages/core/providers/github/pipeline-runs.js +132 -0
  94. package/packages/core/providers/github/pipeline-source.js +195 -0
  95. package/packages/core/providers/github/pipeline-stable-baseline.js +86 -0
  96. package/packages/core/providers/github/pipeline-stable-source.js +372 -0
  97. package/packages/core/providers/github/pipeline-version-artifacts.js +91 -0
  98. package/packages/core/providers/github/pipeline-version-readback.js +117 -0
  99. package/packages/core/providers/github/pipeline-version.js +199 -0
  100. package/packages/core/providers/github/pipeline-worker.js +108 -0
  101. package/packages/core/providers/github/release-evidence-assets.js +97 -0
  102. package/packages/core/publication/candidate/registry-hydration.js +2 -2
  103. package/packages/core/publication/npm/pack-preview.js +2 -2
  104. package/packages/core/publication/npm/pack-result.js +31 -0
  105. package/packages/core/publication/npm/package.js +3 -6
  106. package/packages/core/publication/npm/pipeline-channel.js +36 -0
  107. package/packages/core/publication/npm/pipeline-provider.js +137 -0
  108. package/packages/core/publication/npm/registry.js +12 -9
  109. package/packages/core/publication/pipeline/actions.js +116 -0
  110. package/packages/core/publication/pipeline/apply.js +189 -0
  111. package/packages/core/publication/pipeline/build-segments.js +107 -0
  112. package/packages/core/publication/pipeline/capsules.js +92 -0
  113. package/packages/core/publication/pipeline/context.js +39 -0
  114. package/packages/core/publication/pipeline/development-anchor.js +91 -0
  115. package/packages/core/publication/pipeline/development-current.js +63 -0
  116. package/packages/core/publication/pipeline/development-pr.js +112 -0
  117. package/packages/core/publication/pipeline/development-proof.js +76 -0
  118. package/packages/core/publication/pipeline/development-transition.js +112 -0
  119. package/packages/core/publication/pipeline/distribution.js +130 -0
  120. package/packages/core/publication/pipeline/documents.js +192 -0
  121. package/packages/core/publication/pipeline/effects.js +192 -0
  122. package/packages/core/publication/pipeline/files.js +66 -0
  123. package/packages/core/publication/pipeline/imported-materials.js +32 -0
  124. package/packages/core/publication/pipeline/journal.js +122 -0
  125. package/packages/core/publication/pipeline/next-development.js +117 -0
  126. package/packages/core/publication/pipeline/pack.js +180 -0
  127. package/packages/core/publication/pipeline/package-policy.js +22 -0
  128. package/packages/core/publication/pipeline/plan.js +136 -0
  129. package/packages/core/publication/pipeline/prepare.js +139 -0
  130. package/packages/core/publication/pipeline/qualification.js +175 -0
  131. package/packages/core/publication/pipeline/qualify.js +104 -0
  132. package/packages/core/publication/pipeline/recovery-admission.js +122 -0
  133. package/packages/core/publication/pipeline/recovery-build-download.js +80 -0
  134. package/packages/core/publication/pipeline/recovery-build-plan.js +122 -0
  135. package/packages/core/publication/pipeline/recovery-build-readback.js +68 -0
  136. package/packages/core/publication/pipeline/recovery-capsules.js +54 -0
  137. package/packages/core/publication/pipeline/recovery-import.js +80 -0
  138. package/packages/core/publication/pipeline/recovery-inspection.js +218 -0
  139. package/packages/core/publication/pipeline/recovery-materials.js +104 -0
  140. package/packages/core/publication/pipeline/recovery-plan.js +46 -0
  141. package/packages/core/publication/pipeline/recovery-prepare.js +73 -0
  142. package/packages/core/publication/pipeline/recovery-qualification.js +128 -0
  143. package/packages/core/publication/pipeline/recovery-readback.js +56 -0
  144. package/packages/core/publication/pipeline/recovery-signing.js +58 -0
  145. package/packages/core/publication/pipeline/sealed-products.js +79 -0
  146. package/packages/core/publication/pipeline/settle.js +42 -0
  147. package/packages/core/publication/pipeline/signing.js +140 -0
  148. package/packages/core/publication/pipeline/source-plan.js +83 -0
  149. package/packages/core/publication/pipeline/stable-entry.js +219 -0
  150. package/packages/core/publication/pipeline/stable-products.js +317 -0
  151. package/packages/core/publication/pipeline/stable-wait.js +170 -0
  152. package/packages/core/publication/pipeline/stable.js +82 -0
  153. package/packages/core/publication/pipeline/version-actions.js +93 -0
  154. package/packages/core/publication/pipeline/version-build.js +130 -0
  155. package/packages/core/publication/pipeline/version-context.js +143 -0
  156. package/packages/core/publication/pipeline/version-material.js +36 -0
  157. package/packages/core/publication/pipeline/version-preparation.js +69 -0
  158. package/packages/core/publication/pipeline/version-regeneration.js +162 -0
  159. package/packages/core/publication/pipeline/version-source-guard.js +69 -0
  160. package/packages/core/publication/pipeline/version.js +81 -0
  161. package/packages/core/publication/pipeline/worker.js +72 -0
  162. package/packages/core/publication/publication-reproducibility.js +2 -2
  163. package/packages/core/release/discussion/binary.js +11 -0
  164. package/packages/core/release/discussion/checkpoints.js +45 -4
  165. package/packages/core/release/discussion/envelope.js +1 -0
  166. package/packages/core/release/discussion/evidence.js +51 -0
  167. package/packages/core/release/discussion/presentation.js +92 -0
  168. package/packages/core/release/discussion/publication.js +3 -1
  169. package/packages/core/release/discussion/qualification.js +1 -0
  170. package/packages/core/release/discussion/reader.js +9 -0
  171. package/packages/core/release/discussion/session.js +2 -0
  172. package/packages/core/release/discussion/store.js +28 -31
  173. package/packages/core/release/discussion/threads.js +83 -0
  174. package/packages/core/release/next-development-transition.js +15 -15
  175. package/packages/core/release/promote-candidate/product-provider-adapters.js +4 -3
  176. package/packages/core/release/promote-ref/internal/npm-existing-evidence.js +2 -1
  177. package/packages/core/release/promote-ref/internal/publish-command.js +16 -5
  178. package/packages/core/release/stable-release-gate.js +11 -0
  179. package/packages/core/runtime/buildchain-domain.wasm +0 -0
  180. package/packages/core/runtime/domain-wasm-artifact.js +2 -2
  181. package/packages/core/runtime/entry/actions.js +27 -25
  182. package/packages/core/runtime/entry/attempt.js +25 -0
  183. package/packages/core/runtime/entry/selection.js +14 -0
  184. package/packages/core/runtime/entry/source.js +62 -0
  185. package/packages/core/workflow/attempt/identity.js +170 -0
  186. package/packages/core/workflow/attempt/journal.js +89 -0
  187. package/packages/core/workflow/attempt/materials.js +72 -0
  188. package/packages/core/workflow/attempt/reader-entry.js +12 -0
  189. package/packages/core/workflow/attempt/reader.js +149 -0
  190. package/packages/core/workflow/attempt/records.js +153 -0
  191. package/packages/core/workflow/attempt/store.js +75 -0
  192. package/packages/core/workflow/commands/pipeline-build.mjs +23 -0
  193. package/packages/core/workflow/pipeline/action-output.js +11 -0
  194. package/packages/core/workflow/pipeline/actions.js +52 -0
  195. package/packages/core/workflow/pipeline/build-control.js +140 -0
  196. package/packages/core/workflow/pipeline/build-evidence.js +210 -0
  197. package/packages/core/workflow/pipeline/build-qualification.js +145 -0
  198. package/packages/core/workflow/pipeline/build-result.js +78 -0
  199. package/packages/core/workflow/pipeline/build.js +142 -0
  200. package/packages/core/workflow/pipeline/cancellation.js +169 -0
  201. package/packages/core/workflow/pipeline/channel-control.js +157 -0
  202. package/packages/core/workflow/pipeline/channel-source.js +53 -0
  203. package/packages/core/workflow/pipeline/controller.js +141 -0
  204. package/packages/core/workflow/pipeline/delivery-control.js +249 -0
  205. package/packages/core/workflow/pipeline/delivery-observation.js +124 -0
  206. package/packages/core/workflow/pipeline/delivery-request.js +198 -0
  207. package/packages/core/workflow/pipeline/events.js +84 -0
  208. package/packages/core/workflow/pipeline/fence.js +42 -0
  209. package/packages/core/workflow/pipeline/group-control.js +59 -0
  210. package/packages/core/workflow/pipeline/guard-build.js +74 -0
  211. package/packages/core/workflow/pipeline/guard.js +158 -0
  212. package/packages/core/workflow/pipeline/host.js +144 -0
  213. package/packages/core/workflow/pipeline/integration-status.js +113 -0
  214. package/packages/core/workflow/pipeline/materials.js +58 -0
  215. package/packages/core/workflow/pipeline/notifications.js +36 -0
  216. package/packages/core/workflow/pipeline/parent-notification.js +27 -0
  217. package/packages/core/workflow/pipeline/platforms.js +21 -0
  218. package/packages/core/workflow/pipeline/progress.js +108 -0
  219. package/packages/core/workflow/pipeline/projection.js +20 -0
  220. package/packages/core/workflow/pipeline/reconcile.js +194 -0
  221. package/packages/core/workflow/pipeline/record-action.js +27 -0
  222. package/packages/core/workflow/pipeline/recorder-status.js +69 -0
  223. package/packages/core/workflow/pipeline/recovery-action.js +30 -0
  224. package/packages/core/workflow/pipeline/recovery-admission.js +122 -0
  225. package/packages/core/workflow/pipeline/recovery-build-control.js +95 -0
  226. package/packages/core/workflow/pipeline/recovery-build-evidence.js +71 -0
  227. package/packages/core/workflow/pipeline/recovery-build.js +121 -0
  228. package/packages/core/workflow/pipeline/recovery-controller.js +91 -0
  229. package/packages/core/workflow/pipeline/recovery-integration.js +79 -0
  230. package/packages/core/workflow/pipeline/recovery-merge-proof.js +90 -0
  231. package/packages/core/workflow/pipeline/recovery-ownership-settlement.js +150 -0
  232. package/packages/core/workflow/pipeline/recovery-ownership.js +91 -0
  233. package/packages/core/workflow/pipeline/recovery-plan.js +126 -0
  234. package/packages/core/workflow/pipeline/recovery-runtime.js +68 -0
  235. package/packages/core/workflow/pipeline/recovery-session.js +129 -0
  236. package/packages/core/workflow/pipeline/recovery-transition.js +135 -0
  237. package/packages/core/workflow/pipeline/recovery-unrecorded-build.js +74 -0
  238. package/packages/core/workflow/pipeline/runtime-source.js +111 -0
  239. package/packages/core/workflow/pipeline/selection.js +111 -0
  240. package/packages/core/workflow/pipeline/session.js +72 -0
  241. package/packages/core/workflow/pipeline/settlement.js +137 -0
  242. package/packages/core/workflow/pipeline/wake.js +38 -0
  243. package/packages/core/workflow/pipeline/web-status.js +108 -0
  244. package/packages/core/workflow/workflow-taxonomy.mjs +67 -47
  245. package/scripts/build-release-discussion-reader.mjs +11 -7
  246. package/scripts/check-inventory.mjs +7 -70
  247. package/scripts/check-pipeline-publication-topology.mjs +50 -0
  248. package/scripts/check-public-dogfood-contract.mjs +38 -51
  249. package/scripts/check-release-topology.mjs +15 -8
  250. package/scripts/check-self-consumer-contract.mjs +133 -0
  251. package/scripts/check-universal-workflow-bootstrap.mjs +4 -6
  252. package/scripts/generate-minimal-consumer-contract.mjs +155 -0
  253. package/scripts/generate-site-bundle.mjs +1 -1
  254. package/scripts/inventory/binary.mjs +9 -19
  255. package/scripts/inventory/promotion.mjs +2 -13
  256. package/scripts/prepare-product-toolchain.mjs +10 -0
  257. package/scripts/verify-golden-path.mjs +5 -1
  258. package/scripts/verify-product-platform.mjs +65 -0
  259. package/scripts/verify-standalone-product.mjs +42 -0
  260. package/templates/minimal-consumer/binary/.buildchain/buildchain.toml +45 -0
  261. package/templates/minimal-consumer/binary/.github/workflows/buildchain-recover.yml +30 -0
  262. package/templates/minimal-consumer/binary/.github/workflows/buildchain.yml +28 -0
  263. package/templates/minimal-consumer/binary/package.json +6 -0
  264. package/templates/minimal-consumer/binary/src/build.mjs +5 -0
  265. package/templates/minimal-consumer/binary/src/hello.c +2 -0
  266. package/templates/minimal-consumer/binary/src/verify.mjs +3 -0
  267. package/templates/minimal-consumer/npm/.buildchain/buildchain.toml +46 -0
  268. package/templates/minimal-consumer/npm/.github/workflows/buildchain-recover.yml +30 -0
  269. package/templates/minimal-consumer/npm/.github/workflows/buildchain.yml +28 -0
  270. package/templates/minimal-consumer/npm/package.json +6 -0
  271. package/templates/minimal-consumer/npm/src/build.mjs +5 -0
  272. package/templates/minimal-consumer/npm/src/verify.mjs +3 -0
  273. package/templates/minimal-consumer/paper/.buildchain/buildchain.toml +45 -0
  274. package/templates/minimal-consumer/paper/.github/workflows/buildchain-recover.yml +30 -0
  275. package/templates/minimal-consumer/paper/.github/workflows/buildchain.yml +28 -0
  276. package/templates/minimal-consumer/paper/package.json +6 -0
  277. package/templates/minimal-consumer/paper/src/build.mjs +12 -0
  278. package/templates/minimal-consumer/paper/src/verify.mjs +3 -0
@@ -0,0 +1,139 @@
1
+ import { claimPipelinePublicationWorker } from "./worker.js";
2
+ import { recordDigest } from "../../release/discussion/envelope.js";
3
+ import { pipelinePlatforms } from "../../workflow/pipeline/platforms.js";
4
+ import { githubPipelineVersion } from "../../providers/github/pipeline-version.js";
5
+ import { createPipelinePublicationPlan } from "./source-plan.js";
6
+ import { preparePipelineStableQualification } from "./stable-wait.js";
7
+ import { prepareRecoveredPublication } from "./recovery-prepare.js";
8
+ import {
9
+ preparePipelineVersionContext,
10
+ retainedPipelineVersionRegeneration,
11
+ } from "./version-context.js";
12
+
13
+ function publicationNextOperation(phase, existing, recovery) {
14
+ if (phase !== "publish") return "settle";
15
+ if (existing.length) return "apply";
16
+ if (recovery?.mode === "prepared" || recovery?.build?.scheduled.length === 0)
17
+ return "qualify";
18
+ return "build";
19
+ }
20
+
21
+ export async function preparePipelinePublication(attempt, publisherSha, host) {
22
+ const claimed = await claimPipelinePublicationWorker(
23
+ attempt,
24
+ publisherSha,
25
+ host,
26
+ );
27
+ if (!claimed) return { operation: "wait" };
28
+ const { session, journal, phase } = claimed;
29
+ const observed = session.observed;
30
+ const recovery = await prepareRecoveredPublication(
31
+ session,
32
+ publisherSha,
33
+ host,
34
+ journal,
35
+ phase,
36
+ );
37
+ const plans = await journal.materials("publication/plan/");
38
+ if (plans.length > 1)
39
+ throw new Error("Publication has conflicting retained plans");
40
+ const version = githubPipelineVersion(host.request, host.repository);
41
+ let plan = plans[0];
42
+ if (!plan) {
43
+ plan = await createPipelinePublicationPlan(
44
+ session,
45
+ publisherSha,
46
+ host,
47
+ version,
48
+ );
49
+ await journal.record("publication/plan", plan);
50
+ }
51
+ const wait = await preparePipelineStableQualification(plan, host, journal, {
52
+ attempt,
53
+ generation: observed.generation,
54
+ phase,
55
+ });
56
+ if (wait) return { operation: "wait", wait };
57
+ if (
58
+ !recovery &&
59
+ (plan.publisher.workflowSha !== publisherSha ||
60
+ plan.runtime.commit !== host.runtime.sha)
61
+ )
62
+ throw new Error(
63
+ "Publication resume requires its retained publisher and runtime; use typed recovery for upgrades",
64
+ );
65
+ const materializations = await journal.materials(
66
+ "publication/materialization/",
67
+ );
68
+ if (materializations.length > 1)
69
+ throw new Error("Publication materialized source is ambiguous");
70
+ let materialization = materializations[0];
71
+ if (!materialization) {
72
+ const regeneration = await retainedPipelineVersionRegeneration(
73
+ journal,
74
+ plan,
75
+ "publication",
76
+ host.runtime,
77
+ );
78
+ if (plan.versionPolicy.derived_files?.length && !regeneration) {
79
+ const publication = {
80
+ attempt,
81
+ generation: observed.generation,
82
+ plan,
83
+ ...(recovery ? { recovery } : {}),
84
+ };
85
+ return {
86
+ operation: "regenerate",
87
+ context: await preparePipelineVersionContext(
88
+ plan,
89
+ "publication",
90
+ publication,
91
+ host,
92
+ journal,
93
+ publisherSha,
94
+ ),
95
+ };
96
+ }
97
+ await journal.fence();
98
+ materialization = await version.materialize(plan, regeneration);
99
+ const verified = await host.source.source(
100
+ materialization.source.commit,
101
+ plan.source.configPath,
102
+ );
103
+ if (recordDigest(verified.plan) !== plan.contractRoot)
104
+ throw new Error("Version materialization altered the product contract");
105
+ const { root, ...body } = materialization;
106
+ materialization = { ...body, source: verified.identity };
107
+ materialization.root = recordDigest(materialization);
108
+ await journal.record("publication/materialization", materialization);
109
+ }
110
+ const admitted = await host.source.source(
111
+ materialization.source.commit,
112
+ plan.source.configPath,
113
+ );
114
+ if (
115
+ recordDigest(admitted.identity) !== recordDigest(materialization.source) ||
116
+ recordDigest(admitted.plan) !== plan.contractRoot
117
+ )
118
+ throw new Error("Retained publication source or contract drifted");
119
+ const existing = await journal.materials("publication/qualified/");
120
+ const context = {
121
+ schema: "buildchain.pipeline-publication-context/v1",
122
+ attempt,
123
+ generation: observed.generation,
124
+ plan,
125
+ materialization,
126
+ platforms: pipelinePlatforms(admitted.plan).filter(
127
+ ({ platform }) =>
128
+ !recovery?.build || recovery.build.scheduled.includes(platform),
129
+ ),
130
+ runId: host.runId,
131
+ runAttempt: host.runAttempt,
132
+ ...(recovery ? { recovery } : {}),
133
+ };
134
+ await journal.record("publication/context", context, { phase });
135
+ return {
136
+ operation: publicationNextOperation(phase, existing, recovery),
137
+ context,
138
+ };
139
+ }
@@ -0,0 +1,175 @@
1
+ import {
2
+ PUBLICATION_BUILD_AGGREGATE,
3
+ publicationArtifactProducer,
4
+ verifyPublicationBuildAggregate,
5
+ } from "./build-segments.js";
6
+ import path from "node:path";
7
+ import { recordDigest } from "../../release/discussion/envelope.js";
8
+ import { createDomainPublicationQualificationReceipt } from "../publication-qualification.js";
9
+ import { verifyPipelinePublicationPlan } from "./plan.js";
10
+ import {
11
+ inspectPipelineFileArtifact,
12
+ verifyPipelineProductFiles,
13
+ } from "./pack.js";
14
+ import { publicationPath } from "./files.js";
15
+ import { assertPipelinePackagePolicy } from "./package-policy.js";
16
+ import { readNpmPackageJsonFromTarball } from "../../release/candidate/payloads.js";
17
+
18
+ function productDescriptor(artifact) {
19
+ const { file, size, digest, package: pkg, ...descriptor } = artifact;
20
+ return descriptor;
21
+ }
22
+
23
+ export function inspectPipelinePublicationArtifacts(directory, manifest, plan) {
24
+ verifyPipelineProductFiles(directory, manifest);
25
+ for (const artifact of manifest.artifacts) {
26
+ if (artifact.filename && path.basename(artifact.file) !== artifact.filename)
27
+ throw new Error(
28
+ "Independent qualification rejected the declared download filename",
29
+ );
30
+ if (artifact.kind === "npm-package") {
31
+ const pkg = readNpmPackageJsonFromTarball(
32
+ publicationPath(directory, artifact.file),
33
+ );
34
+ assertPipelinePackagePolicy(pkg);
35
+ if (
36
+ !artifact.package ||
37
+ pkg.name !== artifact.package.name ||
38
+ pkg.version !== plan.version ||
39
+ pkg.version !== artifact.package.version ||
40
+ pkg.private === true
41
+ )
42
+ throw new Error(
43
+ "Independent npm qualification rejected package identity or version",
44
+ );
45
+ } else
46
+ inspectPipelineFileArtifact(directory, {
47
+ path: artifact.file,
48
+ kind: artifact.kind,
49
+ });
50
+ }
51
+ }
52
+
53
+ export function qualifyPipelineProducts({
54
+ plan,
55
+ source,
56
+ bundles,
57
+ build,
58
+ policyRoot,
59
+ now = new Date(),
60
+ }) {
61
+ verifyPipelinePublicationPlan(plan);
62
+ if (build.schema === PUBLICATION_BUILD_AGGREGATE)
63
+ verifyPublicationBuildAggregate(build, {
64
+ plan,
65
+ materialization: { source },
66
+ runId: build.runId,
67
+ runAttempt: build.runAttempt,
68
+ });
69
+ const { root: buildRoot, ...buildBody } = build;
70
+ if (
71
+ buildRoot !== recordDigest(buildBody) ||
72
+ !Number.isSafeInteger(build.runId) ||
73
+ build.runId < 1 ||
74
+ ![
75
+ "buildchain.pipeline-publication-build-readback/v1",
76
+ PUBLICATION_BUILD_AGGREGATE,
77
+ ].includes(build.schema) ||
78
+ build.outcome !== "success" ||
79
+ build.planRoot !== plan.root ||
80
+ recordDigest(build.source) !== recordDigest(source)
81
+ )
82
+ throw new Error(
83
+ "Publication requires exact independent completed build readback",
84
+ );
85
+ const platforms = [
86
+ ...new Set(plan.outputs.map(({ platform }) => platform)),
87
+ ].sort();
88
+ const observed = bundles.map(({ manifest }) => manifest.platform).sort();
89
+ if (
90
+ recordDigest(platforms) !== recordDigest(observed) ||
91
+ recordDigest(platforms) !== recordDigest(build.platforms)
92
+ )
93
+ throw new Error(
94
+ "Publication build must cover exactly every declared platform",
95
+ );
96
+ const artifactIds = bundles
97
+ .map(({ providerArtifact }) => providerArtifact.id)
98
+ .sort((left, right) => left - right);
99
+ if (
100
+ new Set(artifactIds).size !== platforms.length ||
101
+ recordDigest(artifactIds) !== recordDigest(build.artifactIds)
102
+ )
103
+ throw new Error(
104
+ "Publication readback must cover exactly the retained provider artifacts",
105
+ );
106
+ const descriptors = [],
107
+ artifacts = [];
108
+ for (const { directory, manifest, providerArtifact } of bundles) {
109
+ const producer = publicationArtifactProducer(build, providerArtifact.id);
110
+ if (
111
+ manifest.planRoot !== producer.build.planRoot ||
112
+ recordDigest(manifest.source) !== recordDigest(source) ||
113
+ !build.artifactIds.includes(providerArtifact.id) ||
114
+ providerArtifact.expired ||
115
+ providerArtifact.workflow_run?.id !== producer.build.runId
116
+ )
117
+ throw new Error(
118
+ "Publication artifact is not bound to the exact admitted source run",
119
+ );
120
+ inspectPipelinePublicationArtifacts(directory, manifest, plan);
121
+ descriptors.push(...manifest.artifacts.map(productDescriptor));
122
+ artifacts.push(
123
+ ...manifest.artifacts.map((artifact) => ({
124
+ ...artifact,
125
+ manifestRoot: manifest.root,
126
+ providerArtifactId: providerArtifact.id,
127
+ })),
128
+ );
129
+ }
130
+ descriptors.sort((left, right) => left.id.localeCompare(right.id));
131
+ artifacts.sort((left, right) => left.id.localeCompare(right.id));
132
+ if (recordDigest(descriptors) !== recordDigest(plan.outputs))
133
+ throw new Error(
134
+ "Publication artifacts do not exactly match declared outputs and targets",
135
+ );
136
+ if (new Set(artifacts.map(({ file }) => file)).size !== artifacts.length)
137
+ throw new Error("Publication asset filenames must be unique");
138
+ const packages = artifacts.filter((artifact) => artifact.package);
139
+ if (
140
+ new Set(packages.map(({ package: pkg }) => pkg.name)).size !==
141
+ packages.length
142
+ )
143
+ throw new Error(
144
+ "Multi-platform npm publication requires distinct declared package names",
145
+ );
146
+ const candidateRoot = recordDigest({
147
+ planRoot: plan.root,
148
+ source,
149
+ artifacts,
150
+ });
151
+ const qualification = createDomainPublicationQualificationReceipt({
152
+ repository: source.repository,
153
+ candidateRoot,
154
+ sourceSha: source.commit,
155
+ sourceRoot: recordDigest(source),
156
+ policyDigest: policyRoot,
157
+ artifacts: artifacts.map((artifact) => ({
158
+ role: `${artifact.product}/${artifact.artifact}`,
159
+ platform: artifact.platform,
160
+ artifactRoot: artifact.digest,
161
+ manifestRoot: artifact.manifestRoot,
162
+ })),
163
+ issuedAt: now.toISOString(),
164
+ expiresAt: new Date(now.getTime() + 60 * 60 * 1000).toISOString(),
165
+ });
166
+ const body = {
167
+ schema: "buildchain.pipeline-publication-qualification/v1",
168
+ planRoot: plan.root,
169
+ source,
170
+ build,
171
+ artifacts,
172
+ qualification,
173
+ };
174
+ return { ...body, root: recordDigest(body) };
175
+ }
@@ -0,0 +1,104 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { recordDigest } from "../../release/discussion/envelope.js";
4
+ import { githubPipelinePublicationArtifacts } from "../../providers/github/pipeline-publication-artifacts.js";
5
+ import { publicationContext, uniquePublicationMaterial } from "./context.js";
6
+ import { qualifyPipelineProducts } from "./qualification.js";
7
+ import { pipelineProductCapsules } from "./capsules.js";
8
+ import { retainPipelineProducts } from "./sealed-products.js";
9
+ import { preparePipelineSigning, verifyPipelineSigning } from "./signing.js";
10
+ import { pipelineReleaseDocuments } from "./documents.js";
11
+ import { downloadRecoveryPublicationBuild } from "./recovery-build-download.js";
12
+ import { prepareRecoveredSigning } from "./recovery-signing.js";
13
+
14
+ export async function preparePipelineQualification(context, host, directory) {
15
+ const { journal, archive } = await publicationContext(context, host);
16
+ if (context.recovery?.mode === "prepared")
17
+ return prepareRecoveredSigning(context, host, journal, archive, directory);
18
+ const products = githubPipelinePublicationArtifacts(host);
19
+ const productDirectory = path.join(directory, "products");
20
+ const { build, bundles } = context.recovery?.build
21
+ ? await downloadRecoveryPublicationBuild(context, host, productDirectory)
22
+ : await products.download(context, productDirectory);
23
+ const { plan, materialization } = context;
24
+ const qualified = qualifyPipelineProducts({
25
+ plan,
26
+ source: materialization.source,
27
+ bundles,
28
+ build,
29
+ policyRoot: plan.contractRoot,
30
+ });
31
+ const capsules = pipelineProductCapsules({
32
+ plan,
33
+ materialization,
34
+ qualified,
35
+ bundles,
36
+ });
37
+ const sealed = await retainPipelineProducts(archive, qualified, bundles);
38
+ const prepared = {
39
+ schema: "buildchain.pipeline-qualification-preparation/v1",
40
+ contextRoot: recordDigest(context),
41
+ qualified,
42
+ capsules,
43
+ sealed,
44
+ };
45
+ await journal.fence();
46
+ await journal.record(
47
+ `publication/qualification-prepared/${context.runId}-${context.runAttempt}`,
48
+ prepared,
49
+ );
50
+ return preparePipelineSigning({
51
+ plan,
52
+ materialization,
53
+ qualified,
54
+ directory: path.join(directory, "signing"),
55
+ });
56
+ }
57
+
58
+ export async function sealPipelineQualification(
59
+ context,
60
+ host,
61
+ directory,
62
+ bundlePath,
63
+ ) {
64
+ const { journal, archive } = await publicationContext(context, host);
65
+ const prepared = await uniquePublicationMaterial(
66
+ journal,
67
+ `publication/qualification-prepared/${context.runId}-${context.runAttempt}/`,
68
+ );
69
+ if (prepared.contextRoot !== recordDigest(context))
70
+ throw new Error("Signing preparation changed provider context");
71
+ const { plan, materialization } = context;
72
+ const signing = verifyPipelineSigning({
73
+ plan,
74
+ materialization,
75
+ qualified: prepared.qualified,
76
+ directory: path.join(directory, "verify-signing"),
77
+ bundlePath,
78
+ token: host.token,
79
+ });
80
+ const bundle = await archive.put(fs.readFileSync(bundlePath), {
81
+ name: "attestation.json",
82
+ mediaType: "application/json",
83
+ });
84
+ if (bundle.digest !== signing.bundleDigest)
85
+ throw new Error("Retained attestation differs from verified signing bytes");
86
+ await archive.read(bundle);
87
+ const documents = pipelineReleaseDocuments({
88
+ plan,
89
+ materialization,
90
+ qualified: prepared.qualified,
91
+ capsules: prepared.capsules,
92
+ signing,
93
+ });
94
+ const result = {
95
+ ...prepared,
96
+ schema: "buildchain.pipeline-qualified-products/v1",
97
+ signing,
98
+ bundle,
99
+ documents,
100
+ };
101
+ await journal.fence();
102
+ await journal.record("publication/qualified", result);
103
+ return result;
104
+ }
@@ -0,0 +1,122 @@
1
+ import { recordDigest } from "../../release/discussion/envelope.js";
2
+ import {
3
+ createReleaseInvocation,
4
+ createReleaseReceipt,
5
+ RELEASE_RECEIPT_CONTRACT,
6
+ } from "../../release/release-invocation.js";
7
+ import {
8
+ createDomainPublicationQualificationReceipt,
9
+ validatePublicationQualificationReceipt,
10
+ } from "../publication-qualification.js";
11
+ import { verifyPipelineQualification } from "./documents.js";
12
+
13
+ export const RECOVERY_PUBLICATION_ADMISSION =
14
+ "buildchain.pipeline-recovery-publication-admission/v1";
15
+
16
+ // Only the publisher's byte/signature/provider requalification calls this pure
17
+ // domain bridge. It creates fresh authority for the new execution and preserves
18
+ // the original invocation, transaction, Passport and qualification unchanged.
19
+ export function createRecoveryPublicationAdmission({
20
+ context,
21
+ retained,
22
+ execution,
23
+ readback,
24
+ now = new Date(),
25
+ }) {
26
+ const { plan, materialization } = context;
27
+ const { qualified, documents } = retained;
28
+ const { root, ...body } = readback;
29
+ if (
30
+ !context.recovery ||
31
+ !/^sha256:[0-9a-f]{64}$/u.test(context.recovery.planRoot || "") ||
32
+ context.recovery.predecessor === context.attempt ||
33
+ execution.attempt !== context.attempt ||
34
+ execution.runId !== context.runId ||
35
+ execution.runAttempt !== context.runAttempt ||
36
+ body.schema !== "buildchain.pipeline-recovery-publication-readback/v1" ||
37
+ root !== recordDigest(body) ||
38
+ body.transactionRoot !== documents.transaction.transactionRoot ||
39
+ body.qualificationRoot !== qualified.root ||
40
+ body.signingRoot !== recordDigest(retained.signing) ||
41
+ body.sealedRoot !== retained.sealed.root
42
+ )
43
+ throw new Error(
44
+ "Recovery publication admission requires exact current execution and independent retained-byte readback",
45
+ );
46
+ verifyPipelineQualification({
47
+ plan,
48
+ materialization,
49
+ qualified,
50
+ evaluatedAt: qualified.qualification.issuedAt,
51
+ });
52
+ const qualification = createDomainPublicationQualificationReceipt({
53
+ ...qualified.qualification,
54
+ issuedAt: now.toISOString(),
55
+ expiresAt: new Date(now.getTime() + 60 * 60 * 1000).toISOString(),
56
+ });
57
+ // This observation reports the predecessor transaction at the recovery
58
+ // boundary. It is not a replacement for any original terminal receipt.
59
+ const predecessorReceipt = createReleaseReceipt({
60
+ schema: RELEASE_RECEIPT_CONTRACT,
61
+ transactionRoot: documents.transaction.transactionRoot,
62
+ outcome: "blocked",
63
+ releasePassportRoot: documents.passport.passportRoot,
64
+ providerTransactionRoot: body.effectsRoot,
65
+ providerStateRoot: readback.root,
66
+ providerReceiptRoots: [...new Set(body.receiptRoots)].sort(),
67
+ });
68
+ const invocation = createReleaseInvocation({
69
+ ...documents.invocation.invocation,
70
+ publisher: execution.publisher,
71
+ runtime: execution.runtime,
72
+ authority: {
73
+ policyRoot: plan.contractRoot,
74
+ qualificationRoot: qualification.receiptRoot,
75
+ warrantRoot: qualification.receiptRoot,
76
+ },
77
+ parent: {
78
+ invocationRoot: documents.invocation.roots.invocationRoot,
79
+ transactionRoot: documents.transaction.transactionRoot,
80
+ receiptRoot: predecessorReceipt.receiptRoot,
81
+ },
82
+ });
83
+ const value = {
84
+ schema: RECOVERY_PUBLICATION_ADMISSION,
85
+ contextRoot: recordDigest(context),
86
+ recoveryPlanRoot: context.recovery.planRoot,
87
+ execution,
88
+ originalDocumentsRoot: recordDigest(documents),
89
+ readbackRoot: readback.root,
90
+ qualification,
91
+ predecessorReceipt,
92
+ invocation,
93
+ };
94
+ return { ...value, root: recordDigest(value) };
95
+ }
96
+
97
+ export function verifyRecoveryPublicationAdmission(
98
+ admission,
99
+ { context, retained, execution, readback, now = new Date() },
100
+ ) {
101
+ const expected = createRecoveryPublicationAdmission({
102
+ context,
103
+ retained,
104
+ execution,
105
+ readback,
106
+ now: new Date(admission.qualification.issuedAt),
107
+ });
108
+ if (recordDigest(expected) !== recordDigest(admission))
109
+ throw new Error(
110
+ "Recovery publication authority changed its exact execution or original transaction",
111
+ );
112
+ validatePublicationQualificationReceipt(admission.qualification, {
113
+ repository: context.plan.source.repository,
114
+ candidateRoot: retained.qualified.qualification.candidateRoot,
115
+ sourceSha: retained.qualified.source.commit,
116
+ sourceRoot: recordDigest(retained.qualified.source),
117
+ artifactRoot: retained.qualified.qualification.artifactRoot,
118
+ policyDigest: context.plan.contractRoot,
119
+ evaluatedAt: now.toISOString(),
120
+ });
121
+ return admission;
122
+ }
@@ -0,0 +1,80 @@
1
+ import path from "node:path";
2
+ import { readPipelineCaller } from "../../providers/github/pipeline-run-entry.js";
3
+ import { recordDigest } from "../../release/discussion/envelope.js";
4
+ import { qualifyRecoveryRuntime } from "../../workflow/pipeline/recovery-runtime.js";
5
+ import { githubPipelinePublicationArtifacts } from "../../providers/github/pipeline-publication-artifacts.js";
6
+ import { qualifyPublicationBuildSegments } from "./build-segments.js";
7
+
8
+ export async function downloadRecoveryPublicationBuild(
9
+ context,
10
+ host,
11
+ directory,
12
+ ) {
13
+ const recovery = context.recovery.build;
14
+ const { root, ...body } = recovery;
15
+ if (
16
+ root !== recordDigest(body) ||
17
+ recordDigest(recovery.source) !==
18
+ recordDigest(context.materialization.source)
19
+ )
20
+ throw new Error(
21
+ "Recovery publication build changed its retained source evidence",
22
+ );
23
+ const provider = githubPipelinePublicationArtifacts(host),
24
+ segments = [],
25
+ bundles = [];
26
+ for (const [index, segment] of recovery.segments.entries()) {
27
+ const comparison = await qualifyRecoveryRuntime(
28
+ segment.runtime,
29
+ host.runtime,
30
+ "publication-build",
31
+ host.request,
32
+ );
33
+ if (!comparison.compatible)
34
+ throw new Error(
35
+ "Recovered publication producer changed after runtime admission",
36
+ );
37
+ const original = {
38
+ plan: segment.plan,
39
+ materialization: segment.materialization,
40
+ runId: segment.build.runId,
41
+ runAttempt: segment.build.runAttempt,
42
+ };
43
+ const downloaded = await provider.download(
44
+ original,
45
+ path.join(directory, `predecessor-${index}`),
46
+ segment.build.platforms,
47
+ );
48
+ if (recordDigest(downloaded.build) !== recordDigest(segment.build))
49
+ throw new Error(
50
+ "Recovered publication artifact evidence changed after admission",
51
+ );
52
+ segments.push(segment);
53
+ bundles.push(...downloaded.bundles);
54
+ }
55
+ if (recovery.scheduled.length) {
56
+ const downloaded = await provider.download(
57
+ context,
58
+ path.join(directory, "current"),
59
+ recovery.scheduled,
60
+ );
61
+ segments.push({
62
+ plan: context.plan,
63
+ materialization: context.materialization,
64
+ runtime: host.runtime,
65
+ build: downloaded.build,
66
+ });
67
+ bundles.push(...downloaded.bundles);
68
+ }
69
+ const { run } = await host.runs.read(context.runId, context.runAttempt);
70
+ await readPipelineCaller(
71
+ run,
72
+ context.materialization.source.configPath,
73
+ host.request,
74
+ host.repository,
75
+ );
76
+ return {
77
+ build: qualifyPublicationBuildSegments(context, segments, run.head_sha),
78
+ bundles,
79
+ };
80
+ }