@kungfu-tech/buildchain 4.0.2 → 4.0.3

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 (233) hide show
  1. package/AGENTS.md +4 -4
  2. package/CONTRIBUTING.md +34 -0
  3. package/README.md +1 -1
  4. package/architecture/agent-change-map.md +154 -147
  5. package/architecture/{v4-architecture-constitution.md → architecture-constitution.md} +11 -11
  6. package/architecture/{v3-core-mechanism-inventory.md → baseline-core-mechanism-inventory.md} +1 -1
  7. package/architecture/{v4-bootstrap-authority.json → bootstrap-authority.json} +3 -3
  8. package/architecture/{v3-v4-capability-closure.md → capability-parity-closure.md} +3 -3
  9. package/architecture/{v3-v4-live-capability-inventory.json → capability-parity-live-capability-inventory.json} +4 -4
  10. package/architecture/{v4-capability-state-machine-manifest.json → capability-state-machine-manifest.json} +2 -2
  11. package/architecture/{v4-capability-state-machine-manifest.schema.json → capability-state-machine-manifest.schema.json} +2 -2
  12. package/architecture/ci-lane-change-budget.json +18 -0
  13. package/architecture/{v4-cross-platform-adopter-qualification.json → cross-platform-adopter-qualification.json} +3 -3
  14. package/architecture/{v4-delivery-authority-parity.json → delivery-authority-parity.json} +16 -16
  15. package/architecture/{v4-delivery-warrant-shadow-bootstrap-plan.json → delivery-warrant-shadow-bootstrap-plan.json} +3 -3
  16. package/architecture/{v4-delivery-warrant-shadow-bootstrap-plan.md → delivery-warrant-shadow-bootstrap-plan.md} +7 -7
  17. package/architecture/{v4-floating-consumer-policy.md → floating-consumer-policy.md} +1 -1
  18. package/architecture/implementation-naming.json +1273 -0
  19. package/architecture/internal-capabilities.json +171 -169
  20. package/architecture/maintainability-debt.json +87 -82
  21. package/architecture/maintainability-policy.json +15 -15
  22. package/architecture/{v4-next-development-parity.json → next-development-parity.json} +8 -8
  23. package/architecture/{v4-partial-mutation-recovery-qualification.json → partial-mutation-recovery-qualification.json} +8 -8
  24. package/architecture/{v4-platform-stage-checkpoints.json → platform-stage-checkpoints.json} +2 -2
  25. package/architecture/{v4-provider-operation-journal-contract.json → provider-operation-journal-contract.json} +9 -9
  26. package/architecture/{v4-publication-rehearsal-parity.json → publication-rehearsal-parity.json} +4 -4
  27. package/architecture/{v4-release-activation-shadow-domain.json → release-activation-shadow-domain.json} +8 -8
  28. package/architecture/{v4-release-topology.json → release-topology.json} +112 -56
  29. package/architecture/{v4-release-train-parity.json → release-train-parity.json} +2 -2
  30. package/architecture/{v4-runtime-ref-resume-authority.json → runtime-ref-resume-authority.json} +1 -1
  31. package/architecture/{v4-runtime-semantic-closure.json → runtime-semantic-closure.json} +2 -2
  32. package/architecture/{v4-rust-libnode-bridge-evaluation.json → rust-libnode-bridge-evaluation.json} +3 -3
  33. package/architecture/{v4-rust-libnode-bridge-spike.md → rust-libnode-bridge-spike.md} +6 -6
  34. package/architecture/{v4-rust-wasm-production-authority.json → rust-wasm-production-authority.json} +11 -11
  35. package/architecture/{v4-stable-publication-fence.json → stable-publication-fence.json} +6 -6
  36. package/architecture/{v4-stage-capsule-contract.json → stage-capsule-contract.json} +1 -1
  37. package/architecture/{v4-stage-capsule-qualification.json → stage-capsule-qualification.json} +6 -6
  38. package/architecture/{v4-stage-capsule-resume-planner.json → stage-capsule-resume-planner.json} +2 -2
  39. package/architecture/{v4-stage-capsule-store-contract.json → stage-capsule-store-contract.json} +4 -4
  40. package/architecture/{v4-tail-reseal-parity.json → tail-reseal-parity.json} +28 -28
  41. package/architecture/{v4-universal-workflow-bootstrap.json → universal-workflow-bootstrap.json} +9 -4
  42. package/architecture/{v4-universal-workflow-fault-campaign.json → universal-workflow-fault-campaign.json} +8 -8
  43. package/architecture/{v4-universal-workflow-train-admission.json → universal-workflow-train-admission.json} +8 -8
  44. package/architecture/workflow-taxonomy.json +11 -0
  45. package/bin/buildchain.mjs +3 -3
  46. package/bin/internal/adopter-delivery-cli.mjs +9 -9
  47. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  48. package/crates/{buildchain-v4-bridge → buildchain-host-bridge}/Cargo.lock +1 -1
  49. package/crates/{buildchain-v4-bridge → buildchain-host-bridge}/Cargo.toml +1 -1
  50. package/crates/{buildchain-v4-bridge → buildchain-host-bridge}/src/main.rs +3 -3
  51. package/dist/site/buildchain-contract.json +18 -18
  52. package/dist/site/buildchain-site.json +167 -71
  53. package/dist/site/capability-registry.json +5 -5
  54. package/dist/site/kfd-claims.json +226 -90
  55. package/dist/site/kfd-upstream-aggregate.json +1 -1
  56. package/dist/site/manual-registry.json +7 -7
  57. package/dist/site/node-api-registry.json +12929 -10491
  58. package/dist/site/page-registry.json +155 -59
  59. package/dist/site/public-surface-audit.json +65 -46
  60. package/dist/site/publication-authority-registry.json +27 -17
  61. package/dist/site/publication-registry.json +4 -4
  62. package/dist/site/release-provenance.json +18 -9
  63. package/dist/site/site-manifest.json +11 -11
  64. package/dist/site/workflow-registry.json +86 -52
  65. package/docs/MAP.md +4 -3
  66. package/docs/next-development-transition.md +24 -1
  67. package/docs/node-api-reference.md +435 -175
  68. package/docs/oci-publication.md +173 -0
  69. package/docs/publish-transaction.md +36 -3
  70. package/docs/release-flow.md +1 -1
  71. package/docs/reusable-build-surface.md +1 -1
  72. package/docs/v4-adopter-delivery.md +2 -2
  73. package/docs/v4-canonical-contracts.md +7 -7
  74. package/docs/v4-delivery-warrant-read-candidate.md +3 -3
  75. package/docs/v4-delivery-warrant-semantic-diff.md +3 -3
  76. package/docs/v4-production-release.md +8 -2
  77. package/docs/v4-publication-rehearsal.md +3 -3
  78. package/docs/v4-runtime-ref-resume-authority.md +2 -2
  79. package/docs/v4-rust-wasm-production-authority.md +6 -6
  80. package/docs/v4-stage-capsule.md +7 -7
  81. package/docs/v4-tail-reseal.md +1 -1
  82. package/docs/workflow-catalog.md +1 -0
  83. package/package.json +37 -26
  84. package/packages/compatibility/adopter-delivery.js +16 -0
  85. package/packages/compatibility/cross-platform-adopter-qualification.js +12 -0
  86. package/packages/compatibility/floating-consumer-policy.js +14 -0
  87. package/packages/compatibility/publication-qualification.js +11 -0
  88. package/packages/compatibility/publication-rehearsal.js +18 -0
  89. package/packages/compatibility/runtime-ref-resume-authority.js +13 -0
  90. package/packages/compatibility/tail-reseal-receipt.js +7 -0
  91. package/packages/compatibility/tail-reseal.js +11 -0
  92. package/packages/core/{v4-adopter-delivery-parity.js → adopter-delivery-parity.js} +12 -12
  93. package/packages/core/{v4-adopter-delivery.js → adopter-delivery.js} +24 -24
  94. package/packages/core/buildchain-contract.js +5 -5
  95. package/packages/core/buildchain-domain.wasm +0 -0
  96. package/packages/core/buildchain-publication-authority.js +1 -0
  97. package/packages/core/{v4-canonical-contracts.js → canonical-contracts.js} +32 -31
  98. package/packages/core/{v4-cross-platform-adopter-qualification.js → cross-platform-adopter-qualification.js} +19 -19
  99. package/packages/core/{v4-delivery-warrant-fixture-runner.js → delivery-warrant-fixture-runner.js} +36 -32
  100. package/packages/core/{v4-delivery-warrant-read-candidate.js → delivery-warrant-read-candidate.js} +40 -40
  101. package/packages/core/{v4-delivery-warrant-semantic-diff-gate.js → delivery-warrant-semantic-diff-gate.js} +58 -59
  102. package/packages/core/{v4-delivery-warrant-shadow-adapter.js → delivery-warrant-shadow-adapter.js} +15 -15
  103. package/packages/core/dev-delivery-execution-transfer.js +6 -6
  104. package/packages/core/dev-delivery-native-proof.js +12 -8
  105. package/packages/core/dev-delivery-warrant-legacy-recovery.js +1 -1
  106. package/packages/core/dev-delivery-warrant-native-compatibility.js +4 -4
  107. package/packages/core/dev-delivery-warrant-state.js +11 -11
  108. package/packages/core/dev-delivery-warrant.js +3 -3
  109. package/packages/core/domain-wasm-artifact.js +7 -0
  110. package/packages/core/{v4-domain-wasm.js → domain-wasm.js} +31 -31
  111. package/packages/core/{v4-floating-consumer-evidence.js → floating-consumer-evidence.js} +13 -13
  112. package/packages/core/{v4-floating-consumer-policy.js → floating-consumer-policy.js} +27 -29
  113. package/packages/core/{v4-floating-consumer-release-passport.js → floating-consumer-release-passport.js} +25 -25
  114. package/packages/core/index.js +4 -8
  115. package/packages/core/next-development-projection.js +24 -1
  116. package/packages/core/next-development-transition.js +5 -6
  117. package/packages/core/oci-compose-qualification.js +143 -0
  118. package/packages/core/oci-publication-bundle.js +281 -0
  119. package/packages/core/oci-publication-graph.js +206 -0
  120. package/packages/core/paper-runtime-channels.js +2 -2
  121. package/packages/core/paper.js +3 -3
  122. package/packages/core/partial-mutation-recovery-qualification.js +18 -0
  123. package/packages/core/{v4-platform-stage-checkpoints.js → platform-stage-checkpoints.js} +37 -37
  124. package/packages/core/{v4-product-publication.js → product-publication.js} +11 -9
  125. package/packages/core/{v4-protected-publication-source.js → protected-publication-source.js} +3 -3
  126. package/packages/core/provider-operation-journal.js +62 -0
  127. package/packages/core/{v4-provider-readback-idempotency.js → provider-readback-idempotency.js} +33 -33
  128. package/packages/core/public-export-compatibility.js +33 -0
  129. package/packages/core/publication-development.js +72 -0
  130. package/packages/core/{v4-publication-qualification.js → publication-qualification.js} +17 -17
  131. package/packages/core/{v4-publication-rehearsal-capsule.js → publication-rehearsal-capsule.js} +23 -23
  132. package/packages/core/publication-rehearsal-projection.js +2 -2
  133. package/packages/core/{v4-publication-rehearsal-provider-bindings.js → publication-rehearsal-provider-bindings.js} +4 -4
  134. package/packages/core/publication-rehearsal-runtime.js +21 -21
  135. package/packages/core/{v4-publication-rehearsal.js → publication-rehearsal.js} +33 -33
  136. package/packages/core/publication-sealed-bundle.js +78 -13
  137. package/packages/core/release-activation-shadow.js +32 -0
  138. package/packages/core/release-candidate.js +6 -6
  139. package/packages/core/{v4-release-invocation.js → release-invocation.js} +15 -15
  140. package/packages/core/release-passport.js +36 -36
  141. package/packages/core/release-tail-product-capabilities.js +9 -0
  142. package/packages/core/release-tail-provider-plane.js +10 -10
  143. package/packages/core/{v4-runtime-ref-resume-authority.js → runtime-ref-resume-authority.js} +36 -40
  144. package/packages/core/{v4-runtime-selector-persistence.js → runtime-selector-persistence.js} +7 -7
  145. package/packages/core/stable-publication-fence.js +28 -0
  146. package/packages/core/{v4-stage-capsule-local-store.js → stage-capsule-local-store.js} +56 -54
  147. package/packages/core/{v4-stage-capsule-qualification-campaign.js → stage-capsule-qualification-campaign.js} +37 -38
  148. package/packages/core/{v4-stage-capsule-qualification.js → stage-capsule-qualification.js} +28 -29
  149. package/packages/core/{v4-stage-capsule-resume-planner.js → stage-capsule-resume-planner.js} +29 -29
  150. package/packages/core/{v4-stage-capsule-store.js → stage-capsule-store.js} +59 -56
  151. package/packages/core/{v4-stage-capsule.js → stage-capsule.js} +45 -45
  152. package/packages/core/{v4-tail-reseal-contract.js → tail-reseal-contract.js} +6 -6
  153. package/packages/core/{v4-tail-reseal-github.js → tail-reseal-github.js} +12 -12
  154. package/packages/core/{v4-tail-reseal-receipt.js → tail-reseal-receipt.js} +22 -22
  155. package/packages/core/{v4-tail-reseal.js → tail-reseal.js} +29 -29
  156. package/packages/core/{v4-universal-workflow-bootstrap.js → universal-workflow-bootstrap.js} +23 -23
  157. package/scripts/{v4-architecture.mjs → architecture.mjs} +6 -6
  158. package/scripts/binary-publication-evidence.mjs +1 -1
  159. package/scripts/{v4-bridge-bootstrap.mjs → bridge-bootstrap.mjs} +14 -18
  160. package/scripts/{v4-bridge-evidence.mjs → bridge-evidence.mjs} +8 -8
  161. package/scripts/{build-v4-domain-wasm.mjs → build-domain-wasm.mjs} +6 -18
  162. package/scripts/{v3-v4-capability-catalog.mjs → capability-parity-catalog.mjs} +19 -19
  163. package/scripts/check-action-bundles.mjs +2 -2
  164. package/scripts/{check-v3-v4-capability-inventory.mjs → check-capability-parity-inventory.mjs} +42 -42
  165. package/scripts/check-core-mechanism-inventory.mjs +4 -1
  166. package/scripts/{check-v4-floating-consumer-policy-contract.mjs → check-floating-consumer-policy-contract.mjs} +8 -8
  167. package/scripts/check-implementation-naming.mjs +135 -0
  168. package/scripts/check-inventory.mjs +6 -5
  169. package/scripts/check-maintainability.mjs +9 -2
  170. package/scripts/{check-v4-public-dogfood-contract.mjs → check-public-dogfood-contract.mjs} +15 -15
  171. package/scripts/{check-v4-release-topology.mjs → check-release-topology.mjs} +53 -31
  172. package/scripts/{check-v4-runtime-semantic-closure.mjs → check-runtime-semantic-closure.mjs} +19 -16
  173. package/scripts/{check-v4-universal-workflow-bootstrap.mjs → check-universal-workflow-bootstrap.mjs} +14 -14
  174. package/scripts/{v4-consumer-policy.mjs → consumer-policy.mjs} +15 -15
  175. package/scripts/{copy-v4-domain-wasm.mjs → copy-domain-wasm.mjs} +3 -3
  176. package/scripts/{v4-cross-platform-adopter-qualification.mjs → cross-platform-adopter-qualification.mjs} +9 -9
  177. package/scripts/{v4-declarative-promotion-admission.mjs → declarative-promotion-admission.mjs} +4 -4
  178. package/scripts/dev-delivery-warrant-options.mjs +1 -1
  179. package/scripts/dev-delivery-warrant-store.mjs +29 -10
  180. package/scripts/dev-delivery-warrant.mjs +4 -4
  181. package/scripts/dev-pr-delivery-warrant.mjs +1 -1
  182. package/scripts/generate-channel-build-workflow.mjs +2 -2
  183. package/scripts/generate-channel-promotion-workflow.mjs +14 -31
  184. package/scripts/generate-release-candidate-passport.mjs +23 -23
  185. package/scripts/generate-site-bundle.mjs +17 -17
  186. package/scripts/{generate-v4-universal-workflow-facades.mjs → generate-universal-workflow-facades.mjs} +61 -21
  187. package/scripts/{v4-host-adapter.mjs → host-adapter.mjs} +1 -1
  188. package/scripts/implementation-references.mjs +25 -0
  189. package/scripts/init-repo.mjs +1 -1
  190. package/scripts/next-development-review.mjs +17 -11
  191. package/scripts/oci-compose-preview.mjs +261 -0
  192. package/scripts/{v4-platform-stage-checkpoint-rehearsal.mjs → platform-stage-checkpoint-rehearsal.mjs} +17 -17
  193. package/scripts/{v4-product-publication-intent.mjs → product-publication-intent.mjs} +16 -8
  194. package/scripts/publication-candidate-kind.mjs +93 -0
  195. package/scripts/publication-candidate-sealer.mjs +16 -1
  196. package/scripts/{v4-publication-rehearsal-fixture.mjs → publication-rehearsal-fixture.mjs} +5 -5
  197. package/scripts/{v4-publication-settlement.mjs → publication-settlement.mjs} +8 -8
  198. package/scripts/{v4-release-candidate-adapter.mjs → release-candidate-adapter.mjs} +4 -4
  199. package/scripts/release-candidate-resolver.mjs +15 -26
  200. package/scripts/release-tail.mjs +5 -5
  201. package/scripts/resume-from-candidate-run.mjs +35 -42
  202. package/scripts/site-capability-metadata.mjs +9 -0
  203. package/scripts/source-verification-evidence.mjs +7 -7
  204. package/scripts/{v4-stage-capsule-qualification.mjs → stage-capsule-qualification.mjs} +62 -62
  205. package/scripts/{v4-stage-capsule-resume-rehearsal.mjs → stage-capsule-resume-rehearsal.mjs} +6 -6
  206. package/scripts/{v4-tail-reseal-macos-rehearsal.mjs → tail-reseal-macos-rehearsal.mjs} +9 -9
  207. package/scripts/{v4-tail-reseal.mjs → tail-reseal.mjs} +19 -19
  208. package/scripts/universal-facade-maintainability.mjs +12 -3
  209. package/scripts/{v4-universal-workflow-backflow.mjs → universal-workflow-backflow.mjs} +10 -10
  210. package/scripts/{v4-universal-workflow-engine.mjs → universal-workflow-engine.mjs} +40 -38
  211. package/scripts/{v4-universal-workflow-self-dogfood.mjs → universal-workflow-self-dogfood.mjs} +12 -12
  212. package/scripts/verify-version-state-delta.mjs +4 -2
  213. package/scripts/{v4-warrant-shadow-plan.mjs → warrant-shadow-plan.mjs} +6 -6
  214. package/templates/universal-buildchain-bootstrap-recovery.yml +4 -4
  215. package/packages/core/buildchain-v4-domain.wasm +0 -0
  216. package/packages/core/v4-domain-wasm-artifact.js +0 -7
  217. package/packages/core/v4-partial-mutation-recovery-qualification.js +0 -18
  218. package/packages/core/v4-provider-operation-journal.js +0 -62
  219. package/packages/core/v4-release-activation-shadow.js +0 -32
  220. package/packages/core/v4-stable-publication-fence.js +0 -28
  221. /package/actions/{v4-release-candidate-promote → release-candidate-promote}/README.md +0 -0
  222. /package/architecture/{v4-adopter-delivery-parity.json → adopter-delivery-parity.json} +0 -0
  223. /package/architecture/{v3-core-mechanism-inventory.json → baseline-core-mechanism-inventory.json} +0 -0
  224. /package/architecture/{v4-canonical-contract-fixtures.json → canonical-contract-fixtures.json} +0 -0
  225. /package/architecture/{v4-compatibility-facts-parity.json → compatibility-facts-parity.json} +0 -0
  226. /package/architecture/{v4-delivery-warrant-shadow-bootstrap-plan.schema.json → delivery-warrant-shadow-bootstrap-plan.schema.json} +0 -0
  227. /package/architecture/{v4-delivery-warrant-shadow-fixtures.json → delivery-warrant-shadow-fixtures.json} +0 -0
  228. /package/architecture/{v4-delivery-warrant-shadow-fixtures.schema.json → delivery-warrant-shadow-fixtures.schema.json} +0 -0
  229. /package/architecture/{v4-exception-ledger.json → exception-ledger.json} +0 -0
  230. /package/architecture/{v4-exception-ledger.schema.json → exception-ledger.schema.json} +0 -0
  231. /package/architecture/{v4-floating-consumer-policy.json → floating-consumer-policy.json} +0 -0
  232. /package/architecture/{v4-publication-facade-patches.json → publication-facade-patches.json} +0 -0
  233. /package/architecture/{v4-release-invocation-fixtures.json → release-invocation-fixtures.json} +0 -0
