@kungfu-tech/buildchain 4.1.3-alpha.1 → 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 (263) 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 +266 -10
  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 +362 -7
  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 +328 -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 +4 -4
  23. package/architecture/universal-workflow-fault-campaign.json +3 -1
  24. package/architecture/workflow-taxonomy.json +77 -328
  25. package/dist/readers/business-attempt.cjs +809 -0
  26. package/dist/site/buildchain-contract.json +16 -68
  27. package/dist/site/buildchain-site.json +40 -25
  28. package/dist/site/capability-registry.json +4 -4
  29. package/dist/site/controller-registry.json +1 -47
  30. package/dist/site/kfd-claims.json +499 -494
  31. package/dist/site/kfd-upstream-aggregate.json +1 -1
  32. package/dist/site/manual-registry.json +3 -3
  33. package/dist/site/node-api-registry.json +54 -54
  34. package/dist/site/page-registry.json +33 -18
  35. package/dist/site/public-surface-audit.json +286 -349
  36. package/dist/site/publication-authority-registry.json +203 -523
  37. package/dist/site/publication-registry.json +4 -4
  38. package/dist/site/site-manifest.json +7 -7
  39. package/dist/site/workflow-registry.json +590 -940
  40. package/docs/code-organization.md +9 -6
  41. package/docs/node-api-reference.md +102 -102
  42. package/docs/release-governance.md +10 -12
  43. package/docs/runtime-entry.md +73 -5
  44. package/docs/runtime-train-validation.md +39 -11
  45. package/docs/stable-candidate-patrol.md +6 -5
  46. package/docs/workflow-catalog.md +9 -32
  47. package/package.json +4 -3
  48. package/packages/core/adoption/buildchain-kfd-claims.js +7 -7
  49. package/packages/core/build/standalone/build.js +7 -1
  50. package/packages/core/consumer/buildchain-config.js +2 -21
  51. package/packages/core/consumer/contract/entries.js +85 -0
  52. package/packages/core/consumer/contract/examples.js +96 -0
  53. package/packages/core/consumer/contract/identity.js +65 -0
  54. package/packages/core/consumer/contract/inspection.js +45 -0
  55. package/packages/core/consumer/contract/plan.js +149 -0
  56. package/packages/core/consumer/contract/products.js +119 -0
  57. package/packages/core/consumer/contract/reader.js +47 -0
  58. package/packages/core/consumer/contract/shape.js +46 -0
  59. package/packages/core/dev-delivery/candidate/admission.js +9 -5
  60. package/packages/core/dev-delivery/candidate/reservation.js +11 -0
  61. package/packages/core/dev-delivery/candidate/source-paths.js +12 -4
  62. package/packages/core/dev-delivery/commands/project-cut-merge-queue-admission.mjs +47 -20
  63. package/packages/core/dev-delivery/dev-delivery-provider-heartbeat.js +18 -15
  64. package/packages/core/dev-delivery/native/actions.js +9 -1
  65. package/packages/core/dev-delivery/native/heartbeat-action.js +16 -0
  66. package/packages/core/dev-delivery/native/heartbeat.js +6 -4
  67. package/packages/core/dev-delivery/native/process-entry.js +19 -0
  68. package/packages/core/dev-delivery/queue/landing-action.js +53 -0
  69. package/packages/core/dev-delivery/warrant/failure-settlement.js +8 -6
  70. package/packages/core/dev-delivery/warrant/service.js +7 -0
  71. package/packages/core/governance/buildchain-publication-authority.js +8 -23
  72. package/packages/core/governance/github-governance-authority.js +1 -0
  73. package/packages/core/observability/controller-evidence.js +0 -8
  74. package/packages/core/paper/operations/bootstrap.js +2 -0
  75. package/packages/core/paper/paper-npm-bootstrap.js +2 -2
  76. package/packages/core/providers/github/attempt-index.js +60 -0
  77. package/packages/core/providers/github/attempt-journal.js +154 -0
  78. package/packages/core/providers/github/discussions/materials.js +19 -6
  79. package/packages/core/providers/github/pipeline-checkout.js +49 -0
  80. package/packages/core/providers/github/pipeline-entry-runs.js +210 -0
  81. package/packages/core/providers/github/pipeline-events.js +131 -0
  82. package/packages/core/providers/github/pipeline-integration.js +95 -0
  83. package/packages/core/providers/github/pipeline-policy.js +240 -0
  84. package/packages/core/providers/github/pipeline-product-payload.js +50 -0
  85. package/packages/core/providers/github/pipeline-product-release.js +198 -0
  86. package/packages/core/providers/github/pipeline-publication-artifacts.js +153 -0
  87. package/packages/core/providers/github/pipeline-release-evidence.js +30 -0
  88. package/packages/core/providers/github/pipeline-run-entry.js +86 -0
  89. package/packages/core/providers/github/pipeline-runs.js +132 -0
  90. package/packages/core/providers/github/pipeline-source.js +195 -0
  91. package/packages/core/providers/github/pipeline-stable-baseline.js +86 -0
  92. package/packages/core/providers/github/pipeline-stable-source.js +372 -0
  93. package/packages/core/providers/github/pipeline-version-artifacts.js +91 -0
  94. package/packages/core/providers/github/pipeline-version-readback.js +117 -0
  95. package/packages/core/providers/github/pipeline-version.js +199 -0
  96. package/packages/core/providers/github/pipeline-worker.js +108 -0
  97. package/packages/core/providers/github/release-evidence-assets.js +97 -0
  98. package/packages/core/publication/candidate/registry-hydration.js +2 -2
  99. package/packages/core/publication/npm/pack-preview.js +2 -2
  100. package/packages/core/publication/npm/pack-result.js +31 -0
  101. package/packages/core/publication/npm/package.js +3 -6
  102. package/packages/core/publication/npm/pipeline-channel.js +36 -0
  103. package/packages/core/publication/npm/pipeline-provider.js +137 -0
  104. package/packages/core/publication/npm/registry.js +12 -9
  105. package/packages/core/publication/pipeline/actions.js +116 -0
  106. package/packages/core/publication/pipeline/apply.js +189 -0
  107. package/packages/core/publication/pipeline/build-segments.js +107 -0
  108. package/packages/core/publication/pipeline/capsules.js +92 -0
  109. package/packages/core/publication/pipeline/context.js +39 -0
  110. package/packages/core/publication/pipeline/development-anchor.js +91 -0
  111. package/packages/core/publication/pipeline/development-current.js +63 -0
  112. package/packages/core/publication/pipeline/development-pr.js +112 -0
  113. package/packages/core/publication/pipeline/development-proof.js +76 -0
  114. package/packages/core/publication/pipeline/development-transition.js +112 -0
  115. package/packages/core/publication/pipeline/distribution.js +130 -0
  116. package/packages/core/publication/pipeline/documents.js +192 -0
  117. package/packages/core/publication/pipeline/effects.js +192 -0
  118. package/packages/core/publication/pipeline/files.js +66 -0
  119. package/packages/core/publication/pipeline/imported-materials.js +32 -0
  120. package/packages/core/publication/pipeline/journal.js +122 -0
  121. package/packages/core/publication/pipeline/next-development.js +117 -0
  122. package/packages/core/publication/pipeline/pack.js +180 -0
  123. package/packages/core/publication/pipeline/package-policy.js +22 -0
  124. package/packages/core/publication/pipeline/plan.js +136 -0
  125. package/packages/core/publication/pipeline/prepare.js +139 -0
  126. package/packages/core/publication/pipeline/qualification.js +175 -0
  127. package/packages/core/publication/pipeline/qualify.js +104 -0
  128. package/packages/core/publication/pipeline/recovery-admission.js +122 -0
  129. package/packages/core/publication/pipeline/recovery-build-download.js +80 -0
  130. package/packages/core/publication/pipeline/recovery-build-plan.js +122 -0
  131. package/packages/core/publication/pipeline/recovery-build-readback.js +68 -0
  132. package/packages/core/publication/pipeline/recovery-capsules.js +54 -0
  133. package/packages/core/publication/pipeline/recovery-import.js +80 -0
  134. package/packages/core/publication/pipeline/recovery-inspection.js +218 -0
  135. package/packages/core/publication/pipeline/recovery-materials.js +104 -0
  136. package/packages/core/publication/pipeline/recovery-plan.js +46 -0
  137. package/packages/core/publication/pipeline/recovery-prepare.js +73 -0
  138. package/packages/core/publication/pipeline/recovery-qualification.js +128 -0
  139. package/packages/core/publication/pipeline/recovery-readback.js +56 -0
  140. package/packages/core/publication/pipeline/recovery-signing.js +58 -0
  141. package/packages/core/publication/pipeline/sealed-products.js +79 -0
  142. package/packages/core/publication/pipeline/settle.js +42 -0
  143. package/packages/core/publication/pipeline/signing.js +140 -0
  144. package/packages/core/publication/pipeline/source-plan.js +83 -0
  145. package/packages/core/publication/pipeline/stable-entry.js +219 -0
  146. package/packages/core/publication/pipeline/stable-products.js +317 -0
  147. package/packages/core/publication/pipeline/stable-wait.js +170 -0
  148. package/packages/core/publication/pipeline/stable.js +82 -0
  149. package/packages/core/publication/pipeline/version-actions.js +93 -0
  150. package/packages/core/publication/pipeline/version-build.js +130 -0
  151. package/packages/core/publication/pipeline/version-context.js +143 -0
  152. package/packages/core/publication/pipeline/version-material.js +36 -0
  153. package/packages/core/publication/pipeline/version-preparation.js +69 -0
  154. package/packages/core/publication/pipeline/version-regeneration.js +162 -0
  155. package/packages/core/publication/pipeline/version-source-guard.js +69 -0
  156. package/packages/core/publication/pipeline/version.js +81 -0
  157. package/packages/core/publication/pipeline/worker.js +72 -0
  158. package/packages/core/publication/publication-reproducibility.js +2 -2
  159. package/packages/core/release/next-development-transition.js +15 -15
  160. package/packages/core/release/promote-candidate/product-provider-adapters.js +4 -3
  161. package/packages/core/release/promote-ref/internal/npm-existing-evidence.js +2 -1
  162. package/packages/core/release/promote-ref/internal/publish-command.js +16 -5
  163. package/packages/core/release/stable-release-gate.js +11 -0
  164. package/packages/core/runtime/buildchain-domain.wasm +0 -0
  165. package/packages/core/runtime/domain-wasm-artifact.js +2 -2
  166. package/packages/core/runtime/entry/actions.js +27 -25
  167. package/packages/core/runtime/entry/attempt.js +25 -0
  168. package/packages/core/runtime/entry/selection.js +14 -0
  169. package/packages/core/runtime/entry/source.js +62 -0
  170. package/packages/core/workflow/attempt/identity.js +170 -0
  171. package/packages/core/workflow/attempt/journal.js +89 -0
  172. package/packages/core/workflow/attempt/materials.js +72 -0
  173. package/packages/core/workflow/attempt/reader-entry.js +12 -0
  174. package/packages/core/workflow/attempt/reader.js +149 -0
  175. package/packages/core/workflow/attempt/records.js +153 -0
  176. package/packages/core/workflow/attempt/store.js +75 -0
  177. package/packages/core/workflow/commands/pipeline-build.mjs +23 -0
  178. package/packages/core/workflow/pipeline/action-output.js +11 -0
  179. package/packages/core/workflow/pipeline/actions.js +52 -0
  180. package/packages/core/workflow/pipeline/build-control.js +140 -0
  181. package/packages/core/workflow/pipeline/build-evidence.js +210 -0
  182. package/packages/core/workflow/pipeline/build-qualification.js +145 -0
  183. package/packages/core/workflow/pipeline/build-result.js +78 -0
  184. package/packages/core/workflow/pipeline/build.js +142 -0
  185. package/packages/core/workflow/pipeline/cancellation.js +169 -0
  186. package/packages/core/workflow/pipeline/channel-control.js +157 -0
  187. package/packages/core/workflow/pipeline/channel-source.js +53 -0
  188. package/packages/core/workflow/pipeline/controller.js +141 -0
  189. package/packages/core/workflow/pipeline/delivery-control.js +249 -0
  190. package/packages/core/workflow/pipeline/delivery-observation.js +124 -0
  191. package/packages/core/workflow/pipeline/delivery-request.js +198 -0
  192. package/packages/core/workflow/pipeline/events.js +84 -0
  193. package/packages/core/workflow/pipeline/fence.js +42 -0
  194. package/packages/core/workflow/pipeline/group-control.js +59 -0
  195. package/packages/core/workflow/pipeline/guard-build.js +74 -0
  196. package/packages/core/workflow/pipeline/guard.js +158 -0
  197. package/packages/core/workflow/pipeline/host.js +144 -0
  198. package/packages/core/workflow/pipeline/integration-status.js +113 -0
  199. package/packages/core/workflow/pipeline/materials.js +58 -0
  200. package/packages/core/workflow/pipeline/notifications.js +36 -0
  201. package/packages/core/workflow/pipeline/parent-notification.js +27 -0
  202. package/packages/core/workflow/pipeline/platforms.js +21 -0
  203. package/packages/core/workflow/pipeline/progress.js +108 -0
  204. package/packages/core/workflow/pipeline/projection.js +20 -0
  205. package/packages/core/workflow/pipeline/reconcile.js +194 -0
  206. package/packages/core/workflow/pipeline/record-action.js +27 -0
  207. package/packages/core/workflow/pipeline/recorder-status.js +69 -0
  208. package/packages/core/workflow/pipeline/recovery-action.js +30 -0
  209. package/packages/core/workflow/pipeline/recovery-admission.js +122 -0
  210. package/packages/core/workflow/pipeline/recovery-build-control.js +95 -0
  211. package/packages/core/workflow/pipeline/recovery-build-evidence.js +71 -0
  212. package/packages/core/workflow/pipeline/recovery-build.js +121 -0
  213. package/packages/core/workflow/pipeline/recovery-controller.js +91 -0
  214. package/packages/core/workflow/pipeline/recovery-integration.js +79 -0
  215. package/packages/core/workflow/pipeline/recovery-merge-proof.js +90 -0
  216. package/packages/core/workflow/pipeline/recovery-ownership-settlement.js +150 -0
  217. package/packages/core/workflow/pipeline/recovery-ownership.js +91 -0
  218. package/packages/core/workflow/pipeline/recovery-plan.js +126 -0
  219. package/packages/core/workflow/pipeline/recovery-runtime.js +68 -0
  220. package/packages/core/workflow/pipeline/recovery-session.js +129 -0
  221. package/packages/core/workflow/pipeline/recovery-transition.js +135 -0
  222. package/packages/core/workflow/pipeline/recovery-unrecorded-build.js +74 -0
  223. package/packages/core/workflow/pipeline/runtime-source.js +111 -0
  224. package/packages/core/workflow/pipeline/selection.js +111 -0
  225. package/packages/core/workflow/pipeline/session.js +72 -0
  226. package/packages/core/workflow/pipeline/settlement.js +137 -0
  227. package/packages/core/workflow/pipeline/wake.js +38 -0
  228. package/packages/core/workflow/pipeline/web-status.js +108 -0
  229. package/packages/core/workflow/workflow-taxonomy.mjs +67 -47
  230. package/scripts/build-release-discussion-reader.mjs +11 -7
  231. package/scripts/check-inventory.mjs +7 -70
  232. package/scripts/check-pipeline-publication-topology.mjs +50 -0
  233. package/scripts/check-public-dogfood-contract.mjs +38 -51
  234. package/scripts/check-release-topology.mjs +15 -8
  235. package/scripts/check-self-consumer-contract.mjs +133 -0
  236. package/scripts/check-universal-workflow-bootstrap.mjs +4 -6
  237. package/scripts/generate-minimal-consumer-contract.mjs +155 -0
  238. package/scripts/generate-site-bundle.mjs +1 -1
  239. package/scripts/inventory/binary.mjs +9 -19
  240. package/scripts/inventory/promotion.mjs +2 -13
  241. package/scripts/prepare-product-toolchain.mjs +10 -0
  242. package/scripts/verify-golden-path.mjs +5 -1
  243. package/scripts/verify-product-platform.mjs +65 -0
  244. package/scripts/verify-standalone-product.mjs +42 -0
  245. package/templates/minimal-consumer/binary/.buildchain/buildchain.toml +45 -0
  246. package/templates/minimal-consumer/binary/.github/workflows/buildchain-recover.yml +30 -0
  247. package/templates/minimal-consumer/binary/.github/workflows/buildchain.yml +28 -0
  248. package/templates/minimal-consumer/binary/package.json +6 -0
  249. package/templates/minimal-consumer/binary/src/build.mjs +5 -0
  250. package/templates/minimal-consumer/binary/src/hello.c +2 -0
  251. package/templates/minimal-consumer/binary/src/verify.mjs +3 -0
  252. package/templates/minimal-consumer/npm/.buildchain/buildchain.toml +46 -0
  253. package/templates/minimal-consumer/npm/.github/workflows/buildchain-recover.yml +30 -0
  254. package/templates/minimal-consumer/npm/.github/workflows/buildchain.yml +28 -0
  255. package/templates/minimal-consumer/npm/package.json +6 -0
  256. package/templates/minimal-consumer/npm/src/build.mjs +5 -0
  257. package/templates/minimal-consumer/npm/src/verify.mjs +3 -0
  258. package/templates/minimal-consumer/paper/.buildchain/buildchain.toml +45 -0
  259. package/templates/minimal-consumer/paper/.github/workflows/buildchain-recover.yml +30 -0
  260. package/templates/minimal-consumer/paper/.github/workflows/buildchain.yml +28 -0
  261. package/templates/minimal-consumer/paper/package.json +6 -0
  262. package/templates/minimal-consumer/paper/src/build.mjs +12 -0
  263. package/templates/minimal-consumer/paper/src/verify.mjs +3 -0
