@mannyc1/ts-release 0.0.7 → 0.2.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 (635) hide show
  1. package/ARCHITECTURE.md +124 -92
  2. package/CHANGELOG.md +115 -0
  3. package/LICENSE +21 -0
  4. package/README.md +278 -324
  5. package/SPEC.md +206 -310
  6. package/apps/release-ts/README.md +32 -0
  7. package/apps/release-ts/src/cli/command.ts +164 -0
  8. package/apps/release-ts/src/cli/commands.ts +351 -0
  9. package/apps/release-ts/src/cli/main.ts +43 -0
  10. package/apps/release-ts/src/cli/node-main.ts +49 -0
  11. package/apps/ts-release-agents/LICENSE +21 -0
  12. package/apps/ts-release-agents/README.md +21 -0
  13. package/apps/ts-release-agents/evals/cases.json +11 -0
  14. package/apps/ts-release-agents/manifests/claude.json +5 -0
  15. package/apps/ts-release-agents/manifests/codex.json +5 -0
  16. package/apps/ts-release-agents/package.json +11 -0
  17. package/apps/ts-release-agents/skills/release/SKILL.md +48 -0
  18. package/apps/ts-release-agents/skills/release/references/configuration.md +18 -0
  19. package/apps/ts-release-agents/skills/release/references/recovery.md +10 -0
  20. package/apps/ts-release-agents/skills/release/references/staged-workflow.md +12 -0
  21. package/apps/ts-release-agents/skills/release/references/target-selection.md +13 -0
  22. package/apps/ts-release-agents/skills/release/references/verification.md +14 -0
  23. package/apps/ts-release-agents/src/build.ts +105 -0
  24. package/apps/ts-release-agents/src/check.ts +117 -0
  25. package/apps/ts-release-agents/src/install.ts +121 -0
  26. package/apps/ts-release-agents/test/contract.test.ts +56 -0
  27. package/apps/ts-release-agents/tsconfig.json +5 -0
  28. package/dist/api/api.d.ts +15 -0
  29. package/dist/api/api.d.ts.map +1 -0
  30. package/dist/api/api.js +244 -0
  31. package/dist/api/api.js.map +1 -0
  32. package/dist/api/errors.d.ts +39 -0
  33. package/dist/api/errors.d.ts.map +1 -0
  34. package/dist/api/errors.js +32 -0
  35. package/dist/api/errors.js.map +1 -0
  36. package/dist/api/input.d.ts +10 -0
  37. package/dist/api/input.d.ts.map +1 -0
  38. package/dist/api/input.js +73 -0
  39. package/dist/api/input.js.map +1 -0
  40. package/dist/api/runtime.d.ts +18 -0
  41. package/dist/api/runtime.d.ts.map +1 -0
  42. package/dist/api/runtime.js +7 -0
  43. package/dist/api/runtime.js.map +1 -0
  44. package/dist/api/types.d.ts +76 -0
  45. package/dist/api/types.d.ts.map +1 -0
  46. package/dist/api/types.js +17 -0
  47. package/dist/api/types.js.map +1 -0
  48. package/dist/bin/ts-release.js +39239 -0
  49. package/dist/capabilities/bun-targets.d.ts +3 -0
  50. package/dist/capabilities/bun-targets.d.ts.map +1 -0
  51. package/dist/capabilities/bun-targets.js +3 -0
  52. package/dist/capabilities/bun-targets.js.map +1 -0
  53. package/dist/capabilities/field-ownership.d.ts +17 -0
  54. package/dist/capabilities/field-ownership.d.ts.map +1 -0
  55. package/dist/capabilities/field-ownership.js +40 -0
  56. package/dist/capabilities/field-ownership.js.map +1 -0
  57. package/dist/capabilities/module.d.ts +91 -0
  58. package/dist/capabilities/module.d.ts.map +1 -0
  59. package/dist/capabilities/module.js +2 -0
  60. package/dist/capabilities/module.js.map +1 -0
  61. package/dist/capabilities/registry.d.ts +829 -0
  62. package/dist/capabilities/registry.d.ts.map +1 -0
  63. package/dist/capabilities/registry.js +291 -0
  64. package/dist/capabilities/registry.js.map +1 -0
  65. package/dist/config/config.d.ts +6 -0
  66. package/dist/config/config.d.ts.map +1 -0
  67. package/dist/config/config.js +42 -0
  68. package/dist/config/config.js.map +1 -0
  69. package/dist/correction/coordinator.d.ts +248 -0
  70. package/dist/correction/coordinator.d.ts.map +1 -0
  71. package/dist/correction/coordinator.js +274 -0
  72. package/dist/correction/coordinator.js.map +1 -0
  73. package/dist/correction/index.d.ts +3 -0
  74. package/dist/correction/index.d.ts.map +1 -0
  75. package/dist/correction/index.js +3 -0
  76. package/dist/correction/index.js.map +1 -0
  77. package/dist/correction/intent.d.ts +111 -0
  78. package/dist/correction/intent.d.ts.map +1 -0
  79. package/dist/correction/intent.js +146 -0
  80. package/dist/correction/intent.js.map +1 -0
  81. package/dist/drivers/archive.d.ts +8 -0
  82. package/dist/drivers/archive.d.ts.map +1 -0
  83. package/dist/drivers/archive.js +101 -0
  84. package/dist/drivers/archive.js.map +1 -0
  85. package/dist/drivers/contain.d.ts +6 -0
  86. package/dist/drivers/contain.d.ts.map +1 -0
  87. package/dist/drivers/contain.js +17 -0
  88. package/dist/drivers/contain.js.map +1 -0
  89. package/dist/drivers/environment.d.ts +4 -0
  90. package/dist/drivers/environment.d.ts.map +1 -0
  91. package/dist/drivers/environment.js +18 -0
  92. package/dist/drivers/environment.js.map +1 -0
  93. package/dist/drivers/errors.d.ts +9 -0
  94. package/dist/drivers/errors.d.ts.map +1 -0
  95. package/dist/drivers/errors.js +6 -0
  96. package/dist/drivers/errors.js.map +1 -0
  97. package/dist/drivers/glob.d.ts +2 -0
  98. package/dist/drivers/glob.d.ts.map +1 -0
  99. package/dist/drivers/glob.js +162 -0
  100. package/dist/drivers/glob.js.map +1 -0
  101. package/dist/drivers/process.d.ts +48 -0
  102. package/dist/drivers/process.d.ts.map +1 -0
  103. package/dist/drivers/process.js +257 -0
  104. package/dist/drivers/process.js.map +1 -0
  105. package/dist/drivers/redact.d.ts +4 -0
  106. package/dist/drivers/redact.d.ts.map +1 -0
  107. package/dist/drivers/redact.js +24 -0
  108. package/dist/drivers/redact.js.map +1 -0
  109. package/dist/drivers/seccomp-helper-source.d.ts +7 -0
  110. package/dist/drivers/seccomp-helper-source.d.ts.map +1 -0
  111. package/dist/drivers/seccomp-helper-source.js +121 -0
  112. package/dist/drivers/seccomp-helper-source.js.map +1 -0
  113. package/dist/drivers/utils.d.ts +4 -0
  114. package/dist/drivers/utils.d.ts.map +1 -0
  115. package/dist/drivers/utils.js +5 -0
  116. package/dist/drivers/utils.js.map +1 -0
  117. package/dist/drivers/workspace.d.ts +6 -0
  118. package/dist/drivers/workspace.d.ts.map +1 -0
  119. package/dist/drivers/workspace.js +49 -0
  120. package/dist/drivers/workspace.js.map +1 -0
  121. package/dist/extensions/provider-adapter.d.ts +48 -0
  122. package/dist/extensions/provider-adapter.d.ts.map +1 -0
  123. package/dist/extensions/provider-adapter.js +64 -0
  124. package/dist/extensions/provider-adapter.js.map +1 -0
  125. package/dist/host.d.ts +33 -0
  126. package/dist/host.d.ts.map +1 -0
  127. package/dist/host.js +20 -0
  128. package/dist/host.js.map +1 -0
  129. package/dist/index.d.ts +19 -1
  130. package/dist/index.d.ts.map +1 -1
  131. package/dist/index.js +12 -1
  132. package/dist/index.js.map +1 -1
  133. package/dist/model/artifact-collection.d.ts +84 -0
  134. package/dist/model/artifact-collection.d.ts.map +1 -0
  135. package/dist/model/artifact-collection.js +185 -0
  136. package/dist/model/artifact-collection.js.map +1 -0
  137. package/dist/model/authority.d.ts +89 -0
  138. package/dist/model/authority.d.ts.map +1 -0
  139. package/dist/model/authority.js +80 -0
  140. package/dist/model/authority.js.map +1 -0
  141. package/dist/model/bun-targets.d.ts +34 -0
  142. package/dist/model/bun-targets.d.ts.map +1 -0
  143. package/dist/model/bun-targets.js +17 -0
  144. package/dist/model/bun-targets.js.map +1 -0
  145. package/dist/model/canonical.d.ts +8 -0
  146. package/dist/model/canonical.d.ts.map +1 -0
  147. package/dist/model/canonical.js +205 -0
  148. package/dist/model/canonical.js.map +1 -0
  149. package/dist/model/catalog.d.ts +76 -0
  150. package/dist/model/catalog.d.ts.map +1 -0
  151. package/dist/model/catalog.js +174 -0
  152. package/dist/model/catalog.js.map +1 -0
  153. package/dist/model/digest.d.ts +53 -0
  154. package/dist/model/digest.d.ts.map +1 -0
  155. package/dist/model/digest.js +127 -0
  156. package/dist/model/digest.js.map +1 -0
  157. package/dist/model/errors.d.ts +14 -0
  158. package/dist/model/errors.d.ts.map +1 -0
  159. package/dist/model/errors.js +8 -0
  160. package/dist/model/errors.js.map +1 -0
  161. package/dist/model/primitives.d.ts +19 -0
  162. package/dist/model/primitives.d.ts.map +1 -0
  163. package/dist/model/primitives.js +18 -0
  164. package/dist/model/primitives.js.map +1 -0
  165. package/dist/model/pypi.d.ts +12 -0
  166. package/dist/model/pypi.d.ts.map +1 -0
  167. package/dist/model/pypi.js +20 -0
  168. package/dist/model/pypi.js.map +1 -0
  169. package/dist/model/python-distribution.d.ts +33 -0
  170. package/dist/model/python-distribution.d.ts.map +1 -0
  171. package/dist/model/python-distribution.js +367 -0
  172. package/dist/model/python-distribution.js.map +1 -0
  173. package/dist/model/secret-patterns.d.ts +2 -0
  174. package/dist/model/secret-patterns.d.ts.map +1 -0
  175. package/dist/model/secret-patterns.js +12 -0
  176. package/dist/model/secret-patterns.js.map +1 -0
  177. package/dist/platform/bun.d.ts +7 -0
  178. package/dist/platform/bun.d.ts.map +1 -0
  179. package/dist/platform/bun.js +15 -0
  180. package/dist/platform/bun.js.map +1 -0
  181. package/dist/platform/credentials.d.ts +32 -0
  182. package/dist/platform/credentials.d.ts.map +1 -0
  183. package/dist/platform/credentials.js +772 -0
  184. package/dist/platform/credentials.js.map +1 -0
  185. package/dist/platform/host-support.d.ts +2 -0
  186. package/dist/platform/host-support.d.ts.map +1 -0
  187. package/dist/platform/host-support.js +4 -0
  188. package/dist/platform/host-support.js.map +1 -0
  189. package/dist/platform/node.d.ts +7 -0
  190. package/dist/platform/node.d.ts.map +1 -0
  191. package/dist/platform/node.js +15 -0
  192. package/dist/platform/node.js.map +1 -0
  193. package/dist/platform/release-runtime.d.ts +11 -0
  194. package/dist/platform/release-runtime.d.ts.map +1 -0
  195. package/dist/platform/release-runtime.js +34 -0
  196. package/dist/platform/release-runtime.js.map +1 -0
  197. package/dist/platform/services.d.ts +15 -0
  198. package/dist/platform/services.d.ts.map +1 -0
  199. package/dist/platform/services.js +30 -0
  200. package/dist/platform/services.js.map +1 -0
  201. package/dist/platform/source-observer.d.ts +5 -0
  202. package/dist/platform/source-observer.d.ts.map +1 -0
  203. package/dist/platform/source-observer.js +159 -0
  204. package/dist/platform/source-observer.js.map +1 -0
  205. package/dist/provider-sdk.d.ts +14 -0
  206. package/dist/provider-sdk.d.ts.map +1 -0
  207. package/dist/provider-sdk.js +10 -0
  208. package/dist/provider-sdk.js.map +1 -0
  209. package/dist/publication/adapter.d.ts +212 -0
  210. package/dist/publication/adapter.d.ts.map +1 -0
  211. package/dist/publication/adapter.js +83 -0
  212. package/dist/publication/adapter.js.map +1 -0
  213. package/dist/publication/authority.d.ts +137 -0
  214. package/dist/publication/authority.d.ts.map +1 -0
  215. package/dist/publication/authority.js +233 -0
  216. package/dist/publication/authority.js.map +1 -0
  217. package/dist/publication/catalog-git.d.ts +37 -0
  218. package/dist/publication/catalog-git.d.ts.map +1 -0
  219. package/dist/publication/catalog-git.js +451 -0
  220. package/dist/publication/catalog-git.js.map +1 -0
  221. package/dist/publication/claim.d.ts +39 -0
  222. package/dist/publication/claim.d.ts.map +1 -0
  223. package/dist/publication/claim.js +31 -0
  224. package/dist/publication/claim.js.map +1 -0
  225. package/dist/publication/coordinator.d.ts +246 -0
  226. package/dist/publication/coordinator.d.ts.map +1 -0
  227. package/dist/publication/coordinator.js +370 -0
  228. package/dist/publication/coordinator.js.map +1 -0
  229. package/dist/publication/github.d.ts +36 -0
  230. package/dist/publication/github.d.ts.map +1 -0
  231. package/dist/publication/github.js +644 -0
  232. package/dist/publication/github.js.map +1 -0
  233. package/dist/publication/http.d.ts +69 -0
  234. package/dist/publication/http.d.ts.map +1 -0
  235. package/dist/publication/http.js +29 -0
  236. package/dist/publication/http.js.map +1 -0
  237. package/dist/publication/index.d.ts +5 -0
  238. package/dist/publication/index.d.ts.map +1 -0
  239. package/dist/publication/index.js +5 -0
  240. package/dist/publication/index.js.map +1 -0
  241. package/dist/publication/npm.d.ts +40 -0
  242. package/dist/publication/npm.d.ts.map +1 -0
  243. package/dist/publication/npm.js +359 -0
  244. package/dist/publication/npm.js.map +1 -0
  245. package/dist/publication/profiles.d.ts +136 -0
  246. package/dist/publication/profiles.d.ts.map +1 -0
  247. package/dist/publication/profiles.js +90 -0
  248. package/dist/publication/profiles.js.map +1 -0
  249. package/dist/publication/publisher.d.ts +82 -0
  250. package/dist/publication/publisher.d.ts.map +1 -0
  251. package/dist/publication/publisher.js +53 -0
  252. package/dist/publication/publisher.js.map +1 -0
  253. package/dist/publication/pypi.d.ts +45 -0
  254. package/dist/publication/pypi.d.ts.map +1 -0
  255. package/dist/publication/pypi.js +369 -0
  256. package/dist/publication/pypi.js.map +1 -0
  257. package/dist/publication/recovery.d.ts +663 -0
  258. package/dist/publication/recovery.d.ts.map +1 -0
  259. package/dist/publication/recovery.js +252 -0
  260. package/dist/publication/recovery.js.map +1 -0
  261. package/dist/publication/report.d.ts +1061 -0
  262. package/dist/publication/report.d.ts.map +1 -0
  263. package/dist/publication/report.js +441 -0
  264. package/dist/publication/report.js.map +1 -0
  265. package/dist/recipes/config.d.ts +296 -0
  266. package/dist/recipes/config.d.ts.map +1 -0
  267. package/dist/recipes/config.js +325 -0
  268. package/dist/recipes/config.js.map +1 -0
  269. package/dist/release/capabilities.d.ts +12 -0
  270. package/dist/release/capabilities.d.ts.map +1 -0
  271. package/dist/release/capabilities.js +395 -0
  272. package/dist/release/capabilities.js.map +1 -0
  273. package/dist/release/compiler.d.ts +5 -0
  274. package/dist/release/compiler.d.ts.map +1 -0
  275. package/dist/release/compiler.js +19 -0
  276. package/dist/release/compiler.js.map +1 -0
  277. package/dist/release/config.d.ts +5 -0
  278. package/dist/release/config.d.ts.map +1 -0
  279. package/dist/release/config.js +10 -0
  280. package/dist/release/config.js.map +1 -0
  281. package/dist/release/context.d.ts +96 -0
  282. package/dist/release/context.d.ts.map +1 -0
  283. package/dist/release/context.js +168 -0
  284. package/dist/release/context.js.map +1 -0
  285. package/dist/release/graph.d.ts +271 -0
  286. package/dist/release/graph.d.ts.map +1 -0
  287. package/dist/release/graph.js +766 -0
  288. package/dist/release/graph.js.map +1 -0
  289. package/dist/release/inspect.d.ts +88 -0
  290. package/dist/release/inspect.d.ts.map +1 -0
  291. package/dist/release/inspect.js +75 -0
  292. package/dist/release/inspect.js.map +1 -0
  293. package/dist/release/prepare.d.ts +24 -0
  294. package/dist/release/prepare.d.ts.map +1 -0
  295. package/dist/release/prepare.js +1116 -0
  296. package/dist/release/prepare.js.map +1 -0
  297. package/dist/release/prepared-ref.d.ts +60 -0
  298. package/dist/release/prepared-ref.d.ts.map +1 -0
  299. package/dist/release/prepared-ref.js +131 -0
  300. package/dist/release/prepared-ref.js.map +1 -0
  301. package/dist/release/prepared-store.d.ts +90 -0
  302. package/dist/release/prepared-store.d.ts.map +1 -0
  303. package/dist/release/prepared-store.js +337 -0
  304. package/dist/release/prepared-store.js.map +1 -0
  305. package/dist/release/prepared.d.ts +172 -0
  306. package/dist/release/prepared.d.ts.map +1 -0
  307. package/dist/release/prepared.js +268 -0
  308. package/dist/release/prepared.js.map +1 -0
  309. package/dist/release/staging.d.ts +31 -0
  310. package/dist/release/staging.d.ts.map +1 -0
  311. package/dist/release/staging.js +218 -0
  312. package/dist/release/staging.js.map +1 -0
  313. package/dist/resolve/authored.d.ts +53 -0
  314. package/dist/resolve/authored.d.ts.map +1 -0
  315. package/dist/resolve/authored.js +55 -0
  316. package/dist/resolve/authored.js.map +1 -0
  317. package/dist/resolve/encode.d.ts +3 -0
  318. package/dist/resolve/encode.d.ts.map +1 -0
  319. package/dist/resolve/encode.js +21 -0
  320. package/dist/resolve/encode.js.map +1 -0
  321. package/dist/resolve/errors.d.ts +7 -0
  322. package/dist/resolve/errors.d.ts.map +1 -0
  323. package/dist/resolve/errors.js +17 -0
  324. package/dist/resolve/errors.js.map +1 -0
  325. package/dist/resolve/facts.d.ts +12 -0
  326. package/dist/resolve/facts.d.ts.map +1 -0
  327. package/dist/resolve/facts.js +18 -0
  328. package/dist/resolve/facts.js.map +1 -0
  329. package/dist/resolve/resolve.d.ts +10 -0
  330. package/dist/resolve/resolve.d.ts.map +1 -0
  331. package/dist/resolve/resolve.js +242 -0
  332. package/dist/resolve/resolve.js.map +1 -0
  333. package/dist/store.d.ts +11 -0
  334. package/dist/store.d.ts.map +1 -0
  335. package/dist/store.js +9 -0
  336. package/dist/store.js.map +1 -0
  337. package/examples/README.md +20 -30
  338. package/examples/github-release/release.config.json +11 -18
  339. package/examples/homebrew-tap/README.md +11 -0
  340. package/examples/homebrew-tap/release.config.json +40 -28
  341. package/examples/multi-target/release.config.json +28 -53
  342. package/examples/npm-first-publish/release.config.json +23 -25
  343. package/examples/npm-only/release.config.json +22 -30
  344. package/examples/portable-cli/README.md +17 -0
  345. package/examples/portable-cli/artifacts/portable-example-0.1.0-darwin-arm64 +1 -0
  346. package/examples/portable-cli/artifacts/portable-example-0.1.0-darwin-x64 +1 -0
  347. package/examples/portable-cli/artifacts/portable-example-0.1.0-linux-arm64 +1 -0
  348. package/examples/portable-cli/artifacts/portable-example-0.1.0-linux-x64 +1 -0
  349. package/examples/portable-cli/artifacts/portable-example-0.1.0-windows-x64.exe +1 -0
  350. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-macosx_10_15_x86_64.whl +1 -0
  351. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-macosx_11_0_arm64.whl +1 -0
  352. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-manylinux2014_aarch64.whl +1 -0
  353. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-manylinux2014_x86_64.whl +1 -0
  354. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-win_amd64.whl +1 -0
  355. package/examples/portable-cli/package.json +9 -0
  356. package/examples/portable-cli/release.config.json +53 -0
  357. package/examples/portable-cli/src/cli.ts +3 -0
  358. package/examples/pypi-registry/README.md +8 -0
  359. package/examples/scoop-bucket/README.md +10 -0
  360. package/examples/scoop-bucket/release.config.json +40 -30
  361. package/package.json +60 -156
  362. package/src/api/api.ts +365 -0
  363. package/src/api/errors.ts +36 -0
  364. package/src/api/input.ts +92 -0
  365. package/src/api/runtime.ts +14 -0
  366. package/src/api/types.ts +103 -0
  367. package/src/capabilities/bun-targets.ts +2 -0
  368. package/src/capabilities/field-ownership.ts +60 -0
  369. package/src/capabilities/module.ts +158 -0
  370. package/src/capabilities/registry.ts +333 -0
  371. package/src/config/config.ts +48 -0
  372. package/src/correction/coordinator.ts +336 -0
  373. package/src/correction/index.ts +2 -0
  374. package/src/correction/intent.ts +172 -0
  375. package/src/drivers/archive.ts +102 -0
  376. package/src/drivers/contain.ts +20 -0
  377. package/src/drivers/environment.ts +28 -0
  378. package/src/drivers/errors.ts +6 -0
  379. package/src/drivers/glob.ts +137 -0
  380. package/src/drivers/process.ts +340 -0
  381. package/src/drivers/redact.ts +31 -0
  382. package/src/drivers/seccomp-helper-source.ts +121 -0
  383. package/src/drivers/utils.ts +10 -0
  384. package/src/drivers/workspace.ts +40 -0
  385. package/src/extensions/provider-adapter.ts +99 -0
  386. package/src/host.ts +140 -0
  387. package/src/index.ts +64 -0
  388. package/src/model/artifact-collection.ts +237 -0
  389. package/src/model/authority.ts +116 -0
  390. package/src/model/bun-targets.ts +34 -0
  391. package/src/model/canonical.ts +205 -0
  392. package/src/model/catalog.ts +220 -0
  393. package/src/model/digest.ts +172 -0
  394. package/src/model/errors.ts +6 -0
  395. package/src/model/primitives.ts +37 -0
  396. package/src/model/pypi.ts +30 -0
  397. package/src/model/python-distribution.ts +360 -0
  398. package/src/model/secret-patterns.ts +11 -0
  399. package/src/platform/bun.ts +38 -0
  400. package/src/platform/credentials.ts +1083 -0
  401. package/src/platform/host-support.ts +4 -0
  402. package/src/platform/node.ts +38 -0
  403. package/src/platform/release-runtime.ts +47 -0
  404. package/src/platform/services.ts +57 -0
  405. package/src/platform/source-observer.ts +193 -0
  406. package/src/provider-sdk.ts +48 -0
  407. package/src/publication/adapter.ts +132 -0
  408. package/src/publication/authority.ts +397 -0
  409. package/src/publication/catalog-git.ts +603 -0
  410. package/src/publication/claim.ts +51 -0
  411. package/src/publication/coordinator.ts +578 -0
  412. package/src/publication/github.ts +954 -0
  413. package/src/publication/http.ts +101 -0
  414. package/src/publication/index.ts +4 -0
  415. package/src/publication/npm.ts +525 -0
  416. package/src/publication/profiles.ts +94 -0
  417. package/src/publication/publisher.ts +130 -0
  418. package/src/publication/pypi.ts +463 -0
  419. package/src/publication/recovery.ts +349 -0
  420. package/src/publication/report.ts +628 -0
  421. package/src/recipes/config.ts +395 -0
  422. package/src/release/capabilities.ts +459 -0
  423. package/src/release/compiler.ts +28 -0
  424. package/src/release/config.ts +12 -0
  425. package/src/release/context.ts +223 -0
  426. package/src/release/graph.ts +866 -0
  427. package/src/release/inspect.ts +83 -0
  428. package/src/release/prepare.ts +1331 -0
  429. package/src/release/prepared-ref.ts +193 -0
  430. package/src/release/prepared-store.ts +377 -0
  431. package/src/release/prepared.ts +299 -0
  432. package/src/release/staging.ts +257 -0
  433. package/src/resolve/authored.ts +64 -0
  434. package/src/resolve/encode.ts +22 -0
  435. package/src/resolve/errors.ts +15 -0
  436. package/src/resolve/facts.ts +18 -0
  437. package/src/resolve/resolve.ts +328 -0
  438. package/src/store.ts +28 -0
  439. package/templates/README.md +55 -72
  440. package/templates/bun-cli-github/README.md +7 -9
  441. package/templates/bun-cli-github/release.config.json +35 -73
  442. package/templates/github-actions/release.yml +57 -0
  443. package/templates/github-actions/reviewed-release.yml +80 -0
  444. package/templates/multi-target-homebrew/README.md +10 -0
  445. package/templates/multi-target-homebrew/release.config.json +43 -60
  446. package/templates/multi-target-scoop/README.md +10 -0
  447. package/templates/multi-target-scoop/release.config.json +43 -62
  448. package/templates/npm-github/release.config.json +25 -38
  449. package/templates/npm-github/reviewed-release.config.json +37 -0
  450. package/templates/npm-only/README.md +10 -0
  451. package/templates/npm-only/release.config.json +15 -30
  452. package/templates/portable-cli/README.md +13 -0
  453. package/templates/portable-cli/release.config.json +53 -0
  454. package/dist/artifacts/adapter.d.ts +0 -39
  455. package/dist/artifacts/adapter.d.ts.map +0 -1
  456. package/dist/artifacts/adapter.js +0 -23
  457. package/dist/artifacts/adapter.js.map +0 -1
  458. package/dist/artifacts/registry.d.ts +0 -21
  459. package/dist/artifacts/registry.d.ts.map +0 -1
  460. package/dist/artifacts/registry.js +0 -21
  461. package/dist/artifacts/registry.js.map +0 -1
  462. package/dist/config/errors.d.ts +0 -24
  463. package/dist/config/errors.d.ts.map +0 -1
  464. package/dist/config/errors.js +0 -19
  465. package/dist/config/errors.js.map +0 -1
  466. package/dist/config/load.d.ts +0 -9
  467. package/dist/config/load.d.ts.map +0 -1
  468. package/dist/config/load.js +0 -29
  469. package/dist/config/load.js.map +0 -1
  470. package/dist/config/schema.d.ts +0 -12
  471. package/dist/config/schema.d.ts.map +0 -1
  472. package/dist/config/schema.js +0 -19
  473. package/dist/config/schema.js.map +0 -1
  474. package/dist/domain/artifact.d.ts +0 -118
  475. package/dist/domain/artifact.d.ts.map +0 -1
  476. package/dist/domain/artifact.js +0 -197
  477. package/dist/domain/artifact.js.map +0 -1
  478. package/dist/domain/evidence.d.ts +0 -59
  479. package/dist/domain/evidence.d.ts.map +0 -1
  480. package/dist/domain/evidence.js +0 -51
  481. package/dist/domain/evidence.js.map +0 -1
  482. package/dist/domain/operation.d.ts +0 -143
  483. package/dist/domain/operation.d.ts.map +0 -1
  484. package/dist/domain/operation.js +0 -198
  485. package/dist/domain/operation.js.map +0 -1
  486. package/dist/domain/release.d.ts +0 -93
  487. package/dist/domain/release.d.ts.map +0 -1
  488. package/dist/domain/release.js +0 -82
  489. package/dist/domain/release.js.map +0 -1
  490. package/dist/domain/remote-state.d.ts +0 -58
  491. package/dist/domain/remote-state.d.ts.map +0 -1
  492. package/dist/domain/remote-state.js +0 -55
  493. package/dist/domain/remote-state.js.map +0 -1
  494. package/dist/domain/target.d.ts +0 -148
  495. package/dist/domain/target.d.ts.map +0 -1
  496. package/dist/domain/target.js +0 -151
  497. package/dist/domain/target.js.map +0 -1
  498. package/dist/host/host.d.ts +0 -32
  499. package/dist/host/host.d.ts.map +0 -1
  500. package/dist/host/host.js +0 -24
  501. package/dist/host/host.js.map +0 -1
  502. package/dist/host/http-live.d.ts +0 -6
  503. package/dist/host/http-live.d.ts.map +0 -1
  504. package/dist/host/http-live.js +0 -86
  505. package/dist/host/http-live.js.map +0 -1
  506. package/dist/host/http.d.ts +0 -43
  507. package/dist/host/http.d.ts.map +0 -1
  508. package/dist/host/http.js +0 -59
  509. package/dist/host/http.js.map +0 -1
  510. package/dist/host/platform.d.ts +0 -10
  511. package/dist/host/platform.d.ts.map +0 -1
  512. package/dist/host/platform.js +0 -114
  513. package/dist/host/platform.js.map +0 -1
  514. package/dist/host/test.d.ts +0 -23
  515. package/dist/host/test.d.ts.map +0 -1
  516. package/dist/host/test.js +0 -222
  517. package/dist/host/test.js.map +0 -1
  518. package/dist/internal/workspace-path.d.ts +0 -15
  519. package/dist/internal/workspace-path.d.ts.map +0 -1
  520. package/dist/internal/workspace-path.js +0 -36
  521. package/dist/internal/workspace-path.js.map +0 -1
  522. package/dist/planner/artifact-inventory.d.ts +0 -9
  523. package/dist/planner/artifact-inventory.d.ts.map +0 -1
  524. package/dist/planner/artifact-inventory.js +0 -86
  525. package/dist/planner/artifact-inventory.js.map +0 -1
  526. package/dist/planner/create-release-plan.d.ts +0 -5
  527. package/dist/planner/create-release-plan.d.ts.map +0 -1
  528. package/dist/planner/create-release-plan.js +0 -25
  529. package/dist/planner/create-release-plan.js.map +0 -1
  530. package/dist/planner/errors.d.ts +0 -67
  531. package/dist/planner/errors.d.ts.map +0 -1
  532. package/dist/planner/errors.js +0 -58
  533. package/dist/planner/errors.js.map +0 -1
  534. package/dist/planner/evidence-recorder.d.ts +0 -28
  535. package/dist/planner/evidence-recorder.d.ts.map +0 -1
  536. package/dist/planner/evidence-recorder.js +0 -362
  537. package/dist/planner/evidence-recorder.js.map +0 -1
  538. package/dist/planner/executor.d.ts +0 -58
  539. package/dist/planner/executor.d.ts.map +0 -1
  540. package/dist/planner/executor.js +0 -147
  541. package/dist/planner/executor.js.map +0 -1
  542. package/dist/planner/normalize-release.d.ts +0 -19
  543. package/dist/planner/normalize-release.d.ts.map +0 -1
  544. package/dist/planner/normalize-release.js +0 -342
  545. package/dist/planner/normalize-release.js.map +0 -1
  546. package/dist/planner/reconcile.d.ts +0 -17
  547. package/dist/planner/reconcile.d.ts.map +0 -1
  548. package/dist/planner/reconcile.js +0 -259
  549. package/dist/planner/reconcile.js.map +0 -1
  550. package/dist/planner/render-plan.d.ts +0 -15
  551. package/dist/planner/render-plan.d.ts.map +0 -1
  552. package/dist/planner/render-plan.js +0 -231
  553. package/dist/planner/render-plan.js.map +0 -1
  554. package/dist/targets/adapter-helpers.d.ts +0 -79
  555. package/dist/targets/adapter-helpers.d.ts.map +0 -1
  556. package/dist/targets/adapter-helpers.js +0 -194
  557. package/dist/targets/adapter-helpers.js.map +0 -1
  558. package/dist/targets/adapter.d.ts +0 -17
  559. package/dist/targets/adapter.d.ts.map +0 -1
  560. package/dist/targets/adapter.js +0 -2
  561. package/dist/targets/adapter.js.map +0 -1
  562. package/dist/targets/github-release.d.ts +0 -25
  563. package/dist/targets/github-release.d.ts.map +0 -1
  564. package/dist/targets/github-release.js +0 -82
  565. package/dist/targets/github-release.js.map +0 -1
  566. package/dist/targets/github.d.ts +0 -11
  567. package/dist/targets/github.d.ts.map +0 -1
  568. package/dist/targets/github.js +0 -117
  569. package/dist/targets/github.js.map +0 -1
  570. package/dist/targets/homebrew.d.ts +0 -11
  571. package/dist/targets/homebrew.d.ts.map +0 -1
  572. package/dist/targets/homebrew.js +0 -210
  573. package/dist/targets/homebrew.js.map +0 -1
  574. package/dist/targets/live.d.ts +0 -5
  575. package/dist/targets/live.d.ts.map +0 -1
  576. package/dist/targets/live.js +0 -50
  577. package/dist/targets/live.js.map +0 -1
  578. package/dist/targets/npm.d.ts +0 -10
  579. package/dist/targets/npm.d.ts.map +0 -1
  580. package/dist/targets/npm.js +0 -112
  581. package/dist/targets/npm.js.map +0 -1
  582. package/dist/targets/pypi.d.ts +0 -11
  583. package/dist/targets/pypi.d.ts.map +0 -1
  584. package/dist/targets/pypi.js +0 -141
  585. package/dist/targets/pypi.js.map +0 -1
  586. package/dist/targets/registry.d.ts +0 -25
  587. package/dist/targets/registry.d.ts.map +0 -1
  588. package/dist/targets/registry.js +0 -34
  589. package/dist/targets/registry.js.map +0 -1
  590. package/dist/targets/scoop.d.ts +0 -11
  591. package/dist/targets/scoop.d.ts.map +0 -1
  592. package/dist/targets/scoop.js +0 -88
  593. package/dist/targets/scoop.js.map +0 -1
  594. package/dist/types/effect-internal.d.ts +0 -5
  595. package/dist/types/effect-internal.d.ts.map +0 -1
  596. package/dist/types/effect-internal.js +0 -2
  597. package/dist/types/effect-internal.js.map +0 -1
  598. package/dist/workflows/config.d.ts +0 -202
  599. package/dist/workflows/config.d.ts.map +0 -1
  600. package/dist/workflows/config.js +0 -420
  601. package/dist/workflows/config.js.map +0 -1
  602. package/dist/workflows/diagnostics.d.ts +0 -62
  603. package/dist/workflows/diagnostics.d.ts.map +0 -1
  604. package/dist/workflows/diagnostics.js +0 -530
  605. package/dist/workflows/diagnostics.js.map +0 -1
  606. package/dist/workflows/distribution.d.ts +0 -25
  607. package/dist/workflows/distribution.d.ts.map +0 -1
  608. package/dist/workflows/distribution.js +0 -24
  609. package/dist/workflows/distribution.js.map +0 -1
  610. package/dist/workflows/evidence.d.ts +0 -17
  611. package/dist/workflows/evidence.d.ts.map +0 -1
  612. package/dist/workflows/evidence.js +0 -32
  613. package/dist/workflows/evidence.js.map +0 -1
  614. package/dist/workflows/index.d.ts +0 -8
  615. package/dist/workflows/index.d.ts.map +0 -1
  616. package/dist/workflows/index.js +0 -7
  617. package/dist/workflows/index.js.map +0 -1
  618. package/dist/workflows/init.d.ts +0 -84
  619. package/dist/workflows/init.d.ts.map +0 -1
  620. package/dist/workflows/init.js +0 -487
  621. package/dist/workflows/init.js.map +0 -1
  622. package/dist/workflows/live.d.ts +0 -9
  623. package/dist/workflows/live.d.ts.map +0 -1
  624. package/dist/workflows/live.js +0 -7
  625. package/dist/workflows/live.js.map +0 -1
  626. package/dist/workflows/options.d.ts +0 -30
  627. package/dist/workflows/options.d.ts.map +0 -1
  628. package/dist/workflows/options.js +0 -16
  629. package/dist/workflows/options.js.map +0 -1
  630. package/examples/non-strict-skips/artifacts/release-example-skips-0.1.0.tgz +0 -1
  631. package/examples/non-strict-skips/release.config.json +0 -31
  632. package/examples/pypi-registry/release.config.json +0 -31
  633. package/templates/github-actions/plan-and-approved-execute.yml +0 -58
  634. package/templates/github-actions/plan-only.yml +0 -33
  635. package/templates/github-actions/trusted-publishing.yml +0 -61
