@kungfu-tech/buildchain 4.1.3-alpha.1 → 4.1.3-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/architecture/action-taxonomy.json +16 -0
  2. package/architecture/agent-change-map.md +239 -2
  3. package/architecture/ci-lane-change-budget.json +289 -0
  4. package/architecture/decisions/0005-minimal-consumer-contract.md +122 -0
  5. package/architecture/decisions/0006-business-attempt-journal.md +89 -0
  6. package/architecture/decisions/0007-hosted-pipeline-controller.md +106 -0
  7. package/architecture/decisions/0008-pipeline-product-publication.md +99 -0
  8. package/architecture/internal-capabilities.json +331 -2
  9. package/architecture/maintainability-debt.json +3 -1
  10. package/architecture/maintainability-policy.json +8 -8
  11. package/architecture/minimal-consumer-migration.json +7558 -0
  12. package/architecture/release-topology.json +245 -11
  13. package/architecture/universal-workflow-bootstrap.json +10 -0
  14. package/architecture/universal-workflow-capability-policy.json +1 -1
  15. package/architecture/workflow-taxonomy.json +55 -0
  16. package/dist/readers/business-attempt.cjs +805 -0
  17. package/dist/site/buildchain-contract.json +7 -5
  18. package/dist/site/buildchain-site.json +18 -13
  19. package/dist/site/capability-registry.json +3 -3
  20. package/dist/site/kfd-claims.json +311 -10
  21. package/dist/site/kfd-upstream-aggregate.json +1 -1
  22. package/dist/site/manual-registry.json +1 -1
  23. package/dist/site/node-api-registry.json +7 -7
  24. package/dist/site/page-registry.json +13 -8
  25. package/dist/site/public-surface-audit.json +201 -7
  26. package/dist/site/publication-authority-registry.json +121 -1
  27. package/dist/site/publication-registry.json +4 -4
  28. package/dist/site/site-manifest.json +5 -5
  29. package/dist/site/workflow-registry.json +416 -7
  30. package/docs/node-api-reference.md +11 -11
  31. package/docs/runtime-entry.md +45 -5
  32. package/docs/workflow-catalog.md +5 -0
  33. package/package.json +4 -3
  34. package/packages/core/consumer/contract/entries.js +85 -0
  35. package/packages/core/consumer/contract/examples.js +96 -0
  36. package/packages/core/consumer/contract/identity.js +65 -0
  37. package/packages/core/consumer/contract/inspection.js +45 -0
  38. package/packages/core/consumer/contract/plan.js +99 -0
  39. package/packages/core/consumer/contract/products.js +101 -0
  40. package/packages/core/consumer/contract/shape.js +46 -0
  41. package/packages/core/dev-delivery/candidate/admission.js +9 -5
  42. package/packages/core/dev-delivery/candidate/reservation.js +11 -0
  43. package/packages/core/dev-delivery/candidate/source-paths.js +12 -4
  44. package/packages/core/dev-delivery/native/actions.js +7 -1
  45. package/packages/core/dev-delivery/native/heartbeat-action.js +16 -0
  46. package/packages/core/dev-delivery/native/heartbeat.js +6 -4
  47. package/packages/core/dev-delivery/queue/landing-action.js +5 -0
  48. package/packages/core/governance/buildchain-publication-authority.js +5 -0
  49. package/packages/core/paper/operations/bootstrap.js +2 -0
  50. package/packages/core/paper/paper-npm-bootstrap.js +2 -2
  51. package/packages/core/providers/github/attempt-index.js +60 -0
  52. package/packages/core/providers/github/attempt-journal.js +154 -0
  53. package/packages/core/providers/github/discussions/materials.js +7 -2
  54. package/packages/core/providers/github/pipeline-checkout.js +49 -0
  55. package/packages/core/providers/github/pipeline-events.js +52 -0
  56. package/packages/core/providers/github/pipeline-integration.js +95 -0
  57. package/packages/core/providers/github/pipeline-policy.js +213 -0
  58. package/packages/core/providers/github/pipeline-product-payload.js +50 -0
  59. package/packages/core/providers/github/pipeline-product-release.js +191 -0
  60. package/packages/core/providers/github/pipeline-publication-artifacts.js +153 -0
  61. package/packages/core/providers/github/pipeline-run-entry.js +86 -0
  62. package/packages/core/providers/github/pipeline-runs.js +132 -0
  63. package/packages/core/providers/github/pipeline-source.js +178 -0
  64. package/packages/core/providers/github/pipeline-version.js +201 -0
  65. package/packages/core/providers/github/pipeline-worker.js +108 -0
  66. package/packages/core/publication/candidate/registry-hydration.js +2 -2
  67. package/packages/core/publication/npm/pack-preview.js +2 -2
  68. package/packages/core/publication/npm/pack-result.js +31 -0
  69. package/packages/core/publication/npm/package.js +3 -6
  70. package/packages/core/publication/npm/pipeline-channel.js +87 -0
  71. package/packages/core/publication/npm/pipeline-provider.js +109 -0
  72. package/packages/core/publication/npm/registry.js +12 -9
  73. package/packages/core/publication/pipeline/actions.js +100 -0
  74. package/packages/core/publication/pipeline/apply.js +182 -0
  75. package/packages/core/publication/pipeline/build-segments.js +107 -0
  76. package/packages/core/publication/pipeline/capsules.js +89 -0
  77. package/packages/core/publication/pipeline/context.js +39 -0
  78. package/packages/core/publication/pipeline/development-anchor.js +91 -0
  79. package/packages/core/publication/pipeline/development-current.js +63 -0
  80. package/packages/core/publication/pipeline/development-pr.js +85 -0
  81. package/packages/core/publication/pipeline/development-proof.js +76 -0
  82. package/packages/core/publication/pipeline/development-transition.js +112 -0
  83. package/packages/core/publication/pipeline/distribution.js +124 -0
  84. package/packages/core/publication/pipeline/documents.js +156 -0
  85. package/packages/core/publication/pipeline/effects.js +162 -0
  86. package/packages/core/publication/pipeline/files.js +66 -0
  87. package/packages/core/publication/pipeline/imported-materials.js +32 -0
  88. package/packages/core/publication/pipeline/journal.js +110 -0
  89. package/packages/core/publication/pipeline/next-development.js +116 -0
  90. package/packages/core/publication/pipeline/pack.js +151 -0
  91. package/packages/core/publication/pipeline/package-policy.js +22 -0
  92. package/packages/core/publication/pipeline/plan.js +114 -0
  93. package/packages/core/publication/pipeline/prepare.js +151 -0
  94. package/packages/core/publication/pipeline/qualification.js +170 -0
  95. package/packages/core/publication/pipeline/qualify.js +104 -0
  96. package/packages/core/publication/pipeline/recovery-admission.js +122 -0
  97. package/packages/core/publication/pipeline/recovery-build-download.js +80 -0
  98. package/packages/core/publication/pipeline/recovery-build-plan.js +122 -0
  99. package/packages/core/publication/pipeline/recovery-build-readback.js +68 -0
  100. package/packages/core/publication/pipeline/recovery-capsules.js +54 -0
  101. package/packages/core/publication/pipeline/recovery-import.js +80 -0
  102. package/packages/core/publication/pipeline/recovery-inspection.js +218 -0
  103. package/packages/core/publication/pipeline/recovery-materials.js +104 -0
  104. package/packages/core/publication/pipeline/recovery-plan.js +46 -0
  105. package/packages/core/publication/pipeline/recovery-prepare.js +73 -0
  106. package/packages/core/publication/pipeline/recovery-qualification.js +128 -0
  107. package/packages/core/publication/pipeline/recovery-readback.js +56 -0
  108. package/packages/core/publication/pipeline/recovery-signing.js +58 -0
  109. package/packages/core/publication/pipeline/sealed-products.js +79 -0
  110. package/packages/core/publication/pipeline/settle.js +43 -0
  111. package/packages/core/publication/pipeline/signing.js +140 -0
  112. package/packages/core/publication/pipeline/source-plan.js +83 -0
  113. package/packages/core/publication/pipeline/version.js +81 -0
  114. package/packages/core/publication/publication-reproducibility.js +2 -2
  115. package/packages/core/release/promote-candidate/product-provider-adapters.js +4 -3
  116. package/packages/core/release/promote-ref/internal/npm-existing-evidence.js +2 -1
  117. package/packages/core/release/promote-ref/internal/publish-command.js +16 -5
  118. package/packages/core/runtime/buildchain-domain.wasm +0 -0
  119. package/packages/core/runtime/domain-wasm-artifact.js +2 -2
  120. package/packages/core/runtime/entry/actions.js +27 -25
  121. package/packages/core/runtime/entry/attempt.js +25 -0
  122. package/packages/core/runtime/entry/selection.js +14 -0
  123. package/packages/core/runtime/entry/source.js +62 -0
  124. package/packages/core/workflow/attempt/identity.js +165 -0
  125. package/packages/core/workflow/attempt/journal.js +87 -0
  126. package/packages/core/workflow/attempt/materials.js +72 -0
  127. package/packages/core/workflow/attempt/reader-entry.js +12 -0
  128. package/packages/core/workflow/attempt/reader.js +149 -0
  129. package/packages/core/workflow/attempt/records.js +153 -0
  130. package/packages/core/workflow/attempt/store.js +75 -0
  131. package/packages/core/workflow/commands/pipeline-build.mjs +23 -0
  132. package/packages/core/workflow/pipeline/action-output.js +11 -0
  133. package/packages/core/workflow/pipeline/actions.js +62 -0
  134. package/packages/core/workflow/pipeline/build-control.js +140 -0
  135. package/packages/core/workflow/pipeline/build-evidence.js +92 -0
  136. package/packages/core/workflow/pipeline/build-qualification.js +145 -0
  137. package/packages/core/workflow/pipeline/build-result.js +69 -0
  138. package/packages/core/workflow/pipeline/build.js +95 -0
  139. package/packages/core/workflow/pipeline/cancellation.js +169 -0
  140. package/packages/core/workflow/pipeline/channel-control.js +127 -0
  141. package/packages/core/workflow/pipeline/controller.js +133 -0
  142. package/packages/core/workflow/pipeline/delivery-control.js +204 -0
  143. package/packages/core/workflow/pipeline/delivery-observation.js +118 -0
  144. package/packages/core/workflow/pipeline/delivery-request.js +198 -0
  145. package/packages/core/workflow/pipeline/events.js +84 -0
  146. package/packages/core/workflow/pipeline/fence.js +42 -0
  147. package/packages/core/workflow/pipeline/group-control.js +56 -0
  148. package/packages/core/workflow/pipeline/guard-build.js +37 -0
  149. package/packages/core/workflow/pipeline/guard.js +156 -0
  150. package/packages/core/workflow/pipeline/host.js +114 -0
  151. package/packages/core/workflow/pipeline/materials.js +58 -0
  152. package/packages/core/workflow/pipeline/notifications.js +36 -0
  153. package/packages/core/workflow/pipeline/parent-notification.js +27 -0
  154. package/packages/core/workflow/pipeline/platforms.js +21 -0
  155. package/packages/core/workflow/pipeline/progress.js +108 -0
  156. package/packages/core/workflow/pipeline/projection.js +20 -0
  157. package/packages/core/workflow/pipeline/reconcile.js +194 -0
  158. package/packages/core/workflow/pipeline/recovery-action.js +30 -0
  159. package/packages/core/workflow/pipeline/recovery-admission.js +122 -0
  160. package/packages/core/workflow/pipeline/recovery-build-control.js +95 -0
  161. package/packages/core/workflow/pipeline/recovery-build-evidence.js +71 -0
  162. package/packages/core/workflow/pipeline/recovery-build.js +121 -0
  163. package/packages/core/workflow/pipeline/recovery-controller.js +91 -0
  164. package/packages/core/workflow/pipeline/recovery-integration.js +66 -0
  165. package/packages/core/workflow/pipeline/recovery-merge-proof.js +90 -0
  166. package/packages/core/workflow/pipeline/recovery-ownership-settlement.js +150 -0
  167. package/packages/core/workflow/pipeline/recovery-ownership.js +91 -0
  168. package/packages/core/workflow/pipeline/recovery-plan.js +126 -0
  169. package/packages/core/workflow/pipeline/recovery-runtime.js +68 -0
  170. package/packages/core/workflow/pipeline/recovery-session.js +129 -0
  171. package/packages/core/workflow/pipeline/recovery-transition.js +135 -0
  172. package/packages/core/workflow/pipeline/recovery-unrecorded-build.js +74 -0
  173. package/packages/core/workflow/pipeline/runtime-source.js +111 -0
  174. package/packages/core/workflow/pipeline/selection.js +106 -0
  175. package/packages/core/workflow/pipeline/session.js +72 -0
  176. package/packages/core/workflow/pipeline/settlement.js +137 -0
  177. package/packages/core/workflow/pipeline/wake.js +38 -0
  178. package/packages/core/workflow/pipeline/web-status.js +108 -0
  179. package/scripts/build-release-discussion-reader.mjs +11 -7
  180. package/scripts/check-pipeline-publication-topology.mjs +36 -0
  181. package/scripts/check-release-topology.mjs +6 -1
  182. package/scripts/generate-minimal-consumer-contract.mjs +155 -0
  183. package/scripts/verify-golden-path.mjs +5 -1
  184. package/templates/minimal-consumer/binary/.buildchain/buildchain.toml +45 -0
  185. package/templates/minimal-consumer/binary/.github/workflows/buildchain-recover.yml +30 -0
  186. package/templates/minimal-consumer/binary/.github/workflows/buildchain.yml +28 -0
  187. package/templates/minimal-consumer/binary/package.json +6 -0
  188. package/templates/minimal-consumer/binary/src/build.mjs +5 -0
  189. package/templates/minimal-consumer/binary/src/hello.c +2 -0
  190. package/templates/minimal-consumer/binary/src/verify.mjs +3 -0
  191. package/templates/minimal-consumer/npm/.buildchain/buildchain.toml +46 -0
  192. package/templates/minimal-consumer/npm/.github/workflows/buildchain-recover.yml +30 -0
  193. package/templates/minimal-consumer/npm/.github/workflows/buildchain.yml +28 -0
  194. package/templates/minimal-consumer/npm/package.json +6 -0
  195. package/templates/minimal-consumer/npm/src/build.mjs +5 -0
  196. package/templates/minimal-consumer/npm/src/verify.mjs +3 -0
  197. package/templates/minimal-consumer/paper/.buildchain/buildchain.toml +45 -0
  198. package/templates/minimal-consumer/paper/.github/workflows/buildchain-recover.yml +30 -0
  199. package/templates/minimal-consumer/paper/.github/workflows/buildchain.yml +28 -0
  200. package/templates/minimal-consumer/paper/package.json +6 -0
  201. package/templates/minimal-consumer/paper/src/build.mjs +12 -0
  202. package/templates/minimal-consumer/paper/src/verify.mjs +3 -0
