dorfl 0.14.2 → 0.15.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 (418) hide show
  1. package/dist/advance-ci-template.d.ts +17 -16
  2. package/dist/advance-ci-template.d.ts.map +1 -1
  3. package/dist/advance-ci-template.js +42 -53
  4. package/dist/advance-ci-template.js.map +1 -1
  5. package/dist/advance-drivers.d.ts +8 -0
  6. package/dist/advance-drivers.d.ts.map +1 -1
  7. package/dist/advance-drivers.js +28 -4
  8. package/dist/advance-drivers.js.map +1 -1
  9. package/dist/advance-isolated.d.ts.map +1 -1
  10. package/dist/advance-isolated.js +3 -2
  11. package/dist/advance-isolated.js.map +1 -1
  12. package/dist/advance-lifecycle-template.d.ts +41 -40
  13. package/dist/advance-lifecycle-template.d.ts.map +1 -1
  14. package/dist/advance-lifecycle-template.js +309 -489
  15. package/dist/advance-lifecycle-template.js.map +1 -1
  16. package/dist/advance-loop-driver.d.ts.map +1 -1
  17. package/dist/advance-loop-driver.js +3 -2
  18. package/dist/advance-loop-driver.js.map +1 -1
  19. package/dist/advance-treeless-publish.d.ts +31 -0
  20. package/dist/advance-treeless-publish.d.ts.map +1 -1
  21. package/dist/advance-treeless-publish.js +40 -0
  22. package/dist/advance-treeless-publish.js.map +1 -1
  23. package/dist/advance.d.ts +63 -3
  24. package/dist/advance.d.ts.map +1 -1
  25. package/dist/advance.js +80 -12
  26. package/dist/advance.js.map +1 -1
  27. package/dist/advancing-lock.d.ts +9 -0
  28. package/dist/advancing-lock.d.ts.map +1 -1
  29. package/dist/advancing-lock.js +21 -1
  30. package/dist/advancing-lock.js.map +1 -1
  31. package/dist/agent-env.d.ts +61 -0
  32. package/dist/agent-env.d.ts.map +1 -0
  33. package/dist/agent-env.js +144 -0
  34. package/dist/agent-env.js.map +1 -0
  35. package/dist/agent-stop.d.ts +35 -0
  36. package/dist/agent-stop.d.ts.map +1 -1
  37. package/dist/agent-stop.js +62 -0
  38. package/dist/agent-stop.js.map +1 -1
  39. package/dist/apply-merge-action.d.ts +76 -34
  40. package/dist/apply-merge-action.d.ts.map +1 -1
  41. package/dist/apply-merge-action.js +339 -129
  42. package/dist/apply-merge-action.js.map +1 -1
  43. package/dist/apply-stuck-action.js +2 -2
  44. package/dist/apply-stuck-action.js.map +1 -1
  45. package/dist/ci-agent-result.d.ts +151 -0
  46. package/dist/ci-agent-result.d.ts.map +1 -0
  47. package/dist/ci-agent-result.js +314 -0
  48. package/dist/ci-agent-result.js.map +1 -0
  49. package/dist/ci-handoff-apply.d.ts +162 -0
  50. package/dist/ci-handoff-apply.d.ts.map +1 -0
  51. package/dist/ci-handoff-apply.js +497 -0
  52. package/dist/ci-handoff-apply.js.map +1 -0
  53. package/dist/ci-handoff-format.d.ts +369 -0
  54. package/dist/ci-handoff-format.d.ts.map +1 -0
  55. package/dist/ci-handoff-format.js +646 -0
  56. package/dist/ci-handoff-format.js.map +1 -0
  57. package/dist/ci-handoff-lfs.d.ts +146 -0
  58. package/dist/ci-handoff-lfs.d.ts.map +1 -0
  59. package/dist/ci-handoff-lfs.js +397 -0
  60. package/dist/ci-handoff-lfs.js.map +1 -0
  61. package/dist/ci-handoff.d.ts +99 -0
  62. package/dist/ci-handoff.d.ts.map +1 -0
  63. package/dist/ci-handoff.js +277 -0
  64. package/dist/ci-handoff.js.map +1 -0
  65. package/dist/ci-lock-outputs.d.ts +93 -0
  66. package/dist/ci-lock-outputs.d.ts.map +1 -0
  67. package/dist/ci-lock-outputs.js +223 -0
  68. package/dist/ci-lock-outputs.js.map +1 -0
  69. package/dist/ci-phase-build.d.ts +178 -0
  70. package/dist/ci-phase-build.d.ts.map +1 -0
  71. package/dist/ci-phase-build.js +989 -0
  72. package/dist/ci-phase-build.js.map +1 -0
  73. package/dist/ci-phase-driver.d.ts +247 -0
  74. package/dist/ci-phase-driver.d.ts.map +1 -0
  75. package/dist/ci-phase-driver.js +370 -0
  76. package/dist/ci-phase-driver.js.map +1 -0
  77. package/dist/ci-phase-intake.d.ts +172 -0
  78. package/dist/ci-phase-intake.d.ts.map +1 -0
  79. package/dist/ci-phase-intake.js +555 -0
  80. package/dist/ci-phase-intake.js.map +1 -0
  81. package/dist/ci-phase-tasking.d.ts +205 -0
  82. package/dist/ci-phase-tasking.d.ts.map +1 -0
  83. package/dist/ci-phase-tasking.js +906 -0
  84. package/dist/ci-phase-tasking.js.map +1 -0
  85. package/dist/ci-phase-treeless.d.ts +224 -0
  86. package/dist/ci-phase-treeless.d.ts.map +1 -0
  87. package/dist/ci-phase-treeless.js +1482 -0
  88. package/dist/ci-phase-treeless.js.map +1 -0
  89. package/dist/ci-read-token.d.ts +47 -0
  90. package/dist/ci-read-token.d.ts.map +1 -0
  91. package/dist/ci-read-token.js +80 -0
  92. package/dist/ci-read-token.js.map +1 -0
  93. package/dist/cli.d.ts.map +1 -1
  94. package/dist/cli.js +447 -22
  95. package/dist/cli.js.map +1 -1
  96. package/dist/close-job-template.d.ts +10 -1
  97. package/dist/close-job-template.d.ts.map +1 -1
  98. package/dist/close-job-template.js +46 -14
  99. package/dist/close-job-template.js.map +1 -1
  100. package/dist/close-job.d.ts +20 -5
  101. package/dist/close-job.d.ts.map +1 -1
  102. package/dist/close-job.js +65 -12
  103. package/dist/close-job.js.map +1 -1
  104. package/dist/complete.d.ts.map +1 -1
  105. package/dist/complete.js +20 -1
  106. package/dist/complete.js.map +1 -1
  107. package/dist/config.d.ts +41 -44
  108. package/dist/config.d.ts.map +1 -1
  109. package/dist/config.js +2 -4
  110. package/dist/config.js.map +1 -1
  111. package/dist/continue-branch.d.ts +63 -15
  112. package/dist/continue-branch.d.ts.map +1 -1
  113. package/dist/continue-branch.js +75 -8
  114. package/dist/continue-branch.js.map +1 -1
  115. package/dist/crashed-run-save.d.ts +73 -0
  116. package/dist/crashed-run-save.d.ts.map +1 -0
  117. package/dist/crashed-run-save.js +86 -0
  118. package/dist/crashed-run-save.js.map +1 -0
  119. package/dist/cwd-section.d.ts.map +1 -1
  120. package/dist/cwd-section.js +10 -2
  121. package/dist/cwd-section.js.map +1 -1
  122. package/dist/decision-engine.d.ts +10 -0
  123. package/dist/decision-engine.d.ts.map +1 -1
  124. package/dist/decision-engine.js.map +1 -1
  125. package/dist/do-config.d.ts +2 -24
  126. package/dist/do-config.d.ts.map +1 -1
  127. package/dist/do-config.js +1 -33
  128. package/dist/do-config.js.map +1 -1
  129. package/dist/do.d.ts +149 -1
  130. package/dist/do.d.ts.map +1 -1
  131. package/dist/do.js +115 -25
  132. package/dist/do.js.map +1 -1
  133. package/dist/dorfl-item-template.d.ts +98 -0
  134. package/dist/dorfl-item-template.d.ts.map +1 -0
  135. package/dist/dorfl-item-template.js +504 -0
  136. package/dist/dorfl-item-template.js.map +1 -0
  137. package/dist/env-config.d.ts.map +1 -1
  138. package/dist/env-config.js +7 -5
  139. package/dist/env-config.js.map +1 -1
  140. package/dist/frontmatter.d.ts +52 -0
  141. package/dist/frontmatter.d.ts.map +1 -1
  142. package/dist/frontmatter.js +136 -2
  143. package/dist/frontmatter.js.map +1 -1
  144. package/dist/gc.d.ts +13 -0
  145. package/dist/gc.d.ts.map +1 -1
  146. package/dist/gc.js +16 -2
  147. package/dist/gc.js.map +1 -1
  148. package/dist/git.d.ts +18 -1
  149. package/dist/git.d.ts.map +1 -1
  150. package/dist/git.js +70 -5
  151. package/dist/git.js.map +1 -1
  152. package/dist/harness.d.ts +44 -5
  153. package/dist/harness.d.ts.map +1 -1
  154. package/dist/harness.js +24 -1
  155. package/dist/harness.js.map +1 -1
  156. package/dist/index.d.ts +11 -4
  157. package/dist/index.d.ts.map +1 -1
  158. package/dist/index.js +7 -3
  159. package/dist/index.js.map +1 -1
  160. package/dist/install-ci-action-pins.d.ts +123 -0
  161. package/dist/install-ci-action-pins.d.ts.map +1 -0
  162. package/dist/install-ci-action-pins.js +190 -0
  163. package/dist/install-ci-action-pins.js.map +1 -0
  164. package/dist/install-ci-actions-pr-creation.d.ts +70 -0
  165. package/dist/install-ci-actions-pr-creation.d.ts.map +1 -0
  166. package/dist/install-ci-actions-pr-creation.js +106 -0
  167. package/dist/install-ci-actions-pr-creation.js.map +1 -0
  168. package/dist/install-ci-capabilities/dorfl-item.d.ts +12 -0
  169. package/dist/install-ci-capabilities/dorfl-item.d.ts.map +1 -0
  170. package/dist/install-ci-capabilities/dorfl-item.js +26 -0
  171. package/dist/install-ci-capabilities/dorfl-item.js.map +1 -0
  172. package/dist/install-ci-core.d.ts +155 -37
  173. package/dist/install-ci-core.d.ts.map +1 -1
  174. package/dist/install-ci-core.js +304 -200
  175. package/dist/install-ci-core.js.map +1 -1
  176. package/dist/install-ci-github.d.ts +47 -2
  177. package/dist/install-ci-github.d.ts.map +1 -1
  178. package/dist/install-ci-github.js +113 -1
  179. package/dist/install-ci-github.js.map +1 -1
  180. package/dist/install-ci.d.ts +25 -6
  181. package/dist/install-ci.d.ts.map +1 -1
  182. package/dist/install-ci.js +72 -36
  183. package/dist/install-ci.js.map +1 -1
  184. package/dist/intake-marker.d.ts +6 -1
  185. package/dist/intake-marker.d.ts.map +1 -1
  186. package/dist/intake-marker.js.map +1 -1
  187. package/dist/intake-triage.d.ts.map +1 -1
  188. package/dist/intake-triage.js +6 -2
  189. package/dist/intake-triage.js.map +1 -1
  190. package/dist/intake-trigger-template.d.ts +31 -25
  191. package/dist/intake-trigger-template.d.ts.map +1 -1
  192. package/dist/intake-trigger-template.js +107 -292
  193. package/dist/intake-trigger-template.js.map +1 -1
  194. package/dist/intake.d.ts +186 -4
  195. package/dist/intake.d.ts.map +1 -1
  196. package/dist/intake.js +533 -416
  197. package/dist/intake.js.map +1 -1
  198. package/dist/integration-core.d.ts +154 -1
  199. package/dist/integration-core.d.ts.map +1 -1
  200. package/dist/integration-core.js +759 -486
  201. package/dist/integration-core.js.map +1 -1
  202. package/dist/integrator.d.ts +12 -0
  203. package/dist/integrator.d.ts.map +1 -1
  204. package/dist/integrator.js +21 -0
  205. package/dist/integrator.js.map +1 -1
  206. package/dist/isolation.d.ts +12 -2
  207. package/dist/isolation.d.ts.map +1 -1
  208. package/dist/isolation.js +19 -8
  209. package/dist/isolation.js.map +1 -1
  210. package/dist/item-lock.d.ts +93 -11
  211. package/dist/item-lock.d.ts.map +1 -1
  212. package/dist/item-lock.js +233 -65
  213. package/dist/item-lock.js.map +1 -1
  214. package/dist/land-question-residue.d.ts +58 -0
  215. package/dist/land-question-residue.d.ts.map +1 -0
  216. package/dist/land-question-residue.js +95 -0
  217. package/dist/land-question-residue.js.map +1 -0
  218. package/dist/lifecycle-gather.d.ts +16 -0
  219. package/dist/lifecycle-gather.d.ts.map +1 -1
  220. package/dist/lifecycle-gather.js +54 -6
  221. package/dist/lifecycle-gather.js.map +1 -1
  222. package/dist/merge-answer.d.ts +41 -0
  223. package/dist/merge-answer.d.ts.map +1 -0
  224. package/dist/merge-answer.js +60 -0
  225. package/dist/merge-answer.js.map +1 -0
  226. package/dist/merge-question-surfacer.d.ts +105 -30
  227. package/dist/merge-question-surfacer.d.ts.map +1 -1
  228. package/dist/merge-question-surfacer.js +202 -91
  229. package/dist/merge-question-surfacer.js.map +1 -1
  230. package/dist/merge-question-tick.d.ts +64 -0
  231. package/dist/merge-question-tick.d.ts.map +1 -0
  232. package/dist/merge-question-tick.js +96 -0
  233. package/dist/merge-question-tick.js.map +1 -0
  234. package/dist/mint-adr.d.ts.map +1 -1
  235. package/dist/mint-adr.js +20 -2
  236. package/dist/mint-adr.js.map +1 -1
  237. package/dist/needs-attention.d.ts +72 -2
  238. package/dist/needs-attention.d.ts.map +1 -1
  239. package/dist/needs-attention.js +275 -38
  240. package/dist/needs-attention.js.map +1 -1
  241. package/dist/phase-recorder.d.ts +168 -0
  242. package/dist/phase-recorder.d.ts.map +1 -0
  243. package/dist/phase-recorder.js +218 -0
  244. package/dist/phase-recorder.js.map +1 -0
  245. package/dist/phase.d.ts +94 -0
  246. package/dist/phase.d.ts.map +1 -0
  247. package/dist/phase.js +148 -0
  248. package/dist/phase.js.map +1 -0
  249. package/dist/pi-harness.d.ts.map +1 -1
  250. package/dist/pi-harness.js +19 -6
  251. package/dist/pi-harness.js.map +1 -1
  252. package/dist/prepare.d.ts.map +1 -1
  253. package/dist/prepare.js +4 -0
  254. package/dist/prepare.js.map +1 -1
  255. package/dist/prompt.d.ts +4 -0
  256. package/dist/prompt.d.ts.map +1 -1
  257. package/dist/prompt.js +48 -1
  258. package/dist/prompt.js.map +1 -1
  259. package/dist/protocol/CLAIM-PROTOCOL.md +3 -1
  260. package/dist/protocol/WORK-CONTRACT.md +1 -1
  261. package/dist/reap-branches.d.ts +48 -0
  262. package/dist/reap-branches.d.ts.map +1 -1
  263. package/dist/reap-branches.js +64 -0
  264. package/dist/reap-branches.js.map +1 -1
  265. package/dist/ref-write.d.ts +198 -0
  266. package/dist/ref-write.d.ts.map +1 -0
  267. package/dist/ref-write.js +79 -0
  268. package/dist/ref-write.js.map +1 -0
  269. package/dist/repo-config.js +8 -8
  270. package/dist/repo-config.js.map +1 -1
  271. package/dist/repo-mirror.d.ts +36 -0
  272. package/dist/repo-mirror.d.ts.map +1 -1
  273. package/dist/repo-mirror.js +91 -1
  274. package/dist/repo-mirror.js.map +1 -1
  275. package/dist/review-verdict.d.ts +1 -1
  276. package/dist/review-verdict.js +1 -1
  277. package/dist/run.d.ts +7 -1
  278. package/dist/run.d.ts.map +1 -1
  279. package/dist/run.js +28 -4
  280. package/dist/run.js.map +1 -1
  281. package/dist/scan.d.ts +19 -1
  282. package/dist/scan.d.ts.map +1 -1
  283. package/dist/scan.js +21 -8
  284. package/dist/scan.js.map +1 -1
  285. package/dist/sidecar.d.ts +14 -0
  286. package/dist/sidecar.d.ts.map +1 -1
  287. package/dist/sidecar.js +21 -0
  288. package/dist/sidecar.js.map +1 -1
  289. package/dist/single-job-warning.d.ts +63 -0
  290. package/dist/single-job-warning.d.ts.map +1 -0
  291. package/dist/single-job-warning.js +110 -0
  292. package/dist/single-job-warning.js.map +1 -0
  293. package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +3 -1
  294. package/dist/skills/setup/protocol/WORK-CONTRACT.md +1 -1
  295. package/dist/spec-complete.d.ts +13 -6
  296. package/dist/spec-complete.d.ts.map +1 -1
  297. package/dist/spec-complete.js +23 -11
  298. package/dist/spec-complete.js.map +1 -1
  299. package/dist/start.d.ts.map +1 -1
  300. package/dist/start.js +47 -14
  301. package/dist/start.js.map +1 -1
  302. package/dist/status.d.ts +12 -0
  303. package/dist/status.d.ts.map +1 -1
  304. package/dist/status.js +11 -3
  305. package/dist/status.js.map +1 -1
  306. package/dist/tasking.d.ts +246 -1
  307. package/dist/tasking.d.ts.map +1 -1
  308. package/dist/tasking.js +452 -358
  309. package/dist/tasking.js.map +1 -1
  310. package/dist/triage-persist.d.ts.map +1 -1
  311. package/dist/triage-persist.js +21 -2
  312. package/dist/triage-persist.js.map +1 -1
  313. package/dist/verify-workflow-template.d.ts.map +1 -1
  314. package/dist/verify-workflow-template.js +8 -4
  315. package/dist/verify-workflow-template.js.map +1 -1
  316. package/dist/verify.d.ts.map +1 -1
  317. package/dist/verify.js +4 -0
  318. package/dist/verify.js.map +1 -1
  319. package/dist/watch-session.d.ts +42 -13
  320. package/dist/watch-session.d.ts.map +1 -1
  321. package/dist/watch-session.js +52 -23
  322. package/dist/watch-session.js.map +1 -1
  323. package/dist/work-layout.d.ts +10 -4
  324. package/dist/work-layout.d.ts.map +1 -1
  325. package/dist/work-layout.js +11 -3
  326. package/dist/work-layout.js.map +1 -1
  327. package/dist/workspace.d.ts +65 -4
  328. package/dist/workspace.d.ts.map +1 -1
  329. package/dist/workspace.js +159 -11
  330. package/dist/workspace.js.map +1 -1
  331. package/package.json +1 -1
  332. package/src/advance-ci-template.ts +80 -81
  333. package/src/advance-drivers.ts +45 -6
  334. package/src/advance-isolated.ts +3 -5
  335. package/src/advance-lifecycle-template.ts +375 -538
  336. package/src/advance-loop-driver.ts +3 -5
  337. package/src/advance-treeless-publish.ts +61 -0
  338. package/src/advance.ts +145 -15
  339. package/src/advancing-lock.ts +36 -1
  340. package/src/agent-env.ts +174 -0
  341. package/src/agent-stop.ts +81 -0
  342. package/src/apply-merge-action.ts +401 -154
  343. package/src/apply-stuck-action.ts +2 -2
  344. package/src/ci-agent-result.ts +421 -0
  345. package/src/ci-handoff-apply.ts +737 -0
  346. package/src/ci-handoff-format.ts +926 -0
  347. package/src/ci-handoff-lfs.ts +519 -0
  348. package/src/ci-handoff.ts +392 -0
  349. package/src/ci-lock-outputs.ts +273 -0
  350. package/src/ci-phase-build.ts +1328 -0
  351. package/src/ci-phase-driver.ts +548 -0
  352. package/src/ci-phase-intake.ts +861 -0
  353. package/src/ci-phase-tasking.ts +1255 -0
  354. package/src/ci-phase-treeless.ts +2028 -0
  355. package/src/ci-read-token.ts +90 -0
  356. package/src/cli.ts +591 -29
  357. package/src/close-job-template.ts +52 -15
  358. package/src/close-job.ts +77 -12
  359. package/src/complete.ts +19 -1
  360. package/src/config.ts +43 -48
  361. package/src/continue-branch.ts +121 -16
  362. package/src/crashed-run-save.ts +162 -0
  363. package/src/cwd-section.ts +10 -0
  364. package/src/decision-engine.ts +10 -0
  365. package/src/do-config.ts +2 -57
  366. package/src/do.ts +170 -28
  367. package/src/dorfl-item-template.ts +556 -0
  368. package/src/env-config.ts +7 -5
  369. package/src/frontmatter.ts +155 -2
  370. package/src/gc.ts +27 -2
  371. package/src/git.ts +83 -5
  372. package/src/harness.ts +58 -6
  373. package/src/index.ts +50 -1
  374. package/src/install-ci-action-pins.ts +209 -0
  375. package/src/install-ci-actions-pr-creation.ts +186 -0
  376. package/src/install-ci-capabilities/dorfl-item.ts +33 -0
  377. package/src/install-ci-core.ts +388 -233
  378. package/src/install-ci-github.ts +149 -1
  379. package/src/install-ci.ts +83 -37
  380. package/src/intake-marker.ts +6 -1
  381. package/src/intake-triage.ts +7 -3
  382. package/src/intake-trigger-template.ts +126 -327
  383. package/src/intake.ts +725 -544
  384. package/src/integration-core.ts +975 -523
  385. package/src/integrator.ts +28 -0
  386. package/src/isolation.ts +29 -10
  387. package/src/item-lock.ts +308 -92
  388. package/src/land-question-residue.ts +122 -0
  389. package/src/lifecycle-gather.ts +82 -10
  390. package/src/merge-answer.ts +82 -0
  391. package/src/merge-question-surfacer.ts +320 -112
  392. package/src/merge-question-tick.ts +182 -0
  393. package/src/mint-adr.ts +28 -2
  394. package/src/needs-attention.ts +350 -42
  395. package/src/phase-recorder.ts +350 -0
  396. package/src/phase.ts +177 -0
  397. package/src/pi-harness.ts +25 -5
  398. package/src/prepare.ts +4 -0
  399. package/src/prompt.ts +68 -1
  400. package/src/reap-branches.ts +107 -0
  401. package/src/ref-write.ts +324 -0
  402. package/src/repo-config.ts +8 -8
  403. package/src/repo-mirror.ts +107 -1
  404. package/src/review-verdict.ts +1 -1
  405. package/src/run.ts +47 -6
  406. package/src/scan.ts +37 -7
  407. package/src/sidecar.ts +36 -0
  408. package/src/single-job-warning.ts +135 -0
  409. package/src/spec-complete.ts +36 -14
  410. package/src/start.ts +55 -16
  411. package/src/status.ts +29 -3
  412. package/src/tasking.ts +593 -378
  413. package/src/triage-persist.ts +24 -2
  414. package/src/verify-workflow-template.ts +8 -4
  415. package/src/verify.ts +4 -0
  416. package/src/watch-session.ts +86 -28
  417. package/src/work-layout.ts +11 -3
  418. package/src/workspace.ts +210 -14
