@mannyc1/ts-release 0.0.7 → 0.3.0

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 (695) hide show
  1. package/ARCHITECTURE.md +184 -92
  2. package/CHANGELOG.md +197 -0
  3. package/LICENSE +21 -0
  4. package/README.md +404 -327
  5. package/SPEC.md +257 -310
  6. package/apps/release-ts/README.md +39 -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 +122 -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 +39346 -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 +9 -0
  82. package/dist/drivers/archive.d.ts.map +1 -0
  83. package/dist/drivers/archive.js +105 -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/python-wheel.d.ts +21 -0
  106. package/dist/drivers/python-wheel.d.ts.map +1 -0
  107. package/dist/drivers/python-wheel.js +109 -0
  108. package/dist/drivers/python-wheel.js.map +1 -0
  109. package/dist/drivers/redact.d.ts +4 -0
  110. package/dist/drivers/redact.d.ts.map +1 -0
  111. package/dist/drivers/redact.js +24 -0
  112. package/dist/drivers/redact.js.map +1 -0
  113. package/dist/drivers/seccomp-helper-source.d.ts +7 -0
  114. package/dist/drivers/seccomp-helper-source.d.ts.map +1 -0
  115. package/dist/drivers/seccomp-helper-source.js +121 -0
  116. package/dist/drivers/seccomp-helper-source.js.map +1 -0
  117. package/dist/drivers/utils.d.ts +4 -0
  118. package/dist/drivers/utils.d.ts.map +1 -0
  119. package/dist/drivers/utils.js +5 -0
  120. package/dist/drivers/utils.js.map +1 -0
  121. package/dist/drivers/workspace.d.ts +9 -0
  122. package/dist/drivers/workspace.d.ts.map +1 -0
  123. package/dist/drivers/workspace.js +53 -0
  124. package/dist/drivers/workspace.js.map +1 -0
  125. package/dist/extensions/provider-adapter.d.ts +48 -0
  126. package/dist/extensions/provider-adapter.d.ts.map +1 -0
  127. package/dist/extensions/provider-adapter.js +64 -0
  128. package/dist/extensions/provider-adapter.js.map +1 -0
  129. package/dist/host.d.ts +33 -0
  130. package/dist/host.d.ts.map +1 -0
  131. package/dist/host.js +20 -0
  132. package/dist/host.js.map +1 -0
  133. package/dist/index.d.ts +19 -1
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +12 -1
  136. package/dist/index.js.map +1 -1
  137. package/dist/model/artifact-collection.d.ts +84 -0
  138. package/dist/model/artifact-collection.d.ts.map +1 -0
  139. package/dist/model/artifact-collection.js +185 -0
  140. package/dist/model/artifact-collection.js.map +1 -0
  141. package/dist/model/authority.d.ts +89 -0
  142. package/dist/model/authority.d.ts.map +1 -0
  143. package/dist/model/authority.js +80 -0
  144. package/dist/model/authority.js.map +1 -0
  145. package/dist/model/bun-targets.d.ts +34 -0
  146. package/dist/model/bun-targets.d.ts.map +1 -0
  147. package/dist/model/bun-targets.js +17 -0
  148. package/dist/model/bun-targets.js.map +1 -0
  149. package/dist/model/canonical.d.ts +8 -0
  150. package/dist/model/canonical.d.ts.map +1 -0
  151. package/dist/model/canonical.js +205 -0
  152. package/dist/model/canonical.js.map +1 -0
  153. package/dist/model/catalog.d.ts +76 -0
  154. package/dist/model/catalog.d.ts.map +1 -0
  155. package/dist/model/catalog.js +174 -0
  156. package/dist/model/catalog.js.map +1 -0
  157. package/dist/model/digest.d.ts +53 -0
  158. package/dist/model/digest.d.ts.map +1 -0
  159. package/dist/model/digest.js +127 -0
  160. package/dist/model/digest.js.map +1 -0
  161. package/dist/model/errors.d.ts +14 -0
  162. package/dist/model/errors.d.ts.map +1 -0
  163. package/dist/model/errors.js +8 -0
  164. package/dist/model/errors.js.map +1 -0
  165. package/dist/model/primitives.d.ts +19 -0
  166. package/dist/model/primitives.d.ts.map +1 -0
  167. package/dist/model/primitives.js +18 -0
  168. package/dist/model/primitives.js.map +1 -0
  169. package/dist/model/pypi.d.ts +12 -0
  170. package/dist/model/pypi.d.ts.map +1 -0
  171. package/dist/model/pypi.js +20 -0
  172. package/dist/model/pypi.js.map +1 -0
  173. package/dist/model/python-distribution.d.ts +33 -0
  174. package/dist/model/python-distribution.d.ts.map +1 -0
  175. package/dist/model/python-distribution.js +367 -0
  176. package/dist/model/python-distribution.js.map +1 -0
  177. package/dist/model/secret-patterns.d.ts +2 -0
  178. package/dist/model/secret-patterns.d.ts.map +1 -0
  179. package/dist/model/secret-patterns.js +12 -0
  180. package/dist/model/secret-patterns.js.map +1 -0
  181. package/dist/operation-journal/authority.d.ts +3 -0
  182. package/dist/operation-journal/authority.d.ts.map +1 -0
  183. package/dist/operation-journal/authority.js +147 -0
  184. package/dist/operation-journal/authority.js.map +1 -0
  185. package/dist/operation-journal/aws/deadline.d.ts +12 -0
  186. package/dist/operation-journal/aws/deadline.d.ts.map +1 -0
  187. package/dist/operation-journal/aws/deadline.js +38 -0
  188. package/dist/operation-journal/aws/deadline.js.map +1 -0
  189. package/dist/operation-journal/aws/oidc.d.ts +27 -0
  190. package/dist/operation-journal/aws/oidc.d.ts.map +1 -0
  191. package/dist/operation-journal/aws/oidc.js +361 -0
  192. package/dist/operation-journal/aws/oidc.js.map +1 -0
  193. package/dist/operation-journal/aws/policy.d.ts +42 -0
  194. package/dist/operation-journal/aws/policy.d.ts.map +1 -0
  195. package/dist/operation-journal/aws/policy.js +404 -0
  196. package/dist/operation-journal/aws/policy.js.map +1 -0
  197. package/dist/operation-journal/aws/s3-boundary.d.ts +17 -0
  198. package/dist/operation-journal/aws/s3-boundary.d.ts.map +1 -0
  199. package/dist/operation-journal/aws/s3-boundary.js +417 -0
  200. package/dist/operation-journal/aws/s3-boundary.js.map +1 -0
  201. package/dist/operation-journal/aws.d.ts +11 -0
  202. package/dist/operation-journal/aws.d.ts.map +1 -0
  203. package/dist/operation-journal/aws.js +50 -0
  204. package/dist/operation-journal/aws.js.map +1 -0
  205. package/dist/operation-journal/canonical.d.ts +52 -0
  206. package/dist/operation-journal/canonical.d.ts.map +1 -0
  207. package/dist/operation-journal/canonical.js +282 -0
  208. package/dist/operation-journal/canonical.js.map +1 -0
  209. package/dist/operation-journal/model.d.ts +251 -0
  210. package/dist/operation-journal/model.d.ts.map +1 -0
  211. package/dist/operation-journal/model.js +56 -0
  212. package/dist/operation-journal/model.js.map +1 -0
  213. package/dist/operation-journal/reducer.d.ts +28 -0
  214. package/dist/operation-journal/reducer.d.ts.map +1 -0
  215. package/dist/operation-journal/reducer.js +73 -0
  216. package/dist/operation-journal/reducer.js.map +1 -0
  217. package/dist/operation-journal/s3.d.ts +7 -0
  218. package/dist/operation-journal/s3.d.ts.map +1 -0
  219. package/dist/operation-journal/s3.js +667 -0
  220. package/dist/operation-journal/s3.js.map +1 -0
  221. package/dist/operation-journal.d.ts +14 -0
  222. package/dist/operation-journal.d.ts.map +1 -0
  223. package/dist/operation-journal.js +12 -0
  224. package/dist/operation-journal.js.map +1 -0
  225. package/dist/platform/bun.d.ts +7 -0
  226. package/dist/platform/bun.d.ts.map +1 -0
  227. package/dist/platform/bun.js +15 -0
  228. package/dist/platform/bun.js.map +1 -0
  229. package/dist/platform/credentials.d.ts +32 -0
  230. package/dist/platform/credentials.d.ts.map +1 -0
  231. package/dist/platform/credentials.js +834 -0
  232. package/dist/platform/credentials.js.map +1 -0
  233. package/dist/platform/host-support.d.ts +2 -0
  234. package/dist/platform/host-support.d.ts.map +1 -0
  235. package/dist/platform/host-support.js +4 -0
  236. package/dist/platform/host-support.js.map +1 -0
  237. package/dist/platform/node.d.ts +7 -0
  238. package/dist/platform/node.d.ts.map +1 -0
  239. package/dist/platform/node.js +15 -0
  240. package/dist/platform/node.js.map +1 -0
  241. package/dist/platform/release-runtime.d.ts +11 -0
  242. package/dist/platform/release-runtime.d.ts.map +1 -0
  243. package/dist/platform/release-runtime.js +34 -0
  244. package/dist/platform/release-runtime.js.map +1 -0
  245. package/dist/platform/services.d.ts +15 -0
  246. package/dist/platform/services.d.ts.map +1 -0
  247. package/dist/platform/services.js +30 -0
  248. package/dist/platform/services.js.map +1 -0
  249. package/dist/platform/source-observer.d.ts +5 -0
  250. package/dist/platform/source-observer.d.ts.map +1 -0
  251. package/dist/platform/source-observer.js +159 -0
  252. package/dist/platform/source-observer.js.map +1 -0
  253. package/dist/provider-sdk.d.ts +14 -0
  254. package/dist/provider-sdk.d.ts.map +1 -0
  255. package/dist/provider-sdk.js +10 -0
  256. package/dist/provider-sdk.js.map +1 -0
  257. package/dist/publication/adapter.d.ts +212 -0
  258. package/dist/publication/adapter.d.ts.map +1 -0
  259. package/dist/publication/adapter.js +83 -0
  260. package/dist/publication/adapter.js.map +1 -0
  261. package/dist/publication/authority.d.ts +137 -0
  262. package/dist/publication/authority.d.ts.map +1 -0
  263. package/dist/publication/authority.js +233 -0
  264. package/dist/publication/authority.js.map +1 -0
  265. package/dist/publication/catalog-git.d.ts +37 -0
  266. package/dist/publication/catalog-git.d.ts.map +1 -0
  267. package/dist/publication/catalog-git.js +451 -0
  268. package/dist/publication/catalog-git.js.map +1 -0
  269. package/dist/publication/claim.d.ts +39 -0
  270. package/dist/publication/claim.d.ts.map +1 -0
  271. package/dist/publication/claim.js +31 -0
  272. package/dist/publication/claim.js.map +1 -0
  273. package/dist/publication/coordinator.d.ts +246 -0
  274. package/dist/publication/coordinator.d.ts.map +1 -0
  275. package/dist/publication/coordinator.js +370 -0
  276. package/dist/publication/coordinator.js.map +1 -0
  277. package/dist/publication/github.d.ts +36 -0
  278. package/dist/publication/github.d.ts.map +1 -0
  279. package/dist/publication/github.js +695 -0
  280. package/dist/publication/github.js.map +1 -0
  281. package/dist/publication/http.d.ts +69 -0
  282. package/dist/publication/http.d.ts.map +1 -0
  283. package/dist/publication/http.js +29 -0
  284. package/dist/publication/http.js.map +1 -0
  285. package/dist/publication/index.d.ts +5 -0
  286. package/dist/publication/index.d.ts.map +1 -0
  287. package/dist/publication/index.js +5 -0
  288. package/dist/publication/index.js.map +1 -0
  289. package/dist/publication/npm.d.ts +40 -0
  290. package/dist/publication/npm.d.ts.map +1 -0
  291. package/dist/publication/npm.js +359 -0
  292. package/dist/publication/npm.js.map +1 -0
  293. package/dist/publication/profiles.d.ts +136 -0
  294. package/dist/publication/profiles.d.ts.map +1 -0
  295. package/dist/publication/profiles.js +90 -0
  296. package/dist/publication/profiles.js.map +1 -0
  297. package/dist/publication/publisher.d.ts +82 -0
  298. package/dist/publication/publisher.d.ts.map +1 -0
  299. package/dist/publication/publisher.js +53 -0
  300. package/dist/publication/publisher.js.map +1 -0
  301. package/dist/publication/pypi.d.ts +45 -0
  302. package/dist/publication/pypi.d.ts.map +1 -0
  303. package/dist/publication/pypi.js +369 -0
  304. package/dist/publication/pypi.js.map +1 -0
  305. package/dist/publication/recovery.d.ts +663 -0
  306. package/dist/publication/recovery.d.ts.map +1 -0
  307. package/dist/publication/recovery.js +252 -0
  308. package/dist/publication/recovery.js.map +1 -0
  309. package/dist/publication/report.d.ts +1061 -0
  310. package/dist/publication/report.d.ts.map +1 -0
  311. package/dist/publication/report.js +441 -0
  312. package/dist/publication/report.js.map +1 -0
  313. package/dist/recipes/config.d.ts +296 -0
  314. package/dist/recipes/config.d.ts.map +1 -0
  315. package/dist/recipes/config.js +325 -0
  316. package/dist/recipes/config.js.map +1 -0
  317. package/dist/release/capabilities.d.ts +12 -0
  318. package/dist/release/capabilities.d.ts.map +1 -0
  319. package/dist/release/capabilities.js +395 -0
  320. package/dist/release/capabilities.js.map +1 -0
  321. package/dist/release/compiler.d.ts +5 -0
  322. package/dist/release/compiler.d.ts.map +1 -0
  323. package/dist/release/compiler.js +19 -0
  324. package/dist/release/compiler.js.map +1 -0
  325. package/dist/release/config.d.ts +5 -0
  326. package/dist/release/config.d.ts.map +1 -0
  327. package/dist/release/config.js +10 -0
  328. package/dist/release/config.js.map +1 -0
  329. package/dist/release/context.d.ts +96 -0
  330. package/dist/release/context.d.ts.map +1 -0
  331. package/dist/release/context.js +168 -0
  332. package/dist/release/context.js.map +1 -0
  333. package/dist/release/graph.d.ts +271 -0
  334. package/dist/release/graph.d.ts.map +1 -0
  335. package/dist/release/graph.js +766 -0
  336. package/dist/release/graph.js.map +1 -0
  337. package/dist/release/inspect.d.ts +88 -0
  338. package/dist/release/inspect.d.ts.map +1 -0
  339. package/dist/release/inspect.js +75 -0
  340. package/dist/release/inspect.js.map +1 -0
  341. package/dist/release/prepare.d.ts +24 -0
  342. package/dist/release/prepare.d.ts.map +1 -0
  343. package/dist/release/prepare.js +1115 -0
  344. package/dist/release/prepare.js.map +1 -0
  345. package/dist/release/prepared-ref.d.ts +60 -0
  346. package/dist/release/prepared-ref.d.ts.map +1 -0
  347. package/dist/release/prepared-ref.js +131 -0
  348. package/dist/release/prepared-ref.js.map +1 -0
  349. package/dist/release/prepared-store.d.ts +90 -0
  350. package/dist/release/prepared-store.d.ts.map +1 -0
  351. package/dist/release/prepared-store.js +339 -0
  352. package/dist/release/prepared-store.js.map +1 -0
  353. package/dist/release/prepared.d.ts +172 -0
  354. package/dist/release/prepared.d.ts.map +1 -0
  355. package/dist/release/prepared.js +268 -0
  356. package/dist/release/prepared.js.map +1 -0
  357. package/dist/release/staging.d.ts +31 -0
  358. package/dist/release/staging.d.ts.map +1 -0
  359. package/dist/release/staging.js +218 -0
  360. package/dist/release/staging.js.map +1 -0
  361. package/dist/resolve/authored.d.ts +53 -0
  362. package/dist/resolve/authored.d.ts.map +1 -0
  363. package/dist/resolve/authored.js +55 -0
  364. package/dist/resolve/authored.js.map +1 -0
  365. package/dist/resolve/encode.d.ts +3 -0
  366. package/dist/resolve/encode.d.ts.map +1 -0
  367. package/dist/resolve/encode.js +21 -0
  368. package/dist/resolve/encode.js.map +1 -0
  369. package/dist/resolve/errors.d.ts +7 -0
  370. package/dist/resolve/errors.d.ts.map +1 -0
  371. package/dist/resolve/errors.js +17 -0
  372. package/dist/resolve/errors.js.map +1 -0
  373. package/dist/resolve/facts.d.ts +12 -0
  374. package/dist/resolve/facts.d.ts.map +1 -0
  375. package/dist/resolve/facts.js +18 -0
  376. package/dist/resolve/facts.js.map +1 -0
  377. package/dist/resolve/resolve.d.ts +10 -0
  378. package/dist/resolve/resolve.d.ts.map +1 -0
  379. package/dist/resolve/resolve.js +242 -0
  380. package/dist/resolve/resolve.js.map +1 -0
  381. package/dist/store.d.ts +11 -0
  382. package/dist/store.d.ts.map +1 -0
  383. package/dist/store.js +9 -0
  384. package/dist/store.js.map +1 -0
  385. package/examples/README.md +20 -30
  386. package/examples/github-release/release.config.json +11 -18
  387. package/examples/homebrew-tap/README.md +11 -0
  388. package/examples/homebrew-tap/release.config.json +40 -28
  389. package/examples/multi-target/release.config.json +28 -53
  390. package/examples/npm-first-publish/release.config.json +23 -25
  391. package/examples/npm-only/release.config.json +22 -30
  392. package/examples/portable-cli/README.md +17 -0
  393. package/examples/portable-cli/artifacts/portable-example-0.1.0-darwin-arm64 +1 -0
  394. package/examples/portable-cli/artifacts/portable-example-0.1.0-darwin-x64 +1 -0
  395. package/examples/portable-cli/artifacts/portable-example-0.1.0-linux-arm64 +1 -0
  396. package/examples/portable-cli/artifacts/portable-example-0.1.0-linux-x64 +1 -0
  397. package/examples/portable-cli/artifacts/portable-example-0.1.0-windows-x64.exe +1 -0
  398. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-macosx_10_15_x86_64.whl +1 -0
  399. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-macosx_11_0_arm64.whl +1 -0
  400. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-manylinux2014_aarch64.whl +1 -0
  401. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-manylinux2014_x86_64.whl +1 -0
  402. package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-win_amd64.whl +1 -0
  403. package/examples/portable-cli/package.json +9 -0
  404. package/examples/portable-cli/release.config.json +53 -0
  405. package/examples/portable-cli/src/cli.ts +3 -0
  406. package/examples/pypi-registry/README.md +8 -0
  407. package/examples/scoop-bucket/README.md +10 -0
  408. package/examples/scoop-bucket/release.config.json +40 -30
  409. package/package.json +73 -154
  410. package/src/api/api.ts +365 -0
  411. package/src/api/errors.ts +36 -0
  412. package/src/api/input.ts +92 -0
  413. package/src/api/runtime.ts +14 -0
  414. package/src/api/types.ts +103 -0
  415. package/src/capabilities/bun-targets.ts +2 -0
  416. package/src/capabilities/field-ownership.ts +60 -0
  417. package/src/capabilities/module.ts +158 -0
  418. package/src/capabilities/registry.ts +333 -0
  419. package/src/config/config.ts +48 -0
  420. package/src/correction/coordinator.ts +336 -0
  421. package/src/correction/index.ts +2 -0
  422. package/src/correction/intent.ts +172 -0
  423. package/src/drivers/archive.ts +111 -0
  424. package/src/drivers/contain.ts +20 -0
  425. package/src/drivers/environment.ts +28 -0
  426. package/src/drivers/errors.ts +6 -0
  427. package/src/drivers/glob.ts +137 -0
  428. package/src/drivers/process.ts +339 -0
  429. package/src/drivers/python-wheel.ts +145 -0
  430. package/src/drivers/redact.ts +31 -0
  431. package/src/drivers/seccomp-helper-source.ts +121 -0
  432. package/src/drivers/utils.ts +10 -0
  433. package/src/drivers/workspace.ts +46 -0
  434. package/src/extensions/provider-adapter.ts +99 -0
  435. package/src/host.ts +140 -0
  436. package/src/index.ts +64 -0
  437. package/src/model/artifact-collection.ts +237 -0
  438. package/src/model/authority.ts +116 -0
  439. package/src/model/bun-targets.ts +34 -0
  440. package/src/model/canonical.ts +205 -0
  441. package/src/model/catalog.ts +220 -0
  442. package/src/model/digest.ts +172 -0
  443. package/src/model/errors.ts +6 -0
  444. package/src/model/primitives.ts +37 -0
  445. package/src/model/pypi.ts +30 -0
  446. package/src/model/python-distribution.ts +360 -0
  447. package/src/model/secret-patterns.ts +11 -0
  448. package/src/operation-journal/authority.ts +162 -0
  449. package/src/operation-journal/aws/deadline.ts +41 -0
  450. package/src/operation-journal/aws/oidc.ts +439 -0
  451. package/src/operation-journal/aws/policy.ts +491 -0
  452. package/src/operation-journal/aws/s3-boundary.ts +562 -0
  453. package/src/operation-journal/aws.ts +60 -0
  454. package/src/operation-journal/canonical.ts +373 -0
  455. package/src/operation-journal/model.ts +301 -0
  456. package/src/operation-journal/reducer.ts +112 -0
  457. package/src/operation-journal/s3.ts +884 -0
  458. package/src/operation-journal.ts +51 -0
  459. package/src/platform/bun.ts +38 -0
  460. package/src/platform/credentials.ts +1151 -0
  461. package/src/platform/host-support.ts +4 -0
  462. package/src/platform/node.ts +38 -0
  463. package/src/platform/release-runtime.ts +47 -0
  464. package/src/platform/services.ts +57 -0
  465. package/src/platform/source-observer.ts +193 -0
  466. package/src/provider-sdk.ts +48 -0
  467. package/src/publication/adapter.ts +132 -0
  468. package/src/publication/authority.ts +397 -0
  469. package/src/publication/catalog-git.ts +603 -0
  470. package/src/publication/claim.ts +51 -0
  471. package/src/publication/coordinator.ts +578 -0
  472. package/src/publication/github.ts +1022 -0
  473. package/src/publication/http.ts +101 -0
  474. package/src/publication/index.ts +4 -0
  475. package/src/publication/npm.ts +525 -0
  476. package/src/publication/profiles.ts +94 -0
  477. package/src/publication/publisher.ts +130 -0
  478. package/src/publication/pypi.ts +463 -0
  479. package/src/publication/recovery.ts +349 -0
  480. package/src/publication/report.ts +628 -0
  481. package/src/recipes/config.ts +395 -0
  482. package/src/release/capabilities.ts +460 -0
  483. package/src/release/compiler.ts +28 -0
  484. package/src/release/config.ts +12 -0
  485. package/src/release/context.ts +223 -0
  486. package/src/release/graph.ts +866 -0
  487. package/src/release/inspect.ts +83 -0
  488. package/src/release/prepare.ts +1329 -0
  489. package/src/release/prepared-ref.ts +193 -0
  490. package/src/release/prepared-store.ts +378 -0
  491. package/src/release/prepared.ts +299 -0
  492. package/src/release/staging.ts +257 -0
  493. package/src/resolve/authored.ts +64 -0
  494. package/src/resolve/encode.ts +22 -0
  495. package/src/resolve/errors.ts +15 -0
  496. package/src/resolve/facts.ts +18 -0
  497. package/src/resolve/resolve.ts +328 -0
  498. package/src/store.ts +28 -0
  499. package/templates/README.md +55 -72
  500. package/templates/bun-cli-github/README.md +7 -9
  501. package/templates/bun-cli-github/release.config.json +35 -73
  502. package/templates/github-actions/release.yml +74 -0
  503. package/templates/github-actions/reviewed-release.yml +115 -0
  504. package/templates/multi-target-homebrew/README.md +10 -0
  505. package/templates/multi-target-homebrew/release.config.json +43 -60
  506. package/templates/multi-target-scoop/README.md +10 -0
  507. package/templates/multi-target-scoop/release.config.json +43 -62
  508. package/templates/npm-github/release.config.json +25 -38
  509. package/templates/npm-github/reviewed-release.config.json +37 -0
  510. package/templates/npm-only/README.md +10 -0
  511. package/templates/npm-only/release.config.json +15 -30
  512. package/templates/portable-cli/README.md +13 -0
  513. package/templates/portable-cli/release.config.json +53 -0
  514. package/dist/artifacts/adapter.d.ts +0 -39
  515. package/dist/artifacts/adapter.d.ts.map +0 -1
  516. package/dist/artifacts/adapter.js +0 -23
  517. package/dist/artifacts/adapter.js.map +0 -1
  518. package/dist/artifacts/registry.d.ts +0 -21
  519. package/dist/artifacts/registry.d.ts.map +0 -1
  520. package/dist/artifacts/registry.js +0 -21
  521. package/dist/artifacts/registry.js.map +0 -1
  522. package/dist/config/errors.d.ts +0 -24
  523. package/dist/config/errors.d.ts.map +0 -1
  524. package/dist/config/errors.js +0 -19
  525. package/dist/config/errors.js.map +0 -1
  526. package/dist/config/load.d.ts +0 -9
  527. package/dist/config/load.d.ts.map +0 -1
  528. package/dist/config/load.js +0 -29
  529. package/dist/config/load.js.map +0 -1
  530. package/dist/config/schema.d.ts +0 -12
  531. package/dist/config/schema.d.ts.map +0 -1
  532. package/dist/config/schema.js +0 -19
  533. package/dist/config/schema.js.map +0 -1
  534. package/dist/domain/artifact.d.ts +0 -118
  535. package/dist/domain/artifact.d.ts.map +0 -1
  536. package/dist/domain/artifact.js +0 -197
  537. package/dist/domain/artifact.js.map +0 -1
  538. package/dist/domain/evidence.d.ts +0 -59
  539. package/dist/domain/evidence.d.ts.map +0 -1
  540. package/dist/domain/evidence.js +0 -51
  541. package/dist/domain/evidence.js.map +0 -1
  542. package/dist/domain/operation.d.ts +0 -143
  543. package/dist/domain/operation.d.ts.map +0 -1
  544. package/dist/domain/operation.js +0 -198
  545. package/dist/domain/operation.js.map +0 -1
  546. package/dist/domain/release.d.ts +0 -93
  547. package/dist/domain/release.d.ts.map +0 -1
  548. package/dist/domain/release.js +0 -82
  549. package/dist/domain/release.js.map +0 -1
  550. package/dist/domain/remote-state.d.ts +0 -58
  551. package/dist/domain/remote-state.d.ts.map +0 -1
  552. package/dist/domain/remote-state.js +0 -55
  553. package/dist/domain/remote-state.js.map +0 -1
  554. package/dist/domain/target.d.ts +0 -148
  555. package/dist/domain/target.d.ts.map +0 -1
  556. package/dist/domain/target.js +0 -151
  557. package/dist/domain/target.js.map +0 -1
  558. package/dist/host/host.d.ts +0 -32
  559. package/dist/host/host.d.ts.map +0 -1
  560. package/dist/host/host.js +0 -24
  561. package/dist/host/host.js.map +0 -1
  562. package/dist/host/http-live.d.ts +0 -6
  563. package/dist/host/http-live.d.ts.map +0 -1
  564. package/dist/host/http-live.js +0 -86
  565. package/dist/host/http-live.js.map +0 -1
  566. package/dist/host/http.d.ts +0 -43
  567. package/dist/host/http.d.ts.map +0 -1
  568. package/dist/host/http.js +0 -59
  569. package/dist/host/http.js.map +0 -1
  570. package/dist/host/platform.d.ts +0 -10
  571. package/dist/host/platform.d.ts.map +0 -1
  572. package/dist/host/platform.js +0 -114
  573. package/dist/host/platform.js.map +0 -1
  574. package/dist/host/test.d.ts +0 -23
  575. package/dist/host/test.d.ts.map +0 -1
  576. package/dist/host/test.js +0 -222
  577. package/dist/host/test.js.map +0 -1
  578. package/dist/internal/workspace-path.d.ts +0 -15
  579. package/dist/internal/workspace-path.d.ts.map +0 -1
  580. package/dist/internal/workspace-path.js +0 -36
  581. package/dist/internal/workspace-path.js.map +0 -1
  582. package/dist/planner/artifact-inventory.d.ts +0 -9
  583. package/dist/planner/artifact-inventory.d.ts.map +0 -1
  584. package/dist/planner/artifact-inventory.js +0 -86
  585. package/dist/planner/artifact-inventory.js.map +0 -1
  586. package/dist/planner/create-release-plan.d.ts +0 -5
  587. package/dist/planner/create-release-plan.d.ts.map +0 -1
  588. package/dist/planner/create-release-plan.js +0 -25
  589. package/dist/planner/create-release-plan.js.map +0 -1
  590. package/dist/planner/errors.d.ts +0 -67
  591. package/dist/planner/errors.d.ts.map +0 -1
  592. package/dist/planner/errors.js +0 -58
  593. package/dist/planner/errors.js.map +0 -1
  594. package/dist/planner/evidence-recorder.d.ts +0 -28
  595. package/dist/planner/evidence-recorder.d.ts.map +0 -1
  596. package/dist/planner/evidence-recorder.js +0 -362
  597. package/dist/planner/evidence-recorder.js.map +0 -1
  598. package/dist/planner/executor.d.ts +0 -58
  599. package/dist/planner/executor.d.ts.map +0 -1
  600. package/dist/planner/executor.js +0 -147
  601. package/dist/planner/executor.js.map +0 -1
  602. package/dist/planner/normalize-release.d.ts +0 -19
  603. package/dist/planner/normalize-release.d.ts.map +0 -1
  604. package/dist/planner/normalize-release.js +0 -342
  605. package/dist/planner/normalize-release.js.map +0 -1
  606. package/dist/planner/reconcile.d.ts +0 -17
  607. package/dist/planner/reconcile.d.ts.map +0 -1
  608. package/dist/planner/reconcile.js +0 -259
  609. package/dist/planner/reconcile.js.map +0 -1
  610. package/dist/planner/render-plan.d.ts +0 -15
  611. package/dist/planner/render-plan.d.ts.map +0 -1
  612. package/dist/planner/render-plan.js +0 -231
  613. package/dist/planner/render-plan.js.map +0 -1
  614. package/dist/targets/adapter-helpers.d.ts +0 -79
  615. package/dist/targets/adapter-helpers.d.ts.map +0 -1
  616. package/dist/targets/adapter-helpers.js +0 -194
  617. package/dist/targets/adapter-helpers.js.map +0 -1
  618. package/dist/targets/adapter.d.ts +0 -17
  619. package/dist/targets/adapter.d.ts.map +0 -1
  620. package/dist/targets/adapter.js +0 -2
  621. package/dist/targets/adapter.js.map +0 -1
  622. package/dist/targets/github-release.d.ts +0 -25
  623. package/dist/targets/github-release.d.ts.map +0 -1
  624. package/dist/targets/github-release.js +0 -82
  625. package/dist/targets/github-release.js.map +0 -1
  626. package/dist/targets/github.d.ts +0 -11
  627. package/dist/targets/github.d.ts.map +0 -1
  628. package/dist/targets/github.js +0 -117
  629. package/dist/targets/github.js.map +0 -1
  630. package/dist/targets/homebrew.d.ts +0 -11
  631. package/dist/targets/homebrew.d.ts.map +0 -1
  632. package/dist/targets/homebrew.js +0 -210
  633. package/dist/targets/homebrew.js.map +0 -1
  634. package/dist/targets/live.d.ts +0 -5
  635. package/dist/targets/live.d.ts.map +0 -1
  636. package/dist/targets/live.js +0 -50
  637. package/dist/targets/live.js.map +0 -1
  638. package/dist/targets/npm.d.ts +0 -10
  639. package/dist/targets/npm.d.ts.map +0 -1
  640. package/dist/targets/npm.js +0 -112
  641. package/dist/targets/npm.js.map +0 -1
  642. package/dist/targets/pypi.d.ts +0 -11
  643. package/dist/targets/pypi.d.ts.map +0 -1
  644. package/dist/targets/pypi.js +0 -141
  645. package/dist/targets/pypi.js.map +0 -1
  646. package/dist/targets/registry.d.ts +0 -25
  647. package/dist/targets/registry.d.ts.map +0 -1
  648. package/dist/targets/registry.js +0 -34
  649. package/dist/targets/registry.js.map +0 -1
  650. package/dist/targets/scoop.d.ts +0 -11
  651. package/dist/targets/scoop.d.ts.map +0 -1
  652. package/dist/targets/scoop.js +0 -88
  653. package/dist/targets/scoop.js.map +0 -1
  654. package/dist/types/effect-internal.d.ts +0 -5
  655. package/dist/types/effect-internal.d.ts.map +0 -1
  656. package/dist/types/effect-internal.js +0 -2
  657. package/dist/types/effect-internal.js.map +0 -1
  658. package/dist/workflows/config.d.ts +0 -202
  659. package/dist/workflows/config.d.ts.map +0 -1
  660. package/dist/workflows/config.js +0 -420
  661. package/dist/workflows/config.js.map +0 -1
  662. package/dist/workflows/diagnostics.d.ts +0 -62
  663. package/dist/workflows/diagnostics.d.ts.map +0 -1
  664. package/dist/workflows/diagnostics.js +0 -530
  665. package/dist/workflows/diagnostics.js.map +0 -1
  666. package/dist/workflows/distribution.d.ts +0 -25
  667. package/dist/workflows/distribution.d.ts.map +0 -1
  668. package/dist/workflows/distribution.js +0 -24
  669. package/dist/workflows/distribution.js.map +0 -1
  670. package/dist/workflows/evidence.d.ts +0 -17
  671. package/dist/workflows/evidence.d.ts.map +0 -1
  672. package/dist/workflows/evidence.js +0 -32
  673. package/dist/workflows/evidence.js.map +0 -1
  674. package/dist/workflows/index.d.ts +0 -8
  675. package/dist/workflows/index.d.ts.map +0 -1
  676. package/dist/workflows/index.js +0 -7
  677. package/dist/workflows/index.js.map +0 -1
  678. package/dist/workflows/init.d.ts +0 -84
  679. package/dist/workflows/init.d.ts.map +0 -1
  680. package/dist/workflows/init.js +0 -487
  681. package/dist/workflows/init.js.map +0 -1
  682. package/dist/workflows/live.d.ts +0 -9
  683. package/dist/workflows/live.d.ts.map +0 -1
  684. package/dist/workflows/live.js +0 -7
  685. package/dist/workflows/live.js.map +0 -1
  686. package/dist/workflows/options.d.ts +0 -30
  687. package/dist/workflows/options.d.ts.map +0 -1
  688. package/dist/workflows/options.js +0 -16
  689. package/dist/workflows/options.js.map +0 -1
  690. package/examples/non-strict-skips/artifacts/release-example-skips-0.1.0.tgz +0 -1
  691. package/examples/non-strict-skips/release.config.json +0 -31
  692. package/examples/pypi-registry/release.config.json +0 -31
  693. package/templates/github-actions/plan-and-approved-execute.yml +0 -58
  694. package/templates/github-actions/plan-only.yml +0 -33
  695. package/templates/github-actions/trusted-publishing.yml +0 -61