@@ -0,0 +1,137 @@
1
+ import {
2
+ createIntegrationDeliveryProof,
3
+ verifyIntegrationDeliveryProof,
4
+ } from "../../dev-delivery/dev-delivery-warrant.js";
5
+ import { readBusinessAttempt } from "../attempt/reader.js";
6
+ import { pipelineCandidate } from "./reconcile.js";
7
+
8
+ async function qualifyWarrant(session, current, queue, materials) {
9
+ if (current.phases.warrant?.payload.state === "success") return;
10
+ if (queue.activeWarrant?.phase !== "qualified")
11
+ throw new Error("Merged pipeline has no qualified active Warrant");
12
+ const reference = await materials.retain(
13
+ `warrant/qualified-${queue.stateRoot.slice(7)}`,
14
+ queue,
15
+ "provider-readback",
16
+ );
17
+ await session.progress.progress({
18
+ attempt: current.identity.id,
19
+ phase: "warrant",
20
+ state: "success",
21
+ eventKey: `qualified:${queue.stateRoot}`,
22
+ materials: [reference],
23
+ });
24
+ }
25
+
26
+ async function integrationProof(session, fresh, host, materials) {
27
+ const { queue, current } = fresh;
28
+ const candidate = pipelineCandidate(queue, current);
29
+ if (!candidate)
30
+ throw new Error("Merged pipeline candidate authority is missing");
31
+ if (candidate.status === "merged") {
32
+ const reference =
33
+ current.materials[
34
+ `merge/proof-${candidate.terminal.evidenceRoot.slice(7)}`
35
+ ];
36
+ if (!reference)
37
+ throw new Error(
38
+ "Merged Warrant has no retained pre-effect integration proof",
39
+ );
40
+ const proof = await materials.read(reference);
41
+ if (
42
+ !verifyIntegrationDeliveryProof(proof, {
43
+ sourceProofRoot: candidate.sourceProofRoot,
44
+ warrantCandidateId: candidate.candidateId,
45
+ }).ok
46
+ )
47
+ throw new Error("Retained integration proof is invalid");
48
+ return { candidate, proof, reference };
49
+ }
50
+ if (candidate.candidateId !== queue.activeWarrant?.candidateId)
51
+ throw new Error("Merged candidate is not the active Warrant");
52
+ await qualifyWarrant(session, current, queue, materials);
53
+ const readback = await host.integration.observe(current);
54
+ const proof = createIntegrationDeliveryProof({
55
+ repository: host.repository,
56
+ protectedBase: session.intent.source.targetBranch,
57
+ sourceProofRoot: candidate.sourceProofRoot,
58
+ warrant: queue.activeWarrant,
59
+ currentBase: readback.mergeCommit,
60
+ replayTree: readback.mergeTree,
61
+ mergeGroupHead: readback.mergeCommit,
62
+ mergeGroupTree: readback.mergeTree,
63
+ requiredContextRoots: [readback.build.root, readback.root],
64
+ verifiedAt: new Date().toISOString(),
65
+ });
66
+ const reference = await materials.retain(
67
+ `merge/proof-${proof.proofRoot.slice(7)}`,
68
+ proof,
69
+ );
70
+ const provider = await materials.retain(
71
+ `merge/provider-${readback.root.slice(7)}`,
72
+ readback,
73
+ "provider-readback",
74
+ );
75
+ await session.progress.progress({
76
+ attempt: current.identity.id,
77
+ phase: "merge",
78
+ state: "waiting",
79
+ eventKey: `merge-request:${proof.proofRoot}`,
80
+ materials: [reference, provider],
81
+ });
82
+ return { candidate, proof, reference };
83
+ }
84
+
85
+ export async function settlePipeline(session, fresh, delivery, host) {
86
+ const materials = host.materialStore(session);
87
+ const { candidate, proof, reference } = await integrationProof(
88
+ session,
89
+ fresh,
90
+ host,
91
+ materials,
92
+ );
93
+ const warrant = fresh.queue.activeWarrant;
94
+ const result = await delivery.service.settle({
95
+ expectedOldStateRoot: fresh.queue.stateRoot,
96
+ pullRequestNumber: candidate.pullRequestNumber,
97
+ expectedSourceHead: candidate.sourceHead,
98
+ outcome: "merged",
99
+ reason: "exact-protected-pipeline-integration",
100
+ evidenceRoot: proof.proofRoot,
101
+ execute: true,
102
+ ...(warrant?.candidateId === candidate.candidateId
103
+ ? {
104
+ fencingToken: warrant.fencingToken,
105
+ leaseGeneration: warrant.generation,
106
+ }
107
+ : {}),
108
+ });
109
+ if (result.observation.activeWarrant?.candidateId === candidate.candidateId)
110
+ throw new Error("Merged settlement retained its active Warrant");
111
+ const receipt = await materials.retain(
112
+ `merge/settlement-${result.receiptRoot.slice(7)}`,
113
+ result,
114
+ );
115
+ await session.progress.progress({
116
+ attempt: fresh.attempt,
117
+ phase: "merge",
118
+ state: "success",
119
+ eventKey: `merged:${result.receiptRoot}`,
120
+ materials: [reference, receipt],
121
+ });
122
+ await host.notifyTerminal?.(session);
123
+ const successor = result.receipt.successorWake;
124
+ if (successor) {
125
+ const next = await host.provider.lookup(
126
+ host.repository,
127
+ successor.pullRequestNumber,
128
+ session.intent.source.targetBranch,
129
+ );
130
+ if (next) await host.wake(readBusinessAttempt(next.snapshot).attempt);
131
+ }
132
+ return {
133
+ operation: "wait",
134
+ reason: "protected-delivery-settled",
135
+ attempt: fresh.attempt,
136
+ };
137
+ }
@@ -0,0 +1,38 @@
1
+ import { githubJsonClient } from "../../providers/github/json-client.js";
2
+ import { githubAttemptJournal } from "../../providers/github/attempt-journal.js";
3
+ import { readBusinessAttempt } from "../attempt/reader.js";
4
+ import { pipelineCandidateRoot } from "./reconcile.js";
5
+
6
+ export async function wakePipelineCandidate(candidate, connection, ports = {}) {
7
+ const repository = connection.repository;
8
+ const request =
9
+ ports.request ||
10
+ githubJsonClient({
11
+ token: connection.token,
12
+ userAgent: "buildchain-pipeline-wake",
13
+ });
14
+ const provider = ports.provider || githubAttemptJournal(request);
15
+ const branch = candidate.protectedBase || connection.branch;
16
+ const loaded = await provider.lookup(
17
+ repository,
18
+ candidate.pullRequestNumber,
19
+ branch,
20
+ );
21
+ if (!loaded)
22
+ throw new Error("Warrant handoff has no admitted business intent");
23
+ const observed = readBusinessAttempt(loaded.snapshot);
24
+ const current = { ...observed.history.at(-1), intent: observed.intent };
25
+ if (
26
+ candidate.sourceHead !== current.generation.source.commit ||
27
+ candidate.sourceRoot !== pipelineCandidateRoot(current)
28
+ )
29
+ throw new Error("Warrant handoff changed its admitted business attempt");
30
+ await request(`/repos/${repository}/dispatches`, {
31
+ method: "POST",
32
+ body: {
33
+ event_type: "buildchain-attempt-wake",
34
+ client_payload: { attempt: observed.attempt },
35
+ },
36
+ });
37
+ return observed.attempt;
38
+ }
@@ -0,0 +1,108 @@
1
+ const labels = {
2
+ admission: "Source admission",
3
+ build: "Product verification",
4
+ review: "Protected review",
5
+ warrant: "Delivery qualification",
6
+ merge: "Protected integration",
7
+ publish: "Product publication",
8
+ distribution: "Distribution",
9
+ "next-development": "Next development version",
10
+ };
11
+ const safe = (value) =>
12
+ String(value || "")
13
+ .replace(/[\r\n|]/gu, " ")
14
+ .replaceAll("&", "&")
15
+ .replaceAll("<", "&lt;")
16
+ .replaceAll(">", "&gt;")
17
+ .replace(/[\\`*_[\]()]/gu, (character) => `\\${character}`)
18
+ .slice(0, 1000);
19
+
20
+ export function pipelineWebStatus(observed, discussionUrl = "") {
21
+ const { intent, attempt } = observed;
22
+ const repositoryUrl = `https://github.com/${intent.repository}`;
23
+ const current = observed.history.at(-1);
24
+ if (!current) throw new Error("Pipeline status requires an admitted attempt");
25
+ const table = [
26
+ "| Step | State |",
27
+ "| --- | --- |",
28
+ ...intent.expectedNodes.map(
29
+ (phase) =>
30
+ `| ${labels[phase]} | ${safe(observed.phases[phase]?.payload.state || "pending")} |`,
31
+ ),
32
+ ].join("\n");
33
+ const lines = [
34
+ `## Buildchain · PR #${intent.source.pullRequest}`,
35
+ `Status: **${safe(observed.status)}**. ${safe(observed.reason)}`,
36
+ `Attempt: \`${attempt}\``,
37
+ `[Pull request](${repositoryUrl}/pull/${intent.source.pullRequest}) · ` +
38
+ `[Source ${current.generation.source.commit.slice(0, 7)}](${repositoryUrl}/commit/${current.generation.source.commit})`,
39
+ table,
40
+ ];
41
+ const publisher = [...observed.history]
42
+ .reverse()
43
+ .find(
44
+ (item) =>
45
+ item.generation.id === current.generation.id &&
46
+ item.phases.publish?.payload.state === "success",
47
+ );
48
+ const published = Boolean(publisher);
49
+ if (published && observed.missing.length)
50
+ lines.push(
51
+ `Publication succeeded and its immutable results remain preserved. The whole attempt is incomplete: ${observed.missing.map((phase) => labels[phase]).join(", ")}.`,
52
+ );
53
+ if (publisher && publisher.identity.id !== current.identity.id)
54
+ lines.push(
55
+ `Publication succeeded in predecessor \`${publisher.identity.id}\`. Current qualification and remaining steps are shown separately above.`,
56
+ );
57
+ if (current.identity.predecessor)
58
+ lines.push(
59
+ `Recovered from \`${current.identity.predecessor}\`; its original evidence remains in history.`,
60
+ );
61
+ if (observed.status !== "complete")
62
+ lines.push(
63
+ `[Recover this attempt](${repositoryUrl}/actions/workflows/buildchain-recover.yml): choose Run workflow and enter the exact attempt above. Select a repaired runtime only when needed.`,
64
+ );
65
+ lines.push(
66
+ "Execution links provide the provider's ordinary cancellation control. Cancellation stops remaining work; it does not undo completed publication.",
67
+ );
68
+ const runs = [
69
+ ...new Map(
70
+ current.runs.map((run) => [`${run.runId}:${run.runAttempt}`, run]),
71
+ ).values(),
72
+ ];
73
+ for (const run of runs.slice(-10))
74
+ lines.push(
75
+ `[Execution ${run.runId}, attempt ${run.runAttempt}](${repositoryUrl}/actions/runs/${run.runId}/attempts/${run.runAttempt})`,
76
+ );
77
+ if (discussionUrl) {
78
+ const url = new URL(discussionUrl);
79
+ if (
80
+ url.origin !== "https://github.com" ||
81
+ !url.pathname.startsWith(`/${intent.repository}/discussions/`) ||
82
+ !/\/discussions\/[1-9][0-9]*$/u.test(url.pathname) ||
83
+ url.search ||
84
+ url.hash ||
85
+ url.username ||
86
+ url.password
87
+ )
88
+ throw new Error(
89
+ "Pipeline evidence link belongs to another provider or repository",
90
+ );
91
+ lines.push(`[Retained evidence](${discussionUrl})`);
92
+ }
93
+ return `${lines.join("\n\n")}\n`;
94
+ }
95
+
96
+ export async function projectPipelineStatus(session, { project, core }) {
97
+ let result;
98
+ try {
99
+ result = await project(session);
100
+ } catch {
101
+ core.warning(
102
+ "Discussion projection unavailable; canonical attempt evidence remains retained.",
103
+ );
104
+ }
105
+ const observed = await session.journal.read();
106
+ await core.summary.addRaw(pipelineWebStatus(observed, result?.url)).write();
107
+ return result || { url: "", journalHead: observed.head };
108
+ }
@@ -10,25 +10,29 @@ try {
10
10
  await build({
11
11
  entry: {
12
12
  "release-discussion": "packages/core/release/discussion/reader-entry.js",
13
+ "business-attempt": "packages/core/workflow/attempt/reader-entry.js",
13
14
  },
14
15
  outDir: output,
15
16
  format: ["cjs"],
16
17
  target: "node24",
17
18
  platform: "node",
18
19
  splitting: false,
20
+ noExternal: ["smol-toml"],
19
21
  sourcemap: false,
20
22
  minify: false,
21
23
  clean: false,
22
24
  outExtension: () => ({ js: ".cjs" }),
23
25
  silent: true,
24
26
  });
25
- if (
26
- check &&
27
- !fs
28
- .readFileSync(path.join(output, "release-discussion.cjs"))
29
- .equals(fs.readFileSync("dist/readers/release-discussion.cjs"))
30
- )
31
- throw new Error("Historical release reader bundle is stale");
27
+ if (check)
28
+ for (const name of ["release-discussion", "business-attempt"]) {
29
+ if (
30
+ !fs
31
+ .readFileSync(path.join(output, `${name}.cjs`))
32
+ .equals(fs.readFileSync(`dist/readers/${name}.cjs`))
33
+ )
34
+ throw new Error(`Historical ${name} reader bundle is stale`);
35
+ }
32
36
  } finally {
33
37
  if (check) fs.rmSync(output, { recursive: true, force: true });
34
38
  }
@@ -0,0 +1,36 @@
1
+ import assert from "node:assert/strict";
2
+ import { inspectWorkflowJob } from "./workflow-action-graph.mjs";
3
+
4
+ export function assertPipelinePublicationTopology(
5
+ ledger,
6
+ discoverReleaseTopology,
7
+ root,
8
+ ) {
9
+ const pipeline = ledger.pipelinePublicationScope;
10
+ assert.deepEqual(pipeline.workflowPaths, [
11
+ ".github/workflows/.release-pipeline-products.yml",
12
+ ".github/workflows/public-ops-pipeline.yml",
13
+ ".github/workflows/public-ops-recover.yml",
14
+ ".github/workflows/.ops-pipeline-execute.yml",
15
+ ]);
16
+ const pipelineTopology = discoverReleaseTopology(
17
+ pipeline.workflowPaths,
18
+ pipeline.workflowPaths,
19
+ );
20
+ assert.deepEqual(pipeline.observedTopology, pipelineTopology);
21
+ for (const [name, ceiling] of Object.entries(pipeline.maximumMetrics))
22
+ assert.ok(
23
+ pipelineTopology.metrics[name] <= ceiling,
24
+ `pipeline publication exceeds ${name}`,
25
+ );
26
+ for (const [job, module] of [
27
+ ["qualify", "packages/core/publication/pipeline/qualify.js"],
28
+ ["apply", "packages/core/publication/pipeline/apply.js"],
29
+ ["settle", "packages/core/publication/pipeline/settle.js"],
30
+ ])
31
+ assert.ok(
32
+ inspectWorkflowJob(pipeline.workflowPaths[0], job, root).modules.has(
33
+ module,
34
+ ),
35
+ );
36
+ }
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import assert from "node:assert/strict";
4
+ import { assertPipelinePublicationTopology } from "./check-pipeline-publication-topology.mjs";
4
5
  import { inspectWorkflowJob, readWorkflow } from "./workflow-action-graph.mjs";
5
6
  import crypto from "node:crypto";
6
7
  import fs from "node:fs";
@@ -28,6 +29,7 @@ const PRIVILEGED_ENTRYPOINTS = [
28
29
  "packages/core/release/next-development/actions.js",
29
30
  "packages/core/publication/oci/preview-actions.js",
30
31
  "packages/core/publication/settlement/actions.js",
32
+ "packages/core/publication/pipeline/actions.js",
31
33
  ];
32
34
 
33
35
  export function localModuleSpecifiers(source) {
@@ -150,7 +152,9 @@ export function discoverReleaseAuthorityClosure() {
150
152
  [".js", ".mjs", ".cjs"],
151
153
  /(?:release-tail-provider-(?:adapters|plane)|create(?:GitHubReleaseAssets|SignedStaticChannel|SiteReleaseActivation|ReleasedEvidence)Adapter)/u,
152
154
  );
153
- const runtimeSelectors = productionFiles("packages/core/runtime/entry", [".js"]).sort();
155
+ const runtimeSelectors = productionFiles("packages/core/runtime/entry", [
156
+ ".js",
157
+ ]).sort();
154
158
  const terminalProjections = matchingProductionFiles(
155
159
  [".github/workflows", "actions", "packages/core", "scripts"],
156
160
  [".yml", ".yaml", ".js", ".mjs", ".cjs"],
@@ -523,6 +527,7 @@ export function checkReleaseTopology() {
523
527
  assert.equal(ledger.contract, "kungfu-buildchain-v4-release-topology/v1");
524
528
  assertClosedWorld(ledger.closedWorld.workflowPaths);
525
529
  assertAuthorityClosure(ledger);
530
+ assertPipelinePublicationTopology(ledger, discoverReleaseTopology, root);
526
531
  const preview = ledger.postPublicationScope;
527
532
  assert.deepEqual(preview.workflowPaths, [
528
533
  ".github/workflows/public-release-oci-compose-preview.yml",
@@ -0,0 +1,155 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+ import { parse as yaml } from "yaml";
5
+ import { format } from "prettier";
6
+ import { standardConsumerExample } from "../packages/core/consumer/contract/examples.js";
7
+ import { inspectConsumerContract } from "../packages/core/consumer/contract/inspection.js";
8
+ import { workflowPath } from "../packages/core/workflow/workflow-taxonomy.mjs";
9
+
10
+ const root = process.cwd(),
11
+ check = process.argv.includes("--check");
12
+ const tracked = execFileSync("git", ["ls-files", "-z"], { encoding: "utf8" })
13
+ .split("\0")
14
+ .filter(Boolean);
15
+
16
+ function output(file, value) {
17
+ const target = path.join(root, file);
18
+ if (check) {
19
+ if (!fs.existsSync(target) || fs.readFileSync(target, "utf8") !== value)
20
+ throw new Error(`${file}: generated contract drift`);
21
+ } else {
22
+ fs.mkdirSync(path.dirname(target), { recursive: true });
23
+ fs.writeFileSync(target, value);
24
+ }
25
+ }
26
+
27
+ function destination(name) {
28
+ if (name === "config-path") return "normal-entry.config-path";
29
+ if (name === "runtime-ref") return "recovery-entry.runtime-ref";
30
+ if (/token|secret|private-key|app-id/u.test(name))
31
+ return "setup.provider-authority";
32
+ if (/run|resume|recover|transaction|discussion|attempt/u.test(name))
33
+ return "attempt.material-index";
34
+ if (/root|proof|nonce|warrant|fence|generation|candidate/u.test(name))
35
+ return "attempt.internal-authority";
36
+ return "internal.plan-derived-input";
37
+ }
38
+
39
+ function workflowInventory() {
40
+ const taxonomy = JSON.parse(
41
+ fs.readFileSync("architecture/workflow-taxonomy.json", "utf8"),
42
+ );
43
+ return taxonomy.entries
44
+ .filter((entry) => ["public", "self"].includes(entry.role))
45
+ .map((entry) => {
46
+ const file = workflowPath(entry),
47
+ document = yaml(fs.readFileSync(file, "utf8"));
48
+ return {
49
+ path: file,
50
+ role: entry.role,
51
+ events: Object.keys(document.on || {}).sort(),
52
+ secrets: Object.keys(document.on?.workflow_call?.secrets || {}).sort(),
53
+ outputs: Object.keys(document.on?.workflow_call?.outputs || {}).sort(),
54
+ destination:
55
+ entry.role === "self"
56
+ ? "self-dogfood.two-callers-or-internal-component"
57
+ : "internal-component.behind-pipeline-or-recovery",
58
+ interfaces: ["workflow_call", "workflow_dispatch"].flatMap((trigger) =>
59
+ Object.entries(document.on?.[trigger]?.inputs || {}).map(
60
+ ([name, definition]) => ({
61
+ trigger,
62
+ name,
63
+ definition,
64
+ destination: destination(name),
65
+ }),
66
+ ),
67
+ ),
68
+ calls: Object.entries(document.jobs || {})
69
+ .filter(([, job]) => job.uses)
70
+ .map(([id, job]) => ({
71
+ id,
72
+ uses: job.uses,
73
+ inputs: Object.keys(job.with || {}).sort(),
74
+ destination: "internal.workflow-composition",
75
+ })),
76
+ };
77
+ })
78
+ .sort((a, b) => a.path.localeCompare(b.path));
79
+ }
80
+
81
+ function consumerSourceInventory() {
82
+ return tracked
83
+ .filter(
84
+ (file) =>
85
+ (/^(?:templates|fixtures|\.buildchain)\//u.test(file) ||
86
+ file === "package.json") &&
87
+ /\.(?:ya?ml|toml|json|[cm]?js|sh|py|ps1)$/u.test(file) &&
88
+ !file.startsWith("templates/minimal-consumer/"),
89
+ )
90
+ .map((file) => {
91
+ const bytes = fs.readFileSync(file, "utf8");
92
+ const kind = /\.ya?ml$/u.test(file)
93
+ ? "caller"
94
+ : /\.toml$/u.test(file)
95
+ ? "config"
96
+ : "consumer-source";
97
+ return {
98
+ path: file,
99
+ kind,
100
+ destination:
101
+ kind === "caller"
102
+ ? "replace-with-shared-pair"
103
+ : kind === "config"
104
+ ? "closed-toml-plan"
105
+ : "retain-product-source; move-orchestration-into-runtime",
106
+ commandReferences: [
107
+ ...new Set(
108
+ bytes.match(
109
+ /(?:packages\/core|scripts)\/[A-Za-z0-9_./-]+\.(?:[cm]?js|sh|py|ps1)/gu,
110
+ ) || [],
111
+ ),
112
+ ].sort(),
113
+ };
114
+ });
115
+ }
116
+
117
+ for (const type of ["npm", "binary", "paper"]) {
118
+ const files = standardConsumerExample(type);
119
+ if (check) {
120
+ const directory = `templates/minimal-consumer/${type}`;
121
+ for (const entry of fs.readdirSync(directory, {
122
+ recursive: true,
123
+ withFileTypes: true,
124
+ })) {
125
+ if (entry.isDirectory()) continue;
126
+ const file = path
127
+ .relative(directory, path.join(entry.parentPath, entry.name))
128
+ .split(path.sep)
129
+ .join("/");
130
+ if (!entry.isFile() || !Object.hasOwn(files, file))
131
+ throw new Error(
132
+ `${directory}/${file}: undeclared generated consumer file`,
133
+ );
134
+ }
135
+ }
136
+ const inspection = inspectConsumerContract(files);
137
+ if (!inspection.ok) throw new Error(inspection.issues.join("\n"));
138
+ for (const [file, bytes] of Object.entries(files))
139
+ output(`templates/minimal-consumer/${type}/${file}`, bytes);
140
+ }
141
+ output(
142
+ "architecture/minimal-consumer-migration.json",
143
+ await format(
144
+ JSON.stringify({
145
+ schema: "buildchain.consumer-migration/v1",
146
+ stage: "contract-defined; execution-entry-not-yet-published",
147
+ workflows: workflowInventory(),
148
+ consumerSources: consumerSourceInventory(),
149
+ }),
150
+ { parser: "json" },
151
+ ),
152
+ );
153
+ console.log(
154
+ "Minimal consumer contract: three shared-caller examples and complete migration inventory verified.",
155
+ );
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
+ import { readNpmPackResult } from "../packages/core/publication/npm/pack-result.js";
3
4
  import os from "node:os";
4
5
  import path from "node:path";
5
6
  import { fileURLToPath } from "node:url";
@@ -44,7 +45,10 @@ export function verifyGoldenPath() {
44
45
  ["pack", "--json", "--pack-destination", packDir],
45
46
  { cwd: root, json: true },
46
47
  );
47
- const tarball = path.join(packDir, packed[0].filename);
48
+ const tarball = path.join(
49
+ packDir,
50
+ readNpmPackResult(JSON.stringify(packed)).filename,
51
+ );
48
52
  fs.writeFileSync(
49
53
  path.join(consumer, "package.json"),
50
54
  `${JSON.stringify(
@@ -0,0 +1,45 @@
1
+ schema = 2
2
+
3
+ [[products]]
4
+ id = "main"
5
+ type = "binary"
6
+ platforms = ["linux-x64"]
7
+ build = ["node src/build.mjs"]
8
+ verify = ["node src/verify.mjs"]
9
+
10
+ [[products.artifacts]]
11
+ id = "main"
12
+ path = "dist/hello.tar.gz"
13
+ kind = "archive"
14
+
15
+ [[products.targets]]
16
+ provider = "github-release"
17
+ artifacts = ["main"]
18
+
19
+ [version]
20
+ strategy = "semver"
21
+
22
+ [[version.files]]
23
+ path = "package.json"
24
+ format = "json"
25
+ key = "version"
26
+
27
+ [[channels]]
28
+ from = "feature/*"
29
+ to = "dev/v1/v1.0"
30
+ operation = "develop"
31
+
32
+ [[channels]]
33
+ from = "dev/v1/v1.0"
34
+ to = "alpha/v1/v1.0"
35
+ operation = "alpha"
36
+
37
+ [[channels]]
38
+ from = "alpha/v1/v1.0"
39
+ to = "release/v1/v1.0"
40
+ operation = "stable"
41
+
42
+ [review]
43
+ minimum_approvals = 1
44
+ code_owners = true
45
+ merge_queue = true
@@ -0,0 +1,30 @@
1
+ name: Buildchain recovery
2
+ on:
3
+ workflow_dispatch:
4
+ inputs:
5
+ attempt:
6
+ description: Exact Buildchain attempt to recover
7
+ required: true
8
+ type: string
9
+ runtime-ref:
10
+ description: Optional repaired runtime
11
+ required: false
12
+ type: string
13
+ permissions:
14
+ contents: read
15
+ jobs:
16
+ buildchain:
17
+ uses: kungfu-systems/buildchain/.github/workflows/public-ops-recover.yml@v4
18
+ with:
19
+ attempt: ${{ inputs.attempt }}
20
+ runtime-ref: ${{ inputs.runtime-ref }}
21
+ secrets: inherit
22
+ permissions:
23
+ actions: write
24
+ attestations: write
25
+ checks: write
26
+ contents: write
27
+ discussions: write
28
+ id-token: write
29
+ pull-requests: write
30
+ statuses: write
@@ -0,0 +1,28 @@
1
+ name: Buildchain pipeline
2
+ on:
3
+ pull_request:
4
+ types: [opened, reopened, synchronize, ready_for_review, converted_to_draft, closed, labeled, unlabeled, edited, enqueued, dequeued]
5
+ pull_request_review:
6
+ types: [submitted, dismissed]
7
+ pull_request_target:
8
+ types: [closed]
9
+ merge_group:
10
+ types: [checks_requested]
11
+ push:
12
+ repository_dispatch:
13
+ types: [buildchain-attempt-wake]
14
+ permissions:
15
+ contents: read
16
+ jobs:
17
+ buildchain:
18
+ uses: kungfu-systems/buildchain/.github/workflows/public-ops-pipeline.yml@v4
19
+ secrets: inherit
20
+ permissions:
21
+ actions: write
22
+ attestations: write
23
+ checks: write
24
+ contents: write
25
+ discussions: write
26
+ id-token: write
27
+ pull-requests: write
28
+ statuses: write
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "minimal-binary",
3
+ "private": true,
4
+ "version": "1.0.0-alpha.1",
5
+ "type": "module"
6
+ }