@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
@@ -79,6 +79,7 @@ export async function executeReleaseDiscussion(request, _admission, context) {
79
79
  store: journal.store,
80
80
  octokit: context.octokit,
81
81
  });
82
+ await retained.diagnostics("transport", "qualification-probe");
82
83
  const probe = await retained.checkpoint("transport", {
83
84
  schema: "buildchain.material-qualification/v1",
84
85
  value: payload.key,
@@ -23,6 +23,10 @@ export function readReleaseDiscussion({ body, records }) {
23
23
  new Set(intent.expectedNodes).size !== intent.expectedNodes.length
24
24
  )
25
25
  throw new Error("Missing or duplicate expected nodes");
26
+ if (intent.organization && intent.organization !== "attempt-threads/v1")
27
+ throw new Error(
28
+ "Discussion organization requires its historical runtime reader",
29
+ );
26
30
  const attempts = groupAttempts(validateRecords(intent, records));
27
31
  if (!attempts.size)
28
32
  return {
@@ -156,6 +160,11 @@ export function discussionStatus(state) {
156
160
  status: state.status,
157
161
  attempt: state.attempt || "",
158
162
  attempts: state.attempts,
163
+ threads: [...(state.roots || new Map())].map(([attempt, comment]) => ({
164
+ attempt,
165
+ commentId: comment.id,
166
+ url: comment.url,
167
+ })),
159
168
  missingNodes: state.missingNodes,
160
169
  handoff: state.handoff || null,
161
170
  nodes: Object.fromEntries(
@@ -39,6 +39,7 @@ export async function openReleaseSession({
39
39
  dryRun = false,
40
40
  recover = false,
41
41
  expectedNodes = RELEASE_NODES,
42
+ onFailure = async () => {},
42
43
  }) {
43
44
  const intent = createIntent({
44
45
  repository,
@@ -101,6 +102,7 @@ export async function openReleaseSession({
101
102
  { code: String(error.code || "execution-failed") },
102
103
  1,
103
104
  );
105
+ await onFailure(node, error);
104
106
  } catch (recordError) {
105
107
  error.discussionRecordingError = recordError.message;
106
108
  }
@@ -1,3 +1,5 @@
1
+ import { readDiscussionThreads, eventParent } from "./threads.js";
2
+ import { renderIntent, renderEvent } from "./presentation.js";
1
3
  import { collectDiscussionPages } from "../../providers/github/discussions/transport.js";
2
4
  import {
3
5
  canonicalJson,
@@ -95,11 +97,10 @@ export function releaseDiscussionStore(
95
97
  discussion = await transport.create({
96
98
  repositoryId: repository.id,
97
99
  categoryId: selected[0].id,
98
- title: `Buildchain release: ${intent.key}`,
99
- body: encodeRecord(
100
- intent,
101
- `Release transaction for ${intent.key}. Expected nodes: ${intent.expectedNodes.join(", ")}.`,
102
- ),
100
+ title: intent.source?.qualification
101
+ ? `Buildchain qualification: ${intent.source.qualification}`
102
+ : `Buildchain release: ${intent.key}`,
103
+ body: encodeRecord(intent, renderIntent(intent)),
103
104
  });
104
105
  } catch (error) {
105
106
  discussion = await observeAfterUnknown(find, error);
@@ -110,39 +111,30 @@ export function releaseDiscussionStore(
110
111
  }
111
112
  async function read(session) {
112
113
  const discussion = await transport.get(session.discussion.id);
113
- assertDiscussion(discussion, session.intent, session.writerId);
114
- const comments = await collectDiscussionPages((after) =>
115
- transport.comments(discussion.id, after),
116
- );
117
- const trusted = comments.filter(
118
- (comment) => comment.author?.id === session.writerId,
114
+ const intent = assertDiscussion(
115
+ discussion,
116
+ session.intent,
117
+ session.writerId,
119
118
  );
120
- if (
121
- trusted.some(
122
- (comment) => comment.lastEditedAt && decodeRecord(comment.body),
123
- )
124
- )
125
- throw new Error(
126
- "A transaction record was edited; historical facts are no longer intact",
127
- );
128
- const records = trusted
129
- .map((comment) => decodeRecord(comment.body))
130
- .filter(Boolean);
119
+ const threads = await readDiscussionThreads(transport, {
120
+ ...session,
121
+ intent,
122
+ discussion,
123
+ });
131
124
  return {
132
125
  discussion,
133
- comments: trusted,
134
- records,
135
- ...readReleaseDiscussion({ body: discussion.body, records }),
126
+ ...threads,
127
+ ...readReleaseDiscussion({
128
+ body: discussion.body,
129
+ records: threads.records,
130
+ }),
136
131
  };
137
132
  }
138
133
  async function append(session, record) {
139
134
  if (session.dryRun) return { dryRun: true };
140
- const body = encodeRecord(
141
- record,
142
- `${record.node}: ${record.status} (attempt ${record.attempt})`,
143
- );
135
+ let observed;
144
136
  const find = async () => {
145
- const observed = await read(session);
137
+ observed = await read(session);
146
138
  const existing = observed.comments.find(
147
139
  (comment) => decodeRecord(comment.body)?.id === record.id,
148
140
  );
@@ -155,8 +147,13 @@ export function releaseDiscussionStore(
155
147
  };
156
148
  const existing = await find();
157
149
  if (existing) return existing;
150
+ const parent = eventParent(observed, record);
151
+ const body = encodeRecord(
152
+ record,
153
+ renderEvent(record, session.intent, observed),
154
+ );
158
155
  try {
159
- await transport.append(session.discussion.id, body);
156
+ await transport.append(session.discussion.id, body, parent);
160
157
  } catch (error) {
161
158
  return observeAfterUnknown(find, error);
162
159
  }
@@ -0,0 +1,83 @@
1
+ import { decodeRecord } from "./envelope.js";
2
+ import { collectDiscussionPages } from "../../providers/github/discussions/transport.js";
3
+
4
+ export function isAttemptRoot(record) {
5
+ return (
6
+ record?.node === "attempt" &&
7
+ record.kind === "progress" &&
8
+ record.status === "running" &&
9
+ record.sequence === 0
10
+ );
11
+ }
12
+
13
+ // Provider placement is checked before the pure reader reduces event semantics.
14
+ // Historical flat records retain their original representation; new writes always
15
+ // use roots and replies. An explicit intent organization makes placement strict.
16
+ export async function readDiscussionThreads(transport, session) {
17
+ const top = await collectDiscussionPages((after) =>
18
+ transport.comments(session.discussion.id, after),
19
+ );
20
+ const comments = [...top];
21
+ for (const root of top) {
22
+ if (root.replies?.totalCount === 0) continue;
23
+ const replies = await collectDiscussionPages((after) =>
24
+ transport.replies(root.id, after),
25
+ );
26
+ for (const reply of replies) {
27
+ if (reply.replyTo?.id !== root.id)
28
+ throw new Error(
29
+ "Discussion reply parent differs from its queried thread",
30
+ );
31
+ }
32
+ comments.push(...replies);
33
+ if (Buffer.byteLength(JSON.stringify(comments)) > 32 * 1024 * 1024)
34
+ throw new Error("Discussion threads exceed the complete-view byte bound");
35
+ }
36
+ const trusted = comments.filter(
37
+ (comment) => comment.author?.id === session.writerId,
38
+ );
39
+ const records = new Map();
40
+ for (const comment of trusted) {
41
+ const record = decodeRecord(comment.body);
42
+ if (!record) continue;
43
+ if (comment.lastEditedAt)
44
+ throw new Error(
45
+ "A transaction record was edited; historical facts are no longer intact",
46
+ );
47
+ records.set(comment.id, record);
48
+ }
49
+ const roots = new Map();
50
+ for (const comment of trusted) {
51
+ const record = records.get(comment.id);
52
+ if (!isAttemptRoot(record)) continue;
53
+ if (comment.replyTo || roots.has(record.attempt))
54
+ throw new Error("Ambiguous or nested Discussion attempt root");
55
+ roots.set(record.attempt, comment);
56
+ }
57
+ for (const comment of trusted) {
58
+ const record = records.get(comment.id);
59
+ if (!record || isAttemptRoot(record)) continue;
60
+ const root = roots.get(record.attempt);
61
+ if (comment.replyTo) {
62
+ if (!root || comment.replyTo.id !== root.id)
63
+ throw new Error(
64
+ "Transaction event is attached to a different attempt root",
65
+ );
66
+ } else if (session.intent.organization === "attempt-threads/v1") {
67
+ throw new Error("Transaction event requires its attempt root reply");
68
+ }
69
+ }
70
+ return { comments: trusted, records: [...records.values()], roots };
71
+ }
72
+
73
+ export function eventParent(state, record) {
74
+ const root = state.roots.get(record.attempt);
75
+ if (isAttemptRoot(record)) {
76
+ if (root)
77
+ throw new Error("Attempt root already exists with different content");
78
+ return null;
79
+ }
80
+ if (!root)
81
+ throw new Error("Open the attempt root before appending its events");
82
+ return root.id;
83
+ }
@@ -3,6 +3,7 @@
3
3
  import crypto from "node:crypto";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
+ import { isDeepStrictEqual } from "node:util";
6
7
 
7
8
  import {
8
9
  discoverConfiguredDerivedVersionMaterial,
@@ -606,7 +607,7 @@ function assertStaticTransition(record, recreated) {
606
607
  throw new Error(`next-development ${field} drifted`);
607
608
  }
608
609
  for (const field of ["adapter", "declaredPaths", "effectBounds", "target"]) {
609
- if (JSON.stringify(record[field]) !== JSON.stringify(recreated[field])) {
610
+ if (!isDeepStrictEqual(record[field], recreated[field])) {
610
611
  const label = field === "effectBounds" ? "effect bounds" : field;
611
612
  throw new Error(`next-development ${label} drifted`);
612
613
  }
@@ -638,20 +639,19 @@ function assertStaticTransition(record, recreated) {
638
639
  }
639
640
 
640
641
  function assertMaterialization(record) {
641
- if (record.materialization !== null) {
642
- const { materializationRoot, ...body } = record.materialization;
643
- if (materializationRoot !== nextDevelopmentRoot(body)) {
644
- throw new Error("next-development materialization root drifted");
645
- }
646
- const materializedPaths = (record.materialization.paths || []).map(
647
- (entry) => entry.path,
648
- );
649
- if (
650
- JSON.stringify(materializedPaths) !==
651
- JSON.stringify(record.adapter.sourcePaths)
652
- ) {
653
- throw new Error("next-development materialization escaped source paths");
654
- }
642
+ if (record.materialization === null) return;
643
+ const { materializationRoot, ...body } = record.materialization;
644
+ if (materializationRoot !== nextDevelopmentRoot(body))
645
+ throw new Error("next-development materialization root drifted");
646
+ const { paths = [] } = record.materialization;
647
+ const materializedPaths = paths.map(({ path }) => path);
648
+ // Reference adapters cover sources; transaction adapters cover all outputs.
649
+ if (
650
+ ![record.adapter.sourcePaths, record.adapter.allowedChangePaths].some(
651
+ (expected) => isDeepStrictEqual(materializedPaths, expected),
652
+ )
653
+ ) {
654
+ throw new Error("next-development materialization paths drifted");
655
655
  }
656
656
  }
657
657
 
@@ -1,3 +1,5 @@
1
+ import { parseNpmView } from "../../publication/npm/registry.js";
2
+ import { readNpmPackResult } from "../../publication/npm/pack-result.js";
1
3
  import { createOciPublicationAdapter } from "./oci-provider.js";
2
4
  import crypto from "node:crypto";
3
5
  import fs from "node:fs";
@@ -306,8 +308,7 @@ function createPackedPackage(context) {
306
308
  { cwd: context.cwd, encoding: "utf8" },
307
309
  "npm pack",
308
310
  );
309
- const payload = JSON.parse(String(result.stdout || "[]"));
310
- const pack = Array.isArray(payload) ? payload[0] : payload;
311
+ const pack = readNpmPackResult(String(result.stdout || "[]"));
311
312
  if (
312
313
  pack?.name !== context.intent.packageName ||
313
314
  pack?.version !== context.intent.version ||
@@ -363,7 +364,7 @@ async function npmReadback(context, packedPackage, effect) {
363
364
  "npm-readback-failed",
364
365
  );
365
366
  }
366
- const integrity = JSON.parse(String(result.stdout || '""'));
367
+ const { integrity } = parseNpmView(result.stdout);
367
368
  if (integrity !== expected.integrity) return conflict("npm-integrity-conflict");
368
369
  return observed(effect, {
369
370
  kind: "npm-package",
@@ -1,3 +1,4 @@
1
+ import { parseNpmView } from "../../../publication/npm/registry.js";
1
2
  import { npmPackageSpec } from "./publish-contract.js";
2
3
  import { execNpmSync } from "./npm-distribution.js";
3
4
  import fs from "node:fs";
@@ -14,7 +15,7 @@ export function readExistingNpmIntegrity({ cwd, artifact }) {
14
15
  if (!output) {
15
16
  throw new Error("empty dist.integrity");
16
17
  }
17
- return JSON.parse(output);
18
+ return parseNpmView(output).integrity;
18
19
  } catch (error) {
19
20
  const message = error.stderr?.toString?.().trim() || error.message;
20
21
  throw new Error(
@@ -1,3 +1,4 @@
1
+ import { readNpmPackResult } from "../../../publication/npm/pack-result.js";
1
2
  import { npmPublishTransaction } from "../../../publication/npm/transaction.js";
2
3
  import { npmPublicationEnvironment } from "../../../publication/npm/environment.js";
3
4
  import {
@@ -15,10 +16,21 @@ import {
15
16
  updateVersionStateContents,
16
17
  } from "../../version-state.js";
17
18
  import { publishTransactionEnvironment } from "./transaction-context.js";
18
- export function runPublishCommand({ cwd, command, provider, loadedConfig, env }) {
19
+ export function runPublishCommand({
20
+ cwd,
21
+ command,
22
+ provider,
23
+ loadedConfig,
24
+ env,
25
+ }) {
19
26
  if (provider) {
20
- if (provider.kind !== "npm" || !provider.directory || command) throw new Error("Invalid or ambiguous declarative publication provider");
21
- npmPublishTransaction({ cwd: path.resolve(cwd, provider.directory), publication: npmPublicationEnvironment(env), env: { ...process.env, ...env } });
27
+ if (provider.kind !== "npm" || !provider.directory || command)
28
+ throw new Error("Invalid or ambiguous declarative publication provider");
29
+ npmPublishTransaction({
30
+ cwd: path.resolve(cwd, provider.directory),
31
+ publication: npmPublicationEnvironment(env),
32
+ env: { ...process.env, ...env },
33
+ });
22
34
  return "provider:npm";
23
35
  }
24
36
  const lifecyclePublish = getLifecycleStage(loadedConfig, "publish");
@@ -64,7 +76,7 @@ export function rematerializedNpmPackEnvironment({
64
76
  path.join(os.tmpdir(), "buildchain-rematerialized-npm-"),
65
77
  );
66
78
  try {
67
- const packed = JSON.parse(
79
+ const result = readNpmPackResult(
68
80
  execNpmSync(
69
81
  [
70
82
  "pack",
@@ -82,7 +94,6 @@ export function rematerializedNpmPackEnvironment({
82
94
  },
83
95
  ),
84
96
  );
85
- const result = Array.isArray(packed) ? packed[0] : packed;
86
97
  if (!result?.filename)
87
98
  throw new Error("rematerialized npm pack did not return a filename");
88
99
  if (result.name !== pkg.name || result.version !== version) {
@@ -184,8 +184,19 @@ export function evaluateStableReleaseGate({
184
184
  "version-bound impact declares a summary and at least one surface",
185
185
  { summary: string(impact?.summary), surfaceIds: surfaceImpacts.map((entry) => string(entry?.id)).filter(Boolean) },
186
186
  ));
187
+ checks.push(check(
188
+ string(impact?.release?.version) === candidateTag.replace(/^v/, ""),
189
+ "stable.impact_version",
190
+ "product impact belongs to the exact candidate version",
191
+ { expectedVersion: candidateTag.replace(/^v/, ""), observedVersion: string(impact?.release?.version) },
192
+ ));
187
193
 
188
194
  const canaryById = new Map(canaries.map((canary) => [string(canary?.id), canary]));
195
+ checks.push(check(
196
+ canaryById.size === canaries.length,
197
+ "canary.evidence_inventory",
198
+ "each canary has one unambiguous observation",
199
+ ));
189
200
  const canaryCompletionTimes = [];
190
201
  for (const required of policy.requiredCanaries) {
191
202
  const evidence = canaryById.get(required.id);
@@ -2,6 +2,6 @@
2
2
  export const DOMAIN_WASM_ABI_VERSION = 1;
3
3
  export const DOMAIN_WASM_RUSTC = "rustc 1.96.0 (ac68faa20 2026-05-25)";
4
4
  export const DOMAIN_WASM_SOURCE_SHA256 =
5
- "83550684878224e6820594a44fc48f805c5ed1b3182ac32ebeb51b74cfd20a7f";
5
+ "9275a02bbc3daac66cac3021384bcaaf43bd52ed9af56b4f1ac67f1cbc69fb71";
6
6
  export const DOMAIN_WASM_SHA256 =
7
- "504e23c9241f43611813f0fbd213b7a694946328b00e23d22f6ba678e7b0c841";
7
+ "d42ab5ae8d42253d40ab49057e460285a848925cece176c4d68e63e90ca86c72";
@@ -1,45 +1,38 @@
1
1
  import { resolveRecoverySource } from "./recovery.js";
2
2
  import { getOctokit } from "@actions/github";
3
3
  import { runtimeEntryProvider } from "./github.js";
4
+ import { resolveRuntimeActionSource } from "./source.js";
4
5
  import {
5
6
  preparedRuntimeSelection,
6
7
  selectExecutionRuntime,
7
8
  } from "./selection.js";
9
+ import { selectAttemptRecoveryRuntime } from "./attempt.js";
8
10
 
9
11
  export async function selectExecutionRuntimeAction(
10
12
  core,
11
13
  env,
12
- { providerFactory = runtimeEntryProvider, githubFactory = getOctokit } = {},
14
+ {
15
+ providerFactory = runtimeEntryProvider,
16
+ githubFactory = getOctokit,
17
+ pipelineLookup,
18
+ } = {},
13
19
  ) {
14
20
  const retained = core.getInput("selection");
15
21
  const transport = retained
16
22
  ? preparedRuntimeSelection(JSON.parse(retained))
17
23
  : undefined;
18
24
  const github = githubFactory(core.getInput("token", { required: true }));
19
- let source = {
20
- repository: core.getInput("source-repository") || env.GITHUB_REPOSITORY,
21
- sha: core.getInput("source-sha") || env.GITHUB_SHA,
22
- ref: env.GITHUB_REF,
23
- };
24
- const resumeRunId = core.getInput("resume-run-id");
25
- if (resumeRunId) {
26
- const reader = providerFactory(github, {
27
- sourceRepository: source.repository,
28
- sourceSha: source.sha,
29
- actor: env.GITHUB_ACTOR,
30
- eventName: env.GITHUB_EVENT_NAME,
31
- });
32
- await reader.authorize({ origin: "runtime-parameter" });
33
- source = await resolveRecoverySource(
34
- {
35
- repository: source.repository,
36
- runId: resumeRunId,
37
- currentRunId: env.GITHUB_RUN_ID,
38
- workflow: env.GITHUB_WORKFLOW_REF?.slice(source.repository.length + 1),
39
- },
40
- reader.readRun,
41
- );
42
- }
25
+ const {
26
+ source: resolvedSource,
27
+ continuation,
28
+ resumeRunId,
29
+ pipelineAttempt,
30
+ } = await resolveRuntimeActionSource(core, env, {
31
+ providerFactory,
32
+ github,
33
+ pipelineLookup,
34
+ });
35
+ let source = resolvedSource;
43
36
  const provider = providerFactory(github, {
44
37
  sourceRepository: source.repository,
45
38
  sourceSha: source.sha,
@@ -91,6 +84,15 @@ export async function selectExecutionRuntimeAction(
91
84
  selection.source?.sha !== source.sha
92
85
  )
93
86
  throw new Error("Runtime transport changed the admitted consumer source");
87
+ } else if (continuation?.recovery && (!runtimeRef || pipelineAttempt)) {
88
+ if (runtimeRef)
89
+ throw new Error(
90
+ "An admitted recovery runtime can change only through a new recovery attempt",
91
+ );
92
+ selection = await selectAttemptRecoveryRuntime(
93
+ continuation.recovery,
94
+ provider,
95
+ );
94
96
  } else {
95
97
  selection = await selectExecutionRuntime(
96
98
  {
@@ -0,0 +1,25 @@
1
+ import { validateRuntime } from "../../release/discussion/envelope.js";
2
+ import { selectExecutionRuntime } from "./selection.js";
3
+
4
+ // This input is read from the canonical current attempt, never from workflow
5
+ // inputs. Continuation retains its admitted repair without reopening an override.
6
+ export async function selectAttemptRecoveryRuntime(recovery, provider) {
7
+ validateRuntime(recovery.runtime);
8
+ if (
9
+ !/^attempt-[0-9a-f]{64}$/u.test(recovery.attempt || "") ||
10
+ !/^sha256:[0-9a-f]{64}$/u.test(recovery.recordRoot || "") ||
11
+ recovery.runtime.repository !== "kungfu-systems/buildchain"
12
+ )
13
+ throw new Error(
14
+ "Attempt runtime continuation requires its canonical recovery admission",
15
+ );
16
+ const selected = await selectExecutionRuntime(
17
+ { runtimeRef: recovery.runtime.sha },
18
+ {
19
+ ...provider,
20
+ authorize: (input) =>
21
+ provider.authorize({ ...input, origin: "attempt-recovery" }),
22
+ },
23
+ );
24
+ return { ...selected, origin: "attempt-recovery", recovery };
25
+ }
@@ -1,3 +1,6 @@
1
+ import { consumerContractLock } from "../../consumer/contract/identity.js";
2
+ import { canonicalJson } from "../../release/discussion/envelope.js";
3
+
1
4
  const exactSha = /^[a-f0-9]{40}$/iu;
2
5
  const repository = "kungfu-systems/buildchain";
3
6
  const trainRef =
@@ -28,6 +31,14 @@ export function runtimeSelector({ runtimeRef = "", lock, workflowSha }) {
28
31
  return { ref: runtimeRef, origin: "runtime-parameter" };
29
32
  }
30
33
  if (lock) {
34
+ if (lock.schema === "buildchain.consumer-contract-lock/v2") {
35
+ const validated = consumerContractLock(lock);
36
+ if (canonicalJson(validated) !== canonicalJson(lock))
37
+ throw new Error(
38
+ "Minimal consumer contract lock contains unsupported fields",
39
+ );
40
+ return { ref: validated.runtime.sha, origin: "consumer-contract-lock" };
41
+ }
31
42
  if (lock.contract !== "kungfu-buildchain-contract-lock")
32
43
  throw new Error("Unsupported Buildchain contract lock");
33
44
  const sha = commit(lock.buildchain?.resolvedSha, "Contract lock runtime");
@@ -92,6 +103,9 @@ export async function selectExecutionRuntime(
92
103
  ...(selected.origin === "contract-lock"
93
104
  ? { contract: { digest: request.lock.buildchain.contractDigest } }
94
105
  : {}),
106
+ ...(selected.origin === "consumer-contract-lock"
107
+ ? { contract: { consumerConfigDigest: request.lock.configDigest } }
108
+ : {}),
95
109
  class:
96
110
  selected.origin === "contract-lock" &&
97
111
  request.lock.buildchain.ref === "v4-alpha"
@@ -0,0 +1,62 @@
1
+ import { resolveRecoverySource } from "./recovery.js";
2
+ import {
3
+ pipelineRuntimeContinuation,
4
+ pipelineRecoveryContinuation,
5
+ } from "../../workflow/pipeline/runtime-source.js";
6
+
7
+ export async function resolveRuntimeActionSource(
8
+ core,
9
+ env,
10
+ { providerFactory, github, pipelineLookup },
11
+ ) {
12
+ let source = {
13
+ repository: core.getInput("source-repository") || env.GITHUB_REPOSITORY,
14
+ sha: core.getInput("source-sha") || env.GITHUB_SHA,
15
+ ref: env.GITHUB_REF,
16
+ };
17
+ const pipelineAttempt = core.getInput("pipeline-attempt");
18
+ const recoveryAttempt = core.getInput("recovery-attempt");
19
+ const resumeRunId = core.getInput("resume-run-id");
20
+ if (
21
+ [pipelineAttempt, recoveryAttempt, resumeRunId].filter(Boolean).length > 1
22
+ )
23
+ throw new Error("Runtime source selectors are mutually exclusive");
24
+ let continuation;
25
+ if (recoveryAttempt) {
26
+ continuation = await pipelineRecoveryContinuation(
27
+ recoveryAttempt,
28
+ source.repository,
29
+ core.getInput("token", { required: true }),
30
+ pipelineLookup,
31
+ );
32
+ source = continuation.source;
33
+ }
34
+ if (pipelineAttempt) {
35
+ continuation = await pipelineRuntimeContinuation(
36
+ pipelineAttempt,
37
+ source.repository,
38
+ core.getInput("token", { required: true }),
39
+ pipelineLookup,
40
+ );
41
+ source = continuation.source;
42
+ }
43
+ if (resumeRunId) {
44
+ const reader = providerFactory(github, {
45
+ sourceRepository: source.repository,
46
+ sourceSha: source.sha,
47
+ actor: env.GITHUB_ACTOR,
48
+ eventName: env.GITHUB_EVENT_NAME,
49
+ });
50
+ await reader.authorize({ origin: "runtime-parameter" });
51
+ source = await resolveRecoverySource(
52
+ {
53
+ repository: source.repository,
54
+ runId: resumeRunId,
55
+ currentRunId: env.GITHUB_RUN_ID,
56
+ workflow: env.GITHUB_WORKFLOW_REF?.slice(source.repository.length + 1),
57
+ },
58
+ reader.readRun,
59
+ );
60
+ }
61
+ return { source, continuation, resumeRunId, pipelineAttempt };
62
+ }