@@ -175,7 +175,13 @@ confidence: high
175
175
  sensitivity: public
176
176
  evidence_grade: A
177
177
  review_state: self-reviewed
178
- last_reviewed: 2026-08-11
178
+ last_reviewed: 2026-09-07
179
+ ai_provenance:
180
+ model_family: GPT-6
181
+ product: Codex
182
+ generated_at: 2026-09-07
183
+ visible_context: Release completion, protected development provider, and independent review sources.
184
+ invisible_context_boundary: No private credentials or external unpublished release state.
179
185
  ---
180
186
 
181
187
  # Next-development Transition
@@ -229,6 +235,23 @@ can settle, Patrol reads every prepared version path at the candidate SHA and
229
235
  requires the exact reserved blob identities; missing or stale state blocks
230
236
  before a Release Cut or heavy candidate build.
231
237
 
238
+ ## Stable publication and the next patch
239
+
240
+ A completed stable publication with \`semver/auto\` schedules the next patch at
241
+ Alpha zero: \`4.0.2\` prepares \`4.0.3-alpha.0\`. Publication settlement is
242
+ retained before the development PR starts. Preparation reads the exact current
243
+ protected Dev commit and regenerates only its declared version files, preserving
244
+ code that arrived after the stable candidate. The independent reviewer verifies
245
+ the completed stable publication and reconstructs every tracked byte from the
246
+ protected parent; a PR body alone grants no authority.
247
+
248
+ A replay returns \`already-current\` or \`already-advanced\` when Dev already
249
+ contains the target or a later version. A late Alpha completion cannot regress
250
+ Dev to an older patch. A moved protected base fails before creating the version
251
+ commit and requires a fresh preparation attempt. Pending review leaves the
252
+ completed publication intact. Anchored version models still require a reviewed
253
+ manual anchor.
254
+
232
255
  ## Version models
233
256
 
234
257
  \`semver/auto\` increments the Alpha sequence on the same semantic patch. For
@@ -159,15 +159,14 @@ function normalizeModel(value = {}) {
159
159
  return model;
160
160
  }
161
161
 
162
- function normalizeCompletedAlpha(value = {}) {
162
+ function normalizeCompletedAlpha(value = {}, model) {
163
163
  const version = String(value.version || "").trim();
164
- if (!ALPHA.test(version))
164
+ const expression = model?.strategy === "anchored" ? /-alpha\.(0|[1-9]\d*)$/u : ALPHA;
165
+ if (!SEMVER.test(version) || !expression.test(version))
165
166
  throw new Error("completedAlpha.version must be an alpha semantic version");
166
167
  const exactTag = String(value.exactTag || "").trim();
167
168
  if (exactTag !== `v${version}`)
168
- throw new Error(
169
- "completedAlpha.exactTag must match completedAlpha.version",
170
- );
169
+ throw new Error("completedAlpha.exactTag must match completedAlpha.version");
171
170
  const normalized = {
172
171
  outcome: String(value.outcome || "").trim(),
173
172
  version,
@@ -251,8 +250,8 @@ function initialStateRoot(status, idempotencyKey) {
251
250
  }
252
251
 
253
252
  export function createNextDevelopmentTransition(input = {}) {
254
- const completedAlpha = normalizeCompletedAlpha(input.completedAlpha);
255
253
  const model = normalizeModel(input.model);
254
+ const completedAlpha = normalizeCompletedAlpha(input.completedAlpha, model);
256
255
  const adapter = normalizeAdapter(input);
257
256
  const declaredPaths = [
258
257
  ...adapter.sourcePaths,
@@ -0,0 +1,143 @@
1
+ import { domainContentRoot } from "./canonical-contracts.js";
2
+ import { releaseTailRoot } from "./release-tail-provider-plane.js";
3
+ import { ociPublicationTag } from "./oci-publication-graph.js";
4
+
5
+ function check(condition, message) {
6
+ if (!condition) throw new Error(`Compose preview: ${message}`);
7
+ }
8
+
9
+ export function verifyComposePublication({
10
+ family,
11
+ readback,
12
+ documents,
13
+ repository,
14
+ tag,
15
+ }) {
16
+ const { root, ...body } = family;
17
+ const version = tag.replace(/^v/u, "");
18
+ const operation = documents.productState.operations.find(
19
+ (entry) => entry.capabilityId === "product.oci.publish",
20
+ );
21
+ check(
22
+ family.schema === "kungfu-buildchain-oci-family/v2" &&
23
+ root === domainContentRoot("oci-publication-family", body) &&
24
+ family.repository === repository &&
25
+ family.version === version &&
26
+ family.sourceSha === documents.passport.source.builtSourceSha &&
27
+ documents.passport.source.builtSourceTreeSha ===
28
+ documents.invocation.candidate.tree &&
29
+ readback.sourceSha === documents.invocation.candidate.commit &&
30
+ readback.familyRoot === root &&
31
+ readback.version === version &&
32
+ readback.candidateSourceSha === family.sourceSha &&
33
+ operation?.receipt.evidenceRoots.includes(releaseTailRoot(readback)),
34
+ "unbound public OCI family",
35
+ );
36
+ const applications = family.images.filter(
37
+ (entry) => entry.kind === "compose" && entry.preview,
38
+ );
39
+ check(applications.length === 1, "expected one declared Compose preview");
40
+ const application = applications[0],
41
+ policy = application.preview;
42
+ check(
43
+ policy.alias === "compose-preview" &&
44
+ /^\.github\/workflows\/[a-z0-9-]+\.yml$/u.test(
45
+ policy.qualificationWorkflow,
46
+ ) &&
47
+ /^(none|sha256:[0-9a-f]{64})$/u.test(policy.previousDigest),
48
+ "invalid preview policy",
49
+ );
50
+ const image = family.images.find(
51
+ (entry) =>
52
+ entry.name === application.targetImage && entry.kind !== "compose",
53
+ );
54
+ check(
55
+ image &&
56
+ application.repository === image.repository &&
57
+ image.repository === `ghcr.io/${repository}/${image.name}` &&
58
+ /^[a-z0-9][a-z0-9._-]*$/u.test(image.name),
59
+ "foreign application destination",
60
+ );
61
+ for (const entry of [image, application])
62
+ check(
63
+ readback.images.some(
64
+ (observed) =>
65
+ observed.name === entry.name &&
66
+ observed.repository === entry.repository &&
67
+ observed.digest === entry.digest &&
68
+ observed.ref === ociPublicationTag(entry, version) &&
69
+ observed.anonymous === true,
70
+ ),
71
+ "missing public artifact readback",
72
+ );
73
+ return {
74
+ application,
75
+ image,
76
+ policy,
77
+ familyRoot: root,
78
+ repository,
79
+ tag,
80
+ sourceSha: documents.product.publication.releaseSha,
81
+ publicationReceiptRoot: documents.receipt.receiptRoot,
82
+ };
83
+ }
84
+
85
+ export function verifyComposeQualification(context, run, receipt) {
86
+ const { application, image, policy, repository, tag, sourceSha, familyRoot } =
87
+ context;
88
+ check(
89
+ run.repository?.full_name === repository &&
90
+ run.head_repository?.full_name === repository &&
91
+ run.event === "workflow_dispatch" &&
92
+ run.status === "completed" &&
93
+ run.conclusion === "success" &&
94
+ run.head_sha === sourceSha &&
95
+ run.path.split("@")[0] === policy.qualificationWorkflow,
96
+ "qualification run is not the exact published source",
97
+ );
98
+ if (!receipt) return;
99
+ check(
100
+ receipt.schema === "kungfu-buildchain-compose-qualification/v1" &&
101
+ receipt.repository === repository &&
102
+ receipt.tag === tag &&
103
+ receipt.sourceSha === sourceSha &&
104
+ receipt.familyRoot === familyRoot &&
105
+ String(receipt.runId) === String(run.id) &&
106
+ Number(receipt.runAttempt) === run.run_attempt &&
107
+ receipt.image === `${image.repository}@${image.digest}` &&
108
+ receipt.application ===
109
+ `${application.repository}@${application.digest}` &&
110
+ receipt.previousDigest === policy.previousDigest &&
111
+ receipt.passed === true,
112
+ "qualification receipt identity mismatch",
113
+ );
114
+ for (const name of [
115
+ "freshInstall",
116
+ "restartPersistence",
117
+ "upgradePersistence",
118
+ "rollbackPersistence",
119
+ "accountIsolation",
120
+ "hardenedRuntime",
121
+ ])
122
+ check(receipt.checks?.[name] === true, `qualification lacks ${name}`);
123
+ check(
124
+ Array.isArray(image.platforms) &&
125
+ image.platforms.length === 2 &&
126
+ JSON.stringify([...image.platforms].sort()) ===
127
+ JSON.stringify(["linux/amd64", "linux/arm64"]) &&
128
+ image.platforms.every(
129
+ (platform) => receipt.platforms?.[platform]?.passed === true,
130
+ ),
131
+ "qualification lacks both platforms",
132
+ );
133
+ check(
134
+ Array.isArray(receipt.evidence) &&
135
+ receipt.evidence.length > 0 &&
136
+ receipt.evidence.every(
137
+ (entry) =>
138
+ /^[a-zA-Z0-9][a-zA-Z0-9._-]*\.json$/u.test(entry.path) &&
139
+ /^sha256:[0-9a-f]{64}$/u.test(entry.sha256),
140
+ ),
141
+ "missing qualification evidence bindings",
142
+ );
143
+ }
@@ -0,0 +1,281 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { domainContentRoot } from "./canonical-contracts.js";
5
+ import {
6
+ verifyOciGraph,
7
+ verifyOciDestination,
8
+ } from "./oci-publication-graph.js";
9
+
10
+ export const OCI_FAMILY_SCHEMA = "kungfu-buildchain-oci-family/v1";
11
+ const digestPattern = /^sha256:[0-9a-f]{64}$/u;
12
+ const namePattern = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/u;
13
+
14
+ export function sealOciPublicationBundle({ bundleRoot, body }) {
15
+ const manifest = {
16
+ ...body,
17
+ root: domainContentRoot("oci-publication-family", body),
18
+ };
19
+ verifyOciPublicationBundle({
20
+ bundleRoot,
21
+ manifest,
22
+ repository: body.repository,
23
+ sourceSha: body.sourceSha,
24
+ version: body.version,
25
+ });
26
+ return manifest;
27
+ }
28
+
29
+ function requireValue(condition, message) {
30
+ if (!condition) throw new Error(`OCI bundle: ${message}`);
31
+ }
32
+
33
+ export function ociBundleFile(root, relative) {
34
+ requireValue(
35
+ typeof relative === "string" &&
36
+ relative.length > 0 &&
37
+ !relative.includes("\\") &&
38
+ !path.isAbsolute(relative) &&
39
+ !relative.split(/[\\/]/u).some((p) => p === ".." || p === "." || !p),
40
+ "unsafe file path",
41
+ );
42
+ const base = fs.realpathSync(root);
43
+ let current = base;
44
+ for (const segment of relative.split("/")) {
45
+ current = path.join(current, segment);
46
+ requireValue(
47
+ !fs.lstatSync(current).isSymbolicLink(),
48
+ "symlinks are forbidden",
49
+ );
50
+ }
51
+ requireValue(
52
+ fs.statSync(current).isFile() &&
53
+ fs.realpathSync(current).startsWith(`${base}${path.sep}`),
54
+ "file escapes bundle",
55
+ );
56
+ return current;
57
+ }
58
+
59
+ function fileDigest(file) {
60
+ const hash = crypto.createHash("sha256"),
61
+ buffer = Buffer.alloc(1024 * 1024);
62
+ const fd = fs.openSync(file, "r");
63
+ try {
64
+ for (;;) {
65
+ const size = fs.readSync(fd, buffer);
66
+ if (!size) break;
67
+ hash.update(buffer.subarray(0, size));
68
+ }
69
+ } finally {
70
+ fs.closeSync(fd);
71
+ }
72
+ return `sha256:${hash.digest("hex")}`;
73
+ }
74
+
75
+ function verifyImageProvenance(image, config, sourceSha, version) {
76
+ const labels = config.config?.Labels || {};
77
+ requireValue(
78
+ image.content &&
79
+ /^[0-9a-f]{40}$/u.test(image.content.sourceSha) &&
80
+ typeof image.content.version === "string" &&
81
+ image.content.version.length > 0,
82
+ "missing image content provenance",
83
+ );
84
+ requireValue(
85
+ labels["org.opencontainers.image.revision"] === image.content.sourceSha &&
86
+ labels["org.opencontainers.image.version"] === image.content.version,
87
+ "image content provenance mismatch",
88
+ );
89
+ if (image.action === "built")
90
+ requireValue(
91
+ image.content.sourceSha === sourceSha &&
92
+ image.content.version === version,
93
+ "built image is not from the candidate source",
94
+ );
95
+ if (image.contractMajor !== undefined)
96
+ requireValue(
97
+ Number.isInteger(image.contractMajor) &&
98
+ image.contractMajor > 0 &&
99
+ labels["io.kungfu.image.contract-major"] ===
100
+ String(image.contractMajor),
101
+ "image contract major mismatch",
102
+ );
103
+ if (image.parentDigest)
104
+ requireValue(
105
+ digestPattern.test(image.parentDigest) &&
106
+ labels["io.kungfu.image.parent-digest"] === image.parentDigest,
107
+ "image parent digest mismatch",
108
+ );
109
+ if (image.content.materialSha)
110
+ requireValue(
111
+ /^[0-9a-f]{40}$/u.test(image.content.materialSha) &&
112
+ labels["io.kungfu.buildchain.release-material-sha"] ===
113
+ image.content.materialSha,
114
+ "image material provenance mismatch",
115
+ );
116
+ }
117
+
118
+ export function verifyOciPublicationBundle({
119
+ bundleRoot,
120
+ manifest,
121
+ repository,
122
+ sourceSha,
123
+ version,
124
+ }) {
125
+ requireValue(
126
+ [OCI_FAMILY_SCHEMA, "kungfu-buildchain-oci-family/v2"].includes(
127
+ manifest?.schema,
128
+ ),
129
+ "unsupported manifest schema",
130
+ );
131
+ const { root, ...body } = manifest;
132
+ const computed = domainContentRoot("oci-publication-family", body);
133
+ requireValue(root === computed, "manifest root mismatch");
134
+ requireValue(
135
+ manifest.repository === repository &&
136
+ manifest.sourceSha === sourceSha &&
137
+ manifest.version === version,
138
+ "candidate identity mismatch",
139
+ );
140
+ requireValue(
141
+ /^[a-z0-9-]+\/[a-z0-9._-]+$/u.test(repository),
142
+ "invalid repository",
143
+ );
144
+ requireValue(
145
+ Array.isArray(manifest.images) && manifest.images.length > 0,
146
+ "empty image family",
147
+ );
148
+ const expected = manifest.expectedImages;
149
+ requireValue(
150
+ Array.isArray(expected) &&
151
+ expected.length === new Set(expected).size &&
152
+ expected.every((n) => namePattern.test(n)),
153
+ "invalid expected image family",
154
+ );
155
+ const actual = manifest.images.map((i) => i.name).sort();
156
+ requireValue(
157
+ JSON.stringify(actual) === JSON.stringify([...expected].sort()),
158
+ "incomplete or duplicate image family",
159
+ );
160
+ const files = new Map();
161
+ const graphs = new Map();
162
+ for (const image of manifest.images) {
163
+ verifyOciDestination(image, manifest, requireValue);
164
+ requireValue(
165
+ digestPattern.test(image.digest) &&
166
+ ["built", "reused"].includes(image.action),
167
+ "invalid image identity",
168
+ );
169
+ requireValue(
170
+ /^linux\/(amd64|arm64)$/u.test(image.platform) ||
171
+ (manifest.schema.endsWith("/v2") &&
172
+ ["multi-platform", "compose"].includes(image.platform)),
173
+ "unsupported image platform",
174
+ );
175
+ const prefix = image.layout;
176
+ const descriptorFile = (descriptor) => {
177
+ requireValue(
178
+ digestPattern.test(descriptor.digest) &&
179
+ Number.isSafeInteger(descriptor.size) &&
180
+ descriptor.size >= 0 &&
181
+ !descriptor.urls,
182
+ "invalid OCI descriptor",
183
+ );
184
+ const relative = `${prefix}/blobs/sha256/${descriptor.digest.slice(7)}`;
185
+ const file = ociBundleFile(bundleRoot, relative);
186
+ requireValue(
187
+ fs.statSync(file).size === descriptor.size,
188
+ "OCI blob size mismatch",
189
+ );
190
+ if (!files.has(file))
191
+ requireValue(
192
+ fileDigest(file) === descriptor.digest,
193
+ "OCI blob digest mismatch",
194
+ );
195
+ if (Object.hasOwn(descriptor, "data")) {
196
+ requireValue(
197
+ descriptor.size <= 1024 * 1024 &&
198
+ descriptor.data === fs.readFileSync(file).toString("base64"),
199
+ "embedded OCI content mismatch",
200
+ );
201
+ }
202
+ files.set(file, descriptor);
203
+ return file;
204
+ };
205
+ const layout = JSON.parse(
206
+ fs.readFileSync(ociBundleFile(bundleRoot, `${prefix}/oci-layout`)),
207
+ );
208
+ requireValue(
209
+ layout.imageLayoutVersion === "1.0.0",
210
+ "unsupported OCI layout",
211
+ );
212
+ const index = JSON.parse(
213
+ fs.readFileSync(ociBundleFile(bundleRoot, `${prefix}/index.json`)),
214
+ );
215
+ const selected = (index.manifests || []).filter(
216
+ (descriptor) =>
217
+ descriptor.digest === image.digest &&
218
+ (descriptor.annotations?.["org.opencontainers.image.ref.name"] ===
219
+ image.name ||
220
+ index.manifests.length === 1),
221
+ );
222
+ requireValue(
223
+ index.schemaVersion === 2 && selected.length === 1,
224
+ "OCI index must select one exact image manifest",
225
+ );
226
+ if (manifest.schema.endsWith("/v2")) {
227
+ graphs.set(
228
+ image.name,
229
+ verifyOciGraph({
230
+ image,
231
+ manifest,
232
+ descriptor: selected[0],
233
+ descriptorFile,
234
+ requireValue,
235
+ verifyProvenance: (config) =>
236
+ verifyImageProvenance(image, config, sourceSha, version),
237
+ }),
238
+ );
239
+ } else {
240
+ const document = JSON.parse(fs.readFileSync(descriptorFile(selected[0])));
241
+ requireValue(
242
+ document.schemaVersion === 2 &&
243
+ [
244
+ "application/vnd.oci.image.manifest.v1+json",
245
+ "application/vnd.docker.distribution.manifest.v2+json",
246
+ ].includes(document.mediaType) &&
247
+ Array.isArray(document.layers),
248
+ "unsupported OCI image manifest",
249
+ );
250
+ const config = JSON.parse(
251
+ fs.readFileSync(descriptorFile(document.config)),
252
+ );
253
+ requireValue(
254
+ `${config.os}/${config.architecture}` === image.platform,
255
+ "OCI platform mismatch",
256
+ );
257
+ verifyImageProvenance(image, config, sourceSha, version);
258
+ for (const layer of document.layers) descriptorFile(layer);
259
+ graphs.set(image.name, {
260
+ manifests: [selected[0]],
261
+ blobs: [document.config, ...document.layers],
262
+ });
263
+ }
264
+ const smokeFile = ociBundleFile(bundleRoot, image.smoke.path);
265
+ requireValue(
266
+ fileDigest(smokeFile) === image.smoke.sha256,
267
+ "smoke evidence digest mismatch",
268
+ );
269
+ const smoke = JSON.parse(fs.readFileSync(smokeFile));
270
+ requireValue(
271
+ smoke.passed === true && smoke.image === image.name,
272
+ "smoke qualification failed",
273
+ );
274
+ }
275
+ return {
276
+ root: computed,
277
+ manifest,
278
+ graphs,
279
+ bundleRoot: fs.realpathSync(bundleRoot),
280
+ };
281
+ }
@@ -0,0 +1,206 @@
1
+ import fs from "node:fs";
2
+
3
+ const imageTypes = [
4
+ "application/vnd.oci.image.manifest.v1+json",
5
+ "application/vnd.docker.distribution.manifest.v2+json",
6
+ ];
7
+ const indexTypes = [
8
+ "application/vnd.oci.image.index.v1+json",
9
+ "application/vnd.docker.distribution.manifest.list.v2+json",
10
+ ];
11
+ const composeType = "application/vnd.docker.compose.project";
12
+
13
+ export function ociPublicationTag(image, version) {
14
+ return `${image.kind === "compose" ? "compose-" : ""}v${version}`;
15
+ }
16
+
17
+ export function verifyOciDestination(image, manifest, requireValue) {
18
+ const compose = manifest.schema.endsWith("/v2") && image.kind === "compose";
19
+ requireValue(
20
+ manifest.schema.endsWith("/v2") || !image.kind,
21
+ "artifact kinds require family v2",
22
+ );
23
+ const target =
24
+ compose &&
25
+ manifest.images.find(
26
+ (entry) => entry.name === image.targetImage && entry.kind !== "compose",
27
+ );
28
+ requireValue(
29
+ !image.kind || ["image", "compose"].includes(image.kind),
30
+ "unsupported artifact kind",
31
+ );
32
+ requireValue(!compose || target, "Compose target image is absent");
33
+ requireValue(
34
+ image.repository ===
35
+ `ghcr.io/${manifest.repository}/${compose ? target.name : image.name}`,
36
+ "destination must belong to the consumer repository",
37
+ );
38
+ requireValue(
39
+ manifest.images.filter(
40
+ (entry) =>
41
+ entry.repository === image.repository &&
42
+ ociPublicationTag(entry, manifest.version) ===
43
+ ociPublicationTag(image, manifest.version),
44
+ ).length === 1,
45
+ "duplicate publication destination",
46
+ );
47
+ }
48
+
49
+ export function verifyOciGraph({
50
+ image,
51
+ manifest,
52
+ descriptor,
53
+ descriptorFile,
54
+ requireValue: check,
55
+ verifyProvenance,
56
+ }) {
57
+ const manifests = [],
58
+ blobs = new Map(),
59
+ platforms = [],
60
+ attestations = [];
61
+ const visited = new Set();
62
+ const read = (entry) => JSON.parse(fs.readFileSync(descriptorFile(entry)));
63
+ function blob(entry) {
64
+ descriptorFile(entry);
65
+ blobs.set(entry.digest, entry);
66
+ }
67
+ function visit(entry, depth = 0) {
68
+ check(
69
+ depth < 4 && visited.size < 256 && !visited.has(entry.digest),
70
+ "duplicate or excessive OCI graph",
71
+ );
72
+ visited.add(entry.digest);
73
+ const document = read(entry);
74
+ check(
75
+ document.schemaVersion === 2 && document.mediaType === entry.mediaType,
76
+ "OCI descriptor media type mismatch",
77
+ );
78
+ if (indexTypes.includes(document.mediaType)) {
79
+ check(
80
+ image.kind !== "compose" &&
81
+ Array.isArray(document.manifests) &&
82
+ document.manifests.length > 0,
83
+ "invalid OCI index",
84
+ );
85
+ for (const child of document.manifests) visit(child, depth + 1);
86
+ } else {
87
+ check(
88
+ imageTypes.includes(document.mediaType) &&
89
+ Array.isArray(document.layers),
90
+ "unsupported OCI manifest",
91
+ );
92
+ const config = read(document.config);
93
+ blob(document.config);
94
+ for (const layer of document.layers) blob(layer);
95
+ if (image.kind === "compose") {
96
+ verifyCompose(document, image, manifest, read, check);
97
+ } else if (
98
+ entry.annotations?.["vnd.docker.reference.type"] ===
99
+ "attestation-manifest"
100
+ ) {
101
+ check(
102
+ entry.platform?.os === "unknown" &&
103
+ entry.platform?.architecture === "unknown" &&
104
+ document.layers.length > 0 &&
105
+ document.layers.every(
106
+ (layer) => layer.mediaType === "application/vnd.in-toto+json",
107
+ ),
108
+ "invalid image attestation",
109
+ );
110
+ attestations.push({
111
+ target: entry.annotations["vnd.docker.reference.digest"],
112
+ statements: document.layers.map(read),
113
+ });
114
+ } else {
115
+ const platform = `${config.os}/${config.architecture}`;
116
+ check(
117
+ /^linux\/(amd64|arm64)$/u.test(platform) &&
118
+ (!entry.platform ||
119
+ platform ===
120
+ `${entry.platform.os}/${entry.platform.architecture}`),
121
+ "OCI platform mismatch",
122
+ );
123
+ check(
124
+ !platforms.some((found) => found.platform === platform),
125
+ "duplicate image platform",
126
+ );
127
+ platforms.push({ platform, digest: entry.digest });
128
+ verifyProvenance(config);
129
+ }
130
+ }
131
+ manifests.push(entry);
132
+ }
133
+ visit(descriptor);
134
+ if (image.kind !== "compose") {
135
+ const expected =
136
+ image.platform === "multi-platform" ? image.platforms : [image.platform];
137
+ check(
138
+ Array.isArray(expected) &&
139
+ expected.length > 0 &&
140
+ new Set(expected).size === expected.length &&
141
+ JSON.stringify([...expected].sort()) ===
142
+ JSON.stringify(platforms.map((entry) => entry.platform).sort()),
143
+ "incomplete image platform family",
144
+ );
145
+ for (const attestation of attestations) {
146
+ check(
147
+ platforms.some((entry) => entry.digest === attestation.target) &&
148
+ attestation.statements.every((statement) =>
149
+ statement.subject?.some(
150
+ (subject) =>
151
+ `sha256:${subject.digest?.sha256}` === attestation.target,
152
+ ),
153
+ ),
154
+ "image attestation subject mismatch",
155
+ );
156
+ }
157
+ }
158
+ return { manifests, blobs: [...blobs.values()], platforms };
159
+ }
160
+
161
+ function verifyCompose(document, image, manifest, read, check) {
162
+ if (image.preview)
163
+ check(
164
+ image.preview.alias === "compose-preview" &&
165
+ /^\.github\/workflows\/[a-z0-9-]+\.yml$/u.test(
166
+ image.preview.qualificationWorkflow,
167
+ ) &&
168
+ /^(none|sha256:[0-9a-f]{64})$/u.test(image.preview.previousDigest),
169
+ "invalid Compose preview policy",
170
+ );
171
+ check(
172
+ image.platform === "compose" &&
173
+ document.artifactType === composeType &&
174
+ document.config.mediaType === "application/vnd.oci.empty.v1+json" &&
175
+ document.layers.length === 1 &&
176
+ document.layers[0].mediaType ===
177
+ "application/vnd.docker.compose.file+yaml",
178
+ "unsupported Compose artifact",
179
+ );
180
+ // JSON is a YAML subset; this bounded public form needs no executable YAML loader.
181
+ const project = read(document.layers[0]);
182
+ check(
183
+ project.services && Object.keys(project.services).length > 0,
184
+ "empty Compose application",
185
+ );
186
+ const target = manifest.images.find(
187
+ (entry) => entry.name === image.targetImage,
188
+ );
189
+ const references = Object.values(project.services).map(
190
+ (service) => service.image,
191
+ );
192
+ check(
193
+ references.every(
194
+ (ref) =>
195
+ typeof ref === "string" &&
196
+ /^[a-z0-9./:_-]+@sha256:[0-9a-f]{64}$/u.test(ref),
197
+ ) && references.includes(`${target.repository}@${target.digest}`),
198
+ "Compose images must bind exact digests",
199
+ );
200
+ check(
201
+ image.content?.sourceSha === manifest.sourceSha &&
202
+ image.content?.version === manifest.version &&
203
+ image.action === "built",
204
+ "Compose candidate provenance mismatch",
205
+ );
206
+ }