@@ -0,0 +1,96 @@
1
+ import { choice } from "./shape.js";
2
+ import { consumerWorkflows } from "./entries.js";
3
+ import { CONFIG_PATH } from "./plan.js";
4
+ import { PRODUCT_TYPES } from "./products.js";
5
+
6
+ const ARTIFACTS = {
7
+ npm: { path: "dist/package", kind: "npm-package", provider: "npm" },
8
+ binary: {
9
+ path: "dist/hello.tar.gz",
10
+ kind: "archive",
11
+ provider: "github-release",
12
+ },
13
+ paper: { path: "dist/paper.pdf", kind: "pdf", provider: "github-release" },
14
+ };
15
+
16
+ function productSource(type) {
17
+ const prefix =
18
+ 'import fs from "node:fs";\nfs.mkdirSync("dist", { recursive: true });\n';
19
+ if (type === "npm")
20
+ return `${prefix}fs.mkdirSync("dist/package", { recursive: true });\nfs.writeFileSync("dist/package/index.js", 'export const hello = "hello";\\n');\nfs.writeFileSync("dist/package/package.json", JSON.stringify({ name: "@example/minimal", version: JSON.parse(fs.readFileSync("package.json")).version, type: "module", exports: "./index.js" }));\n`;
21
+ if (type === "binary")
22
+ return `${prefix}import { execFileSync } from "node:child_process";\nexecFileSync("cc", ["src/hello.c", "-o", "dist/hello"], { stdio: "inherit" });\nexecFileSync("tar", ["-czf", "dist/hello.tar.gz", "-C", "dist", "hello"], { stdio: "inherit" });\n`;
23
+ return `${prefix}const stream = "BT /F1 24 Tf 50 700 Td (Minimal Paper) Tj ET";
24
+ const objects = ["<< /Type /Catalog /Pages 2 0 R >>", "<< /Type /Pages /Kids [3 0 R] /Count 1 >>", "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>", "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>", \`<< /Length \${stream.length} >>\\nstream\\n\${stream}\\nendstream\`];
25
+ let pdf = "%PDF-1.4\\n";
26
+ const offsets = [0];
27
+ for (const [index, object] of objects.entries()) { offsets.push(Buffer.byteLength(pdf)); pdf += \`\${index + 1} 0 obj\\n\${object}\\nendobj\\n\`; }
28
+ const xref = Buffer.byteLength(pdf);
29
+ pdf += \`xref\\n0 \${offsets.length}\\n0000000000 65535 f \\n\`;
30
+ for (const offset of offsets.slice(1)) pdf += \`\${String(offset).padStart(10, "0")} 00000 n \\n\`;
31
+ pdf += \`trailer\\n<< /Size \${offsets.length} /Root 1 0 R >>\\nstartxref\\n\${xref}\\n%%EOF\\n\`;
32
+ fs.writeFileSync("dist/paper.pdf", pdf);
33
+ `;
34
+ }
35
+
36
+ export function standardConsumerExample(type, channel = "v4") {
37
+ choice(type, PRODUCT_TYPES, "product.type");
38
+ const artifact = ARTIFACTS[type];
39
+ const npmAccess = type === "npm" ? 'access = "public"\n' : "";
40
+ const files = {
41
+ ...consumerWorkflows(channel),
42
+ "package.json": `${JSON.stringify({ name: `minimal-${type}`, private: true, version: "1.0.0-alpha.1", type: "module" }, null, 2)}\n`,
43
+ "src/build.mjs": productSource(type),
44
+ "src/verify.mjs": `import assert from "node:assert/strict";\nimport fs from "node:fs";\nassert.ok(fs.statSync(${JSON.stringify(artifact.path)}).${type === "npm" ? "isDirectory()" : "size > 0"});\n`,
45
+ [CONFIG_PATH]: `schema = 2
46
+
47
+ [[products]]
48
+ id = "main"
49
+ type = "${type}"
50
+ platforms = ["linux-x64"]
51
+ build = ["node src/build.mjs"]
52
+ verify = ["node src/verify.mjs"]
53
+
54
+ [[products.artifacts]]
55
+ id = "main"
56
+ path = "${artifact.path}"
57
+ kind = "${artifact.kind}"
58
+
59
+ [[products.targets]]
60
+ provider = "${artifact.provider}"
61
+ artifacts = ["main"]
62
+ ${npmAccess}
63
+ [version]
64
+ strategy = "semver"
65
+
66
+ [[version.files]]
67
+ path = "package.json"
68
+ format = "json"
69
+ key = "version"
70
+
71
+ [[channels]]
72
+ from = "feature/*"
73
+ to = "dev/v1/v1.0"
74
+ operation = "develop"
75
+
76
+ [[channels]]
77
+ from = "dev/v1/v1.0"
78
+ to = "alpha/v1/v1.0"
79
+ operation = "alpha"
80
+
81
+ [[channels]]
82
+ from = "alpha/v1/v1.0"
83
+ to = "release/v1/v1.0"
84
+ operation = "stable"
85
+
86
+ [review]
87
+ minimum_approvals = 1
88
+ code_owners = true
89
+ merge_queue = true
90
+ `,
91
+ };
92
+ if (type === "binary")
93
+ files["src/hello.c"] =
94
+ '#include <stdio.h>\nint main(void) { puts("hello"); return 0; }\n';
95
+ return files;
96
+ }
@@ -0,0 +1,65 @@
1
+ import { createHash } from "node:crypto";
2
+ import { object, relativePath, text } from "./shape.js";
3
+ import { compileConsumerPlan, CONSUMER_CONTRACT } from "./plan.js";
4
+
5
+ export function contentDigest(bytes) {
6
+ return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
7
+ }
8
+
9
+ // Source identities are prepared from trusted GitHub readback, never caller inputs.
10
+ export function bindConsumerSource(source, configBytes) {
11
+ object(
12
+ source,
13
+ ["repository", "commit", "tree", "configPath", "configBlob"],
14
+ [],
15
+ "source",
16
+ );
17
+ text(
18
+ source.repository,
19
+ "source.repository",
20
+ /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u,
21
+ );
22
+ for (const key of ["commit", "tree", "configBlob"])
23
+ text(source[key], `source.${key}`, /^[0-9a-f]{40}$/u);
24
+ relativePath(source.configPath, "source.configPath");
25
+ const bytes = Buffer.from(configBytes);
26
+ const blob = createHash("sha1")
27
+ .update(`blob ${bytes.length}\0`)
28
+ .update(bytes)
29
+ .digest("hex");
30
+ if (blob !== source.configBlob)
31
+ throw new Error("source.configBlob: config bytes do not match Git blob");
32
+ const plan = compileConsumerPlan(bytes.toString("utf8"));
33
+ const identity = {
34
+ schema: "buildchain.consumer-source/v1",
35
+ repository: source.repository,
36
+ commit: source.commit,
37
+ tree: source.tree,
38
+ configPath: source.configPath,
39
+ configBlob: blob,
40
+ configDigest: contentDigest(bytes),
41
+ contract: CONSUMER_CONTRACT,
42
+ };
43
+ return {
44
+ identity,
45
+ identityDigest: contentDigest(JSON.stringify(identity)),
46
+ plan,
47
+ };
48
+ }
49
+
50
+ export function consumerContractLock({ entry, runtime, configDigest }) {
51
+ for (const [name, value] of Object.entries({ entry, runtime })) {
52
+ object(value, ["repository", "sha"], [], name);
53
+ if (value.repository !== "kungfu-systems/buildchain")
54
+ throw new Error(`${name}: untrusted Buildchain repository`);
55
+ text(value.sha, `${name}.sha`, /^[0-9a-f]{40}$/u);
56
+ }
57
+ text(configDigest, "configDigest", /^sha256:[0-9a-f]{64}$/u);
58
+ return {
59
+ schema: "buildchain.consumer-contract-lock/v2",
60
+ contract: CONSUMER_CONTRACT,
61
+ entry: { ...entry },
62
+ runtime: { ...runtime },
63
+ configDigest,
64
+ };
65
+ }
@@ -0,0 +1,45 @@
1
+ import { consumerWorkflows } from "./entries.js";
2
+ import { compileConsumerPlan, CONFIG_PATH } from "./plan.js";
3
+
4
+ const INTERNAL_WIRING = [
5
+ /\b(?:request-json|native-roots-json|used-nonces-json|warrant-result|source-proof|integration-proof)\b/iu,
6
+ /\bbuildchain\s+(?:dev\s+(?:warrant|proof)|release|promote|recover|publish)\b/iu,
7
+ /\b(?:npm\s+publish|gh\s+release\s+(?:create|upload)|gh\s+run\s+download)\b/iu,
8
+ /actions\/download-artifact@/u,
9
+ /\.buildchain\/runtime\/|packages\/core\/(?:publication|release|dev-delivery)\//u,
10
+ /\b(?:fencingToken|candidateRoot|sourceProofRoot|usedNonces|generationRoot|compareAndSwap)\b/u,
11
+ /buildchain[-./][\w-]*(?:warrant|proof|fence|nonce|candidate|authority|transaction)/iu,
12
+ ];
13
+
14
+ // The supplied file map must be the complete tracked consumer tree, including
15
+ // package scripts and the source files they invoke. Runtime source is never run.
16
+ export function inspectConsumerContract(
17
+ files,
18
+ { channel = "v4", configPath = CONFIG_PATH } = {},
19
+ ) {
20
+ const issues = [];
21
+ let plan;
22
+ try {
23
+ plan = compileConsumerPlan(files[configPath]);
24
+ } catch (error) {
25
+ issues.push(`${configPath}: ${error.message}`);
26
+ }
27
+ const workflows = consumerWorkflows(channel, configPath);
28
+ for (const [file, expected] of Object.entries(workflows))
29
+ if (files[file] !== expected)
30
+ issues.push(`${file}: must match the generated thin caller`);
31
+ for (const [file, source] of Object.entries(files)) {
32
+ if (
33
+ /^\.github\/workflows\/.*\.ya?ml$/u.test(file) &&
34
+ !Object.hasOwn(workflows, file)
35
+ )
36
+ issues.push(`${file}: extra consumer workflow is forbidden`);
37
+ if (!/\.(?:toml|json|ya?ml|[cm]?js|ts|py|sh|ps1|rb)$/u.test(file)) continue;
38
+ for (const pattern of INTERNAL_WIRING)
39
+ if (pattern.test(String(source)))
40
+ issues.push(
41
+ `${file}: consumer owns internal orchestration (${pattern.source})`,
42
+ );
43
+ }
44
+ return { ok: issues.length === 0, issues, plan };
45
+ }
@@ -0,0 +1,149 @@
1
+ import { parse } from "smol-toml";
2
+ import { choice, list, object, relativePath, text, unique } from "./shape.js";
3
+ import { compileProducts } from "./products.js";
4
+
5
+ export const CONSUMER_CONTRACT = "buildchain.consumer-contract/v2";
6
+ export const CONSUMER_PLAN = "buildchain.consumer-plan/v1";
7
+ export const CONFIG_PATH = ".buildchain/buildchain.toml";
8
+
9
+ function compileVersion(value) {
10
+ object(value, ["strategy", "files"], ["derived_files"], "version");
11
+ choice(value.strategy, ["semver", "anchored"], "version.strategy");
12
+ list(value.files, "version.files", (item, field) => {
13
+ object(item, ["path", "format", "key"], [], field);
14
+ relativePath(item.path, `${field}.path`);
15
+ choice(item.format, ["json", "toml"], `${field}.format`);
16
+ text(item.key, `${field}.key`, /^[A-Za-z0-9_.-]+$/u);
17
+ });
18
+ unique(
19
+ value.files.map((item) => `${item.path}:${item.key}`),
20
+ "version.files",
21
+ );
22
+ if (value.derived_files !== undefined)
23
+ list(value.derived_files, "version.derived_files", relativePath);
24
+ return structuredClone(value);
25
+ }
26
+
27
+ function compileChannels(values) {
28
+ const channels = list(values, "channels", (value, field) => {
29
+ object(value, ["from", "to", "operation"], [], field);
30
+ text(
31
+ value.from,
32
+ `${field}.from`,
33
+ /^(?:feature|fix|chore|docs|ci|refactor|dev|alpha|release)\/[A-Za-z0-9/*._-]+$/u,
34
+ );
35
+ text(
36
+ value.to,
37
+ `${field}.to`,
38
+ /^(?:dev|alpha|release|publish-gate)\/[A-Za-z0-9/._-]+$/u,
39
+ );
40
+ choice(
41
+ value.operation,
42
+ ["develop", "alpha", "stable", "major"],
43
+ `${field}.operation`,
44
+ );
45
+ const source = value.from.split("/")[0],
46
+ target = value.to.split("/")[0];
47
+ const legal = {
48
+ develop:
49
+ ["feature", "fix", "chore", "docs", "ci", "refactor"].includes(
50
+ source,
51
+ ) && target === "dev",
52
+ alpha: source === "dev" && target === "alpha",
53
+ stable: source === "alpha" && target === "release",
54
+ major: source === "release" && value.to === "publish-gate/major",
55
+ };
56
+ if (
57
+ !legal[value.operation] ||
58
+ value.from.includes("..") ||
59
+ value.to.includes("..")
60
+ )
61
+ throw new Error(`${field}: unlawful channel route`);
62
+ return { ...value };
63
+ });
64
+ unique(
65
+ channels.map((value) => `${value.from}:${value.to}`),
66
+ "channels",
67
+ );
68
+ return channels;
69
+ }
70
+
71
+ function compileReview(value) {
72
+ object(
73
+ value,
74
+ ["minimum_approvals", "code_owners", "merge_queue"],
75
+ [],
76
+ "review",
77
+ );
78
+ if (!Number.isInteger(value.minimum_approvals) || value.minimum_approvals < 1)
79
+ throw new Error(
80
+ "review.minimum_approvals: independent approval is required",
81
+ );
82
+ if (value.code_owners !== true || value.merge_queue !== true)
83
+ throw new Error(
84
+ "review: code owners and protected merge queue are required",
85
+ );
86
+ return { ...value };
87
+ }
88
+
89
+ function compileStable(value) {
90
+ object(
91
+ value,
92
+ [
93
+ "minimum_interval_seconds",
94
+ "minimum_soak_seconds",
95
+ "product_paths",
96
+ "impact_file",
97
+ "require_published_entry",
98
+ ],
99
+ [],
100
+ "stable",
101
+ );
102
+ for (const field of ["minimum_interval_seconds", "minimum_soak_seconds"])
103
+ if (!Number.isSafeInteger(value[field]) || value[field] < 0)
104
+ throw new Error(
105
+ `stable.${field}: expected non-negative safe integer seconds`,
106
+ );
107
+ const paths = list(
108
+ value.product_paths,
109
+ "stable.product_paths",
110
+ (item, field) => {
111
+ text(item, field);
112
+ relativePath(item.endsWith("/") ? item.slice(0, -1) : item, field);
113
+ if (item.includes("*"))
114
+ throw new Error(`${field}: expected a literal product path`);
115
+ return item;
116
+ },
117
+ );
118
+ unique(paths, "stable.product_paths");
119
+ relativePath(value.impact_file, "stable.impact_file");
120
+ if (!value.impact_file.endsWith(".json") || value.impact_file.includes("*"))
121
+ throw new Error(
122
+ "stable.impact_file: expected one product impact JSON file",
123
+ );
124
+ if (typeof value.require_published_entry !== "boolean")
125
+ throw new Error("stable.require_published_entry: expected a boolean");
126
+ return structuredClone(value);
127
+ }
128
+
129
+ // This compiler receives bytes. It has no filesystem, process, network or provider port.
130
+ export function compileConsumerPlan(source) {
131
+ const config = parse(source);
132
+ object(
133
+ config,
134
+ ["schema", "products", "version", "channels", "review"],
135
+ ["stable"],
136
+ );
137
+ choice(config.schema, [2], "schema");
138
+ return {
139
+ schema: CONSUMER_PLAN,
140
+ contract: CONSUMER_CONTRACT,
141
+ products: compileProducts(config.products),
142
+ version: compileVersion(config.version),
143
+ channels: compileChannels(config.channels),
144
+ review: compileReview(config.review),
145
+ ...(config.stable === undefined
146
+ ? {}
147
+ : { stable: compileStable(config.stable) }),
148
+ };
149
+ }
@@ -0,0 +1,119 @@
1
+ import {
2
+ choice,
3
+ list,
4
+ object,
5
+ relativePath,
6
+ slug,
7
+ text,
8
+ unique,
9
+ } from "./shape.js";
10
+
11
+ export const PRODUCT_TYPES = ["npm", "binary", "paper"];
12
+ export const PLATFORMS = [
13
+ "linux-x64",
14
+ "linux-arm64",
15
+ "macos-arm64",
16
+ "macos-x64",
17
+ "windows-x64",
18
+ ];
19
+
20
+ function artifact(value, location) {
21
+ object(value, ["id", "path", "kind"], ["filename"], location);
22
+ slug(value.id, `${location}.id`);
23
+ if (value.path !== "." || value.kind !== "npm-package")
24
+ relativePath(value.path, `${location}.path`);
25
+ choice(value.kind, ["npm-package", "archive", "pdf"], `${location}.kind`);
26
+ if (value.filename !== undefined) {
27
+ text(
28
+ value.filename,
29
+ `${location}.filename`,
30
+ /^[A-Za-z0-9][A-Za-z0-9._-]{0,254}$/u,
31
+ );
32
+ const extension =
33
+ value.kind === "npm-package"
34
+ ? /\.tgz$/u
35
+ : value.kind === "pdf"
36
+ ? /\.pdf$/u
37
+ : /\.(?:tar\.gz|tar\.xz|tgz|zip|tar)$/u;
38
+ if (!extension.test(value.filename))
39
+ throw new Error(
40
+ `${location}.filename: artifact extension does not match its kind`,
41
+ );
42
+ }
43
+ return { ...value };
44
+ }
45
+
46
+ function target(value, location) {
47
+ object(value, ["provider", "artifacts"], ["access"], location);
48
+ choice(value.provider, ["npm", "github-release"], `${location}.provider`);
49
+ list(value.artifacts, `${location}.artifacts`, slug);
50
+ unique(value.artifacts, `${location}.artifacts`);
51
+ if (value.provider === "npm")
52
+ choice(value.access, ["public", "restricted"], `${location}.access`);
53
+ else if (Object.hasOwn(value, "access"))
54
+ throw new Error(`${location}.access: npm only`);
55
+ return structuredClone(value);
56
+ }
57
+
58
+ export function compileProducts(values) {
59
+ const products = list(values, "products", (value, location) => {
60
+ object(
61
+ value,
62
+ ["id", "type", "platforms", "build", "verify", "artifacts", "targets"],
63
+ ["install", "directory"],
64
+ location,
65
+ );
66
+ slug(value.id, `${location}.id`);
67
+ choice(value.type, PRODUCT_TYPES, `${location}.type`);
68
+ list(value.platforms, `${location}.platforms`, (item, field) =>
69
+ choice(item, PLATFORMS, field),
70
+ );
71
+ unique(value.platforms, `${location}.platforms`);
72
+ for (const operation of [
73
+ "build",
74
+ "verify",
75
+ ...(value.install !== undefined ? ["install"] : []),
76
+ ])
77
+ list(value[operation], `${location}.${operation}`, text);
78
+ if (value.directory !== undefined)
79
+ relativePath(value.directory, `${location}.directory`);
80
+ const artifacts = list(value.artifacts, `${location}.artifacts`, artifact);
81
+ unique(
82
+ artifacts.map((item) => item.id),
83
+ `${location}.artifacts`,
84
+ );
85
+ const targets = list(value.targets, `${location}.targets`, target);
86
+ unique(
87
+ targets.map((item) => item.provider),
88
+ `${location}.targets`,
89
+ );
90
+ const expectedKind = {
91
+ npm: "npm-package",
92
+ binary: "archive",
93
+ paper: "pdf",
94
+ }[value.type];
95
+ if (artifacts.some((item) => item.kind !== expectedKind))
96
+ throw new Error(`${location}: artifact kind does not match product type`);
97
+ const published = targets.flatMap((item) => item.artifacts);
98
+ if (
99
+ artifacts.some((item) => !published.includes(item.id)) ||
100
+ published.some((id) => !artifacts.some((item) => item.id === id))
101
+ )
102
+ throw new Error(
103
+ `${location}: publication targets must cover exactly declared artifacts`,
104
+ );
105
+ if (
106
+ targets.some((item) => item.provider === "npm") !==
107
+ (value.type === "npm")
108
+ )
109
+ throw new Error(
110
+ `${location}: npm products require an npm target; other products cannot publish to npm`,
111
+ );
112
+ return structuredClone({ ...value, artifacts, targets });
113
+ });
114
+ unique(
115
+ products.map((item) => item.id),
116
+ "products",
117
+ );
118
+ return products;
119
+ }
@@ -0,0 +1,47 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { parse } from "smol-toml";
4
+ import { resolveBuildchainConfigPath } from "../../contracts/buildchain-layout.js";
5
+ import { compileConsumerPlan } from "./plan.js";
6
+
7
+ function productConfiguration(source, config) {
8
+ const plan = compileConsumerPlan(source);
9
+ // Read-only version tooling uses the established normalized field names.
10
+ // Product commands remain in the plan; no publish lifecycle is synthesized.
11
+ return {
12
+ ...config,
13
+ version: {
14
+ required: true,
15
+ strategy: plan.version.strategy,
16
+ next: plan.version.strategy === "anchored" ? "manual" : "auto",
17
+ files: plan.version.files.map(({ format, ...file }) => ({
18
+ ...file,
19
+ type: format,
20
+ })),
21
+ derivedFiles: plan.version.derived_files || [],
22
+ },
23
+ };
24
+ }
25
+
26
+ export function readProductConfiguration(cwd, normalizeLegacy) {
27
+ const configPath = resolveBuildchainConfigPath(cwd);
28
+ const filePath = path.join(cwd, configPath);
29
+ if (!fs.existsSync(filePath)) return undefined;
30
+ const source = fs.readFileSync(filePath, "utf8");
31
+ let config;
32
+ try {
33
+ config = parse(source);
34
+ } catch (error) {
35
+ throw new Error(`${configPath} parse failed: ${error.message}`);
36
+ }
37
+ if (![1, 2].includes(config.schema))
38
+ throw new Error(`${configPath} schema must be 1 or 2`);
39
+ return {
40
+ path: configPath,
41
+ filePath,
42
+ config:
43
+ config.schema === 2
44
+ ? productConfiguration(source, config)
45
+ : normalizeLegacy(config),
46
+ };
47
+ }
@@ -0,0 +1,46 @@
1
+ // Closed data validation shared by TOML, event identities and managed locks.
2
+ export function object(value, required, optional = [], location = "config") {
3
+ if (!value || Object.getPrototypeOf(value) !== Object.prototype)
4
+ throw new Error(`${location}: expected a table`);
5
+ for (const key of Object.keys(value))
6
+ if (![...required, ...optional].includes(key))
7
+ throw new Error(`${location}.${key}: unknown field`);
8
+ for (const key of required)
9
+ if (!Object.hasOwn(value, key))
10
+ throw new Error(`${location}.${key}: required field`);
11
+ return value;
12
+ }
13
+
14
+ export function text(value, location, pattern = /\S/u) {
15
+ if (typeof value !== "string" || !pattern.test(value) || value.includes("\0"))
16
+ throw new Error(`${location}: invalid string`);
17
+ return value;
18
+ }
19
+
20
+ export function choice(value, choices, location) {
21
+ if (!choices.includes(value))
22
+ throw new Error(`${location}: expected ${choices.join(" or ")}`);
23
+ return value;
24
+ }
25
+
26
+ export function list(value, location, validate) {
27
+ if (!Array.isArray(value) || !value.length)
28
+ throw new Error(`${location}: expected a nonempty list`);
29
+ return value.map((item, index) => validate(item, `${location}[${index}]`));
30
+ }
31
+
32
+ export function unique(values, location) {
33
+ if (new Set(values).size !== values.length)
34
+ throw new Error(`${location}: duplicate identity`);
35
+ }
36
+
37
+ export function relativePath(value, location) {
38
+ text(value, location, /^[A-Za-z0-9_.][A-Za-z0-9_./*-]*$/u);
39
+ if (value.split("/").some((part) => !part || part === ".." || part === "."))
40
+ throw new Error(`${location}: expected a repository-relative path`);
41
+ return value;
42
+ }
43
+
44
+ export function slug(value, location) {
45
+ return text(value, location, /^[a-z][a-z0-9-]*$/u);
46
+ }
@@ -1,10 +1,7 @@
1
1
  import path from "node:path";
2
2
  import fs from "node:fs";
3
3
  import crypto from "node:crypto";
4
- import {
5
- sourceCoordinates,
6
- validateNativeContract,
7
- } from "./coordinates.js";
4
+ import { sourceCoordinates, validateNativeContract } from "./coordinates.js";
8
5
  import {
9
6
  verifyProjectCutReplayProof,
10
7
  createSourceQualificationProof,
@@ -16,6 +13,7 @@ import { assertBranchUnlocked } from "../../providers/dev-delivery/protection.js
16
13
  import { deriveSourcePaths } from "./source-paths.js";
17
14
  import { admissionPolicyRequest } from "../admission/request.js";
18
15
  import { runAdmissionTransaction } from "../admission/transaction.js";
16
+ import { guardPipelineAdmission } from "../../workflow/pipeline/guard.js";
19
17
  export function admitDeliveryRequest(input, defaultBranch) {
20
18
  const target = sourceCoordinates({
21
19
  branch: input["target-branch"],
@@ -46,6 +44,7 @@ export async function qualifyDeliverySource(
46
44
  },
47
45
  dependencies = {},
48
46
  ) {
47
+ const pipeline = await guardPipelineAdmission(request, { repository, token });
49
48
  const outputs = {
50
49
  "runtime-sha": runtimeSha,
51
50
  "qualify-outcome": "skipped",
@@ -146,7 +145,12 @@ export async function qualifyDeliverySource(
146
145
  if (!affectedPaths.length)
147
146
  affectedPaths = await (dependencies.derivePaths || deriveSourcePaths)(
148
147
  source,
149
- { token, apiUrl, environment },
148
+ {
149
+ token,
150
+ apiUrl,
151
+ environment,
152
+ qualifiedBase: pipeline?.history.at(-1).generation.baseCommit,
153
+ },
150
154
  );
151
155
  const receiptRoot = qualification.result.receiptRoot;
152
156
  if (!/^sha256:[0-9a-f]{64}$/u.test(receiptRoot || ""))
@@ -10,6 +10,8 @@ import { dispatchDeliveryHandoff } from "../warrant/handoff.js";
10
10
  import { resolveCandidateAffectedPaths } from "./evidence.js";
11
11
  import { deliverySubmissionRequest } from "./submission.js";
12
12
  import { jsonList } from "../warrant/values.js";
13
+ import { guardPipelineAdmission } from "../../workflow/pipeline/guard.js";
14
+ import { wakePipelineCandidate } from "../../workflow/pipeline/wake.js";
13
15
  export async function reserveDeliveryCandidate(
14
16
  {
15
17
  workspace,
@@ -31,6 +33,7 @@ export async function reserveDeliveryCandidate(
31
33
  );
32
34
  if (!predecessorsOk || proofOutcome !== "success")
33
35
  throw new Error("Delivery candidate submission failed");
36
+ await guardPipelineAdmission(input, connection);
34
37
  const service =
35
38
  dependencies.service ||
36
39
  createDeliveryWarrantService({ ...connection, branch });
@@ -77,6 +80,14 @@ export async function reserveDeliveryCandidate(
77
80
  warrant.pullRequestNumber !== Number(input["expected-pr-number"]) ||
78
81
  warrant.sourceHead !== input["expected-head-sha"]
79
82
  ) {
83
+ if (input["pipeline-attempt"]) {
84
+ await wakePipelineCandidate(warrant, { ...connection, branch });
85
+ return {
86
+ "warrant-outcome": "success",
87
+ "warrant-handoff-required": "true",
88
+ "warrant-handoff-dispatched": "true",
89
+ };
90
+ }
80
91
  outputs = await dispatchDeliveryHandoff(
81
92
  {
82
93
  warrant,
@@ -52,7 +52,7 @@ export function pathsAtQualifiedSource(directory, qualifiedBase, source) {
52
52
 
53
53
  export async function deriveSourcePaths(
54
54
  source,
55
- { token, apiUrl, environment = process.env },
55
+ { token, apiUrl, environment = process.env, qualifiedBase },
56
56
  provider = new GitHubTwoPhaseClient({
57
57
  repository: source.repository,
58
58
  token,
@@ -65,10 +65,18 @@ export async function deriveSourcePaths(
65
65
  /^[1-9]\d*$/u.test(String(source.sourceWorkflowRunId || "")),
66
66
  "Exact source run coordinates are required",
67
67
  );
68
- const run = await provider.request(
69
- `/repos/${source.repository}/actions/runs/${source.sourceWorkflowRunId}`,
68
+ if (qualifiedBase === undefined) {
69
+ const run = await provider.request(
70
+ `/repos/${source.repository}/actions/runs/${source.sourceWorkflowRunId}`,
71
+ );
72
+ qualifiedBase = qualifiedRunBase(run, source);
73
+ }
74
+ // The internal pipeline guard already requalifies the source run and immutable
75
+ // generation. Zero-delta recovery still verifies the full source identity below.
76
+ requireValue(
77
+ /^[0-9a-f]{40}$/u.test(qualifiedBase || ""),
78
+ "Exact qualified source base is required",
70
79
  );
71
- const qualifiedBase = qualifiedRunBase(run, source);
72
80
  const directory = fs.mkdtempSync(
73
81
  path.join(os.tmpdir(), "buildchain-source-paths-"),
74
82
  );