@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,94 @@
1
+ import {
2
+ githubRecoveryCapabilityProfile
3
+ } from "./github.js"
4
+ import {
5
+ npmRecoveryCapabilityProfile
6
+ } from "./npm.js"
7
+ import { pypiRecoveryCapabilityProfile } from "./pypi.js"
8
+ import { catalogRecoveryCapabilityProfile } from "./catalog-git.js"
9
+ import { validatePublicationProfiles } from "./recovery.js"
10
+
11
+ /**
12
+ * Canonical provider registrations shared by executable capability modules,
13
+ * runtime dispatch, and generated recovery documentation. An authored
14
+ * correction proposal is not an installed conditional correction adapter.
15
+ */
16
+ export const installedPublicationProfiles = validatePublicationProfiles(Object.freeze({
17
+ npm: Object.freeze({
18
+ id: "publish.npm",
19
+ provider: "npm",
20
+ preparedTag: "PreparedNpmPublication",
21
+ recovery: npmRecoveryCapabilityProfile,
22
+ correctionAdapters: [] as const,
23
+ evidence: Object.freeze({
24
+ reviewedAt: "2026-08-12",
25
+ observationSources: Object.freeze([
26
+ "https://github.com/npm/registry/blob/main/docs/responses/package-metadata.md",
27
+ "https://docs.npmjs.com/cli/v11/commands/npm-publish/"
28
+ ]),
29
+ correctionSources: Object.freeze([
30
+ "https://docs.npmjs.com/cli/v11/commands/npm-deprecate/",
31
+ "https://docs.npmjs.com/policies/unpublish/"
32
+ ]),
33
+ correctionFinding: "Official npm documentation exposes deprecation but no conditional update bound to an observed package generation."
34
+ })
35
+ }),
36
+ pypi: Object.freeze({
37
+ id: "publish.pypi",
38
+ provider: "pypi",
39
+ preparedTag: "PreparedPyPiPublication",
40
+ recovery: pypiRecoveryCapabilityProfile,
41
+ correctionAdapters: [] as const,
42
+ evidence: Object.freeze({
43
+ reviewedAt: "2026-08-13",
44
+ observationSources: Object.freeze([
45
+ "https://packaging.python.org/en/latest/specifications/simple-repository-api/",
46
+ "https://packaging.python.org/en/latest/specifications/file-yanking/"
47
+ ]),
48
+ correctionSources: Object.freeze([
49
+ "https://docs.pypi.org/project-management/yanking/"
50
+ ]),
51
+ correctionFinding: "PyPI documents yanking behavior but no stable exact conditional per-file update API; no correction adapter is installed."
52
+ })
53
+ }),
54
+ catalogGit: Object.freeze({
55
+ id: "publish.catalog-git",
56
+ provider: "catalog-git",
57
+ preparedTag: "PreparedCatalogPublication",
58
+ recovery: catalogRecoveryCapabilityProfile,
59
+ correctionAdapters: ["forward-catalog-state"] as const,
60
+ evidence: Object.freeze({
61
+ reviewedAt: "2026-08-13",
62
+ observationSources: Object.freeze([
63
+ "https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28",
64
+ "https://docs.github.com/en/rest/git/trees?apiVersion=2022-11-28"
65
+ ]),
66
+ correctionSources: Object.freeze([
67
+ "https://docs.github.com/en/rest/git/trees?apiVersion=2022-11-28#create-a-tree",
68
+ "https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28#update-a-reference"
69
+ ]),
70
+ correctionFinding: "Catalog correction is an exact forward SemVer replacement of the managed target/state pair on one observed Git commit; the ref update is non-forced."
71
+ })
72
+ }),
73
+ github: Object.freeze({
74
+ id: "publish.github",
75
+ provider: "github",
76
+ preparedTag: "PreparedGitHubPublication",
77
+ recovery: githubRecoveryCapabilityProfile,
78
+ correctionAdapters: [] as const,
79
+ evidence: Object.freeze({
80
+ reviewedAt: "2026-08-12",
81
+ observationSources: Object.freeze([
82
+ "https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28",
83
+ "https://docs.github.com/en/rest/git/tags?apiVersion=2022-11-28",
84
+ "https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28",
85
+ "https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28"
86
+ ]),
87
+ correctionSources: Object.freeze([
88
+ "https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#update-a-release",
89
+ "https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api?apiVersion=2022-11-28#use-conditional-requests"
90
+ ]),
91
+ correctionFinding: "GitHub documents conditional reads, but unsafe-method conditions are unsupported unless an endpoint says otherwise; the release update endpoint documents none."
92
+ })
93
+ })
94
+ }))
@@ -0,0 +1,130 @@
1
+ import * as Context from "effect/Context"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Schema from "effect/Schema"
4
+ import type * as Scope from "effect/Scope"
5
+ import type { NonEmptyName, Version } from "../model/primitives.js"
6
+ import type { PreparedNpmPublication } from "../release/prepared.js"
7
+ import type { CredentialAuthorityError, MutationCredentialGrant, ScopedSecret, WorkloadIdentity } from "./authority.js"
8
+ import type { HttpAuthorizationError } from "./http.js"
9
+ import type { PublisherOperation } from "./authority.js"
10
+
11
+ const NpmUserConfigTypeId: unique symbol = Symbol("ts-release/NpmUserConfig")
12
+
13
+ /** Opaque handle: only CertifiedPublisherSpawn can eliminate it to a path. */
14
+ export interface NpmUserConfig {
15
+ readonly _tag: "NpmUserConfig"
16
+ readonly [NpmUserConfigTypeId]: typeof NpmUserConfigTypeId
17
+ }
18
+
19
+ export const makeNpmUserConfigHandle = (): NpmUserConfig => Object.freeze({
20
+ _tag: "NpmUserConfig" as const,
21
+ [NpmUserConfigTypeId]: NpmUserConfigTypeId
22
+ } as NpmUserConfig)
23
+
24
+ export type NpmPublishOperation = Extract<PublisherOperation, { readonly _tag: "PublishOperation" }>
25
+
26
+ export interface NpmUserConfigInput {
27
+ readonly operation: NpmPublishOperation
28
+ readonly registryUrl: string
29
+ }
30
+
31
+ export interface NpmUserConfigResourceShape {
32
+ readonly acquire: (
33
+ input: NpmUserConfigInput,
34
+ grant: ScopedSecret
35
+ ) => Effect.Effect<NpmUserConfig, CredentialAuthorityError | HttpAuthorizationError, Scope.Scope>
36
+ }
37
+
38
+ export class NpmUserConfigResource
39
+ extends Context.Service<NpmUserConfigResource, NpmUserConfigResourceShape>()(
40
+ "ts-release/NpmUserConfigResource"
41
+ ) {}
42
+
43
+ interface CertifiedPublisherSpecBase {
44
+ readonly operation: NpmPublishOperation
45
+ readonly cwd: string
46
+ readonly tarballPath: string
47
+ readonly packageName: NonEmptyName
48
+ readonly version: Version
49
+ readonly registryUrl: PreparedNpmPublication["registryUrl"]
50
+ readonly distTag: PreparedNpmPublication["distTag"]
51
+ readonly access: PreparedNpmPublication["access"]
52
+ readonly provenance: PreparedNpmPublication["provenance"]
53
+ }
54
+
55
+ export interface NpmPublisherSpec extends CertifiedPublisherSpecBase {
56
+ readonly _tag: "NpmPublisherSpec"
57
+ readonly operation: NpmPublishOperation
58
+ readonly userConfig: NpmUserConfig
59
+ }
60
+
61
+ export interface WorkloadPublisherSpec extends CertifiedPublisherSpecBase {
62
+ readonly _tag: "WorkloadPublisherSpec"
63
+ }
64
+
65
+ export type CertifiedPublisherSpec = NpmPublisherSpec | WorkloadPublisherSpec
66
+
67
+ /** The credential-bearing host owns the only admitted npm mutation argv. */
68
+ export const npmPublishArgv = (
69
+ spec: CertifiedPublisherSpec
70
+ ): readonly [string, ...Array<string>] => [
71
+ "npm",
72
+ "publish",
73
+ spec.tarballPath,
74
+ "--ignore-scripts",
75
+ "--registry",
76
+ spec.registryUrl,
77
+ "--tag",
78
+ spec.distTag,
79
+ "--access",
80
+ spec.access,
81
+ ...(spec.provenance === "required"
82
+ ? ["--provenance"]
83
+ : spec.provenance === "disabled"
84
+ ? ["--provenance=false"]
85
+ : []),
86
+ "--json"
87
+ ]
88
+
89
+ export class RejectedBeforeStart
90
+ extends Schema.TaggedClass<RejectedBeforeStart>()("RejectedBeforeStart", {
91
+ commitment: Schema.Literal("before-dispatch"),
92
+ reason: Schema.NonEmptyString
93
+ }) {}
94
+
95
+ export class PublisherExited
96
+ extends Schema.TaggedClass<PublisherExited>()("PublisherExited", {
97
+ commitment: Schema.Literal("started"),
98
+ exitCode: Schema.Int,
99
+ stdout: Schema.String,
100
+ stderr: Schema.String
101
+ }) {}
102
+
103
+ export class PublisherOutcomeUnknown
104
+ extends Schema.TaggedClass<PublisherOutcomeUnknown>()("PublisherOutcomeUnknown", {
105
+ commitment: Schema.Literal("unknown"),
106
+ reason: Schema.NonEmptyString
107
+ }) {}
108
+
109
+ export const CertifiedPublisherResult = Schema.Union([
110
+ RejectedBeforeStart,
111
+ PublisherExited,
112
+ PublisherOutcomeUnknown
113
+ ])
114
+ export type CertifiedPublisherResult = typeof CertifiedPublisherResult.Type
115
+
116
+ export interface CertifiedPublisherSpawnShape {
117
+ readonly preflightTrustedNpm: (
118
+ operation: NpmPublishOperation,
119
+ grant: WorkloadIdentity
120
+ ) => Effect.Effect<void, CredentialAuthorityError | HttpAuthorizationError>
121
+ readonly spawn: (
122
+ spec: CertifiedPublisherSpec,
123
+ grant: MutationCredentialGrant
124
+ ) => Effect.Effect<CertifiedPublisherResult, CredentialAuthorityError | HttpAuthorizationError>
125
+ }
126
+
127
+ export class CertifiedPublisherSpawn
128
+ extends Context.Service<CertifiedPublisherSpawn, CertifiedPublisherSpawnShape>()(
129
+ "ts-release/CertifiedPublisherSpawn"
130
+ ) {}
@@ -0,0 +1,463 @@
1
+ import * as Effect from "effect/Effect"
2
+ import * as Schema from "effect/Schema"
3
+ import type { CredentialRequest, ResolvedAuthStrategy } from "../model/authority.js"
4
+ import { CanonicalAudience, CredentialRequest as CredentialRequestSchema } from "../model/authority.js"
5
+ import { digestEquals, formatSha256Hex, parseSha256Hex, sha256Digest } from "../model/digest.js"
6
+ import { NonEmptyName } from "../model/primitives.js"
7
+ import { encodePreparedRelease, type PreparedPyPiFile, type PreparedPyPiPublication } from "../release/prepared.js"
8
+ import type { PreparedBundle } from "../release/prepared-store.js"
9
+ import type { CredentialGrant, MutationCredentialGrant } from "./authority.js"
10
+ import {
11
+ PublicationClaimRequest,
12
+ unavailablePublicationClaimStore,
13
+ type PublicationClaimStoreShape
14
+ } from "./claim.js"
15
+ import type { AuthorizedMutationHttpShape, HttpAuthorizerShape, HttpResponse } from "./http.js"
16
+ import {
17
+ ReleaseSubjectError,
18
+ type ReleaseObservationContext,
19
+ type ReleaseSubject
20
+ } from "./coordinator.js"
21
+ import {
22
+ AbsenceBasis,
23
+ AuthoritativelyAbsent,
24
+ Conflict,
25
+ Difference,
26
+ InconclusiveObservation,
27
+ MutationPrecondition,
28
+ NeedsMutation,
29
+ OutcomeUnknown,
30
+ PresentDifferent,
31
+ PresentEquivalent,
32
+ ProviderAlreadyEquivalent,
33
+ ProviderBlocked,
34
+ ProviderRejectionFact,
35
+ RejectedByProvider,
36
+ SafeReason,
37
+ Started,
38
+ VisibilityBasis,
39
+ VisibilityPending,
40
+ type MutationDecision,
41
+ type Observation,
42
+ type ProviderDecision
43
+ } from "./report.js"
44
+ import { makeRecoveryCapabilityProfile } from "./recovery.js"
45
+
46
+ export const pypiRecoveryCapabilityProfile = makeRecoveryCapabilityProfile({
47
+ observation: "exact",
48
+ authoritativeAbsence: "provider-specific",
49
+ createAuthorization: "none",
50
+ replay: "unsafe",
51
+ identifierReuse: "consumed-after-delete",
52
+ correction: [],
53
+ exposure: "persistent-to-consumers",
54
+ historyRequirement: "durable-cas-required",
55
+ readConvergence: {
56
+ contract: {
57
+ _tag: "assumed",
58
+ basis: "ASSUMED/UNVERIFIED: no authorized live mutation evidence establishes PyPI Simple API read-convergence timing."
59
+ },
60
+ observationRetry: {
61
+ maxAttempts: 6,
62
+ backoff: { baseMs: 2_000, factor: 2, capMs: 30_000 },
63
+ totalBudgetMs: 120_000
64
+ },
65
+ retryEligible: "VisibilityPending | Inconclusive",
66
+ exhaustion: "UncertainSubject with full trace"
67
+ }
68
+ })
69
+
70
+ export const pypiProviderProtocolDocumentation = Object.freeze({
71
+ reviewedAt: "2026-08-13",
72
+ simpleApi: "https://packaging.python.org/en/latest/specifications/simple-repository-api/",
73
+ yanking: "https://packaging.python.org/en/latest/specifications/file-yanking/",
74
+ upload: "https://docs.pypi.org/api/upload/",
75
+ trustedPublishing: "https://docs.pypi.org/trusted-publishers/using-a-publisher/"
76
+ })
77
+
78
+ /** No stable exact conditional per-file yank mutation is installed. */
79
+ export class PyPiYankUnsupported
80
+ extends Schema.TaggedErrorClass<PyPiYankUnsupported>()("PyPiYankUnsupported", {
81
+ filename: Schema.NonEmptyString,
82
+ reason: SafeReason
83
+ }) {}
84
+
85
+ const asObject = (value: unknown): Readonly<Record<string, unknown>> | undefined =>
86
+ typeof value === "object" && value !== null && !Array.isArray(value)
87
+ ? value as Readonly<Record<string, unknown>>
88
+ : undefined
89
+
90
+ const parseJson = (body: Uint8Array | string): unknown | undefined => {
91
+ try {
92
+ const text = typeof body === "string" ? body : new TextDecoder("utf-8", { fatal: true }).decode(body)
93
+ return JSON.parse(text) as unknown
94
+ } catch {
95
+ return undefined
96
+ }
97
+ }
98
+
99
+ const header = (response: HttpResponse, name: string): string | undefined => {
100
+ const found = Object.entries(response.headers).find(([candidate]) => candidate.toLowerCase() === name.toLowerCase())
101
+ return found?.[1]
102
+ }
103
+
104
+ const contentType = (value: string | undefined): string | undefined => value?.split(";", 1)[0]?.trim().toLowerCase()
105
+
106
+ type SimpleFile = {
107
+ readonly filename: string
108
+ readonly size: number
109
+ readonly sha256: string
110
+ readonly yanked: false | string
111
+ }
112
+
113
+ type SimpleProject = {
114
+ readonly apiVersion: string
115
+ readonly name: string
116
+ readonly files: ReadonlyArray<SimpleFile>
117
+ }
118
+
119
+ const simpleProject = (value: unknown): SimpleProject | undefined => {
120
+ const root = asObject(value)
121
+ const meta = root === undefined ? undefined : asObject(root.meta)
122
+ if (root === undefined || meta === undefined || typeof meta["api-version"] !== "string" ||
123
+ typeof root.name !== "string" || !Array.isArray(root.files)) return undefined
124
+ const files: Array<SimpleFile> = []
125
+ const filenames = new Set<string>()
126
+ for (const raw of root.files) {
127
+ const file = asObject(raw)
128
+ const hashes = file === undefined ? undefined : asObject(file.hashes)
129
+ if (file === undefined || hashes === undefined || typeof file.filename !== "string" ||
130
+ !Number.isSafeInteger(file.size) || (file.size as number) < 0 ||
131
+ typeof hashes.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(hashes.sha256)) return undefined
132
+ if (filenames.has(file.filename)) return undefined
133
+ filenames.add(file.filename)
134
+ const rawYanked = file.yanked
135
+ if (rawYanked !== undefined && typeof rawYanked !== "boolean" && typeof rawYanked !== "string") return undefined
136
+ files.push({
137
+ filename: file.filename,
138
+ size: file.size as number,
139
+ sha256: hashes.sha256,
140
+ yanked: rawYanked === true ? "yanked without a reason" : typeof rawYanked === "string" && rawYanked.length > 0
141
+ ? rawYanked : false
142
+ })
143
+ }
144
+ return { apiVersion: meta["api-version"], name: root.name, files }
145
+ }
146
+
147
+ const fingerprint = (value: string): SafeReason => SafeReason.make(
148
+ `provider value sha256-${formatSha256Hex(sha256Digest(new TextEncoder().encode(value)))}`
149
+ )
150
+
151
+ const difference = (field: string, expected: string, observed: string): Difference => Difference.make({
152
+ field: NonEmptyName.make(field),
153
+ expected: SafeReason.make(expected),
154
+ observed: fingerprint(observed)
155
+ })
156
+
157
+ const inconclusive = (file: PreparedPyPiFile, reason: string): InconclusiveObservation =>
158
+ InconclusiveObservation.make({ subject: file.authority.subject, reason: SafeReason.make(reason) })
159
+
160
+ const observationRequests = (
161
+ publication: PreparedPyPiPublication,
162
+ file: PreparedPyPiFile
163
+ ): readonly [CredentialRequest, ...Array<CredentialRequest>] => {
164
+ const strategy: ResolvedAuthStrategy = file.authority.observationStrategies[0]!
165
+ return [CredentialRequestSchema.make({
166
+ subject: file.authority.subject,
167
+ provider: file.authority.provider,
168
+ audience: CanonicalAudience.make(publication.projectUrl),
169
+ purpose: "observe",
170
+ strategy
171
+ })]
172
+ }
173
+
174
+ const mutationRequest = (file: PreparedPyPiFile): CredentialRequest => CredentialRequestSchema.make({
175
+ subject: file.authority.subject,
176
+ provider: file.authority.provider,
177
+ audience: file.authority.audience,
178
+ purpose: "publish",
179
+ strategy: file.authority.publishStrategy
180
+ })
181
+
182
+ const visibilityPending = (file: PreparedPyPiFile): VisibilityPending => VisibilityPending.make({
183
+ subject: file.authority.subject,
184
+ expectation: SafeReason.make("The exact uploaded PyPI filename, size, and SHA-256 become visible."),
185
+ basis: VisibilityBasis.make({
186
+ kind: NonEmptyName.make("pypi-post-upload-simple-read"),
187
+ detail: SafeReason.make("The same invocation dispatched one exact upload and is rereading the JSON Simple API only.")
188
+ })
189
+ })
190
+
191
+ const visibleFileAbsent = (file: PreparedPyPiFile): AuthoritativelyAbsent => AuthoritativelyAbsent.make({
192
+ subject: file.authority.subject,
193
+ basis: AbsenceBasis.make({
194
+ kind: NonEmptyName.make("pypi-visible-project-file-absent"),
195
+ detail: SafeReason.make("A standards-compliant visible project page omitted the exact prepared filename.")
196
+ })
197
+ })
198
+
199
+ const decide = (file: PreparedPyPiFile, observation: Observation): ProviderDecision => {
200
+ switch (observation._tag) {
201
+ case "PresentEquivalent": return ProviderAlreadyEquivalent.make({ subject: file.authority.subject })
202
+ case "PresentDifferent": return Conflict.make({ subject: file.authority.subject, differences: observation.differences })
203
+ case "AuthoritativelyAbsent": return observation.basis.kind === "pypi-visible-project-file-absent"
204
+ ? NeedsMutation.make({
205
+ subject: file.authority.subject,
206
+ precondition: MutationPrecondition.make({ kind: NonEmptyName.make("pypi-visible-project-file-absent") })
207
+ })
208
+ : ProviderBlocked.make({
209
+ subject: file.authority.subject,
210
+ reason: SafeReason.make("PyPI absence lacks a visible-project exact-filename proof.")
211
+ })
212
+ case "VisibilityPending":
213
+ case "Inconclusive": return ProviderBlocked.make({
214
+ subject: file.authority.subject,
215
+ reason: SafeReason.make("PyPI observation did not prove exact equivalence or visible-project file absence.")
216
+ })
217
+ }
218
+ }
219
+
220
+ const bytesConcat = (parts: ReadonlyArray<Uint8Array>): Uint8Array => {
221
+ const length = parts.reduce((sum, part) => sum + part.length, 0)
222
+ const result = new Uint8Array(length)
223
+ let offset = 0
224
+ for (const part of parts) { result.set(part, offset); offset += part.length }
225
+ return result
226
+ }
227
+
228
+ const containsBytes = (haystack: Uint8Array, needle: Uint8Array): boolean => {
229
+ if (needle.length === 0 || needle.length > haystack.length) return false
230
+ outer: for (let offset = 0; offset <= haystack.length - needle.length; offset += 1) {
231
+ for (let index = 0; index < needle.length; index += 1) {
232
+ if (haystack[offset + index] !== needle[index]) continue outer
233
+ }
234
+ return true
235
+ }
236
+ return false
237
+ }
238
+
239
+ const multipart = (
240
+ publication: PreparedPyPiPublication,
241
+ file: PreparedPyPiFile,
242
+ bytes: Uint8Array
243
+ ): { readonly contentType: string, readonly body: Uint8Array } => {
244
+ const encode = (value: string): Uint8Array => new TextEncoder().encode(value)
245
+ const boundaryBase = `ts-release-${file.sha256.hex}`
246
+ let boundary = boundaryBase
247
+ for (let suffix = 0; containsBytes(bytes, encode(boundary)); suffix += 1) {
248
+ boundary = `${boundaryBase}-${suffix + 1}`
249
+ }
250
+ const fields: ReadonlyArray<readonly [string, string]> = [
251
+ [":action", "file_upload"],
252
+ ["protocol_version", "1"],
253
+ ["sha256_digest", file.sha256.hex],
254
+ ["filetype", file.distribution._tag === "wheel" ? "bdist_wheel" : "sdist"],
255
+ ["pyversion", file.distribution.pythonTag],
256
+ ["metadata_version", file.distribution.metadataVersion],
257
+ ["name", publication.project],
258
+ ["version", publication.version]
259
+ ]
260
+ const parts: Array<Uint8Array> = []
261
+ for (const [name, value] of fields) parts.push(encode(
262
+ `--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`
263
+ ))
264
+ parts.push(encode(
265
+ `--${boundary}\r\nContent-Disposition: form-data; name="content"; filename="${file.filename}"\r\n` +
266
+ `Content-Type: ${file.mediaType}\r\n\r\n`
267
+ ))
268
+ parts.push(bytes)
269
+ parts.push(encode(`\r\n--${boundary}--\r\n`))
270
+ return { contentType: `multipart/form-data; boundary=${boundary}`, body: bytesConcat(parts) }
271
+ }
272
+
273
+ const validDecision = (decision: MutationDecision): boolean =>
274
+ decision._tag === "NeedsMutation" && decision.precondition.kind === "pypi-visible-project-file-absent"
275
+
276
+ const mutationFailure = (file: PreparedPyPiFile, cause: unknown): ReleaseSubjectError =>
277
+ new ReleaseSubjectError({
278
+ subject: file.authority.subject,
279
+ phase: "mutate",
280
+ commitment: typeof cause === "object" && cause !== null && "commitment" in cause && cause.commitment === "unknown"
281
+ ? "unknown" : "before-dispatch",
282
+ reason: SafeReason.make("The typed PyPI upload boundary rejected or lost the exact prepared file operation.")
283
+ })
284
+
285
+ /** One exact independently recoverable Simple-API/upload subject. */
286
+ export const makePyPiSubject = (
287
+ bundle: PreparedBundle,
288
+ publication: PreparedPyPiPublication,
289
+ file: PreparedPyPiFile,
290
+ http: HttpAuthorizerShape,
291
+ mutationHttp: AuthorizedMutationHttpShape,
292
+ prerequisites: ReadonlyArray<PreparedPyPiFile> = [],
293
+ claims: PublicationClaimStoreShape = unavailablePublicationClaimStore
294
+ ): ReleaseSubject => {
295
+ const bytes = bundle.blobs.get(file.artifactId.toString())
296
+ const preparedDigest = sha256Digest(encodePreparedRelease(bundle.manifest))
297
+ const observe = Effect.fn("PyPiFileSubject.observe")(function*(
298
+ grant: CredentialGrant,
299
+ context: ReleaseObservationContext
300
+ ) {
301
+ if (grant._tag !== "AnonymousAccess") {
302
+ return inconclusive(file, "PyPI Simple API observation requires the prepared anonymous strategy.")
303
+ }
304
+ if (bytes === undefined || bytes.length !== file.size || !digestEquals(sha256Digest(bytes), file.sha256)) {
305
+ return inconclusive(file, "The exact prepared PyPI artifact bytes are unavailable.")
306
+ }
307
+ const response = yield* http.execute({
308
+ subject: file.authority.subject,
309
+ method: "GET",
310
+ url: publication.projectUrl,
311
+ headers: { accept: "application/vnd.pypi.simple.v1+json" }
312
+ }, grant).pipe(Effect.mapError((cause) => new ReleaseSubjectError({
313
+ subject: file.authority.subject,
314
+ phase: "observe",
315
+ commitment: cause._tag === "CredentialPlatformError" ? cause.commitment : "before-dispatch",
316
+ reason: SafeReason.make("PyPI Simple API observation could not be completed by the host HTTP boundary.")
317
+ })))
318
+ if (response.status === 404) {
319
+ return context.phase === "post-mutation" && context.attempt._tag !== "RejectedBeforeDispatch"
320
+ ? visibilityPending(file)
321
+ : inconclusive(file, "A PyPI project 404 does not prove public absence, private absence, or first-create authority.")
322
+ }
323
+ if (response.status < 200 || response.status >= 300) {
324
+ return inconclusive(file, `PyPI Simple API observation returned HTTP ${response.status}.`)
325
+ }
326
+ if (contentType(header(response, "content-type")) !== "application/vnd.pypi.simple.v1+json") {
327
+ return inconclusive(file, "PyPI Simple API response did not negotiate the required v1 JSON media type.")
328
+ }
329
+ const project = simpleProject(parseJson(response.body))
330
+ if (project === undefined) return inconclusive(file, "PyPI Simple API response was malformed or omitted exact file equality fields.")
331
+ const version = /^(\d+)\.(\d+)$/u.exec(project.apiVersion)
332
+ if (version === null || Number(version[1]) !== 1 || Number(version[2]) < 1) {
333
+ return inconclusive(file, "PyPI Simple API version is older than 1.1 or has an unsupported major version.")
334
+ }
335
+ if (project.name !== publication.project) {
336
+ return PresentDifferent.make({
337
+ subject: file.authority.subject,
338
+ differences: [difference("project", publication.project, project.name)]
339
+ })
340
+ }
341
+ const observed = project.files.find((candidate) => candidate.filename === file.filename)
342
+ if (observed === undefined) {
343
+ return context.phase === "post-mutation" && context.attempt._tag !== "RejectedBeforeDispatch"
344
+ ? visibilityPending(file)
345
+ : visibleFileAbsent(file)
346
+ }
347
+ const differences: Array<Difference> = []
348
+ if (observed.size !== file.size) differences.push(difference("size", String(file.size), String(observed.size)))
349
+ try {
350
+ if (!digestEquals(parseSha256Hex(observed.sha256), file.sha256)) {
351
+ differences.push(difference("sha256", file.sha256.hex, observed.sha256))
352
+ }
353
+ } catch {
354
+ return inconclusive(file, "PyPI Simple API returned a malformed SHA-256 digest.")
355
+ }
356
+ if (observed.yanked !== false) differences.push(difference("yanked", "false", observed.yanked))
357
+ return differences.length === 0
358
+ ? PresentEquivalent.make({ subject: file.authority.subject })
359
+ : PresentDifferent.make({
360
+ subject: file.authority.subject,
361
+ differences: differences as [Difference, ...Array<Difference>]
362
+ })
363
+ })
364
+
365
+ const mutate = (decision: MutationDecision, grant: MutationCredentialGrant) => {
366
+ if (!validDecision(decision) || bytes === undefined) return Effect.fail(new ReleaseSubjectError({
367
+ subject: file.authority.subject,
368
+ phase: "mutate",
369
+ commitment: "before-dispatch",
370
+ reason: SafeReason.make("PyPI mutation lacks its exact visible-project file-absence proof or prepared bytes.")
371
+ }))
372
+ if (grant._tag === "WorkloadIdentity" || publication.authentication.strategy !== "token") {
373
+ return Effect.fail(new ReleaseSubjectError({
374
+ subject: file.authority.subject,
375
+ phase: "mutate",
376
+ commitment: "before-dispatch",
377
+ reason: SafeReason.make("PyPI trusted publishing is owned externally by pypa/gh-action-pypi-publish@release/v1.")
378
+ }))
379
+ }
380
+ const operation = {
381
+ _tag: "PublishOperation" as const,
382
+ subject: file.authority.subject,
383
+ provider: file.authority.provider,
384
+ audience: file.authority.audience,
385
+ purpose: "publish" as const,
386
+ decision
387
+ }
388
+ const form = multipart(publication, file, bytes)
389
+ return mutationHttp.execute(operation, {
390
+ method: "POST",
391
+ url: publication.uploadUrl,
392
+ credentialScheme: "pypi-token-basic",
393
+ headers: {
394
+ accept: "text/plain, application/json;q=0.1",
395
+ "content-type": form.contentType,
396
+ "content-length": String(form.body.length)
397
+ },
398
+ body: form.body
399
+ }, grant).pipe(
400
+ Effect.map((response) => response.status >= 200 && response.status < 300
401
+ ? Started.make({ subject: file.authority.subject })
402
+ : response.status >= 300 && response.status < 400
403
+ ? OutcomeUnknown.make({
404
+ subject: file.authority.subject,
405
+ reason: SafeReason.make("PyPI upload returned a redirect that the typed boundary did not follow.")
406
+ })
407
+ : RejectedByProvider.make({
408
+ subject: file.authority.subject,
409
+ fact: ProviderRejectionFact.make({
410
+ subject: file.authority.subject,
411
+ code: NonEmptyName.make(`pypi-http-${response.status}`),
412
+ detail: SafeReason.make(`PyPI upload returned HTTP ${response.status}; exact reobservation determines remote state.`)
413
+ })
414
+ })),
415
+ Effect.mapError((cause) => mutationFailure(file, cause))
416
+ )
417
+ }
418
+
419
+ const claimMutation = (decision: MutationDecision) => publication.authentication.strategy !== "token"
420
+ ? Effect.fail(new ReleaseSubjectError({
421
+ subject: file.authority.subject,
422
+ phase: "mutate",
423
+ commitment: "before-dispatch",
424
+ reason: SafeReason.make("PyPI trusted publishing is external-host-owned and cannot enter the stock terminal-claim or upload path.")
425
+ }))
426
+ : !validDecision(decision)
427
+ ? Effect.fail(new ReleaseSubjectError({
428
+ subject: file.authority.subject,
429
+ phase: "mutate",
430
+ commitment: "before-dispatch",
431
+ reason: SafeReason.make("PyPI terminal claim requires the exact visible-project file-absence decision.")
432
+ }))
433
+ : claims.claim(PublicationClaimRequest.make({
434
+ subject: file.authority.subject,
435
+ preparedDigest
436
+ })).pipe(Effect.mapError(() => new ReleaseSubjectError({
437
+ subject: file.authority.subject,
438
+ phase: "mutate",
439
+ commitment: "before-dispatch",
440
+ reason: SafeReason.make("The shared terminal PyPI publication claim was unavailable or occupied.")
441
+ })))
442
+
443
+ return {
444
+ id: file.authority.subject,
445
+ recovery: pypiRecoveryCapabilityProfile,
446
+ ...(prerequisites.length === 0 ? {} : { prerequisites: prerequisites.map((prior) => prior.authority.subject) }),
447
+ observationRequests: observationRequests(publication, file),
448
+ mutationRequest: mutationRequest(file),
449
+ claimMutation,
450
+ observe,
451
+ decide: (observation) => decide(file, observation),
452
+ mutate
453
+ }
454
+ }
455
+
456
+ export const makePyPiSubjects = (
457
+ bundle: PreparedBundle,
458
+ publication: PreparedPyPiPublication,
459
+ http: HttpAuthorizerShape,
460
+ mutationHttp: AuthorizedMutationHttpShape,
461
+ claims: PublicationClaimStoreShape = unavailablePublicationClaimStore
462
+ ): ReadonlyArray<ReleaseSubject> => publication.files.map((file, index) =>
463
+ makePyPiSubject(bundle, publication, file, http, mutationHttp, publication.files.slice(0, index), claims))