@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,122 @@
1
+ import { recordDigest } from "../../release/discussion/envelope.js";
2
+ import { assertPipelineExecution } from "../../workflow/pipeline/fence.js";
3
+ import {
4
+ PUBLICATION_IMPORT,
5
+ publicationImportedValues,
6
+ } from "./imported-materials.js";
7
+
8
+ export function pipelinePublicationJournal(session, host) {
9
+ const initial = session.observed;
10
+ const expected = {
11
+ attempt: initial.attempt,
12
+ generation: initial.generation,
13
+ intent: session.intent.id,
14
+ sourceRoot: recordDigest(initial.history.at(-1).generation.source),
15
+ };
16
+ const store = host.materialStore(session);
17
+ async function fence() {
18
+ const observed = await session.journal.read();
19
+ assertPipelineExecution(observed, expected);
20
+ const writer = observed.history
21
+ .at(-1)
22
+ .events.filter((event) =>
23
+ event.payload.materials.some((material) =>
24
+ material.id.startsWith("publication/worker/"),
25
+ ),
26
+ )
27
+ .at(-1)?.payload.writer;
28
+ if (
29
+ writer &&
30
+ (writer.runId !== String(host.runId) ||
31
+ writer.runAttempt !== String(host.runAttempt))
32
+ )
33
+ throw new Error(
34
+ "Publication worker lost its exact provider execution reservation",
35
+ );
36
+ return observed;
37
+ }
38
+ async function materials(prefix) {
39
+ const observed = await session.journal.read();
40
+ if (observed.attempt !== initial.attempt)
41
+ throw new Error("Publication journal is no longer the current attempt");
42
+ const references = observed.history
43
+ .at(-1)
44
+ .events.flatMap((event) => event.payload.materials)
45
+ .filter(
46
+ (reference) =>
47
+ reference.id.startsWith(prefix) ||
48
+ reference.id.startsWith("publication/recovery-import/"),
49
+ );
50
+ const unique = [
51
+ ...new Map(
52
+ references.map((reference) => [reference.digest, reference]),
53
+ ).values(),
54
+ ];
55
+ const values = [];
56
+ for (const reference of unique) {
57
+ const value = await store.read(reference);
58
+ if (value.schema === PUBLICATION_IMPORT)
59
+ values.push(
60
+ ...publicationImportedValues(value)
61
+ .filter((item) => item.id.startsWith(prefix))
62
+ .map((item) => item.value),
63
+ );
64
+ else if (reference.id.startsWith(prefix)) values.push(value);
65
+ }
66
+ return [
67
+ ...new Map(values.map((value) => [recordDigest(value), value])).values(),
68
+ ];
69
+ }
70
+ async function record(
71
+ id,
72
+ value,
73
+ { phase = "publish", state = "running", expectedHead } = {},
74
+ ) {
75
+ const root = recordDigest(value);
76
+ const observed = await session.journal.read();
77
+ const eventKey = `publication:${id}:${root}`;
78
+ const prior = observed.history
79
+ .at(-1)
80
+ .events.find((event) => event.payload.eventKey === eventKey);
81
+ if (prior) {
82
+ if (
83
+ observed.attempt !== initial.attempt ||
84
+ prior.node !== phase ||
85
+ prior.payload.state !== state ||
86
+ prior.payload.materials.length !== 1 ||
87
+ recordDigest(await store.read(prior.payload.materials[0])) !== root
88
+ )
89
+ throw new Error(
90
+ "Publication retry changed its immutable recorded result",
91
+ );
92
+ return value;
93
+ }
94
+ const reference = await store.retain(
95
+ `${id}/${root.slice(7)}`,
96
+ value,
97
+ "receipt",
98
+ );
99
+ await session.progress.progress({
100
+ attempt: initial.attempt,
101
+ phase,
102
+ state,
103
+ eventKey,
104
+ materials: [reference],
105
+ ...(expectedHead !== undefined ? { expectedHead } : {}),
106
+ });
107
+ return value;
108
+ }
109
+ async function recordImport(value, phase) {
110
+ publicationImportedValues(value);
111
+ const observed = await fence();
112
+ const id = "publication/recovery-import";
113
+ const eventKey = `publication:${id}:${recordDigest(value)}`;
114
+ const prior = observed.history
115
+ .at(-1)
116
+ .events.find((event) => event.payload.eventKey === eventKey);
117
+ // Import is one atomic append, not a new result for each resumed phase.
118
+ // Replay still verifies the original event, state and retained bytes.
119
+ return record(id, value, { phase: prior?.node || phase });
120
+ }
121
+ return { fence, materials, record, recordImport };
122
+ }
@@ -0,0 +1,117 @@
1
+ import { observeAdvancedPipelineDevelopment } from "./development-current.js";
2
+ import { createPipelineDevelopmentTransition } from "./development-transition.js";
3
+ import { githubPipelineVersion } from "../../providers/github/pipeline-version.js";
4
+ import { uniquePublicationMaterial } from "./context.js";
5
+ import { observePipelineVersionAnchor } from "./development-anchor.js";
6
+ import { developmentTransitionReadback } from "./development-proof.js";
7
+ import { prepareNextDevelopmentPullRequest } from "./development-pr.js";
8
+
9
+ export async function nextPipelineDevelopment(context, host, journal) {
10
+ const { plan } = context;
11
+ const publication = await uniquePublicationMaterial(
12
+ journal,
13
+ "publication/complete/",
14
+ );
15
+ const transitions = await journal.materials(
16
+ "publication/next-development-plan/",
17
+ );
18
+ if (transitions.length > 1)
19
+ throw new Error("Next-development has conflicting retained declarations");
20
+ let transition = transitions[0];
21
+ if (!transition) {
22
+ transition = createPipelineDevelopmentTransition(context, publication);
23
+ await journal.record("publication/next-development-plan", transition, {
24
+ phase: "next-development",
25
+ });
26
+ }
27
+ if (transition.model.strategy === "anchored")
28
+ return observePipelineVersionAnchor(context, host, transition);
29
+ const retained = await journal.materials("publication/next-development-pr/");
30
+ if (retained.length > 1)
31
+ throw new Error("Next-development has conflicting retained PRs");
32
+ let selected = retained[0];
33
+ const adapter = githubPipelineVersion(host.request, host.repository);
34
+ if (!selected) {
35
+ const advanced = await observeAdvancedPipelineDevelopment(
36
+ context,
37
+ host,
38
+ transition,
39
+ adapter,
40
+ );
41
+ if (advanced) return advanced;
42
+ selected = await prepareNextDevelopmentPullRequest(
43
+ context,
44
+ host,
45
+ journal,
46
+ transition,
47
+ adapter,
48
+ );
49
+ if (selected.versionContext) return selected;
50
+ }
51
+ const pr = await host.request(
52
+ `/repos/${host.repository}/pulls/${selected.number}`,
53
+ );
54
+ if (
55
+ pr.head?.sha !== selected.source.commit ||
56
+ pr.head?.ref !== selected.branch ||
57
+ pr.base?.ref !== selected.base ||
58
+ pr.head?.repo?.full_name !== host.repository
59
+ )
60
+ throw new Error(
61
+ "Next-development PR changed its retained source or target",
62
+ );
63
+ const before = await adapter.inspect(
64
+ selected.materialization.protectedSource,
65
+ plan.versionPolicy,
66
+ );
67
+ const after = await adapter.inspect(selected.source, plan.versionPolicy);
68
+ const pending = developmentTransitionReadback(transition, {
69
+ before,
70
+ after,
71
+ evidence: selected,
72
+ createdAt: pr.created_at,
73
+ });
74
+ if (!pr.merged)
75
+ return {
76
+ state: "waiting",
77
+ reason:
78
+ pr.state === "open"
79
+ ? "next-development-protected-pr-pending"
80
+ : "next-development-pr-closed",
81
+ pullRequest: selected.number,
82
+ source: selected.source,
83
+ transition: pending,
84
+ };
85
+ const integration = await host.integration.observe({
86
+ intent: {
87
+ source: { pullRequest: selected.number, targetBranch: selected.base },
88
+ },
89
+ generation: {
90
+ source: { ...selected.source, configPath: plan.source.configPath },
91
+ },
92
+ });
93
+ const source = await host.source.source(
94
+ integration.mergeCommit,
95
+ plan.source.configPath,
96
+ );
97
+ const readback = await adapter.inspect(source.identity, plan.versionPolicy);
98
+ if (readback.version !== transition.target.version)
99
+ throw new Error(
100
+ "Protected next-development version readback differs from its declaration",
101
+ );
102
+ return {
103
+ state: "success",
104
+ reason: "protected-next-development-verified",
105
+ transitionRoot: transition.idempotencyKey,
106
+ integration,
107
+ version: readback.version,
108
+ publicationRoot: publication.release.receiptRoot,
109
+ transition: developmentTransitionReadback(transition, {
110
+ before,
111
+ after: readback,
112
+ evidence: integration,
113
+ createdAt: pr.created_at,
114
+ mergedAt: pr.merged_at,
115
+ }),
116
+ };
117
+ }
@@ -0,0 +1,180 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+ import { packNpmArtifact, sealedPackResult } from "../npm/package.js";
5
+ import { readNpmPackageJsonFromTarball } from "../../release/candidate/payloads.js";
6
+ import { createNativeChildEnvironment } from "../../dev-delivery/native/execution.js";
7
+ import { recordDigest } from "../../release/discussion/envelope.js";
8
+ import { verifyPipelinePublicationPlan } from "./plan.js";
9
+ import { assertPipelinePackagePolicy } from "./package-policy.js";
10
+ import {
11
+ publicationPath,
12
+ publicationFile,
13
+ writeImmutablePublicationFile,
14
+ } from "./files.js";
15
+
16
+ function packageArtifact(directory, output, expected, version, environment) {
17
+ const product = publicationPath(directory, expected.path, "directory");
18
+ const pack = packNpmArtifact({
19
+ cwd: product,
20
+ env: createNativeChildEnvironment(environment),
21
+ outputDirectory: output,
22
+ ignoreScripts: true,
23
+ registry: "https://registry.npmjs.org/",
24
+ });
25
+ const sealed = sealedPackResult({
26
+ tarballPath: pack.tarballPath,
27
+ integrity: pack.integrity,
28
+ });
29
+ const pkg = readNpmPackageJsonFromTarball(pack.tarballPath);
30
+ assertPipelinePackagePolicy(pkg);
31
+ if (
32
+ pkg.name !== pack.name ||
33
+ pkg.version !== version ||
34
+ pack.version !== version ||
35
+ pkg.private === true
36
+ )
37
+ throw new Error(
38
+ "Sealed npm package does not match the planned public package version",
39
+ );
40
+ return {
41
+ file: pack.tarballPath,
42
+ suffix: ".tgz",
43
+ package: { name: pkg.name, version, integrity: sealed.integrity },
44
+ };
45
+ }
46
+
47
+ function archiveNames(file, suffix) {
48
+ const options = {
49
+ cwd: path.dirname(file),
50
+ encoding: "utf8",
51
+ maxBuffer: 16 * 1024 * 1024,
52
+ timeout: 30000,
53
+ stdio: ["ignore", "pipe", "pipe"],
54
+ };
55
+ if (suffix === ".zip") {
56
+ const script = [
57
+ "import json,sys,zipfile",
58
+ "with zipfile.ZipFile(sys.argv[1]) as archive:",
59
+ " print(json.dumps(archive.namelist()))",
60
+ ].join("\n");
61
+ return JSON.parse(
62
+ execFileSync(
63
+ process.platform === "win32" ? "python" : "python3",
64
+ ["-c", script, path.basename(file)],
65
+ options,
66
+ ),
67
+ );
68
+ }
69
+ return execFileSync("tar", ["-tf", path.basename(file)], options)
70
+ .trim()
71
+ .split(/\r?\n/u);
72
+ }
73
+
74
+ export function inspectPipelineFileArtifact(directory, expected) {
75
+ const file = publicationPath(directory, expected.path);
76
+ if (expected.kind === "pdf") {
77
+ const bytes = fs.readFileSync(file);
78
+ if (
79
+ !bytes.subarray(0, 8).toString().startsWith("%PDF-") ||
80
+ !bytes.subarray(-1024).toString().includes("%%EOF")
81
+ )
82
+ throw new Error("Declared Paper output is not a complete PDF document");
83
+ return { file, suffix: ".pdf" };
84
+ }
85
+ const suffix = [".tar.gz", ".tar.xz", ".tgz", ".zip", ".tar"].find((value) =>
86
+ file.endsWith(value),
87
+ );
88
+ if (!suffix)
89
+ throw new Error("Declared binary output must be a standard archive");
90
+ const names = archiveNames(file, suffix);
91
+ if (
92
+ !names[0] ||
93
+ names.some(
94
+ (name) =>
95
+ !name.trim() ||
96
+ /[\u0000-\u001f\u007f]/u.test(name) ||
97
+ name.replaceAll("\\", "/").startsWith("/") ||
98
+ /^[A-Za-z]:/u.test(name) ||
99
+ name.replaceAll("\\", "/").split("/").includes(".."),
100
+ )
101
+ )
102
+ throw new Error("Binary archive contains unsafe or empty paths");
103
+ return { file, suffix };
104
+ }
105
+
106
+ export function packPipelineProducts({
107
+ cwd,
108
+ output,
109
+ plan,
110
+ platform,
111
+ source,
112
+ environment = process.env,
113
+ }) {
114
+ verifyPipelinePublicationPlan(plan);
115
+ const expected = plan.outputs.filter((item) => item.platform === platform);
116
+ if (!expected.length)
117
+ throw new Error("Publication platform was not declared");
118
+ fs.mkdirSync(output, { recursive: true });
119
+ const artifacts = expected.map((item) => {
120
+ const directory = publicationPath(cwd, item.directory, "directory");
121
+ const staging = path.join(output, "npm-pack", item.product, item.artifact);
122
+ const packed =
123
+ item.kind === "npm-package"
124
+ ? packageArtifact(directory, staging, item, plan.version, environment)
125
+ : inspectPipelineFileArtifact(directory, item);
126
+ const observed = publicationFile(packed.file);
127
+ const name =
128
+ item.filename ||
129
+ `${item.product}-${platform}-${item.artifact}${packed.suffix}`;
130
+ if (!name.endsWith(packed.suffix))
131
+ throw new Error(
132
+ "Declared publication filename does not match the actual artifact format",
133
+ );
134
+ writeImmutablePublicationFile(
135
+ path.join(output, "payloads", name),
136
+ observed.bytes,
137
+ );
138
+ return {
139
+ ...item,
140
+ file: `payloads/${name}`,
141
+ size: observed.size,
142
+ digest: observed.digest,
143
+ ...(packed.package ? { package: packed.package } : {}),
144
+ };
145
+ });
146
+ const body = {
147
+ schema: "buildchain.pipeline-publication-products/v1",
148
+ planRoot: plan.root,
149
+ source,
150
+ platform,
151
+ artifacts,
152
+ };
153
+ const manifest = { ...body, root: recordDigest(body) };
154
+ writeImmutablePublicationFile(
155
+ path.join(output, "manifest.json"),
156
+ `${JSON.stringify(manifest, null, 2)}\n`,
157
+ );
158
+ return manifest;
159
+ }
160
+
161
+ export function verifyPipelineProductFiles(directory, manifest) {
162
+ const { root, ...body } = manifest;
163
+ if (
164
+ body.schema !== "buildchain.pipeline-publication-products/v1" ||
165
+ root !== recordDigest(body)
166
+ )
167
+ throw new Error("Publication manifest root does not match retained bytes");
168
+ for (const artifact of manifest.artifacts) {
169
+ const observed = publicationFile(publicationPath(directory, artifact.file));
170
+ if (
171
+ observed.size !== artifact.size ||
172
+ observed.digest !== artifact.digest ||
173
+ (artifact.package && observed.integrity !== artifact.package.integrity)
174
+ )
175
+ throw new Error(
176
+ "Publication artifact changed after its manifest was sealed",
177
+ );
178
+ }
179
+ return manifest;
180
+ }
@@ -0,0 +1,22 @@
1
+ export function assertPipelinePackagePolicy(pkg) {
2
+ const config = pkg.publishConfig;
3
+ if (config === undefined) return;
4
+ if (
5
+ !config ||
6
+ Array.isArray(config) ||
7
+ typeof config !== "object" ||
8
+ Object.keys(config).some(
9
+ (key) => !["access", "registry", "tag", "provenance"].includes(key),
10
+ )
11
+ )
12
+ throw new Error(
13
+ "npm publishConfig cannot add provider, credential or execution configuration to the product contract",
14
+ );
15
+ if (
16
+ config.registry !== undefined &&
17
+ !["https://registry.npmjs.org", "https://registry.npmjs.org/"].includes(
18
+ config.registry,
19
+ )
20
+ )
21
+ throw new Error("npm publication requires the declared npmjs provider");
22
+ }
@@ -0,0 +1,136 @@
1
+ import { recordDigest } from "../../release/discussion/envelope.js";
2
+ import { selectProductPublicationIntent } from "../../release/product-publication.js";
3
+
4
+ export function pipelineExpectedProducts(plan) {
5
+ const outputs = plan.products
6
+ .flatMap((product) =>
7
+ product.platforms.flatMap((platform) =>
8
+ product.artifacts.map((artifact) => ({
9
+ id: `${product.id}/${platform}/${artifact.id}`,
10
+ product: product.id,
11
+ platform,
12
+ artifact: artifact.id,
13
+ kind: artifact.kind,
14
+ directory: product.directory || ".",
15
+ path: artifact.path,
16
+ ...(artifact.filename ? { filename: artifact.filename } : {}),
17
+ targets: product.targets
18
+ .filter((target) => target.artifacts.includes(artifact.id))
19
+ .map(({ provider, access }) => ({
20
+ provider,
21
+ ...(access ? { access } : {}),
22
+ })),
23
+ })),
24
+ ),
25
+ )
26
+ .sort((left, right) => left.id.localeCompare(right.id));
27
+ if (
28
+ !outputs.length ||
29
+ outputs.length > 256 ||
30
+ new Set(outputs.map(({ id }) => id)).size !== outputs.length
31
+ )
32
+ throw new Error(
33
+ "Publication requires a bounded unique product/platform/artifact inventory",
34
+ );
35
+ const filenames = outputs.map(
36
+ (output) =>
37
+ output.filename ||
38
+ `${output.product}-${output.platform}-${output.artifact}${
39
+ output.kind === "npm-package"
40
+ ? ".tgz"
41
+ : output.kind === "pdf"
42
+ ? ".pdf"
43
+ : [".tar.gz", ".tar.xz", ".tgz", ".zip", ".tar"].find((suffix) =>
44
+ output.path.endsWith(suffix),
45
+ )
46
+ }`,
47
+ );
48
+ if (new Set(filenames).size !== filenames.length)
49
+ throw new Error(
50
+ "Publication artifact filenames must be unique across every declared platform",
51
+ );
52
+ return outputs;
53
+ }
54
+
55
+ export function planPipelinePublication({
56
+ attempt,
57
+ generation,
58
+ source,
59
+ intentSource = source,
60
+ runtime,
61
+ publisher,
62
+ contract,
63
+ route,
64
+ version,
65
+ sourceTimestamp,
66
+ expectedTagSha = null,
67
+ previousChannelCommit = null,
68
+ }) {
69
+ if (route.operation === "develop")
70
+ throw new Error("Development delivery cannot publish products");
71
+ const developmentRoutes = contract.channels.filter(
72
+ (entry) =>
73
+ entry.operation === "alpha" &&
74
+ entry.to === (route.operation === "alpha" ? route.to : route.from),
75
+ );
76
+ if (developmentRoutes.length !== 1)
77
+ throw new Error(
78
+ "Publication requires one declared protected development channel",
79
+ );
80
+ const outputs = pipelineExpectedProducts(contract);
81
+ // The Rust-owned version/route decision operates on a declaration here.
82
+ // Actual sealed artifact roots are admitted independently after the build.
83
+ const selection = selectProductPublicationIntent({
84
+ channel: route.operation === "alpha" ? "alpha" : "stable",
85
+ targetRef: route.to,
86
+ sourceSha: source.commit,
87
+ sourceTimestamp,
88
+ repository: source.repository,
89
+ artifactKind: "custom",
90
+ requiredArtifactsRoot: recordDigest(outputs),
91
+ candidateVersion: version,
92
+ observedVersions: [],
93
+ });
94
+ if (contract.version.strategy === "anchored" && selection.version !== version)
95
+ throw new Error(
96
+ "Anchored publication requires an explicitly materialized version; inference is forbidden",
97
+ );
98
+ const body = {
99
+ schema: "buildchain.pipeline-publication-plan/v1",
100
+ evidenceVersion: 1,
101
+ attempt,
102
+ generation,
103
+ source,
104
+ intentSource,
105
+ runtime,
106
+ publisher,
107
+ contractRoot: recordDigest(contract),
108
+ version: selection.version,
109
+ candidateVersion: version,
110
+ tag: selection.exactTag,
111
+ channel: selection.channel,
112
+ route,
113
+ sourceTimestamp,
114
+ versionPolicy: contract.version,
115
+ ...(contract.stable
116
+ ? { stablePolicy: structuredClone(contract.stable) }
117
+ : {}),
118
+ developmentBranch: developmentRoutes[0].from,
119
+ expectedTagSha,
120
+ previousChannelCommit,
121
+ outputs,
122
+ versionSelection: selection,
123
+ phases: ["QUALIFY", "APPLY", "SETTLE"],
124
+ };
125
+ return { ...body, root: recordDigest(body) };
126
+ }
127
+
128
+ export function verifyPipelinePublicationPlan(plan) {
129
+ const { root, ...body } = plan || {};
130
+ if (
131
+ body.schema !== "buildchain.pipeline-publication-plan/v1" ||
132
+ root !== recordDigest(body)
133
+ )
134
+ throw new Error("Publication plan bytes do not match their retained root");
135
+ return plan;
136
+ }