@@ -0,0 +1,1022 @@
1
+ import * as Effect from "effect/Effect"
2
+ import type { CredentialRequest, ResolvedAuthStrategy } from "../model/authority.js"
3
+ import { CredentialRequest as CredentialRequestSchema } from "../model/authority.js"
4
+ import {
5
+ digestEquals,
6
+ formatGitHubSha256,
7
+ formatSha256Hex,
8
+ parseGitHubSha256,
9
+ sha256Digest,
10
+ type Sha256Digest
11
+ } from "../model/digest.js"
12
+ import { NonEmptyName } from "../model/primitives.js"
13
+ import type { PreparedGitHubPublication } from "../release/prepared.js"
14
+ import type { PreparedBundle } from "../release/prepared-store.js"
15
+ import type {
16
+ CredentialGrant,
17
+ MutationCredentialGrant,
18
+ PublisherOperation,
19
+ ScopedSecret
20
+ } from "./authority.js"
21
+ import {
22
+ ReleaseSubjectError,
23
+ type ReleaseObservationContext,
24
+ type ReleaseSubject
25
+ } from "./coordinator.js"
26
+ import type {
27
+ AuthorizedMutationHttpShape,
28
+ HttpAuthorizerShape,
29
+ HttpResponse,
30
+ MutationHttpRequest
31
+ } from "./http.js"
32
+ import {
33
+ AbsenceBasis,
34
+ Applied,
35
+ AuthoritativelyAbsent,
36
+ Conflict,
37
+ CreateAuthorizationProof,
38
+ Difference,
39
+ InconclusiveObservation,
40
+ MutationPrecondition,
41
+ NeedsMutation,
42
+ OutcomeUnknown,
43
+ PresentDifferent,
44
+ PresentEquivalent,
45
+ ProviderAlreadyEquivalent,
46
+ ProviderAuthorizedCreate,
47
+ ProviderBlocked,
48
+ ProviderMutationFact,
49
+ SafeReason,
50
+ VisibilityBasis,
51
+ VisibilityPending,
52
+ type MutationAttempt,
53
+ type MutationDecision,
54
+ type Observation,
55
+ type ProviderDecision
56
+ } from "./report.js"
57
+ import { makeRecoveryCapabilityProfile } from "./recovery.js"
58
+
59
+ const githubApiVersion = "2022-11-28"
60
+ const githubJsonHeaders = {
61
+ accept: "application/vnd.github+json",
62
+ "x-github-api-version": githubApiVersion
63
+ } as const
64
+ const gitObjectSha = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u
65
+ const maximumTagDepth = 32
66
+ const maximumAssetPages = 1_000
67
+
68
+ /**
69
+ * Timing values are policy bounds, not measured provider facts. They remain
70
+ * ASSUMED/UNVERIFIED until an explicitly authorized live run records evidence.
71
+ */
72
+ export const githubRecoveryCapabilityProfile = makeRecoveryCapabilityProfile({
73
+ observation: "exact",
74
+ authoritativeAbsence: "provider-specific",
75
+ createAuthorization: "authenticated-namespace-and-unique-coordinate",
76
+ replay: "coordinate-unique",
77
+ identifierReuse: "reusable",
78
+ correction: [],
79
+ exposure: "persistent-to-consumers",
80
+ historyRequirement: "optional-evidence",
81
+ readConvergence: {
82
+ contract: {
83
+ _tag: "assumed",
84
+ basis: "ASSUMED/UNVERIFIED: no authorized live mutation evidence establishes GitHub read-convergence timing."
85
+ },
86
+ observationRetry: {
87
+ maxAttempts: 5,
88
+ backoff: { baseMs: 1_000, factor: 2, capMs: 15_000 },
89
+ totalBudgetMs: 60_000
90
+ },
91
+ retryEligible: "VisibilityPending | Inconclusive",
92
+ exhaustion: "UncertainSubject with full trace"
93
+ }
94
+ })
95
+
96
+ interface RepositoryFacts {
97
+ readonly fullName: string
98
+ }
99
+
100
+ interface GitObjectFacts {
101
+ readonly type: "commit" | "tag"
102
+ readonly sha: string
103
+ }
104
+
105
+ interface ReleaseFacts {
106
+ readonly id: number
107
+ readonly tag: string
108
+ readonly title: string
109
+ readonly body: string
110
+ readonly draft: boolean
111
+ readonly prerelease: boolean
112
+ readonly uploadUrl: string
113
+ }
114
+
115
+ interface ReleaseAssetFacts {
116
+ readonly name: string
117
+ readonly size: number
118
+ readonly mediaType: string
119
+ readonly state: "uploaded"
120
+ readonly digest:
121
+ | { readonly _tag: "Present", readonly value: Sha256Digest }
122
+ | { readonly _tag: "Missing" }
123
+ | { readonly _tag: "Malformed" }
124
+ readonly apiUrl: string
125
+ }
126
+
127
+ type GithubMutationPlan =
128
+ | {
129
+ readonly _tag: "CreateRelease"
130
+ readonly tagAbsent: boolean
131
+ }
132
+ | {
133
+ readonly _tag: "UploadAssets"
134
+ readonly releaseId: number
135
+ readonly uploadUrl: string
136
+ readonly assetIndexes: ReadonlyArray<number>
137
+ }
138
+ | {
139
+ readonly _tag: "PublishRelease"
140
+ readonly releaseId: number
141
+ }
142
+
143
+ type PageResult =
144
+ | { readonly _tag: "Complete", readonly assets: ReadonlyArray<ReleaseAssetFacts> }
145
+ | { readonly _tag: "Inconclusive", readonly reason: string }
146
+
147
+ const asObject = (value: unknown): Readonly<Record<string, unknown>> | undefined =>
148
+ typeof value === "object" && value !== null && !Array.isArray(value)
149
+ ? value as Readonly<Record<string, unknown>>
150
+ : undefined
151
+
152
+ const asString = (value: unknown): string | undefined =>
153
+ typeof value === "string" ? value : undefined
154
+
155
+ const asNonEmptyString = (value: unknown): string | undefined =>
156
+ typeof value === "string" && value.length > 0 ? value : undefined
157
+
158
+ const asBoolean = (value: unknown): boolean | undefined =>
159
+ typeof value === "boolean" ? value : undefined
160
+
161
+ const asNonNegativeSize = (value: unknown): number | undefined =>
162
+ typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined
163
+
164
+ const asPositiveId = (value: unknown): number | undefined =>
165
+ typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined
166
+
167
+ const parseJson = (body: Uint8Array | string): unknown | undefined => {
168
+ try {
169
+ const text = typeof body === "string"
170
+ ? body
171
+ : new TextDecoder("utf-8", { fatal: true }).decode(body)
172
+ return JSON.parse(text) as unknown
173
+ } catch {
174
+ return undefined
175
+ }
176
+ }
177
+
178
+ const responseHeader = (response: HttpResponse, name: string): string | undefined => {
179
+ const lower = name.toLowerCase()
180
+ return Object.entries(response.headers).find(([candidate]) => candidate.toLowerCase() === lower)?.[1]
181
+ }
182
+
183
+ const repositoryFacts = (value: unknown): RepositoryFacts | undefined => {
184
+ const object = asObject(value)
185
+ const fullName = asNonEmptyString(object?.full_name)
186
+ return fullName === undefined ? undefined : { fullName }
187
+ }
188
+
189
+ const gitObjectFacts = (value: unknown): GitObjectFacts | undefined => {
190
+ const object = asObject(value)
191
+ const type = object?.type
192
+ const sha = asString(object?.sha)
193
+ return (type !== "commit" && type !== "tag") || sha === undefined || !gitObjectSha.test(sha)
194
+ ? undefined
195
+ : { type, sha }
196
+ }
197
+
198
+ const parseRef = (
199
+ value: unknown,
200
+ expectedRef: string
201
+ ): GitObjectFacts | undefined => {
202
+ const object = asObject(value)
203
+ return object?.ref !== expectedRef ? undefined : gitObjectFacts(object.object)
204
+ }
205
+
206
+ const parseTagObject = (
207
+ value: unknown,
208
+ expectedSha: string
209
+ ): GitObjectFacts | undefined => {
210
+ const object = asObject(value)
211
+ return object?.sha !== expectedSha ? undefined : gitObjectFacts(object.object)
212
+ }
213
+
214
+ const releaseFacts = (value: unknown): ReleaseFacts | undefined => {
215
+ const object = asObject(value)
216
+ const id = asPositiveId(object?.id)
217
+ const tag = asNonEmptyString(object?.tag_name)
218
+ const title = asString(object?.name)
219
+ const body = object?.body === null ? "" : asString(object?.body)
220
+ const draft = asBoolean(object?.draft)
221
+ const prerelease = asBoolean(object?.prerelease)
222
+ const uploadUrl = asNonEmptyString(object?.upload_url)
223
+ return id === undefined || tag === undefined || title === undefined || body === undefined ||
224
+ draft === undefined || prerelease === undefined || uploadUrl === undefined
225
+ ? undefined
226
+ : { id, tag, title, body, draft, prerelease, uploadUrl }
227
+ }
228
+
229
+ const releaseAssetApiUrl = (
230
+ publication: PreparedGitHubPublication,
231
+ value: unknown
232
+ ): string | undefined => {
233
+ if (typeof value !== "string") return undefined
234
+ const prefix = `${publication.authority.audience}/releases/assets/`
235
+ const id = value.startsWith(prefix) ? value.slice(prefix.length) : ""
236
+ return /^[1-9][0-9]*$/u.test(id) ? value : undefined
237
+ }
238
+
239
+ const releaseAssetDigest = (
240
+ asset: Readonly<Record<string, unknown>>
241
+ ): ReleaseAssetFacts["digest"] => {
242
+ if (!Object.hasOwn(asset, "digest") || asset.digest === null) return { _tag: "Missing" }
243
+ try {
244
+ return { _tag: "Present", value: parseGitHubSha256(asset.digest) }
245
+ } catch {
246
+ return { _tag: "Malformed" }
247
+ }
248
+ }
249
+
250
+ const releaseAssetFacts = (
251
+ publication: PreparedGitHubPublication,
252
+ value: unknown
253
+ ): ReleaseAssetFacts | undefined => {
254
+ const object = asObject(value)
255
+ if (object === undefined) return undefined
256
+ const name = asNonEmptyString(object.name)
257
+ const size = asNonNegativeSize(object.size)
258
+ const mediaType = asNonEmptyString(object.content_type)
259
+ const apiUrl = releaseAssetApiUrl(publication, object.url)
260
+ if (name === undefined || size === undefined || mediaType === undefined ||
261
+ object.state !== "uploaded" || apiUrl === undefined) return undefined
262
+ return {
263
+ name,
264
+ size,
265
+ mediaType,
266
+ state: "uploaded",
267
+ digest: releaseAssetDigest(object),
268
+ apiUrl
269
+ }
270
+ }
271
+
272
+ const observationRequests = (
273
+ publication: PreparedGitHubPublication
274
+ ): readonly [CredentialRequest, ...Array<CredentialRequest>] => {
275
+ const make = (strategy: ResolvedAuthStrategy): CredentialRequest => CredentialRequestSchema.make({
276
+ subject: publication.authority.subject,
277
+ provider: publication.authority.provider,
278
+ audience: publication.authority.audience,
279
+ purpose: "observe",
280
+ strategy
281
+ })
282
+ const first = publication.authority.observationStrategies[0]!
283
+ return [make(first), ...publication.authority.observationStrategies.slice(1).map(make)]
284
+ }
285
+
286
+ const mutationRequest = (publication: PreparedGitHubPublication): CredentialRequest =>
287
+ CredentialRequestSchema.make({
288
+ subject: publication.authority.subject,
289
+ provider: publication.authority.provider,
290
+ audience: publication.authority.audience,
291
+ purpose: "publish",
292
+ strategy: publication.authority.publishStrategy
293
+ })
294
+
295
+ const valueFingerprint = (origin: "prepared" | "provider", value: string): SafeReason =>
296
+ SafeReason.make(
297
+ `${origin} value sha256-${formatSha256Hex(sha256Digest(new TextEncoder().encode(value)))}`
298
+ )
299
+
300
+ const textDifference = (
301
+ field: string,
302
+ expected: string,
303
+ observed: string
304
+ ): Difference => Difference.make({
305
+ field: NonEmptyName.make(field),
306
+ expected: valueFingerprint("prepared", expected),
307
+ observed: valueFingerprint("provider", observed)
308
+ })
309
+
310
+ const scalarDifference = (
311
+ field: string,
312
+ expected: boolean | number | string,
313
+ observed: boolean | number | string
314
+ ): Difference => Difference.make({
315
+ field: NonEmptyName.make(field),
316
+ expected: SafeReason.make(String(expected)),
317
+ observed: SafeReason.make(String(observed))
318
+ })
319
+
320
+ const inconclusive = (
321
+ publication: PreparedGitHubPublication,
322
+ reason: string
323
+ ): InconclusiveObservation => InconclusiveObservation.make({
324
+ subject: publication.authority.subject,
325
+ reason: SafeReason.make(reason)
326
+ })
327
+
328
+ const pending = (
329
+ publication: PreparedGitHubPublication,
330
+ detail: string
331
+ ): VisibilityPending => VisibilityPending.make({
332
+ subject: publication.authority.subject,
333
+ expectation: SafeReason.make("The accepted GitHub mutation is expected to become exactly observable."),
334
+ basis: VisibilityBasis.make({
335
+ kind: NonEmptyName.make("github-read-convergence"),
336
+ detail: SafeReason.make(detail)
337
+ })
338
+ })
339
+
340
+ const absent = (
341
+ publication: PreparedGitHubPublication,
342
+ kind: string,
343
+ detail: string
344
+ ): AuthoritativelyAbsent => AuthoritativelyAbsent.make({
345
+ subject: publication.authority.subject,
346
+ basis: AbsenceBasis.make({
347
+ kind: NonEmptyName.make(kind),
348
+ detail: SafeReason.make(detail)
349
+ })
350
+ })
351
+
352
+ const observationFailure = (
353
+ publication: PreparedGitHubPublication,
354
+ reason: string,
355
+ commitment: "before-dispatch" | "unknown" = "unknown"
356
+ ): ReleaseSubjectError => new ReleaseSubjectError({
357
+ subject: publication.authority.subject,
358
+ phase: "observe",
359
+ commitment,
360
+ reason: SafeReason.make(reason)
361
+ })
362
+
363
+ const mapObservationError = (
364
+ publication: PreparedGitHubPublication,
365
+ error: { readonly _tag: string, readonly commitment?: "before-dispatch" | "unknown" }
366
+ ): ReleaseSubjectError => observationFailure(
367
+ publication,
368
+ "GitHub observation could not be completed by the host HTTP boundary.",
369
+ error._tag === "CredentialPlatformError" ? error.commitment ?? "before-dispatch" : "before-dispatch"
370
+ )
371
+
372
+ const get = (
373
+ publication: PreparedGitHubPublication,
374
+ http: HttpAuthorizerShape,
375
+ grant: Exclude<CredentialGrant, { readonly _tag: "WorkloadIdentity" }>,
376
+ url: string,
377
+ accept = "application/vnd.github+json"
378
+ ): Effect.Effect<HttpResponse, ReleaseSubjectError> => http.execute({
379
+ subject: publication.authority.subject,
380
+ method: "GET",
381
+ url,
382
+ headers: { accept, "x-github-api-version": githubApiVersion }
383
+ }, grant).pipe(Effect.mapError((error) => mapObservationError(publication, error)))
384
+
385
+ const releaseUrl = (publication: PreparedGitHubPublication): string =>
386
+ `${publication.authority.audience}/releases/tags/${encodeURIComponent(publication.tag)}`
387
+
388
+ const refUrl = (publication: PreparedGitHubPublication): string =>
389
+ `${publication.authority.audience}/git/ref/tags/${encodeURIComponent(publication.tag)}`
390
+
391
+ const tagObjectUrl = (publication: PreparedGitHubPublication, sha: string): string =>
392
+ `${publication.authority.audience}/git/tags/${sha}`
393
+
394
+ const assetsPageUrl = (
395
+ publication: PreparedGitHubPublication,
396
+ releaseId: number,
397
+ page: number
398
+ ): string => `${publication.authority.audience}/releases/${releaseId}/assets?per_page=100&page=${page}`
399
+
400
+ const linkNextUrl = (value: string | undefined): { readonly malformed: boolean, readonly url?: string } => {
401
+ if (value === undefined) return { malformed: false }
402
+ const matches = [...value.matchAll(/<([^>]+)>\s*;\s*rel="next"/gu)]
403
+ if (matches.length > 1 || (/rel="next"/u.test(value) && matches.length !== 1)) return { malformed: true }
404
+ const url = matches[0]?.[1]
405
+ return url === undefined ? { malformed: false } : { malformed: false, url }
406
+ }
407
+
408
+ const listAllAssets = Effect.fn("GitHubReleaseSubject.listAllAssets")(function*(
409
+ publication: PreparedGitHubPublication,
410
+ http: HttpAuthorizerShape,
411
+ grant: Exclude<CredentialGrant, { readonly _tag: "WorkloadIdentity" }>,
412
+ releaseId: number
413
+ ) {
414
+ const assets: Array<ReleaseAssetFacts> = []
415
+ let page = 1
416
+ while (page <= maximumAssetPages) {
417
+ const expectedUrl = assetsPageUrl(publication, releaseId, page)
418
+ const response = yield* get(publication, http, grant, expectedUrl)
419
+ if (response.status !== 200) {
420
+ return { _tag: "Inconclusive", reason: `GitHub asset enumeration returned HTTP ${response.status}.` } satisfies PageResult
421
+ }
422
+ const raw = parseJson(response.body)
423
+ if (!Array.isArray(raw)) {
424
+ return { _tag: "Inconclusive", reason: "A GitHub asset page was malformed." } satisfies PageResult
425
+ }
426
+ const parsed = raw.map((value) => releaseAssetFacts(publication, value))
427
+ if (parsed.some((value) => value === undefined)) {
428
+ return {
429
+ _tag: "Inconclusive",
430
+ reason: "A GitHub asset page omitted or malformed an exact asset identity fact."
431
+ } satisfies PageResult
432
+ }
433
+ assets.push(...parsed as Array<ReleaseAssetFacts>)
434
+ const next = linkNextUrl(responseHeader(response, "link"))
435
+ if (next.malformed) {
436
+ return { _tag: "Inconclusive", reason: "GitHub asset pagination metadata was malformed." } satisfies PageResult
437
+ }
438
+ const computedNext = assetsPageUrl(publication, releaseId, page + 1)
439
+ if (next.url !== undefined && next.url !== computedNext) {
440
+ return {
441
+ _tag: "Inconclusive",
442
+ reason: "GitHub asset pagination left the exact prepared repository scope or skipped a page."
443
+ } satisfies PageResult
444
+ }
445
+ if (next.url === undefined && raw.length < 100) {
446
+ return { _tag: "Complete", assets } satisfies PageResult
447
+ }
448
+ page += 1
449
+ }
450
+ return {
451
+ _tag: "Inconclusive",
452
+ reason: "GitHub asset pagination exceeded the bounded full-enumeration limit."
453
+ } satisfies PageResult
454
+ })
455
+
456
+ const compareReleaseMetadata = (
457
+ publication: PreparedGitHubPublication,
458
+ facts: ReleaseFacts,
459
+ options: { readonly ignoreDraft?: boolean } = {}
460
+ ): ReadonlyArray<Difference> => {
461
+ const differences: Array<Difference> = []
462
+ if (facts.tag !== publication.tag.toString()) {
463
+ differences.push(textDifference("release.tag", publication.tag.toString(), facts.tag))
464
+ }
465
+ if (facts.title !== publication.title.toString()) {
466
+ differences.push(textDifference("release.title", publication.title.toString(), facts.title))
467
+ }
468
+ const expectedBody = publication.body ?? ""
469
+ if (facts.body !== expectedBody) differences.push(textDifference("release.body", expectedBody, facts.body))
470
+ if (options.ignoreDraft !== true && facts.draft !== publication.draft) {
471
+ differences.push(scalarDifference("release.draft", publication.draft, facts.draft))
472
+ }
473
+ if (facts.prerelease !== publication.prerelease) {
474
+ differences.push(scalarDifference("release.prerelease", publication.prerelease, facts.prerelease))
475
+ }
476
+ return differences
477
+ }
478
+
479
+ const validateUploadUrl = (
480
+ publication: PreparedGitHubPublication,
481
+ releaseId: number,
482
+ value: string
483
+ ): boolean => value ===
484
+ `https://uploads.github.com/repos/${publication.repository}/releases/${releaseId}/assets{?name,label}`
485
+
486
+ const presentDifferent = (
487
+ publication: PreparedGitHubPublication,
488
+ differences: ReadonlyArray<Difference>
489
+ ): PresentDifferent => PresentDifferent.make({
490
+ subject: publication.authority.subject,
491
+ differences: differences as [Difference, ...Array<Difference>]
492
+ })
493
+
494
+ const decideFor = (
495
+ publication: PreparedGitHubPublication,
496
+ observation: Observation,
497
+ plan: GithubMutationPlan | undefined
498
+ ): ProviderDecision => {
499
+ switch (observation._tag) {
500
+ case "PresentEquivalent":
501
+ return ProviderAlreadyEquivalent.make({ subject: publication.authority.subject })
502
+ case "PresentDifferent":
503
+ return Conflict.make({ subject: publication.authority.subject, differences: observation.differences })
504
+ case "AuthoritativelyAbsent":
505
+ if (plan?._tag === "CreateRelease" && plan.tagAbsent) {
506
+ return ProviderAuthorizedCreate.make({
507
+ subject: publication.authority.subject,
508
+ proof: CreateAuthorizationProof.make({
509
+ kind: NonEmptyName.make("github-authenticated-repository-and-unique-tag")
510
+ })
511
+ })
512
+ }
513
+ if (plan?._tag === "CreateRelease") {
514
+ return NeedsMutation.make({
515
+ subject: publication.authority.subject,
516
+ precondition: MutationPrecondition.make({
517
+ kind: NonEmptyName.make("github-release-absent-at-exact-tag")
518
+ })
519
+ })
520
+ }
521
+ if (plan?._tag === "UploadAssets") {
522
+ return NeedsMutation.make({
523
+ subject: publication.authority.subject,
524
+ precondition: MutationPrecondition.make({
525
+ kind: NonEmptyName.make("github-intended-assets-authoritatively-absent")
526
+ })
527
+ })
528
+ }
529
+ if (plan?._tag === "PublishRelease") {
530
+ return NeedsMutation.make({
531
+ subject: publication.authority.subject,
532
+ precondition: MutationPrecondition.make({
533
+ kind: NonEmptyName.make("github-exact-draft-assets-complete")
534
+ })
535
+ })
536
+ }
537
+ return ProviderBlocked.make({
538
+ subject: publication.authority.subject,
539
+ reason: SafeReason.make("GitHub absence carried no exact mutation plan.")
540
+ })
541
+ case "VisibilityPending":
542
+ case "Inconclusive":
543
+ return ProviderBlocked.make({
544
+ subject: publication.authority.subject,
545
+ reason: SafeReason.make("GitHub publication truth is not yet conclusive enough to mutate.")
546
+ })
547
+ }
548
+ }
549
+
550
+ const mutationFailure = (
551
+ publication: PreparedGitHubPublication,
552
+ reason: string,
553
+ commitment: "before-dispatch" | "unknown" = "before-dispatch"
554
+ ): ReleaseSubjectError => new ReleaseSubjectError({
555
+ subject: publication.authority.subject,
556
+ phase: "mutate",
557
+ commitment,
558
+ reason: SafeReason.make(reason)
559
+ })
560
+
561
+ const unknownAttempt = (
562
+ publication: PreparedGitHubPublication,
563
+ reason: string
564
+ ): OutcomeUnknown => OutcomeUnknown.make({
565
+ subject: publication.authority.subject,
566
+ reason: SafeReason.make(reason)
567
+ })
568
+
569
+ const appliedAttempt = (publication: PreparedGitHubPublication): Applied => Applied.make({
570
+ subject: publication.authority.subject,
571
+ fact: ProviderMutationFact.make({
572
+ subject: publication.authority.subject,
573
+ detail: SafeReason.make(
574
+ "GitHub accepted every mutation dispatched in this convergence phase; final equivalence still requires provider reobservation."
575
+ )
576
+ })
577
+ })
578
+
579
+ type MutationExchange =
580
+ | { readonly _tag: "Response", readonly response: HttpResponse }
581
+ | {
582
+ readonly _tag: "Failure"
583
+ readonly error: { readonly _tag: string, readonly commitment?: "before-dispatch" | "unknown" }
584
+ }
585
+
586
+ const executeMutation = (
587
+ mutationHttp: AuthorizedMutationHttpShape,
588
+ operation: PublisherOperation,
589
+ request: MutationHttpRequest,
590
+ grant: ScopedSecret
591
+ ): Effect.Effect<MutationExchange> => mutationHttp.execute(operation, request, grant).pipe(
592
+ Effect.map((response) => ({ _tag: "Response", response } as const)),
593
+ Effect.catch((error) => Effect.succeed({ _tag: "Failure", error } as const))
594
+ )
595
+
596
+ const uploadRequest = (
597
+ publication: PreparedGitHubPublication,
598
+ uploadUrl: string,
599
+ index: number,
600
+ bytes: Uint8Array
601
+ ): MutationHttpRequest => {
602
+ const asset = publication.assets[index]!
603
+ const template = "{?name,label}"
604
+ const expanded = `${uploadUrl.slice(0, -template.length)}?name=${encodeURIComponent(asset.name)}`
605
+ return {
606
+ method: "POST",
607
+ url: expanded,
608
+ headers: {
609
+ ...githubJsonHeaders,
610
+ "content-type": asset.mediaType,
611
+ "content-length": String(bytes.length)
612
+ },
613
+ body: bytes
614
+ }
615
+ }
616
+
617
+ /**
618
+ * The exact tag ref and recursively peeled commit are established before the
619
+ * release endpoint can contribute any equality or absence fact. Release
620
+ * target_commitish is intentionally ignored: GitHub does not make it a stable
621
+ * equality fact for a release whose tag already exists.
622
+ */
623
+ export const makeGithubSubjects = (
624
+ bundle: PreparedBundle,
625
+ publication: PreparedGitHubPublication,
626
+ http: HttpAuthorizerShape,
627
+ mutationHttp: AuthorizedMutationHttpShape
628
+ ): readonly [ReleaseSubject] => {
629
+ let mutationPlan: GithubMutationPlan | undefined
630
+
631
+ const observe = Effect.fn("GitHubReleaseSubject.observe")(function*(
632
+ grant: CredentialGrant,
633
+ context: ReleaseObservationContext
634
+ ) {
635
+ mutationPlan = undefined
636
+ const mutationMayStillBecomeVisible = context.phase === "post-mutation" &&
637
+ context.attempt._tag !== "RejectedBeforeDispatch" &&
638
+ context.attempt._tag !== "RejectedByProvider"
639
+ if (grant._tag === "WorkloadIdentity") {
640
+ return yield* observationFailure(
641
+ publication,
642
+ "Workload identity cannot authorize GitHub release observation.",
643
+ "before-dispatch"
644
+ )
645
+ }
646
+ const targetCommit = publication.targetCommit.toString()
647
+ if (!gitObjectSha.test(targetCommit)) {
648
+ return inconclusive(publication, "The prepared GitHub target commit is not a canonical full Git object identifier.")
649
+ }
650
+ const intendedNames = publication.assets.map((asset) => asset.name)
651
+ if (new Set(intendedNames).size !== intendedNames.length) {
652
+ return inconclusive(publication, "The prepared GitHub release repeats an asset name and cannot define an exact set.")
653
+ }
654
+ if (publication.assets.some((asset) => bundle.blobs.get(asset.artifactId.toString()) === undefined)) {
655
+ return inconclusive(publication, "The prepared GitHub release is missing exact bytes for an intended asset.")
656
+ }
657
+
658
+ const repositoryResponse = yield* get(publication, http, grant, publication.authority.audience)
659
+ if (repositoryResponse.status !== 200) {
660
+ return inconclusive(publication, `GitHub repository visibility returned HTTP ${repositoryResponse.status}.`)
661
+ }
662
+ const repository = repositoryFacts(parseJson(repositoryResponse.body))
663
+ if (repository === undefined || repository.fullName !== publication.repository) {
664
+ return inconclusive(publication, "GitHub repository visibility did not establish the exact prepared repository coordinate.")
665
+ }
666
+
667
+ const referenceResponse = yield* get(publication, http, grant, refUrl(publication))
668
+ if (referenceResponse.status === 404) {
669
+ if (grant._tag === "AnonymousAccess") {
670
+ return inconclusive(publication, "Anonymous GitHub tag absence cannot authorize creation in the prepared namespace.")
671
+ }
672
+ const danglingRelease = yield* get(publication, http, grant, releaseUrl(publication))
673
+ if (danglingRelease.status === 200) {
674
+ return presentDifferent(publication, [scalarDifference("tag.exists", true, false)])
675
+ }
676
+ if (danglingRelease.status !== 404) {
677
+ return inconclusive(publication, `GitHub release observation returned HTTP ${danglingRelease.status}.`)
678
+ }
679
+ if (mutationMayStillBecomeVisible) {
680
+ return pending(publication, "The exact GitHub tag and release are not visible after the accepted mutation.")
681
+ }
682
+ mutationPlan = { _tag: "CreateRelease", tagAbsent: true }
683
+ return absent(
684
+ publication,
685
+ "github-authenticated-tag-and-release-absent",
686
+ "Authenticated exact-repository reads found neither the prepared tag nor its release coordinate."
687
+ )
688
+ }
689
+ if (referenceResponse.status !== 200) {
690
+ return inconclusive(publication, `GitHub tag-ref observation returned HTTP ${referenceResponse.status}.`)
691
+ }
692
+ let object = parseRef(parseJson(referenceResponse.body), `refs/tags/${publication.tag}`)
693
+ if (object === undefined) {
694
+ return inconclusive(publication, "The GitHub tag-ref response was malformed or named a different ref.")
695
+ }
696
+ const visited = new Set<string>()
697
+ for (let depth = 0; object.type === "tag"; depth += 1) {
698
+ if (depth >= maximumTagDepth || visited.has(object.sha)) {
699
+ return inconclusive(publication, "The GitHub annotated-tag chain was cyclic or exceeded its bounded peel depth.")
700
+ }
701
+ visited.add(object.sha)
702
+ const tagResponse = yield* get(publication, http, grant, tagObjectUrl(publication, object.sha))
703
+ if (tagResponse.status !== 200) {
704
+ return inconclusive(publication, `GitHub annotated-tag observation returned HTTP ${tagResponse.status}.`)
705
+ }
706
+ const peeled = parseTagObject(parseJson(tagResponse.body), object.sha)
707
+ if (peeled === undefined) {
708
+ return inconclusive(publication, "A GitHub annotated-tag object was malformed or changed identity while peeling.")
709
+ }
710
+ object = peeled
711
+ }
712
+ if (object.sha !== targetCommit) {
713
+ return presentDifferent(publication, [textDifference("tag.commit", targetCommit, object.sha)])
714
+ }
715
+
716
+ const releaseResponse = yield* get(publication, http, grant, releaseUrl(publication))
717
+ if (releaseResponse.status === 404) {
718
+ if (grant._tag === "AnonymousAccess") {
719
+ return inconclusive(publication, "Anonymous GitHub release absence may hide a draft release and is not authoritative.")
720
+ }
721
+ if (mutationMayStillBecomeVisible) {
722
+ return pending(publication, "The exact GitHub release is not yet visible after the accepted mutation.")
723
+ }
724
+ mutationPlan = { _tag: "CreateRelease", tagAbsent: false }
725
+ return absent(
726
+ publication,
727
+ "github-authenticated-release-absent",
728
+ "Authenticated exact-repository reads found the prepared tag but no release at that tag."
729
+ )
730
+ }
731
+ if (releaseResponse.status !== 200) {
732
+ return inconclusive(publication, `GitHub release observation returned HTTP ${releaseResponse.status}.`)
733
+ }
734
+ const release = releaseFacts(parseJson(releaseResponse.body))
735
+ if (release === undefined || !validateUploadUrl(publication, release.id, release.uploadUrl)) {
736
+ return inconclusive(publication, "The GitHub release response omitted identity facts or carried an invalid repository-scoped upload template.")
737
+ }
738
+ const stagedForPublicPromotion = publication.draft === false && release.draft === true
739
+ const releaseDifferences = compareReleaseMetadata(publication, release, {
740
+ ignoreDraft: stagedForPublicPromotion
741
+ })
742
+ if (releaseDifferences.length > 0) return presentDifferent(publication, releaseDifferences)
743
+
744
+ const listed = yield* listAllAssets(publication, http, grant, release.id)
745
+ if (listed._tag === "Inconclusive") return inconclusive(publication, listed.reason)
746
+ const assetsByName = new Map<string, Array<ReleaseAssetFacts>>()
747
+ for (const asset of listed.assets) {
748
+ const candidates = assetsByName.get(asset.name) ?? []
749
+ candidates.push(asset)
750
+ assetsByName.set(asset.name, candidates)
751
+ }
752
+ const differences: Array<Difference> = []
753
+ for (const [name, candidates] of assetsByName) {
754
+ if (!intendedNames.includes(name)) {
755
+ differences.push(textDifference("assets.extra-name", "no undeclared asset", name))
756
+ }
757
+ if (candidates.length > 1) {
758
+ differences.push(scalarDifference(`asset.${name}.count`, 1, candidates.length))
759
+ }
760
+ }
761
+ const missingIndexes: Array<number> = []
762
+ const downloads: Array<{ readonly index: number, readonly facts: ReleaseAssetFacts, readonly expected: Sha256Digest }> = []
763
+ for (const [index, intended] of publication.assets.entries()) {
764
+ const candidates = assetsByName.get(intended.name) ?? []
765
+ if (candidates.length === 0) {
766
+ missingIndexes.push(index)
767
+ continue
768
+ }
769
+ if (candidates.length !== 1) continue
770
+ const observed = candidates[0]!
771
+ const bytes = bundle.blobs.get(intended.artifactId.toString())!
772
+ if (observed.size !== bytes.length) {
773
+ differences.push(scalarDifference(`asset.${intended.name}.size`, bytes.length, observed.size))
774
+ }
775
+ if (observed.mediaType !== intended.mediaType) {
776
+ differences.push(textDifference(`asset.${intended.name}.mediaType`, intended.mediaType, observed.mediaType))
777
+ }
778
+ const expected = sha256Digest(bytes)
779
+ if (observed.digest._tag === "Malformed") {
780
+ return inconclusive(publication, "A GitHub asset carried a malformed canonical sha256 digest.")
781
+ }
782
+ if (observed.digest._tag === "Missing") {
783
+ downloads.push({ index, facts: observed, expected })
784
+ } else if (!digestEquals(observed.digest.value, expected)) {
785
+ differences.push(textDifference(
786
+ `asset.${intended.name}.digest`,
787
+ formatGitHubSha256(expected),
788
+ formatGitHubSha256(observed.digest.value)
789
+ ))
790
+ }
791
+ }
792
+ if (differences.length > 0) return presentDifferent(publication, differences)
793
+ for (const download of downloads) {
794
+ const response = yield* get(
795
+ publication,
796
+ http,
797
+ grant,
798
+ download.facts.apiUrl,
799
+ "application/octet-stream"
800
+ ).pipe(
801
+ Effect.map((result) => ({ _tag: "Response", result } as const)),
802
+ Effect.catch(() => Effect.succeed({ _tag: "Unavailable" } as const))
803
+ )
804
+ if (response._tag === "Unavailable" || response.result.status !== 200) {
805
+ return inconclusive(publication, "GitHub asset bytes could not be downloaded for exact sha256 observation.")
806
+ }
807
+ const bytes = typeof response.result.body === "string"
808
+ ? new TextEncoder().encode(response.result.body)
809
+ : response.result.body
810
+ const digest = sha256Digest(bytes)
811
+ if (!digestEquals(digest, download.expected)) {
812
+ return presentDifferent(publication, [textDifference(
813
+ `asset.${publication.assets[download.index]!.name}.digest`,
814
+ formatGitHubSha256(download.expected),
815
+ formatGitHubSha256(digest)
816
+ )])
817
+ }
818
+ }
819
+ if (missingIndexes.length > 0) {
820
+ if (!release.draft) {
821
+ return presentDifferent(publication, missingIndexes.map((index) => scalarDifference(
822
+ `asset.${publication.assets[index]!.name}.count`, 1, 0
823
+ )))
824
+ }
825
+ if (mutationMayStillBecomeVisible) {
826
+ return pending(publication, "One or more intended GitHub assets are not yet visible after the accepted mutation.")
827
+ }
828
+ mutationPlan = {
829
+ _tag: "UploadAssets",
830
+ releaseId: release.id,
831
+ uploadUrl: release.uploadUrl,
832
+ assetIndexes: missingIndexes
833
+ }
834
+ return absent(
835
+ publication,
836
+ "github-intended-assets-absent",
837
+ "A full paginated release-asset enumeration proved at least one intended asset name absent."
838
+ )
839
+ }
840
+ if (stagedForPublicPromotion) {
841
+ mutationPlan = { _tag: "PublishRelease", releaseId: release.id }
842
+ return absent(
843
+ publication,
844
+ "github-exact-draft-assets-complete",
845
+ "The exact private draft and complete asset set are ready for one final public transition."
846
+ )
847
+ }
848
+ return PresentEquivalent.make({ subject: publication.authority.subject })
849
+ })
850
+
851
+ const mutate = Effect.fn("GitHubReleaseSubject.mutate")(function*(
852
+ decision: MutationDecision,
853
+ grant: MutationCredentialGrant
854
+ ): Effect.fn.Return<MutationAttempt, ReleaseSubjectError> {
855
+ if (grant._tag !== "ScopedSecret") {
856
+ return yield* mutationFailure(
857
+ publication,
858
+ "GitHub release mutation requires an opaque scoped-secret grant.",
859
+ "before-dispatch"
860
+ )
861
+ }
862
+ const plan = mutationPlan
863
+ if (decision.subject !== publication.authority.subject || plan === undefined) {
864
+ return yield* mutationFailure(publication, "The GitHub mutation decision has no matching exact observation plan.")
865
+ }
866
+ const expectedDecision = plan._tag === "CreateRelease" && plan.tagAbsent
867
+ ? decision._tag === "ProviderAuthorizedCreate" &&
868
+ decision.proof.kind === "github-authenticated-repository-and-unique-tag"
869
+ : plan._tag === "CreateRelease"
870
+ ? decision._tag === "NeedsMutation" && decision.precondition.kind === "github-release-absent-at-exact-tag"
871
+ : plan._tag === "UploadAssets"
872
+ ? decision._tag === "NeedsMutation" &&
873
+ decision.precondition.kind === "github-intended-assets-authoritatively-absent"
874
+ : decision._tag === "NeedsMutation" &&
875
+ decision.precondition.kind === "github-exact-draft-assets-complete"
876
+ if (!expectedDecision) {
877
+ return yield* mutationFailure(publication, "The GitHub mutation decision does not match its observed precondition.")
878
+ }
879
+ const indexes = plan._tag === "CreateRelease"
880
+ ? publication.assets.map((_, index) => index)
881
+ : plan._tag === "UploadAssets"
882
+ ? [...plan.assetIndexes]
883
+ : []
884
+ const bytesByIndex = new Map<number, Uint8Array>()
885
+ for (const index of indexes) {
886
+ const asset = publication.assets[index]
887
+ const bytes = asset === undefined ? undefined : bundle.blobs.get(asset.artifactId.toString())
888
+ if (asset === undefined || bytes === undefined) {
889
+ return yield* mutationFailure(publication, "An intended GitHub asset is unavailable before dispatch.")
890
+ }
891
+ bytesByIndex.set(index, bytes)
892
+ }
893
+ if (plan._tag === "UploadAssets" && !validateUploadUrl(publication, plan.releaseId, plan.uploadUrl)) {
894
+ return yield* mutationFailure(publication, "The observed GitHub upload template failed validation before dispatch.")
895
+ }
896
+ const operation: PublisherOperation = {
897
+ _tag: "PublishOperation",
898
+ subject: publication.authority.subject,
899
+ provider: publication.authority.provider,
900
+ audience: publication.authority.audience,
901
+ purpose: "publish",
902
+ decision
903
+ }
904
+ let acceptedWrites = 0
905
+ const dispatch = Effect.fn("GitHubReleaseSubject.dispatch")(function*(
906
+ request: MutationHttpRequest,
907
+ acceptedStatus: 200 | 201
908
+ ) {
909
+ const exchange = yield* executeMutation(mutationHttp, operation, request, grant)
910
+ if (exchange._tag === "Failure") {
911
+ if (acceptedWrites > 0 || exchange.error.commitment === "unknown") {
912
+ return { _tag: "Attempt", attempt: unknownAttempt(
913
+ publication,
914
+ "GitHub transport outcome became unknown after mutation dispatch."
915
+ ) } as const
916
+ }
917
+ return yield* mutationFailure(
918
+ publication,
919
+ "The authorized GitHub HTTP boundary rejected mutation before dispatch.",
920
+ "before-dispatch"
921
+ )
922
+ }
923
+ if (exchange.response.status === acceptedStatus) {
924
+ acceptedWrites += 1
925
+ return { _tag: "Accepted", response: exchange.response } as const
926
+ }
927
+ return { _tag: "Attempt", attempt: unknownAttempt(
928
+ publication,
929
+ `GitHub returned conservative mutation status ${exchange.response.status}; exact outcome requires reobservation.`
930
+ ) } as const
931
+ })
932
+
933
+ let releaseId: number
934
+ let uploadUrl: string | undefined
935
+ if (plan._tag === "CreateRelease") {
936
+ const body = JSON.stringify({
937
+ tag_name: publication.tag.toString(),
938
+ target_commitish: publication.targetCommit.toString(),
939
+ name: publication.title.toString(),
940
+ body: publication.body ?? "",
941
+ draft: true,
942
+ prerelease: publication.prerelease
943
+ })
944
+ const created = yield* dispatch({
945
+ method: "POST",
946
+ url: `${publication.authority.audience}/releases`,
947
+ headers: {
948
+ ...githubJsonHeaders,
949
+ "content-type": "application/json",
950
+ "content-length": String(new TextEncoder().encode(body).length)
951
+ },
952
+ body
953
+ }, 201)
954
+ if (created._tag === "Attempt") return created.attempt
955
+ const facts = releaseFacts(parseJson(created.response.body))
956
+ if (facts === undefined || facts.draft !== true ||
957
+ !validateUploadUrl(publication, facts.id, facts.uploadUrl) ||
958
+ compareReleaseMetadata(publication, facts, { ignoreDraft: publication.draft === false }).length > 0) {
959
+ return unknownAttempt(
960
+ publication,
961
+ "GitHub accepted release creation but its response did not establish the exact release identity and repository-scoped upload template."
962
+ )
963
+ }
964
+ releaseId = facts.id
965
+ uploadUrl = facts.uploadUrl
966
+ } else if (plan._tag === "UploadAssets") {
967
+ releaseId = plan.releaseId
968
+ uploadUrl = plan.uploadUrl
969
+ } else {
970
+ releaseId = plan.releaseId
971
+ }
972
+ for (const index of indexes) {
973
+ const bytes = bytesByIndex.get(index)!
974
+ const uploaded = yield* dispatch(uploadRequest(publication, uploadUrl!, index, bytes), 201)
975
+ if (uploaded._tag === "Attempt") return uploaded.attempt
976
+ const intended = publication.assets[index]!
977
+ const facts = releaseAssetFacts(publication, parseJson(uploaded.response.body))
978
+ if (facts === undefined || facts.name !== intended.name || facts.size !== bytes.length ||
979
+ facts.mediaType !== intended.mediaType || facts.digest._tag === "Malformed" ||
980
+ (facts.digest._tag === "Present" && !digestEquals(facts.digest.value, sha256Digest(bytes)))) {
981
+ return unknownAttempt(
982
+ publication,
983
+ "GitHub accepted an asset upload but its response did not establish the exact uploaded asset identity."
984
+ )
985
+ }
986
+ }
987
+ if (plan._tag === "PublishRelease") {
988
+ const body = JSON.stringify({ draft: false })
989
+ const published = yield* dispatch({
990
+ method: "PATCH",
991
+ url: `${publication.authority.audience}/releases/${releaseId}`,
992
+ headers: {
993
+ ...githubJsonHeaders,
994
+ "content-type": "application/json",
995
+ "content-length": String(new TextEncoder().encode(body).length)
996
+ },
997
+ body
998
+ }, 200)
999
+ if (published._tag === "Attempt") return published.attempt
1000
+ const facts = releaseFacts(parseJson(published.response.body))
1001
+ if (facts === undefined || facts.id !== releaseId || facts.draft !== false ||
1002
+ !validateUploadUrl(publication, releaseId, facts.uploadUrl) ||
1003
+ compareReleaseMetadata(publication, facts).length > 0) {
1004
+ return unknownAttempt(
1005
+ publication,
1006
+ "GitHub accepted final release publication but its response did not establish the exact public release identity."
1007
+ )
1008
+ }
1009
+ }
1010
+ return appliedAttempt(publication)
1011
+ })
1012
+
1013
+ return [{
1014
+ id: publication.authority.subject,
1015
+ recovery: githubRecoveryCapabilityProfile,
1016
+ observationRequests: observationRequests(publication),
1017
+ mutationRequest: mutationRequest(publication),
1018
+ observe,
1019
+ decide: (observation) => decideFor(publication, observation, mutationPlan),
1020
+ mutate
1021
+ }]
1022
+ }