@@ -0,0 +1,603 @@
1
+ import { createHash } from "node:crypto"
2
+ import * as Effect from "effect/Effect"
3
+ import type { CredentialRequest } from "../model/authority.js"
4
+ import { CredentialRequest as CredentialRequestSchema, SubjectId } from "../model/authority.js"
5
+ import {
6
+ CatalogManagedState,
7
+ compareCatalogVersions,
8
+ decodeCatalogManagedState
9
+ } from "../model/catalog.js"
10
+ import { digestEquals, sha256Digest } from "../model/digest.js"
11
+ import { NonEmptyName } from "../model/primitives.js"
12
+ import type { PreparedCatalogPublication } from "../release/prepared.js"
13
+ import type { PreparedBundle } from "../release/prepared-store.js"
14
+ import type { CredentialGrant, MutationCredentialGrant, PublisherOperation, ScopedSecret } from "./authority.js"
15
+ import { ReleaseSubjectError, type ReleaseObservationContext, type ReleaseSubject } from "./coordinator.js"
16
+ import type { AuthorizedMutationHttpShape, HttpAuthorizerShape, HttpResponse, MutationHttpRequest } from "./http.js"
17
+ import {
18
+ AbsenceBasis,
19
+ Applied,
20
+ AuthoritativelyAbsent,
21
+ Conflict,
22
+ Difference,
23
+ InconclusiveObservation,
24
+ MutationPrecondition,
25
+ NeedsMutation,
26
+ OutcomeUnknown,
27
+ PresentDifferent,
28
+ PresentEquivalent,
29
+ ProviderAlreadyEquivalent,
30
+ ProviderBlocked,
31
+ ProviderMutationFact,
32
+ ProviderRejectionFact,
33
+ RejectedByProvider,
34
+ SafeReason,
35
+ Started,
36
+ VisibilityBasis,
37
+ VisibilityPending,
38
+ type MutationAttempt,
39
+ type MutationDecision,
40
+ type Observation,
41
+ type ProviderDecision
42
+ } from "./report.js"
43
+ import { makeRecoveryCapabilityProfile } from "./recovery.js"
44
+
45
+ const apiVersion = "2022-11-28"
46
+ const jsonHeaders = {
47
+ accept: "application/vnd.github+json",
48
+ "x-github-api-version": apiVersion
49
+ } as const
50
+ const objectSha = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u
51
+
52
+ export const catalogRecoveryCapabilityProfile = makeRecoveryCapabilityProfile({
53
+ observation: "exact",
54
+ authoritativeAbsence: "provider-specific",
55
+ createAuthorization: "none",
56
+ replay: "conditional",
57
+ identifierReuse: "reusable",
58
+ correction: ["forward-catalog-state"],
59
+ exposure: "persistent-to-consumers",
60
+ historyRequirement: "optional-evidence",
61
+ readConvergence: {
62
+ contract: {
63
+ _tag: "assumed",
64
+ basis: "ASSUMED/UNVERIFIED: no authorized live mutation evidence establishes GitHub Git-data read-convergence timing."
65
+ },
66
+ observationRetry: {
67
+ maxAttempts: 5,
68
+ backoff: { baseMs: 1_000, factor: 2, capMs: 15_000 },
69
+ totalBudgetMs: 60_000
70
+ },
71
+ retryEligible: "VisibilityPending | Inconclusive",
72
+ exhaustion: "UncertainSubject with full trace"
73
+ }
74
+ })
75
+
76
+ type JsonObject = Readonly<Record<string, unknown>>
77
+ const object = (value: unknown): JsonObject | undefined =>
78
+ typeof value === "object" && value !== null && !Array.isArray(value) ? value as JsonObject : undefined
79
+ const string = (value: unknown): string | undefined => typeof value === "string" ? value : undefined
80
+ const parseJson = (body: string | Uint8Array): unknown | undefined => {
81
+ try {
82
+ return JSON.parse(typeof body === "string" ? body : new TextDecoder("utf-8", { fatal: true }).decode(body)) as unknown
83
+ } catch {
84
+ return undefined
85
+ }
86
+ }
87
+ const bytesEqual = (left: Uint8Array | undefined, right: Uint8Array): boolean =>
88
+ left !== undefined && left.length === right.length && left.every((byte, index) => byte === right[index])
89
+
90
+ interface TreeEntry {
91
+ readonly path: string
92
+ readonly mode: string
93
+ readonly type: "blob" | "tree" | "commit"
94
+ readonly sha: string
95
+ }
96
+
97
+ interface RepositorySnapshot {
98
+ readonly commit: string
99
+ readonly rootTree: string
100
+ readonly tree: ReadonlyMap<string, TreeEntry>
101
+ readonly target?: Uint8Array
102
+ readonly state?: Uint8Array
103
+ }
104
+
105
+ interface MutationPlan {
106
+ readonly snapshot: RepositorySnapshot
107
+ }
108
+
109
+ export interface CatalogSubjectPair {
110
+ readonly id?: SubjectId
111
+ readonly purpose?: "publish" | "correct"
112
+ readonly target: Uint8Array
113
+ readonly state: Uint8Array
114
+ /** Exact old pair that authorizes one forward correction. */
115
+ readonly baselineTarget?: Uint8Array
116
+ readonly baselineState?: Uint8Array
117
+ }
118
+
119
+ const observationRequests = (
120
+ publication: PreparedCatalogPublication,
121
+ subject: SubjectId
122
+ ): readonly [CredentialRequest, ...Array<CredentialRequest>] => publication.authority.observationStrategies.map((strategy) =>
123
+ CredentialRequestSchema.make({
124
+ subject,
125
+ provider: publication.authority.provider,
126
+ audience: publication.authority.audience,
127
+ purpose: "observe",
128
+ strategy
129
+ })) as [CredentialRequest, ...Array<CredentialRequest>]
130
+
131
+ const mutationRequest = (
132
+ publication: PreparedCatalogPublication,
133
+ subject: SubjectId,
134
+ purpose: "publish" | "correct"
135
+ ): CredentialRequest => CredentialRequestSchema.make({
136
+ subject,
137
+ provider: publication.authority.provider,
138
+ audience: publication.authority.audience,
139
+ purpose,
140
+ strategy: publication.authority.publishStrategy
141
+ })
142
+
143
+ const failure = (
144
+ subject: SubjectId,
145
+ phase: "observe" | "mutate",
146
+ commitment: "before-dispatch" | "unknown",
147
+ reason: string
148
+ ): ReleaseSubjectError => new ReleaseSubjectError({
149
+ subject,
150
+ phase,
151
+ commitment,
152
+ reason: SafeReason.make(reason)
153
+ })
154
+
155
+ const inconclusive = (subject: SubjectId, reason: string): InconclusiveObservation =>
156
+ InconclusiveObservation.make({ subject, reason: SafeReason.make(reason) })
157
+ const difference = (field: string, expected: string, observed: string): Difference => Difference.make({
158
+ field: NonEmptyName.make(field),
159
+ expected: SafeReason.make(expected),
160
+ observed: SafeReason.make(observed)
161
+ })
162
+ const fingerprint = (bytes: Uint8Array): string => `sha256-${sha256Digest(bytes).hex}`
163
+
164
+ const get = (
165
+ subject: SubjectId,
166
+ http: HttpAuthorizerShape,
167
+ grant: Exclude<CredentialGrant, { readonly _tag: "WorkloadIdentity" }>,
168
+ url: string
169
+ ): Effect.Effect<HttpResponse, ReleaseSubjectError> => http.execute({
170
+ subject,
171
+ method: "GET",
172
+ url,
173
+ headers: jsonHeaders
174
+ }, grant).pipe(Effect.mapError((cause) => failure(
175
+ subject,
176
+ "observe",
177
+ cause._tag === "CredentialPlatformError" ? cause.commitment : "before-dispatch",
178
+ "GitHub catalog observation failed at the authorized HTTP boundary."
179
+ )))
180
+
181
+ const treeEntries = (value: unknown, expectedSha: string): ReadonlyArray<TreeEntry> | undefined => {
182
+ const root = object(value)
183
+ if (root?.sha !== expectedSha || root.truncated !== false || !Array.isArray(root.tree)) return undefined
184
+ const result: Array<TreeEntry> = []
185
+ const paths = new Set<string>()
186
+ for (const raw of root.tree) {
187
+ const entry = object(raw)
188
+ const path = string(entry?.path)
189
+ const mode = string(entry?.mode)
190
+ const type = entry?.type
191
+ const sha = string(entry?.sha)
192
+ if (path === undefined || path.length === 0 || path.startsWith("/") || path.includes("\\") ||
193
+ path.split("/").some((part) => part === "" || part === "..") || paths.has(path) ||
194
+ mode === undefined || !/^[0-7]{6}$/u.test(mode) ||
195
+ (type !== "blob" && type !== "tree" && type !== "commit") || sha === undefined || !objectSha.test(sha)) {
196
+ return undefined
197
+ }
198
+ paths.add(path)
199
+ result.push({ path, mode, type, sha })
200
+ }
201
+ return result
202
+ }
203
+
204
+ const gitObjectDigest = (bytes: Uint8Array, length: number): string => {
205
+ const header = new TextEncoder().encode(`blob ${bytes.length}\0`)
206
+ const content = new Uint8Array(header.length + bytes.length)
207
+ content.set(header)
208
+ content.set(bytes, header.length)
209
+ return createHash(length === 40 ? "sha1" : "sha256").update(content).digest("hex")
210
+ }
211
+
212
+ const blobBytes = (value: unknown, expectedSha: string): Uint8Array | undefined => {
213
+ const blob = object(value)
214
+ const content = string(blob?.content)?.replaceAll(/\s/gu, "")
215
+ if (blob?.sha !== expectedSha || blob.encoding !== "base64" || content === undefined ||
216
+ !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(content)) return undefined
217
+ const bytes = new Uint8Array(Buffer.from(content, "base64"))
218
+ return gitObjectDigest(bytes, expectedSha.length) === expectedSha ? bytes : undefined
219
+ }
220
+
221
+ const readSnapshot = Effect.fn("CatalogGitSubject.readSnapshot")(function*(
222
+ publication: PreparedCatalogPublication,
223
+ subject: SubjectId,
224
+ http: HttpAuthorizerShape,
225
+ grant: Exclude<CredentialGrant, { readonly _tag: "WorkloadIdentity" }>
226
+ ) {
227
+ const base = publication.authority.audience.toString()
228
+ const repository = yield* get(subject, http, grant, base)
229
+ if (repository.status === 404) return { _tag: "Inconclusive", reason: "Catalog repository is missing or hidden." } as const
230
+ if (repository.status !== 200 || object(parseJson(repository.body))?.full_name !== publication.repository) {
231
+ return { _tag: "Inconclusive", reason: "Catalog repository visibility or identity was not proved." } as const
232
+ }
233
+ const fullRef = `refs/heads/${publication.branch}`
234
+ const ref = yield* get(subject, http, grant, `${base}/git/ref/heads/${encodeURIComponent(publication.branch)}`)
235
+ const refObject = object(parseJson(ref.body))
236
+ const commit = string(object(refObject?.object)?.sha)
237
+ if (ref.status !== 200 || refObject?.ref !== fullRef || commit === undefined || !objectSha.test(commit)) {
238
+ return { _tag: "Inconclusive", reason: "Catalog branch ref is missing, hidden, or malformed." } as const
239
+ }
240
+ const commitResponse = yield* get(subject, http, grant, `${base}/git/commits/${commit}`)
241
+ const commitObject = object(parseJson(commitResponse.body))
242
+ const rootTree = string(object(commitObject?.tree)?.sha)
243
+ if (commitResponse.status !== 200 || commitObject?.sha !== commit || rootTree === undefined || !objectSha.test(rootTree)) {
244
+ return { _tag: "Inconclusive", reason: "Catalog branch commit/root tree was malformed." } as const
245
+ }
246
+ const treeResponse = yield* get(subject, http, grant, `${base}/git/trees/${rootTree}?recursive=1`)
247
+ const entries = treeResponse.status === 200 ? treeEntries(parseJson(treeResponse.body), rootTree) : undefined
248
+ if (entries === undefined) return { _tag: "Inconclusive", reason: "Catalog full tree was truncated, malformed, or unavailable." } as const
249
+ const tree = new Map(entries.map((entry) => [entry.path, entry]))
250
+ const targetEntry = tree.get(publication.targetPath.toString())
251
+ const stateEntry = tree.get(publication.statePath.toString())
252
+ if ((targetEntry !== undefined && (targetEntry.type !== "blob" || !["100644", "100755"].includes(targetEntry.mode))) ||
253
+ (stateEntry !== undefined && (stateEntry.type !== "blob" || stateEntry.mode !== "100644"))) {
254
+ return { _tag: "Inconclusive", reason: "Catalog managed paths have unsupported Git object types or modes." } as const
255
+ }
256
+ const readBlob = (entry: TreeEntry | undefined) => entry === undefined
257
+ ? Effect.succeed(undefined)
258
+ : get(subject, http, grant, `${base}/git/blobs/${entry.sha}`).pipe(Effect.map((response) =>
259
+ response.status === 200 ? blobBytes(parseJson(response.body), entry.sha) : undefined))
260
+ const target = yield* readBlob(targetEntry)
261
+ const state = yield* readBlob(stateEntry)
262
+ if ((targetEntry !== undefined && target === undefined) || (stateEntry !== undefined && state === undefined)) {
263
+ return { _tag: "Inconclusive", reason: "Catalog target or managed-state blob was malformed or unavailable." } as const
264
+ }
265
+ return {
266
+ _tag: "Snapshot",
267
+ snapshot: { commit, rootTree, tree, ...(target === undefined ? {} : { target }), ...(state === undefined ? {} : { state }) }
268
+ } as const
269
+ })
270
+
271
+ const pairObservation = (
272
+ publication: PreparedCatalogPublication,
273
+ subject: SubjectId,
274
+ pair: CatalogSubjectPair,
275
+ snapshot: RepositorySnapshot,
276
+ context: ReleaseObservationContext,
277
+ setPlan: (plan: MutationPlan | undefined) => void
278
+ ): Observation => {
279
+ const absentPair = snapshot.target === undefined && snapshot.state === undefined
280
+ if (absentPair) {
281
+ if (context.phase === "post-mutation" && context.attempt._tag !== "RejectedBeforeDispatch" &&
282
+ context.attempt._tag !== "RejectedByProvider") return VisibilityPending.make({
283
+ subject,
284
+ expectation: SafeReason.make("The exact catalog target/state pair becomes visible at the updated branch ref."),
285
+ basis: VisibilityBasis.make({
286
+ kind: NonEmptyName.make("catalog-git-ref-convergence"),
287
+ detail: SafeReason.make("The same invocation attempted one conditional GitHub ref update.")
288
+ })
289
+ })
290
+ setPlan({ snapshot })
291
+ return AuthoritativelyAbsent.make({
292
+ subject,
293
+ basis: AbsenceBasis.make({
294
+ kind: NonEmptyName.make("catalog-visible-repository-pair-absent"),
295
+ detail: SafeReason.make("The visible branch full tree omits both exact managed paths.")
296
+ })
297
+ })
298
+ }
299
+ if (snapshot.target === undefined || snapshot.state === undefined) return PresentDifferent.make({
300
+ subject,
301
+ differences: [difference("catalog.pair", "both files present or absent", "half-present")]
302
+ })
303
+ if (bytesEqual(snapshot.target, pair.target) && bytesEqual(snapshot.state, pair.state)) {
304
+ return PresentEquivalent.make({ subject })
305
+ }
306
+ if (pair.baselineTarget !== undefined && pair.baselineState !== undefined &&
307
+ bytesEqual(snapshot.target, pair.baselineTarget) && bytesEqual(snapshot.state, pair.baselineState)) {
308
+ if (context.phase === "post-mutation" && context.attempt._tag !== "RejectedBeforeDispatch" &&
309
+ context.attempt._tag !== "RejectedByProvider") return VisibilityPending.make({
310
+ subject,
311
+ expectation: SafeReason.make("The forward catalog correction becomes visible at the branch ref."),
312
+ basis: VisibilityBasis.make({
313
+ kind: NonEmptyName.make("catalog-git-ref-convergence"),
314
+ detail: SafeReason.make("The old exact pair remains visible immediately after one conditional update attempt.")
315
+ })
316
+ })
317
+ setPlan({ snapshot })
318
+ return PresentDifferent.make({
319
+ subject,
320
+ differences: [difference("catalog.correction", "forward replacement", "exact prepared baseline")]
321
+ })
322
+ }
323
+ const observedState = decodeCatalogManagedState(snapshot.state)
324
+ const expectedState = decodeCatalogManagedState(pair.state)
325
+ if (observedState === undefined || expectedState === undefined) return inconclusive(
326
+ subject,
327
+ "Catalog managed state is malformed or noncanonical."
328
+ )
329
+ if (!digestEquals(observedState.targetDigest, sha256Digest(snapshot.target)) ||
330
+ !digestEquals(expectedState.targetDigest, sha256Digest(pair.target))) return inconclusive(
331
+ subject,
332
+ "Catalog managed state does not bind the exact adjacent target bytes."
333
+ )
334
+ if (pair.baselineTarget === undefined && observedState.status === "active" && expectedState.status === "active") {
335
+ const ordering = compareCatalogVersions(observedState.generation.toString(), expectedState.generation.toString())
336
+ if (ordering < 0 && observedState.catalogId === expectedState.catalogId &&
337
+ observedState.renderer === expectedState.renderer &&
338
+ observedState.sourceRepository === expectedState.sourceRepository) {
339
+ setPlan({ snapshot })
340
+ return PresentDifferent.make({
341
+ subject,
342
+ differences: [difference("catalog.generation", expectedState.generation.toString(), observedState.generation.toString())]
343
+ })
344
+ }
345
+ }
346
+ return PresentDifferent.make({
347
+ subject,
348
+ differences: [
349
+ difference("catalog.target", fingerprint(pair.target), fingerprint(snapshot.target)),
350
+ difference("catalog.state", fingerprint(pair.state), fingerprint(snapshot.state))
351
+ ]
352
+ })
353
+ }
354
+
355
+ const managedAncestors = (paths: ReadonlyArray<string>): ReadonlySet<string> => {
356
+ const result = new Set<string>()
357
+ for (const path of paths) {
358
+ const parts = path.split("/")
359
+ for (let index = 1; index < parts.length; index += 1) result.add(parts.slice(0, index).join("/"))
360
+ }
361
+ return result
362
+ }
363
+
364
+ const treePreserved = (
365
+ before: ReadonlyMap<string, TreeEntry>,
366
+ after: ReadonlyArray<TreeEntry>,
367
+ targetPath: string,
368
+ targetSha: string,
369
+ statePath: string,
370
+ stateSha: string
371
+ ): boolean => {
372
+ const afterMap = new Map(after.map((entry) => [entry.path, entry]))
373
+ const managed = new Set([targetPath, statePath])
374
+ const ancestors = managedAncestors([targetPath, statePath])
375
+ const target = afterMap.get(targetPath)
376
+ const state = afterMap.get(statePath)
377
+ if (target?.type !== "blob" || target.mode !== "100644" || target.sha !== targetSha ||
378
+ state?.type !== "blob" || state.mode !== "100644" || state.sha !== stateSha) return false
379
+ for (const [path, entry] of before) {
380
+ if (managed.has(path) || ancestors.has(path)) continue
381
+ const candidate = afterMap.get(path)
382
+ if (candidate === undefined || candidate.mode !== entry.mode || candidate.type !== entry.type || candidate.sha !== entry.sha) return false
383
+ }
384
+ for (const path of afterMap.keys()) {
385
+ if (!before.has(path) && !managed.has(path) && !ancestors.has(path)) return false
386
+ }
387
+ return true
388
+ }
389
+
390
+ const mutationAttempt = (
391
+ subject: SubjectId,
392
+ code: string,
393
+ detail: string
394
+ ): RejectedByProvider => RejectedByProvider.make({
395
+ subject,
396
+ fact: ProviderRejectionFact.make({
397
+ subject,
398
+ code: NonEmptyName.make(code),
399
+ detail: SafeReason.make(detail)
400
+ })
401
+ })
402
+
403
+ export const makeCatalogSubject = (
404
+ publication: PreparedCatalogPublication,
405
+ http: HttpAuthorizerShape,
406
+ mutationHttp: AuthorizedMutationHttpShape,
407
+ pair: CatalogSubjectPair
408
+ ): ReleaseSubject => {
409
+ const subject = pair.id ?? publication.authority.subject
410
+ const purpose = pair.purpose ?? "publish"
411
+ let plan: MutationPlan | undefined
412
+ const observe = Effect.fn("CatalogGitSubject.observe")(function*(grant: CredentialGrant, context: ReleaseObservationContext) {
413
+ plan = undefined
414
+ if (grant._tag === "WorkloadIdentity") return inconclusive(subject, "Catalog Git observation does not accept workload identity.")
415
+ const result = yield* readSnapshot(publication, subject, http, grant)
416
+ if (result._tag === "Inconclusive") return inconclusive(subject, result.reason)
417
+ return pairObservation(publication, subject, pair, result.snapshot, context, (value) => { plan = value })
418
+ })
419
+ const decide = (observation: Observation): ProviderDecision => {
420
+ if (observation._tag === "PresentEquivalent") return ProviderAlreadyEquivalent.make({ subject })
421
+ if (plan !== undefined && (observation._tag === "AuthoritativelyAbsent" || observation._tag === "PresentDifferent")) {
422
+ return NeedsMutation.make({
423
+ subject,
424
+ precondition: MutationPrecondition.make({ kind: NonEmptyName.make("catalog-observed-ref-and-root-tree") })
425
+ })
426
+ }
427
+ if (observation._tag === "PresentDifferent") return Conflict.make({ subject, differences: observation.differences })
428
+ return ProviderBlocked.make({
429
+ subject,
430
+ reason: SafeReason.make("Catalog observation did not prove an exact conditional pair update.")
431
+ })
432
+ }
433
+
434
+ const mutate = Effect.fn("CatalogGitSubject.mutate")(function*(
435
+ decision: MutationDecision,
436
+ grant: MutationCredentialGrant
437
+ ): Effect.fn.Return<MutationAttempt, ReleaseSubjectError> {
438
+ const selected = plan
439
+ plan = undefined
440
+ if (decision._tag !== "NeedsMutation" || decision.precondition.kind !== "catalog-observed-ref-and-root-tree" ||
441
+ selected === undefined || grant._tag !== "ScopedSecret") {
442
+ return yield* failure(subject, "mutate", "before-dispatch", "Catalog mutation lacks its exact observed ref/tree plan or token grant.")
443
+ }
444
+ const operation: PublisherOperation = purpose === "publish"
445
+ ? {
446
+ _tag: "PublishOperation",
447
+ subject,
448
+ provider: publication.authority.provider,
449
+ audience: publication.authority.audience,
450
+ purpose,
451
+ decision
452
+ }
453
+ : {
454
+ _tag: "CorrectionOperation",
455
+ subject,
456
+ provider: publication.authority.provider,
457
+ audience: publication.authority.audience,
458
+ purpose,
459
+ decision
460
+ }
461
+ const dispatch = (request: MutationHttpRequest) => mutationHttp.execute(operation, request, grant).pipe(
462
+ Effect.mapError((cause) => failure(
463
+ subject,
464
+ "mutate",
465
+ cause._tag === "CredentialPlatformError" ? cause.commitment : "before-dispatch",
466
+ "GitHub catalog mutation failed at the authorized HTTP boundary."
467
+ ))
468
+ )
469
+ const json = (value: unknown): { readonly body: string, readonly headers: Readonly<Record<string, string>> } => {
470
+ const body = JSON.stringify(value)
471
+ return {
472
+ body,
473
+ headers: { ...jsonHeaders, "content-type": "application/json", "content-length": String(new TextEncoder().encode(body).length) }
474
+ }
475
+ }
476
+ const base = publication.authority.audience.toString()
477
+ const createBlob = Effect.fn("CatalogGitSubject.createBlob")(function*(bytes: Uint8Array) {
478
+ const response = yield* dispatch({
479
+ method: "POST",
480
+ url: `${base}/git/blobs`,
481
+ credentialScheme: "bearer",
482
+ ...json({ content: Buffer.from(bytes).toString("base64"), encoding: "base64" })
483
+ })
484
+ const sha = string(object(parseJson(response.body))?.sha)
485
+ return response.status === 201 && sha !== undefined && objectSha.test(sha) && gitObjectDigest(bytes, sha.length) === sha
486
+ ? { _tag: "Created", sha } as const
487
+ : { _tag: "Rejected", attempt: mutationAttempt(subject, `catalog-blob-http-${response.status}`, "GitHub did not prove exact catalog blob creation.") } as const
488
+ })
489
+ const targetBlob = yield* createBlob(pair.target)
490
+ if (targetBlob._tag === "Rejected") return targetBlob.attempt
491
+ const stateBlob = yield* createBlob(pair.state)
492
+ if (stateBlob._tag === "Rejected") return stateBlob.attempt
493
+ const treeResponse = yield* dispatch({
494
+ method: "POST",
495
+ url: `${base}/git/trees`,
496
+ credentialScheme: "bearer",
497
+ ...json({
498
+ base_tree: selected.snapshot.rootTree,
499
+ tree: [
500
+ { path: publication.targetPath, mode: "100644", type: "blob", sha: targetBlob.sha },
501
+ { path: publication.statePath, mode: "100644", type: "blob", sha: stateBlob.sha }
502
+ ]
503
+ })
504
+ })
505
+ const proposedTree = string(object(parseJson(treeResponse.body))?.sha)
506
+ if (treeResponse.status !== 201 || proposedTree === undefined || !objectSha.test(proposedTree)) {
507
+ return mutationAttempt(subject, `catalog-tree-http-${treeResponse.status}`, "GitHub did not prove base-tree catalog tree creation.")
508
+ }
509
+ const proposedResponse = yield* get(subject, http, grant, `${base}/git/trees/${proposedTree}?recursive=1`)
510
+ const proposedEntries = proposedResponse.status === 200 ? treeEntries(parseJson(proposedResponse.body), proposedTree) : undefined
511
+ if (proposedEntries === undefined || !treePreserved(
512
+ selected.snapshot.tree,
513
+ proposedEntries,
514
+ publication.targetPath.toString(),
515
+ targetBlob.sha,
516
+ publication.statePath.toString(),
517
+ stateBlob.sha
518
+ )) return mutationAttempt(subject, "catalog-tree-preservation-refused", "Proposed GitHub tree did not preserve every unrelated path, object, and mode.")
519
+ const commitResponse = yield* dispatch({
520
+ method: "POST",
521
+ url: `${base}/git/commits`,
522
+ credentialScheme: "bearer",
523
+ ...json({
524
+ message: purpose === "correct"
525
+ ? `Correct ${publication.catalogId} catalog state`
526
+ : `Publish ${publication.catalogId} ${publication.version}`,
527
+ tree: proposedTree,
528
+ parents: [selected.snapshot.commit]
529
+ })
530
+ })
531
+ const commit = string(object(parseJson(commitResponse.body))?.sha)
532
+ if (commitResponse.status !== 201 || commit === undefined || !objectSha.test(commit)) {
533
+ return mutationAttempt(subject, `catalog-commit-http-${commitResponse.status}`, "GitHub did not prove exact-parent catalog commit creation.")
534
+ }
535
+ const refResponse = yield* dispatch({
536
+ method: "PATCH",
537
+ url: `${base}/git/refs/heads/${encodeURIComponent(publication.branch)}`,
538
+ credentialScheme: "bearer",
539
+ ...json({ sha: commit, force: false })
540
+ })
541
+ if (refResponse.status === 409 || refResponse.status === 422) {
542
+ return mutationAttempt(subject, `catalog-ref-http-${refResponse.status}`, "The catalog branch moved or refused the non-force update; reobservation is required.")
543
+ }
544
+ if (refResponse.status !== 200) {
545
+ return OutcomeUnknown.make({
546
+ subject,
547
+ reason: SafeReason.make(`GitHub catalog ref update returned HTTP ${refResponse.status}; exact reobservation decides the outcome.`)
548
+ })
549
+ }
550
+ const ref = object(parseJson(refResponse.body))
551
+ if (ref?.ref !== `refs/heads/${publication.branch}` || object(ref.object)?.sha !== commit) {
552
+ return OutcomeUnknown.make({
553
+ subject,
554
+ reason: SafeReason.make("GitHub accepted the catalog ref request but returned no exact updated-ref proof.")
555
+ })
556
+ }
557
+ return Started.make({ subject })
558
+ })
559
+
560
+ return {
561
+ id: subject,
562
+ recovery: catalogRecoveryCapabilityProfile,
563
+ observationRequests: observationRequests(publication, subject),
564
+ mutationRequest: mutationRequest(publication, subject, purpose),
565
+ observe,
566
+ decide,
567
+ mutate
568
+ }
569
+ }
570
+
571
+ export const makeCatalogPublicationSubject = (
572
+ bundle: PreparedBundle,
573
+ publication: PreparedCatalogPublication,
574
+ http: HttpAuthorizerShape,
575
+ mutationHttp: AuthorizedMutationHttpShape
576
+ ): ReleaseSubject => {
577
+ const target = bundle.blobs.get(publication.targetArtifactId.toString())
578
+ const state = bundle.blobs.get(publication.stateArtifactId.toString())
579
+ if (target === undefined || state === undefined || !digestEquals(sha256Digest(target), publication.targetDigest) ||
580
+ !digestEquals(sha256Digest(state), publication.stateDigest)) {
581
+ const invalid = makeCatalogSubject(publication, http, mutationHttp, {
582
+ target: new Uint8Array(), state: new Uint8Array()
583
+ })
584
+ return {
585
+ ...invalid,
586
+ observe: () => Effect.succeed(inconclusive(
587
+ invalid.id,
588
+ "Prepared catalog target/state bytes are missing or disagree with their durable digests."
589
+ )),
590
+ decide: () => ProviderBlocked.make({
591
+ subject: invalid.id,
592
+ reason: SafeReason.make("Prepared catalog target/state evidence is unavailable.")
593
+ }),
594
+ mutate: () => Effect.fail(failure(
595
+ invalid.id,
596
+ "mutate",
597
+ "before-dispatch",
598
+ "Prepared catalog target/state evidence is unavailable."
599
+ ))
600
+ }
601
+ }
602
+ return makeCatalogSubject(publication, http, mutationHttp, { target, state })
603
+ }
@@ -0,0 +1,51 @@
1
+ import * as Context from "effect/Context"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Schema from "effect/Schema"
4
+ import { SubjectId } from "../model/authority.js"
5
+ import { Sha256Digest } from "../model/digest.js"
6
+ import { SafeReason } from "./report.js"
7
+
8
+ /** Exact immutable CAS key for one prepared mutation subject. */
9
+ export class PublicationClaimRequest
10
+ extends Schema.Class<PublicationClaimRequest>("PublicationClaimRequest")({
11
+ subject: SubjectId,
12
+ preparedDigest: Sha256Digest
13
+ }) {}
14
+
15
+ export class PublicationClaimUnavailable
16
+ extends Schema.TaggedErrorClass<PublicationClaimUnavailable>()("PublicationClaimUnavailable", {
17
+ subject: SubjectId,
18
+ reason: SafeReason
19
+ }) {}
20
+
21
+ export class PublicationClaimOccupied
22
+ extends Schema.TaggedErrorClass<PublicationClaimOccupied>()("PublicationClaimOccupied", {
23
+ subject: SubjectId,
24
+ reason: SafeReason
25
+ }) {}
26
+
27
+ export type PublicationClaimError = PublicationClaimUnavailable | PublicationClaimOccupied
28
+
29
+ export interface PublicationClaimStoreShape {
30
+ /**
31
+ * Atomically create one terminal claim. A successful call can never become
32
+ * claimable again through lease expiry, process death, or local cleanup.
33
+ * The implementation must be shared by every runner that can dispatch the
34
+ * subject; process memory and runner-local files do not satisfy this seam.
35
+ */
36
+ readonly claim: (
37
+ request: PublicationClaimRequest
38
+ ) => Effect.Effect<void, PublicationClaimError>
39
+ }
40
+
41
+ export class PublicationClaimStore
42
+ extends Context.Service<PublicationClaimStore, PublicationClaimStoreShape>()(
43
+ "ts-release/PublicationClaimStore"
44
+ ) {}
45
+
46
+ export const unavailablePublicationClaimStore: PublicationClaimStoreShape = {
47
+ claim: (request) => Effect.fail(PublicationClaimUnavailable.make({
48
+ subject: request.subject,
49
+ reason: SafeReason.make("A shared durable terminal publication-claim store is not installed by this host.")
50
+ }))
51
+ }