@@ -0,0 +1,556 @@
1
+ /**
2
+ * The SPLIT CI ITEM workflows (spec `ci-agent-job-without-write-token` §2, ADR
3
+ * `ci-agent-job-holds-no-write-token`; task `ci-split-generate-workflows`).
4
+ *
5
+ * Every CI item runs as three jobs in a workflow run of its own:
6
+ *
7
+ * - **lock** (write token, no agent, no repository code): classify the item
8
+ * at the arbiter's current `main`, take its locks, publish trusted facts as
9
+ * job outputs (`ci-lock-outputs.ts`);
10
+ * - **agent** (a read-only token, `persist-credentials: false`): run the
11
+ * agents and upload the handoff;
12
+ * - **apply** (write token, no agent, no repository code, `if: always()`):
13
+ * validate the handoff as hostile and do every write.
14
+ *
15
+ * This module generates the two files that carry that shape:
16
+ *
17
+ * - `dorfl-item.yml` ({@link generateItemWorkflow}): the three jobs, reached
18
+ * only through `workflow_call`, with NO workflow-level `concurrency`;
19
+ * - `dorfl-item-dispatch.yml` ({@link generateItemDispatchWorkflow}): the thin
20
+ * `workflow_dispatch` wrapper the advance tick starts once per item. It
21
+ * holds the parallelism slot (`concurrency: dorfl-slot-<slot>` with
22
+ * `queue: max`) and calls `dorfl-item.yml`.
23
+ *
24
+ * `intake.yml` calls `dorfl-item.yml` directly with `item: issue:<N>` under its
25
+ * own per-issue group (decision 10). One item per run means one artifact
26
+ * namespace per item (decision 1): the artifact the apply job downloads (no
27
+ * `run-id`) can only come from this item's agent job.
28
+ *
29
+ * The item decides the verb: `issue:<N>` runs `dorfl intake <N>`, every other
30
+ * namespaced id runs `dorfl advance <item> --<integrationMode>`, one line per
31
+ * phase (`advance --phase` routes build / tasking / tree-less itself from the
32
+ * lock job's `rung`).
33
+ */
34
+
35
+ import {ACTION_PINS, pinnedUses} from './install-ci-action-pins.js';
36
+ import {LOCK_OUTPUT_KEYS} from './ci-lock-outputs.js';
37
+ import type {ResolvedCIConfig} from './install-ci-core.js';
38
+ import {
39
+ PR_IDENTITY_SECRET_NAME,
40
+ SETUP_ACTION_USES,
41
+ WRITER_SETUP_ACTION_USES,
42
+ providerSecretNames,
43
+ providerSecretsWithBlock,
44
+ } from './install-ci-core.js';
45
+
46
+ /** The repo-relative path (under the output base) of the item workflow. */
47
+ export const ITEM_WORKFLOW_PATH = 'workflows/dorfl-item.yml';
48
+
49
+ /** The repo-relative path (under the output base) of the dispatch wrapper. */
50
+ export const ITEM_DISPATCH_WORKFLOW_PATH = 'workflows/dorfl-item-dispatch.yml';
51
+
52
+ /** The file name the advance tick dispatches (`gh workflow run <this>`). */
53
+ export const ITEM_DISPATCH_WORKFLOW_FILE = 'dorfl-item-dispatch.yml';
54
+
55
+ /** The `run-name` prefix of a dispatched item run (`dorfl-item <item>`). */
56
+ export const ITEM_RUN_NAME_PREFIX = 'dorfl-item';
57
+
58
+ /** The four one-shot gate overrides, with their `DORFL_*` env and values. */
59
+ export const GATE_OVERRIDES = [
60
+ {input: 'autoBuild', env: 'DORFL_AUTO_BUILD', values: ['true', 'false']},
61
+ {input: 'autoTask', env: 'DORFL_AUTO_TASK', values: ['true', 'false']},
62
+ {
63
+ input: 'observationTriage',
64
+ env: 'DORFL_OBSERVATION_TRIAGE',
65
+ values: ['off', 'ask', 'auto'],
66
+ },
67
+ {
68
+ input: 'surfaceBlockers',
69
+ env: 'DORFL_SURFACE_BLOCKERS',
70
+ values: ['true', 'false'],
71
+ },
72
+ ] as const;
73
+
74
+ /**
75
+ * The scopes every job of `dorfl-item.yml` requests, together: what a calling
76
+ * job must grant (a called workflow can only narrow its caller's token).
77
+ */
78
+ export const ITEM_WORKFLOW_CALLER_PERMISSIONS = `\
79
+ contents: write
80
+ issues: write
81
+ pull-requests: write
82
+ actions: read
83
+ checks: read`;
84
+
85
+ /** `DISPATCH_AUTO_BUILD` for `DORFL_AUTO_BUILD`. */
86
+ function dispatchVar(env: string): string {
87
+ return env.replace(/^DORFL_/, 'DISPATCH_');
88
+ }
89
+
90
+ /**
91
+ * The gate-override step (a 6-space-indented workflow step): each of the four
92
+ * one-shot overrides reaches the shell through `env:` from `source` (e.g.
93
+ * `inputs` or `github.event.inputs`), is refused when it carries a line break or
94
+ * is not its enum (blank means no override), and is exported as its `DORFL_*`
95
+ * only when non-blank. `ifDispatch` adds the `workflow_dispatch` guard (the
96
+ * lifecycle tick; a schedule or push run exports nothing).
97
+ */
98
+ export function gateOverridesStep(options: {
99
+ source: string;
100
+ ifDispatch: boolean;
101
+ comment: string;
102
+ }): string {
103
+ const envLines = GATE_OVERRIDES.map(
104
+ (g) =>
105
+ ` ${dispatchVar(g.env)}: \${{ ${options.source}.${g.input} }}`,
106
+ ).join('\n');
107
+ const all = GATE_OVERRIDES.map((g) => `\${${dispatchVar(g.env)}}`).join('');
108
+ const enumLines = GATE_OVERRIDES.map(
109
+ (g) =>
110
+ ` case "\${${dispatchVar(g.env)}}" in ''|${g.values.join('|')}) ;; *) echo "::error::the ${g.input} override must be blank, ${g.values.join(', ')}"; exit 1 ;; esac`,
111
+ ).join('\n');
112
+ const writeLines = GATE_OVERRIDES.map(
113
+ (g) =>
114
+ ` [ -n "\${${dispatchVar(g.env)}}" ] && echo "${g.env}=\${${dispatchVar(g.env)}}" >> "$GITHUB_ENV"`,
115
+ ).join('\n');
116
+ const ifLine = options.ifDispatch
117
+ ? `\n if: \${{ github.event_name == 'workflow_dispatch' }}`
118
+ : '';
119
+ return `\
120
+ - name: apply dispatch gate overrides (one-shot, this run only)
121
+ ${options.comment}${ifLine}
122
+ env:
123
+ ${envLines}
124
+ run: |
125
+ case "${all}" in
126
+ *$'\\n'*|*$'\\r'*) echo "::error::a dispatch gate override contains a line break"; exit 1 ;;
127
+ esac
128
+ ${enumLines}
129
+ ${writeLines}
130
+ true`;
131
+ }
132
+
133
+ /** The standard comment on the item workflow's gate-override step. */
134
+ const ITEM_GATE_COMMENT = `\
135
+ # The four one-shot gate overrides of the tick (blank: none, the
136
+ # committed dorfl.json wins). Exported in the lock, agent AND apply jobs,
137
+ # because each resolves the gate family (the lock job's classification
138
+ # picks the rung from it). Each reaches the shell through \`env:\` as DATA,
139
+ # a line break or a value outside its enum is refused (the
140
+ # \`workflow_call\` inputs are free strings), and only a non-blank value is
141
+ # written to $GITHUB_ENV (an empty DORFL_* makes the env coercion throw).`;
142
+
143
+ /**
144
+ * The shell that runs ONE phase of the item: `issue:<N>` is intake, every
145
+ * other id is `advance <item> --<integrationMode>`. `advanceExtra` and
146
+ * `intakeExtra` are the phase-specific arguments (quoted shell words).
147
+ */
148
+ function phaseScript(options: {
149
+ phase: 'lock' | 'agent' | 'apply';
150
+ advanceExtra: string;
151
+ intakeExtra: string;
152
+ prelude?: string;
153
+ }): string {
154
+ const prelude = options.prelude ? `${options.prelude}\n` : '';
155
+ return `\
156
+ set -euo pipefail
157
+ case "\${WORK_ITEM}" in
158
+ ''|-*) echo "::error::the item must be a namespaced id (task:<slug>, spec:<slug>, obs:<slug>, issue:<N>)"; exit 1 ;;
159
+ esac
160
+ case "\${INTEGRATION_MODE}" in
161
+ propose|merge) ;;
162
+ *) echo "::error::integrationMode must be propose or merge"; exit 1 ;;
163
+ esac
164
+ ${prelude} case "\${WORK_ITEM}" in
165
+ issue:*) dorfl intake "\${WORK_ITEM#issue:}" --phase ${options.phase}${options.intakeExtra} --arbiter origin ;;
166
+ *) dorfl advance "\${WORK_ITEM}" "--\${INTEGRATION_MODE}" --phase ${options.phase}${options.advanceExtra} --arbiter origin ;;
167
+ esac`;
168
+ }
169
+
170
+ /** The `workflow_call` secrets block of `dorfl-item.yml`. */
171
+ function callSecretsBlock(config: ResolvedCIConfig): string {
172
+ const names = [...providerSecretNames(config), PR_IDENTITY_SECRET_NAME];
173
+ return names
174
+ .map((name) => ` ${name}:\n required: false`)
175
+ .join('\n');
176
+ }
177
+
178
+ /**
179
+ * The `secrets:` a calling job passes to `dorfl-item.yml`: explicit, never
180
+ * `inherit`, so nothing reaches the item workflow by default. `withPrIdentity`
181
+ * adds {@link PR_IDENTITY_SECRET_NAME} (the advance tick; intake keeps the
182
+ * built-in token, as before the split).
183
+ */
184
+ export function itemCallSecrets(
185
+ config: ResolvedCIConfig,
186
+ withPrIdentity: boolean,
187
+ ): string {
188
+ const names = [
189
+ ...providerSecretNames(config),
190
+ ...(withPrIdentity ? [PR_IDENTITY_SECRET_NAME] : []),
191
+ ];
192
+ if (names.length === 0) {
193
+ return '';
194
+ }
195
+ return (
196
+ '\n secrets:\n' +
197
+ names.map((n) => ` ${n}: \${{ secrets.${n} }}`).join('\n')
198
+ );
199
+ }
200
+
201
+ /**
202
+ * Generate `dorfl-item.yml`: the lock, agent and apply jobs of ONE item.
203
+ * Deterministic. See the module comment for the shape; every value reaches a
204
+ * `run:` script through `env:`.
205
+ */
206
+ export function generateItemWorkflow(config: ResolvedCIConfig): string {
207
+ const workspace = config.installSource === 'workspace';
208
+ const lockOutputs = LOCK_OUTPUT_KEYS.map(
209
+ (k) => ` ${k}: \${{ steps.lock.outputs.${k} }}`,
210
+ ).join('\n');
211
+ const gate = gateOverridesStep({
212
+ source: 'inputs',
213
+ ifDispatch: false,
214
+ comment: ITEM_GATE_COMMENT,
215
+ });
216
+ const workspaceHeader = workspace
217
+ ? `
218
+ #
219
+ # WORKSPACE INSTALL MODE (this repository builds dorfl from source, decision 8
220
+ # of ADR ci-agent-job-holds-no-write-token): all three jobs build dorfl from
221
+ # the item's base sha (the arbiter's main tip the lock job classifies
222
+ # against). In MERGE mode this runs code an earlier agent landed on \`main\`
223
+ # inside the WRITE jobs (lock, apply), which is the trust merge mode already
224
+ # implies: that code is what \`main\` runs anyway.`
225
+ : '';
226
+ const lockBaseStep = workspace
227
+ ? `
228
+ - name: resolve the arbiter tip to build dorfl from (workspace mode)
229
+ id: base
230
+ # The lock phase classifies against <arbiter>/main (its baseSha), not
231
+ # this run's github.sha, so build dorfl from that same tip.
232
+ run: |
233
+ set -euo pipefail
234
+ git fetch --quiet --no-tags origin +refs/heads/main:refs/remotes/origin/main
235
+ echo "sha=$(git rev-parse --verify 'refs/remotes/origin/main^{commit}')" >> "$GITHUB_OUTPUT"`
236
+ : '';
237
+ const lockSetupWith = workspace
238
+ ? `
239
+ with:
240
+ source-ref: \${{ steps.base.outputs.sha }}`
241
+ : '';
242
+ const lockBaseCheck = workspace
243
+ ? `
244
+ - name: check dorfl was built from the base sha (workspace mode)
245
+ # main can move between the fetch above and the lock phase's own fetch;
246
+ # the agent and apply jobs build from the base sha either way.
247
+ env:
248
+ BUILT_FROM: \${{ steps.base.outputs.sha }}
249
+ BASE_SHA: \${{ steps.lock.outputs.baseSha }}
250
+ run: |
251
+ if [ -n "\${BASE_SHA}" ] && [ "\${BASE_SHA}" != "\${BUILT_FROM}" ]; then
252
+ echo "::warning::main moved during the lock job: its dorfl was built from \${BUILT_FROM}, the item's base is \${BASE_SHA} (the agent and apply jobs build from the base)."
253
+ fi`
254
+ : '';
255
+ return `\
256
+ # dorfl — ONE CI ITEM as three jobs (spec ci-agent-job-without-write-token,
257
+ # ADR ci-agent-job-holds-no-write-token). EMITTED by \`dorfl install-ci\`; the
258
+ # human commits it. DO NOT hand-edit a copy: re-run install-ci to upgrade.
259
+ #
260
+ # lock write token, NO agent, NO repository code: classify the item at the
261
+ # arbiter's current main (its tip is the base), take the locks, and
262
+ # publish trusted facts as job outputs.
263
+ # agent read-only token, persist-credentials: false: run the agents, the
264
+ # gate and the review, stop at the first write, upload the handoff.
265
+ # apply write token, NO agent, NO repository code, if: always(): treat the
266
+ # handoff as HOSTILE, validate it against the lock outputs, do every
267
+ # write (or release / surface the item when the agent job failed).
268
+ #
269
+ # Reached only through \`workflow_call\`: from dorfl-item-dispatch.yml (one run
270
+ # per advance item, holding the parallelism slot) and from intake.yml
271
+ # (\`item: issue:<N>\`, under its per-issue group). ONE item per run: the
272
+ # artifact namespace is this run's, so the apply job's download (no run-id)
273
+ # can only read this item's agent job. NO workflow-level concurrency here: the
274
+ # callers own it.
275
+ #
276
+ # The base is the arbiter tip, never the run's commit: a queued run may start
277
+ # hours after its dispatch. The lock job's checkout (github.sha) only loads the
278
+ # local composite action; dorfl fetches <arbiter>/main itself and publishes its
279
+ # tip as \`baseSha\`, which the agent and apply jobs check out.
280
+ #
281
+ # Secrets: the provider key(s) reach the AGENT job only; DORFL_GH_TOKEN and the
282
+ # write-scoped GITHUB_TOKEN reach lock and apply only. The agent job's
283
+ # GITHUB_TOKEN is read-only (its job permissions).
284
+ #
285
+ # All jobs run on GitHub-hosted runners: each job needs a FRESH machine. A
286
+ # self-hosted runner must be ephemeral (one job per machine), or files and
287
+ # processes an agent planted survive into a later lock or apply job.${workspaceHeader}
288
+
289
+ name: dorfl-item
290
+
291
+ on:
292
+ workflow_call:
293
+ inputs:
294
+ item:
295
+ description: 'The namespaced item id: task:<slug>, spec:<slug>, obs:<slug>, observation:<slug> (advance) or issue:<N> (intake).'
296
+ required: true
297
+ type: string
298
+ integrationMode:
299
+ description: 'propose or merge (advance items; the lock phase refuses anything else). Intake reads its document mode from dorfl.json.'
300
+ required: false
301
+ default: 'propose'
302
+ type: string
303
+ autoBuild:
304
+ description: 'One-shot autoBuild override (blank: none).'
305
+ required: false
306
+ default: ''
307
+ type: string
308
+ autoTask:
309
+ description: 'One-shot autoTask override (blank: none).'
310
+ required: false
311
+ default: ''
312
+ type: string
313
+ observationTriage:
314
+ description: 'One-shot observationTriage override (blank: none).'
315
+ required: false
316
+ default: ''
317
+ type: string
318
+ surfaceBlockers:
319
+ description: 'One-shot surfaceBlockers override (blank: none).'
320
+ required: false
321
+ default: ''
322
+ type: string
323
+ secrets:
324
+ ${callSecretsBlock(config)}
325
+
326
+ # Nothing at workflow level: each job grants its own scopes (and a caller's
327
+ # grant caps them).
328
+ permissions: {}
329
+
330
+ jobs:
331
+ # ── LOCK: write token, no agent, no repository code ─────────────────────────
332
+ lock:
333
+ runs-on: ubuntu-latest
334
+ timeout-minutes: 15
335
+ permissions:
336
+ contents: write
337
+ issues: write
338
+ pull-requests: write
339
+ # Exactly the lock phase's facts (ci-lock-outputs.ts), written by dorfl to
340
+ # $GITHUB_OUTPUT. The agent and apply jobs read them back as ONE JSON value,
341
+ # DORFL_LOCK_OUTPUTS = toJSON(needs.lock.outputs), which dorfl re-validates.
342
+ outputs:
343
+ ${lockOutputs}
344
+ steps:
345
+ - uses: ${pinnedUses(ACTION_PINS.checkout)}
346
+ with:
347
+ fetch-depth: 0${lockBaseStep}
348
+ - uses: ${WRITER_SETUP_ACTION_USES}${lockSetupWith}
349
+ ${gate}
350
+ - name: lock the item (no agent, no repository code)
351
+ id: lock
352
+ # dorfl fetches <arbiter>/main, classifies the item at its tip (the
353
+ # base), takes the locks and writes its facts to $GITHUB_OUTPUT (among
354
+ # them agentTimeoutMinutes, read from dorfl.json at the base). Intake
355
+ # derives the origin trust from this run's event and the document mode
356
+ # from dorfl.json at the base.
357
+ env:
358
+ GH_TOKEN: \${{ secrets.${PR_IDENTITY_SECRET_NAME} || secrets.GITHUB_TOKEN }}
359
+ WORK_ITEM: \${{ inputs.item }}
360
+ INTEGRATION_MODE: \${{ inputs.integrationMode }}
361
+ run: |
362
+ ${phaseScript({phase: 'lock', advanceExtra: '', intakeExtra: ''})}${lockBaseCheck}
363
+
364
+ # ── AGENT: read-only token, no persisted credential ────────────────────────
365
+ agent:
366
+ needs: lock
367
+ if: \${{ needs.lock.outputs.acquired == 'true' && needs.lock.outputs.needsAgent == 'true' }}
368
+ runs-on: ubuntu-latest
369
+ # agentDeadlineMinutes + checkpointHeadroomMinutes from dorfl.json at the
370
+ # base, computed by the lock job (a job output is a string: fromJSON).
371
+ timeout-minutes: \${{ fromJSON(needs.lock.outputs.agentTimeoutMinutes) }}
372
+ permissions:
373
+ contents: read
374
+ issues: read
375
+ steps:
376
+ - uses: ${pinnedUses(ACTION_PINS.checkout)}
377
+ with:
378
+ ref: \${{ needs.lock.outputs.baseSha }}
379
+ fetch-depth: 0
380
+ # Nothing in this job may hold a credential the agent could use: the
381
+ # read token reaches dorfl's own git per command, never .git/config.
382
+ persist-credentials: false
383
+ lfs: true
384
+ - uses: ${SETUP_ACTION_USES}${providerSecretsWithBlock(config)}
385
+ - name: enable git LFS in this checkout
386
+ run: git lfs install --local
387
+ ${gate}
388
+ - name: run the agents (stops at the first write, writes the handoff)
389
+ env:
390
+ # Read-only (this job's permissions): dorfl's fetches pass it per
391
+ # command and scrub it from the agent's environment.
392
+ GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
393
+ DORFL_LOCK_OUTPUTS: \${{ toJSON(needs.lock.outputs) }}
394
+ WORK_ITEM: \${{ inputs.item }}
395
+ INTEGRATION_MODE: \${{ inputs.integrationMode }}
396
+ # \`--watch\` streams the build agent's turns into this job log.
397
+ run: |
398
+ ${phaseScript({
399
+ phase: 'agent',
400
+ advanceExtra: ' --watch --handoff-out "${RUNNER_TEMP}/dorfl-handoff"',
401
+ intakeExtra: ' --handoff-out "${RUNNER_TEMP}/dorfl-handoff"',
402
+ })}
403
+ - name: upload the handoff
404
+ if: \${{ always() }}
405
+ uses: ${pinnedUses(ACTION_PINS.uploadArtifact)}
406
+ with:
407
+ name: \${{ needs.lock.outputs.handoffName }}
408
+ path: \${{ runner.temp }}/dorfl-handoff
409
+ retention-days: 1
410
+ if-no-files-found: ignore
411
+
412
+ # ── APPLY: write token, no agent, no repository code ────────────────────────
413
+ apply:
414
+ needs: [lock, agent]
415
+ if: \${{ always() && needs.lock.outputs.acquired == 'true' }}
416
+ runs-on: ubuntu-latest
417
+ timeout-minutes: 30
418
+ # actions: read + checks: read tell a timed-out agent job from a cancelled
419
+ # one (GitHub reports both as cancelled; decision 5).
420
+ permissions:
421
+ contents: write
422
+ issues: write
423
+ pull-requests: write
424
+ actions: read
425
+ checks: read
426
+ steps:
427
+ - uses: ${pinnedUses(ACTION_PINS.checkout)}
428
+ with:
429
+ ref: \${{ needs.lock.outputs.baseSha }}
430
+ fetch-depth: 0
431
+ - uses: ${WRITER_SETUP_ACTION_USES}
432
+ ${gate}
433
+ - name: download the handoff (this run's own artifact)
434
+ # No run-id: only this run's artifacts, i.e. this item's agent job. A
435
+ # missing artifact is the apply phase's to judge, not a job failure.
436
+ if: \${{ needs.agent.result == 'success' }}
437
+ continue-on-error: true
438
+ uses: ${pinnedUses(ACTION_PINS.downloadArtifact)}
439
+ with:
440
+ name: \${{ needs.lock.outputs.handoffName }}
441
+ path: \${{ runner.temp }}/dorfl-handoff
442
+ - name: apply the item (validates the handoff as hostile, does every write)
443
+ env:
444
+ GH_TOKEN: \${{ secrets.${PR_IDENTITY_SECRET_NAME} || secrets.GITHUB_TOKEN }}
445
+ # The agent job's timeout check reads the Actions API with THIS token
446
+ # only (never DORFL_GH_TOKEN).
447
+ GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
448
+ DORFL_LOCK_OUTPUTS: \${{ toJSON(needs.lock.outputs) }}
449
+ AGENT_RESULT: \${{ needs.agent.result }}
450
+ AGENT_TIMEOUT_MINUTES: \${{ needs.lock.outputs.agentTimeoutMinutes }}
451
+ WORK_ITEM: \${{ inputs.item }}
452
+ INTEGRATION_MODE: \${{ inputs.integrationMode }}
453
+ run: |
454
+ ${phaseScript({
455
+ phase: 'apply',
456
+ prelude: `\
457
+ timeout_args=()
458
+ if [ -n "\${AGENT_TIMEOUT_MINUTES}" ]; then
459
+ timeout_args=(--agent-timeout-minutes "\${AGENT_TIMEOUT_MINUTES}")
460
+ fi`,
461
+ advanceExtra:
462
+ ' --agent-result "${AGENT_RESULT}" "${timeout_args[@]}" --handoff-in "${RUNNER_TEMP}/dorfl-handoff"',
463
+ intakeExtra:
464
+ ' --agent-result "${AGENT_RESULT}" "${timeout_args[@]}" --handoff-in "${RUNNER_TEMP}/dorfl-handoff"',
465
+ })}
466
+ `;
467
+ }
468
+
469
+ /**
470
+ * Generate `dorfl-item-dispatch.yml`: the `workflow_dispatch` wrapper the
471
+ * advance tick starts once per item (`gh workflow run`). It holds the
472
+ * parallelism slot and calls `dorfl-item.yml`. Deterministic.
473
+ */
474
+ export function generateItemDispatchWorkflow(config: ResolvedCIConfig): string {
475
+ const gateInputs = GATE_OVERRIDES.map(
476
+ (g) => `\
477
+ ${g.input}:
478
+ description: 'One-shot override of the ${g.input} gate (${g.env}) for this item run only. Blank: no override (config wins).'
479
+ required: false
480
+ default: ''
481
+ type: choice
482
+ options:
483
+ - ''
484
+ ${g.values.map((v) => ` - '${v}'`).join('\n')}`,
485
+ ).join('\n');
486
+ const gateWith = GATE_OVERRIDES.map(
487
+ (g) => ` ${g.input}: \${{ inputs.${g.input} }}`,
488
+ ).join('\n');
489
+ return `\
490
+ # dorfl — ONE ADVANCE ITEM per workflow run (spec
491
+ # ci-agent-job-without-write-token, decision 1 of ADR
492
+ # ci-agent-job-holds-no-write-token). EMITTED by \`dorfl install-ci\`; the human
493
+ # commits it. DO NOT hand-edit a copy: re-run install-ci to upgrade.
494
+ #
495
+ # The advance-lifecycle tick's \`dispatch\` job starts one run of THIS workflow
496
+ # per eligible item (\`gh workflow run\`); its single job calls dorfl-item.yml
497
+ # (lock, agent, apply). A run of its own per item gives the item its own
498
+ # artifact namespace, so no other item's agent can plant its handoff.
499
+ #
500
+ # PARALLELISM: each run joins the concurrency group dorfl-slot-<slot> (slot =
501
+ # item index mod maxParallel) with \`queue: max\`, so at most maxParallel item
502
+ # runs execute at once and the rest wait first-in-first-out, costing no runner
503
+ # minutes. A slot holds at most 100 pending runs; beyond that GitHub cancels,
504
+ # and the next tick re-dispatches (the item is still eligible).
505
+ #
506
+ # The run name \`dorfl-item <item>\` is what the tick de-duplicates on: an item
507
+ # whose run is not completed yet is not dispatched again.
508
+
509
+ name: dorfl-item-dispatch
510
+
511
+ run-name: ${ITEM_RUN_NAME_PREFIX} \${{ inputs.item }}
512
+
513
+ on:
514
+ workflow_dispatch:
515
+ inputs:
516
+ item:
517
+ description: 'The namespaced item id (task:<slug>, spec:<slug>, obs:<slug>, observation:<slug>).'
518
+ required: true
519
+ type: string
520
+ integrationMode:
521
+ description: 'propose: open a PR for the item; merge: land it on main (rebase + CAS push).'
522
+ required: true
523
+ default: 'propose'
524
+ type: choice
525
+ options:
526
+ - propose
527
+ - merge
528
+ slot:
529
+ description: 'The parallelism slot (the concurrency group dorfl-slot-<slot>).'
530
+ required: true
531
+ default: '0'
532
+ type: string
533
+ ${gateInputs}
534
+
535
+ # Nothing at workflow level: the calling job grants what dorfl-item.yml needs.
536
+ permissions: {}
537
+
538
+ concurrency:
539
+ group: dorfl-slot-\${{ inputs.slot }}
540
+ cancel-in-progress: false
541
+ queue: max
542
+
543
+ jobs:
544
+ item:
545
+ # The union of what dorfl-item.yml's jobs request (a called workflow can
546
+ # only narrow this): lock and apply write, apply reads the agent job's
547
+ # check runs.
548
+ permissions:
549
+ ${ITEM_WORKFLOW_CALLER_PERMISSIONS}
550
+ uses: ./.github/workflows/dorfl-item.yml
551
+ with:
552
+ item: \${{ inputs.item }}
553
+ integrationMode: \${{ inputs.integrationMode }}
554
+ ${gateWith}${itemCallSecrets(config, true)}
555
+ `;
556
+ }
package/src/env-config.ts CHANGED
@@ -64,12 +64,14 @@ const KEY_COERCIONS: {[K in keyof Config]?: Coercion} = {
64
64
  // so `DORFL_OBSERVATION_TRIAGE=off|ask|auto` works and a typo FAILS
65
65
  // LOUDLY naming the variable + the valid options.
66
66
  observationTriage: {enum: ['off', 'ask', 'auto']},
67
- // The merge-question SURFACER gate is a 3-state ENUM coercion (mirrors
68
- // `observationTriage`'s shape; SEPARATE axis with a HIGHER default, spec
67
+ // The merge-question SURFACER gate is a 2-state ENUM coercion (a SEPARATE
68
+ // axis from `observationTriage` with a HIGHER default, spec
69
69
  // `land-time-reverify-and-parallel-merge-ceiling` Story 17 / task
70
- // `merge-questions-gate-axis`), so `DORFL_MERGE_QUESTIONS=off|ask|auto`
71
- // works and a typo FAILS LOUDLY naming the variable + the valid options.
72
- mergeQuestions: {enum: ['off', 'ask', 'auto']},
70
+ // `merge-questions-gate-axis`; `auto` removed by task
71
+ // `wire-merge-questions-into-the-advance-tick`), so
72
+ // `DORFL_MERGE_QUESTIONS=off|ask` works and a typo (or the retired `auto`)
73
+ // FAILS LOUDLY naming the variable + the valid options.
74
+ mergeQuestions: {enum: ['off', 'ask']},
73
75
  // The surface-blockers gate is a BOOLEAN coercion (like `autoBuild`), so
74
76
  // `DORFL_SURFACE_BLOCKERS=true|false` works and a typo FAILS LOUDLY.
75
77
  surfaceBlockers: 'boolean',