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.
- package/dist/advance-ci-template.d.ts +17 -16
- package/dist/advance-ci-template.d.ts.map +1 -1
- package/dist/advance-ci-template.js +42 -53
- package/dist/advance-ci-template.js.map +1 -1
- package/dist/advance-drivers.d.ts +8 -0
- package/dist/advance-drivers.d.ts.map +1 -1
- package/dist/advance-drivers.js +28 -4
- package/dist/advance-drivers.js.map +1 -1
- package/dist/advance-isolated.d.ts.map +1 -1
- package/dist/advance-isolated.js +3 -2
- package/dist/advance-isolated.js.map +1 -1
- package/dist/advance-lifecycle-template.d.ts +41 -40
- package/dist/advance-lifecycle-template.d.ts.map +1 -1
- package/dist/advance-lifecycle-template.js +309 -489
- package/dist/advance-lifecycle-template.js.map +1 -1
- package/dist/advance-loop-driver.d.ts.map +1 -1
- package/dist/advance-loop-driver.js +3 -2
- package/dist/advance-loop-driver.js.map +1 -1
- package/dist/advance-treeless-publish.d.ts +31 -0
- package/dist/advance-treeless-publish.d.ts.map +1 -1
- package/dist/advance-treeless-publish.js +40 -0
- package/dist/advance-treeless-publish.js.map +1 -1
- package/dist/advance.d.ts +63 -3
- package/dist/advance.d.ts.map +1 -1
- package/dist/advance.js +80 -12
- package/dist/advance.js.map +1 -1
- package/dist/advancing-lock.d.ts +9 -0
- package/dist/advancing-lock.d.ts.map +1 -1
- package/dist/advancing-lock.js +21 -1
- package/dist/advancing-lock.js.map +1 -1
- package/dist/agent-env.d.ts +61 -0
- package/dist/agent-env.d.ts.map +1 -0
- package/dist/agent-env.js +144 -0
- package/dist/agent-env.js.map +1 -0
- package/dist/agent-stop.d.ts +35 -0
- package/dist/agent-stop.d.ts.map +1 -1
- package/dist/agent-stop.js +62 -0
- package/dist/agent-stop.js.map +1 -1
- package/dist/apply-merge-action.d.ts +76 -34
- package/dist/apply-merge-action.d.ts.map +1 -1
- package/dist/apply-merge-action.js +339 -129
- package/dist/apply-merge-action.js.map +1 -1
- package/dist/apply-stuck-action.js +2 -2
- package/dist/apply-stuck-action.js.map +1 -1
- package/dist/ci-agent-result.d.ts +151 -0
- package/dist/ci-agent-result.d.ts.map +1 -0
- package/dist/ci-agent-result.js +314 -0
- package/dist/ci-agent-result.js.map +1 -0
- package/dist/ci-handoff-apply.d.ts +162 -0
- package/dist/ci-handoff-apply.d.ts.map +1 -0
- package/dist/ci-handoff-apply.js +497 -0
- package/dist/ci-handoff-apply.js.map +1 -0
- package/dist/ci-handoff-format.d.ts +369 -0
- package/dist/ci-handoff-format.d.ts.map +1 -0
- package/dist/ci-handoff-format.js +646 -0
- package/dist/ci-handoff-format.js.map +1 -0
- package/dist/ci-handoff-lfs.d.ts +146 -0
- package/dist/ci-handoff-lfs.d.ts.map +1 -0
- package/dist/ci-handoff-lfs.js +397 -0
- package/dist/ci-handoff-lfs.js.map +1 -0
- package/dist/ci-handoff.d.ts +99 -0
- package/dist/ci-handoff.d.ts.map +1 -0
- package/dist/ci-handoff.js +277 -0
- package/dist/ci-handoff.js.map +1 -0
- package/dist/ci-lock-outputs.d.ts +93 -0
- package/dist/ci-lock-outputs.d.ts.map +1 -0
- package/dist/ci-lock-outputs.js +223 -0
- package/dist/ci-lock-outputs.js.map +1 -0
- package/dist/ci-phase-build.d.ts +178 -0
- package/dist/ci-phase-build.d.ts.map +1 -0
- package/dist/ci-phase-build.js +989 -0
- package/dist/ci-phase-build.js.map +1 -0
- package/dist/ci-phase-driver.d.ts +247 -0
- package/dist/ci-phase-driver.d.ts.map +1 -0
- package/dist/ci-phase-driver.js +370 -0
- package/dist/ci-phase-driver.js.map +1 -0
- package/dist/ci-phase-intake.d.ts +172 -0
- package/dist/ci-phase-intake.d.ts.map +1 -0
- package/dist/ci-phase-intake.js +555 -0
- package/dist/ci-phase-intake.js.map +1 -0
- package/dist/ci-phase-tasking.d.ts +205 -0
- package/dist/ci-phase-tasking.d.ts.map +1 -0
- package/dist/ci-phase-tasking.js +906 -0
- package/dist/ci-phase-tasking.js.map +1 -0
- package/dist/ci-phase-treeless.d.ts +224 -0
- package/dist/ci-phase-treeless.d.ts.map +1 -0
- package/dist/ci-phase-treeless.js +1482 -0
- package/dist/ci-phase-treeless.js.map +1 -0
- package/dist/ci-read-token.d.ts +47 -0
- package/dist/ci-read-token.d.ts.map +1 -0
- package/dist/ci-read-token.js +80 -0
- package/dist/ci-read-token.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +447 -22
- package/dist/cli.js.map +1 -1
- package/dist/close-job-template.d.ts +10 -1
- package/dist/close-job-template.d.ts.map +1 -1
- package/dist/close-job-template.js +46 -14
- package/dist/close-job-template.js.map +1 -1
- package/dist/close-job.d.ts +20 -5
- package/dist/close-job.d.ts.map +1 -1
- package/dist/close-job.js +65 -12
- package/dist/close-job.js.map +1 -1
- package/dist/complete.d.ts.map +1 -1
- package/dist/complete.js +20 -1
- package/dist/complete.js.map +1 -1
- package/dist/config.d.ts +41 -44
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -4
- package/dist/config.js.map +1 -1
- package/dist/continue-branch.d.ts +63 -15
- package/dist/continue-branch.d.ts.map +1 -1
- package/dist/continue-branch.js +75 -8
- package/dist/continue-branch.js.map +1 -1
- package/dist/crashed-run-save.d.ts +73 -0
- package/dist/crashed-run-save.d.ts.map +1 -0
- package/dist/crashed-run-save.js +86 -0
- package/dist/crashed-run-save.js.map +1 -0
- package/dist/cwd-section.d.ts.map +1 -1
- package/dist/cwd-section.js +10 -2
- package/dist/cwd-section.js.map +1 -1
- package/dist/decision-engine.d.ts +10 -0
- package/dist/decision-engine.d.ts.map +1 -1
- package/dist/decision-engine.js.map +1 -1
- package/dist/do-config.d.ts +2 -24
- package/dist/do-config.d.ts.map +1 -1
- package/dist/do-config.js +1 -33
- package/dist/do-config.js.map +1 -1
- package/dist/do.d.ts +149 -1
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +115 -25
- package/dist/do.js.map +1 -1
- package/dist/dorfl-item-template.d.ts +98 -0
- package/dist/dorfl-item-template.d.ts.map +1 -0
- package/dist/dorfl-item-template.js +504 -0
- package/dist/dorfl-item-template.js.map +1 -0
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +7 -5
- package/dist/env-config.js.map +1 -1
- package/dist/frontmatter.d.ts +52 -0
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +136 -2
- package/dist/frontmatter.js.map +1 -1
- package/dist/gc.d.ts +13 -0
- package/dist/gc.d.ts.map +1 -1
- package/dist/gc.js +16 -2
- package/dist/gc.js.map +1 -1
- package/dist/git.d.ts +18 -1
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +70 -5
- package/dist/git.js.map +1 -1
- package/dist/harness.d.ts +44 -5
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +24 -1
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/install-ci-action-pins.d.ts +123 -0
- package/dist/install-ci-action-pins.d.ts.map +1 -0
- package/dist/install-ci-action-pins.js +190 -0
- package/dist/install-ci-action-pins.js.map +1 -0
- package/dist/install-ci-actions-pr-creation.d.ts +70 -0
- package/dist/install-ci-actions-pr-creation.d.ts.map +1 -0
- package/dist/install-ci-actions-pr-creation.js +106 -0
- package/dist/install-ci-actions-pr-creation.js.map +1 -0
- package/dist/install-ci-capabilities/dorfl-item.d.ts +12 -0
- package/dist/install-ci-capabilities/dorfl-item.d.ts.map +1 -0
- package/dist/install-ci-capabilities/dorfl-item.js +26 -0
- package/dist/install-ci-capabilities/dorfl-item.js.map +1 -0
- package/dist/install-ci-core.d.ts +155 -37
- package/dist/install-ci-core.d.ts.map +1 -1
- package/dist/install-ci-core.js +304 -200
- package/dist/install-ci-core.js.map +1 -1
- package/dist/install-ci-github.d.ts +47 -2
- package/dist/install-ci-github.d.ts.map +1 -1
- package/dist/install-ci-github.js +113 -1
- package/dist/install-ci-github.js.map +1 -1
- package/dist/install-ci.d.ts +25 -6
- package/dist/install-ci.d.ts.map +1 -1
- package/dist/install-ci.js +72 -36
- package/dist/install-ci.js.map +1 -1
- package/dist/intake-marker.d.ts +6 -1
- package/dist/intake-marker.d.ts.map +1 -1
- package/dist/intake-marker.js.map +1 -1
- package/dist/intake-triage.d.ts.map +1 -1
- package/dist/intake-triage.js +6 -2
- package/dist/intake-triage.js.map +1 -1
- package/dist/intake-trigger-template.d.ts +31 -25
- package/dist/intake-trigger-template.d.ts.map +1 -1
- package/dist/intake-trigger-template.js +107 -292
- package/dist/intake-trigger-template.js.map +1 -1
- package/dist/intake.d.ts +186 -4
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +533 -416
- package/dist/intake.js.map +1 -1
- package/dist/integration-core.d.ts +154 -1
- package/dist/integration-core.d.ts.map +1 -1
- package/dist/integration-core.js +759 -486
- package/dist/integration-core.js.map +1 -1
- package/dist/integrator.d.ts +12 -0
- package/dist/integrator.d.ts.map +1 -1
- package/dist/integrator.js +21 -0
- package/dist/integrator.js.map +1 -1
- package/dist/isolation.d.ts +12 -2
- package/dist/isolation.d.ts.map +1 -1
- package/dist/isolation.js +19 -8
- package/dist/isolation.js.map +1 -1
- package/dist/item-lock.d.ts +93 -11
- package/dist/item-lock.d.ts.map +1 -1
- package/dist/item-lock.js +233 -65
- package/dist/item-lock.js.map +1 -1
- package/dist/land-question-residue.d.ts +58 -0
- package/dist/land-question-residue.d.ts.map +1 -0
- package/dist/land-question-residue.js +95 -0
- package/dist/land-question-residue.js.map +1 -0
- package/dist/lifecycle-gather.d.ts +16 -0
- package/dist/lifecycle-gather.d.ts.map +1 -1
- package/dist/lifecycle-gather.js +54 -6
- package/dist/lifecycle-gather.js.map +1 -1
- package/dist/merge-answer.d.ts +41 -0
- package/dist/merge-answer.d.ts.map +1 -0
- package/dist/merge-answer.js +60 -0
- package/dist/merge-answer.js.map +1 -0
- package/dist/merge-question-surfacer.d.ts +105 -30
- package/dist/merge-question-surfacer.d.ts.map +1 -1
- package/dist/merge-question-surfacer.js +202 -91
- package/dist/merge-question-surfacer.js.map +1 -1
- package/dist/merge-question-tick.d.ts +64 -0
- package/dist/merge-question-tick.d.ts.map +1 -0
- package/dist/merge-question-tick.js +96 -0
- package/dist/merge-question-tick.js.map +1 -0
- package/dist/mint-adr.d.ts.map +1 -1
- package/dist/mint-adr.js +20 -2
- package/dist/mint-adr.js.map +1 -1
- package/dist/needs-attention.d.ts +72 -2
- package/dist/needs-attention.d.ts.map +1 -1
- package/dist/needs-attention.js +275 -38
- package/dist/needs-attention.js.map +1 -1
- package/dist/phase-recorder.d.ts +168 -0
- package/dist/phase-recorder.d.ts.map +1 -0
- package/dist/phase-recorder.js +218 -0
- package/dist/phase-recorder.js.map +1 -0
- package/dist/phase.d.ts +94 -0
- package/dist/phase.d.ts.map +1 -0
- package/dist/phase.js +148 -0
- package/dist/phase.js.map +1 -0
- package/dist/pi-harness.d.ts.map +1 -1
- package/dist/pi-harness.js +19 -6
- package/dist/pi-harness.js.map +1 -1
- package/dist/prepare.d.ts.map +1 -1
- package/dist/prepare.js +4 -0
- package/dist/prepare.js.map +1 -1
- package/dist/prompt.d.ts +4 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +48 -1
- package/dist/prompt.js.map +1 -1
- package/dist/protocol/CLAIM-PROTOCOL.md +3 -1
- package/dist/protocol/WORK-CONTRACT.md +1 -1
- package/dist/reap-branches.d.ts +48 -0
- package/dist/reap-branches.d.ts.map +1 -1
- package/dist/reap-branches.js +64 -0
- package/dist/reap-branches.js.map +1 -1
- package/dist/ref-write.d.ts +198 -0
- package/dist/ref-write.d.ts.map +1 -0
- package/dist/ref-write.js +79 -0
- package/dist/ref-write.js.map +1 -0
- package/dist/repo-config.js +8 -8
- package/dist/repo-config.js.map +1 -1
- package/dist/repo-mirror.d.ts +36 -0
- package/dist/repo-mirror.d.ts.map +1 -1
- package/dist/repo-mirror.js +91 -1
- package/dist/repo-mirror.js.map +1 -1
- package/dist/review-verdict.d.ts +1 -1
- package/dist/review-verdict.js +1 -1
- package/dist/run.d.ts +7 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +28 -4
- package/dist/run.js.map +1 -1
- package/dist/scan.d.ts +19 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +21 -8
- package/dist/scan.js.map +1 -1
- package/dist/sidecar.d.ts +14 -0
- package/dist/sidecar.d.ts.map +1 -1
- package/dist/sidecar.js +21 -0
- package/dist/sidecar.js.map +1 -1
- package/dist/single-job-warning.d.ts +63 -0
- package/dist/single-job-warning.d.ts.map +1 -0
- package/dist/single-job-warning.js +110 -0
- package/dist/single-job-warning.js.map +1 -0
- package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +3 -1
- package/dist/skills/setup/protocol/WORK-CONTRACT.md +1 -1
- package/dist/spec-complete.d.ts +13 -6
- package/dist/spec-complete.d.ts.map +1 -1
- package/dist/spec-complete.js +23 -11
- package/dist/spec-complete.js.map +1 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +47 -14
- package/dist/start.js.map +1 -1
- package/dist/status.d.ts +12 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +11 -3
- package/dist/status.js.map +1 -1
- package/dist/tasking.d.ts +246 -1
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +452 -358
- package/dist/tasking.js.map +1 -1
- package/dist/triage-persist.d.ts.map +1 -1
- package/dist/triage-persist.js +21 -2
- package/dist/triage-persist.js.map +1 -1
- package/dist/verify-workflow-template.d.ts.map +1 -1
- package/dist/verify-workflow-template.js +8 -4
- package/dist/verify-workflow-template.js.map +1 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +4 -0
- package/dist/verify.js.map +1 -1
- package/dist/watch-session.d.ts +42 -13
- package/dist/watch-session.d.ts.map +1 -1
- package/dist/watch-session.js +52 -23
- package/dist/watch-session.js.map +1 -1
- package/dist/work-layout.d.ts +10 -4
- package/dist/work-layout.d.ts.map +1 -1
- package/dist/work-layout.js +11 -3
- package/dist/work-layout.js.map +1 -1
- package/dist/workspace.d.ts +65 -4
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +159 -11
- package/dist/workspace.js.map +1 -1
- package/package.json +1 -1
- package/src/advance-ci-template.ts +80 -81
- package/src/advance-drivers.ts +45 -6
- package/src/advance-isolated.ts +3 -5
- package/src/advance-lifecycle-template.ts +375 -538
- package/src/advance-loop-driver.ts +3 -5
- package/src/advance-treeless-publish.ts +61 -0
- package/src/advance.ts +145 -15
- package/src/advancing-lock.ts +36 -1
- package/src/agent-env.ts +174 -0
- package/src/agent-stop.ts +81 -0
- package/src/apply-merge-action.ts +401 -154
- package/src/apply-stuck-action.ts +2 -2
- package/src/ci-agent-result.ts +421 -0
- package/src/ci-handoff-apply.ts +737 -0
- package/src/ci-handoff-format.ts +926 -0
- package/src/ci-handoff-lfs.ts +519 -0
- package/src/ci-handoff.ts +392 -0
- package/src/ci-lock-outputs.ts +273 -0
- package/src/ci-phase-build.ts +1328 -0
- package/src/ci-phase-driver.ts +548 -0
- package/src/ci-phase-intake.ts +861 -0
- package/src/ci-phase-tasking.ts +1255 -0
- package/src/ci-phase-treeless.ts +2028 -0
- package/src/ci-read-token.ts +90 -0
- package/src/cli.ts +591 -29
- package/src/close-job-template.ts +52 -15
- package/src/close-job.ts +77 -12
- package/src/complete.ts +19 -1
- package/src/config.ts +43 -48
- package/src/continue-branch.ts +121 -16
- package/src/crashed-run-save.ts +162 -0
- package/src/cwd-section.ts +10 -0
- package/src/decision-engine.ts +10 -0
- package/src/do-config.ts +2 -57
- package/src/do.ts +170 -28
- package/src/dorfl-item-template.ts +556 -0
- package/src/env-config.ts +7 -5
- package/src/frontmatter.ts +155 -2
- package/src/gc.ts +27 -2
- package/src/git.ts +83 -5
- package/src/harness.ts +58 -6
- package/src/index.ts +50 -1
- package/src/install-ci-action-pins.ts +209 -0
- package/src/install-ci-actions-pr-creation.ts +186 -0
- package/src/install-ci-capabilities/dorfl-item.ts +33 -0
- package/src/install-ci-core.ts +388 -233
- package/src/install-ci-github.ts +149 -1
- package/src/install-ci.ts +83 -37
- package/src/intake-marker.ts +6 -1
- package/src/intake-triage.ts +7 -3
- package/src/intake-trigger-template.ts +126 -327
- package/src/intake.ts +725 -544
- package/src/integration-core.ts +975 -523
- package/src/integrator.ts +28 -0
- package/src/isolation.ts +29 -10
- package/src/item-lock.ts +308 -92
- package/src/land-question-residue.ts +122 -0
- package/src/lifecycle-gather.ts +82 -10
- package/src/merge-answer.ts +82 -0
- package/src/merge-question-surfacer.ts +320 -112
- package/src/merge-question-tick.ts +182 -0
- package/src/mint-adr.ts +28 -2
- package/src/needs-attention.ts +350 -42
- package/src/phase-recorder.ts +350 -0
- package/src/phase.ts +177 -0
- package/src/pi-harness.ts +25 -5
- package/src/prepare.ts +4 -0
- package/src/prompt.ts +68 -1
- package/src/reap-branches.ts +107 -0
- package/src/ref-write.ts +324 -0
- package/src/repo-config.ts +8 -8
- package/src/repo-mirror.ts +107 -1
- package/src/review-verdict.ts +1 -1
- package/src/run.ts +47 -6
- package/src/scan.ts +37 -7
- package/src/sidecar.ts +36 -0
- package/src/single-job-warning.ts +135 -0
- package/src/spec-complete.ts +36 -14
- package/src/start.ts +55 -16
- package/src/status.ts +29 -3
- package/src/tasking.ts +593 -378
- package/src/triage-persist.ts +24 -2
- package/src/verify-workflow-template.ts +8 -4
- package/src/verify.ts +4 -0
- package/src/watch-session.ts +86 -28
- package/src/work-layout.ts +11 -3
- package/src/workspace.ts +210 -14
|
@@ -17,38 +17,41 @@
|
|
|
17
17
|
*
|
|
18
18
|
* - the LIFECYCLE rungs (triage observations / surface declared blockers / apply
|
|
19
19
|
* a committed answer), gated by the two calm-default lifecycle gates below;
|
|
20
|
-
* - the on-answer-committed trigger `on: push work/questions
|
|
21
|
-
* the loop promptly when a question
|
|
20
|
+
* - the on-answer-committed trigger `on: push work/questions/**` (on the
|
|
21
|
+
* default branch only), which re-runs the loop promptly when a question
|
|
22
|
+
* sidecar is answered;
|
|
22
23
|
* - (already in the absorbed seed) capability F — the `reap-merged-branches` job
|
|
23
24
|
* (`gc --remote-branches`) on the same `schedule:` cron, opt-out via the
|
|
24
|
-
* `sweepMergedBranches` dispatch input
|
|
25
|
+
* `sweepMergedBranches` dispatch input;
|
|
26
|
+
* - (also in the seed) the no-agent `surface-merge-questions` job (`dorfl
|
|
27
|
+
* surface-merge-questions`, task `wire-merge-questions-into-the-advance-tick`),
|
|
28
|
+
* which asks a merge question for each unmerged work branch with no open PR,
|
|
29
|
+
* behind the `mergeQuestions` config gate.
|
|
25
30
|
*
|
|
26
|
-
*
|
|
31
|
+
* THE SPLIT (spec `ci-agent-job-without-write-token`, ADR
|
|
32
|
+
* `ci-agent-job-holds-no-write-token`, task `ci-split-generate-workflows`): the
|
|
33
|
+
* tick runs NO agent itself. `enumerate` (`contents: read`) lists the eligible
|
|
34
|
+
* items; a `dispatch` job (`actions: write` ONLY, no checkout, no setup) starts
|
|
35
|
+
* one `dorfl-item-dispatch.yml` run per item, which calls `dorfl-item.yml`
|
|
36
|
+
* (lock, agent, apply; `dorfl-item-template.ts`). There is no matrix any more:
|
|
37
|
+
* a matrix would put every item's artifact in one shared namespace (decision 1).
|
|
27
38
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* `
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* matrix (`dorfl scan --json | jq` enumerates one leg per eligible id,
|
|
43
|
-
* each leg carries `--propose` so it can NEVER merge to `main`); `merge` ⇒ ALSO
|
|
44
|
-
* a MATRIX (one leg per item; build/gate/review fan out, the LAND tail is
|
|
45
|
-
* serialised by the engine's `mergeRetries` CAS-retry loop — the git-alone
|
|
46
|
-
* floor — NOT by the workflow shape; SPEC
|
|
47
|
-
* `land-time-reverify-and-parallel-merge-ceiling`). This is integration-mode
|
|
48
|
-
* behaviour, IDENTICAL to the build tick (integration mode is
|
|
49
|
-
* verb-independent).
|
|
39
|
+
* The discipline:
|
|
40
|
+
*
|
|
41
|
+
* - CI ALWAYS invokes `advance` (ADR `ci-config-policy-and-gate-family` §1),
|
|
42
|
+
* now inside the per-item workflow. There is NO `autoAdvance` gate.
|
|
43
|
+
* - The two LIFECYCLE gates are ORTHOGONAL peers, both calm by default
|
|
44
|
+
* (`observationTriage` off, `surfaceBlockers` false). Applying a committed
|
|
45
|
+
* answer has NO gate.
|
|
46
|
+
* - ONE word `integrationMode` drives the `--propose`/`--merge` flag every
|
|
47
|
+
* item run passes (the `dispatch` job forwards it). In merge mode the LAND
|
|
48
|
+
* tail (rebase + CAS push to `main`) is serialised by the engine's
|
|
49
|
+
* `mergeRetries` CAS-retry loop in the apply job, NOT by the workflow
|
|
50
|
+
* shape; a lost CAS re-runs the rebase and the push only, never the gate
|
|
51
|
+
* (decision 2), and the apply phase reports every land whose tree differs
|
|
52
|
+
* from the gated one.
|
|
50
53
|
* - CI runs IN-PLACE (the CI container IS the isolation): no
|
|
51
|
-
* `--isolated`/`--remote`/registry. A
|
|
54
|
+
* `--isolated`/`--remote`/registry. A per-ref concurrency group prevents
|
|
52
55
|
* overlapping ticks; the claim CAS is the real cross-run serialiser.
|
|
53
56
|
* - All invocations use explicit slug prefixes (`task:`/`spec:`), never bare
|
|
54
57
|
* (ADR `command-surface-and-journeys` §3a).
|
|
@@ -60,8 +63,15 @@
|
|
|
60
63
|
* the test generates this artifact under `--fake` and asserts every invariant.
|
|
61
64
|
*/
|
|
62
65
|
|
|
66
|
+
import {ACTION_PINS, pinnedUses} from './install-ci-action-pins.js';
|
|
63
67
|
import type {ResolvedCIConfig} from './install-ci-core.js';
|
|
64
|
-
import {
|
|
68
|
+
import {WRITER_SETUP_ACTION_USES} from './install-ci-core.js';
|
|
69
|
+
import {
|
|
70
|
+
GATE_OVERRIDES,
|
|
71
|
+
ITEM_DISPATCH_WORKFLOW_FILE,
|
|
72
|
+
ITEM_RUN_NAME_PREFIX,
|
|
73
|
+
gateOverridesStep,
|
|
74
|
+
} from './dorfl-item-template.js';
|
|
65
75
|
|
|
66
76
|
/** The capability id (the registry key + the emitted workflow file stem). */
|
|
67
77
|
export const ADVANCE_LIFECYCLE_CAPABILITY_ID = 'advance-lifecycle';
|
|
@@ -74,31 +84,64 @@ export const ADVANCE_LIFECYCLE_CAPABILITY_LABEL =
|
|
|
74
84
|
export const ADVANCE_LIFECYCLE_WORKFLOW_PATH =
|
|
75
85
|
'workflows/advance-lifecycle.yml';
|
|
76
86
|
|
|
87
|
+
/**
|
|
88
|
+
* The `dispatch` job's shell (a `run: |` body, 10-space indented): list the
|
|
89
|
+
* `dorfl-item-dispatch.yml` runs that are not completed, then start one run per
|
|
90
|
+
* item of `ITEMS` (slot = index mod `MAX_PARALLEL`), skipping an item whose
|
|
91
|
+
* `dorfl-item <item>` run is still queued, pending or in progress. Every value
|
|
92
|
+
* arrives through `env:`; it prints one line per item (dispatched or skipped).
|
|
93
|
+
* Exported for the template test, which runs it with `gh` stubbed.
|
|
94
|
+
*/
|
|
95
|
+
export const DISPATCH_SCRIPT = `\
|
|
96
|
+
set -euo pipefail
|
|
97
|
+
# The item runs that have not completed yet (queued, waiting in a
|
|
98
|
+
# slot, or in progress), by run name.
|
|
99
|
+
active="$(gh run list -R "\${REPO}" --workflow ${ITEM_DISPATCH_WORKFLOW_FILE} --limit 1000 --json displayTitle,status --jq '.[] | select(.status != "completed") | .displayTitle')"
|
|
100
|
+
mapfile -t items < <(printf '%s' "\${ITEMS}" | jq -r '.[]')
|
|
101
|
+
i=0
|
|
102
|
+
for item in "\${items[@]}"; do
|
|
103
|
+
slot=$(( i % MAX_PARALLEL ))
|
|
104
|
+
i=$(( i + 1 ))
|
|
105
|
+
if printf '%s\\n' "\${active}" | grep -Fxq -- "${ITEM_RUN_NAME_PREFIX} \${item}"; then
|
|
106
|
+
echo "skipped \${item} slot \${slot} (its '${ITEM_RUN_NAME_PREFIX} \${item}' run is not completed)"
|
|
107
|
+
continue
|
|
108
|
+
fi
|
|
109
|
+
args=(-f "item=\${item}" -f "slot=\${slot}" -f "integrationMode=\${INTEGRATION_MODE}")
|
|
110
|
+
${GATE_OVERRIDES.map(
|
|
111
|
+
(g) =>
|
|
112
|
+
` if [ -n "\${${g.env.replace(/^DORFL_/, 'DISPATCH_')}}" ]; then args+=(-f "${g.input}=\${${g.env.replace(/^DORFL_/, 'DISPATCH_')}}"); fi`,
|
|
113
|
+
).join('\n')}
|
|
114
|
+
gh workflow run ${ITEM_DISPATCH_WORKFLOW_FILE} -R "\${REPO}" --ref "\${DEFAULT_BRANCH}" "\${args[@]}"
|
|
115
|
+
echo "dispatched \${item} slot \${slot} (run '${ITEM_RUN_NAME_PREFIX} \${item}')"
|
|
116
|
+
done`;
|
|
117
|
+
|
|
77
118
|
/**
|
|
78
119
|
* Generate the advance-lifecycle workflow YAML by PARAMETERISING the seed
|
|
79
120
|
* `docs/ci/advance-loop.yml.template`. Deterministic: the same config produces
|
|
80
|
-
* byte-identical output. The
|
|
81
|
-
*
|
|
82
|
-
* scaffolding) — `config` is accepted for parity with the `CapabilityEmitter` seam
|
|
83
|
-
* and future per-config wiring, but the advance-lifecycle shape itself is
|
|
84
|
-
* config-independent.
|
|
85
|
-
*
|
|
86
|
-
* This is the absorbed seed, with two parameterisations: (1) it wires the SHARED
|
|
87
|
-
* composite setup action (`./.github/actions/dorfl-setup`, emitted by the
|
|
88
|
-
* core task) into every job; (2) it surfaces the engine gate family — including
|
|
89
|
-
* the two calm-default LIFECYCLE gates — as the `DORFL_*` env block, so the
|
|
90
|
-
* out-of-the-box tick asks nothing until the user opts in. The seed's capability-F
|
|
91
|
-
* reap job + `sweepMergedBranches` input are PRESERVED verbatim (NOT stripped); no
|
|
92
|
-
* separate gc-sweep workflow is emitted.
|
|
121
|
+
* byte-identical output. The only config it reads is `maxParallel` (the number
|
|
122
|
+
* of concurrency slots the `dispatch` job spreads the item runs over).
|
|
93
123
|
*/
|
|
94
124
|
export function generateAdvanceLifecycleWorkflow(
|
|
95
125
|
config: ResolvedCIConfig,
|
|
96
126
|
): string {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
127
|
+
const enumerateGate = gateOverridesStep({
|
|
128
|
+
source: 'github.event.inputs',
|
|
129
|
+
ifDispatch: true,
|
|
130
|
+
comment: `\
|
|
131
|
+
# MUST run BEFORE \`scan\`: scan's pools are gated by the SAME engine
|
|
132
|
+
# gate family (autoTask/autoBuild + lifecycle observationTriage/
|
|
133
|
+
# surfaceBlockers), so an override that does not reach this job empties
|
|
134
|
+
# the item list and is silently inert. \`if:\` + the inner \`[ -n ... ]\`
|
|
135
|
+
# guard keep schedule/push (and a blank dispatch field) exporting
|
|
136
|
+
# NOTHING: an empty DORFL_* would make env-config coercion throw. The
|
|
137
|
+
# inputs reach the shell through \`env:\` as DATA, never as \`\${{ }}\` text
|
|
138
|
+
# spliced into the script, and the \`case\` guards stop a value from
|
|
139
|
+
# appending its own lines to $GITHUB_ENV.`,
|
|
140
|
+
});
|
|
141
|
+
const dispatchGateEnv = GATE_OVERRIDES.map(
|
|
142
|
+
(g) =>
|
|
143
|
+
` ${g.env.replace(/^DORFL_/, 'DISPATCH_')}: \${{ github.event.inputs.${g.input} }}`,
|
|
144
|
+
).join('\n');
|
|
102
145
|
return `\
|
|
103
146
|
# dorfl — the ADVANCE LIFECYCLE loop in CI (capability C: auto-triage
|
|
104
147
|
# observations + surface declared blockers + apply committed answers, spec
|
|
@@ -112,36 +155,40 @@ export function generateAdvanceLifecycleWorkflow(
|
|
|
112
155
|
# on their own time.
|
|
113
156
|
#
|
|
114
157
|
# CI ALWAYS invokes \`advance\` (NEVER a user-chosen verb): \`advance\` is a strict
|
|
115
|
-
# superset of \`do\`, and with the lifecycle gates at their calm defaults
|
|
158
|
+
# superset of \`do\`, and with the lifecycle gates at their calm defaults it
|
|
116
159
|
# degrades to exactly \`do\`'s build/task behaviour (ADR
|
|
117
|
-
# ci-config-policy-and-gate-family §1).
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
#
|
|
160
|
+
# ci-config-policy-and-gate-family §1). There is NO \`autoAdvance\` gate.
|
|
161
|
+
#
|
|
162
|
+
# THE SPLIT (ADR ci-agent-job-holds-no-write-token): THIS workflow runs no agent.
|
|
163
|
+
# \`enumerate\` lists the eligible items; \`dispatch\` starts ONE
|
|
164
|
+
# dorfl-item-dispatch.yml run per item, which calls dorfl-item.yml: a lock job
|
|
165
|
+
# (write token, no agent), an agent job (read-only token, no persisted
|
|
166
|
+
# credential) and an apply job (write token, no agent). One run per item gives
|
|
167
|
+
# each item its own artifact namespace. Parallelism: the item runs spread over
|
|
168
|
+
# maxParallel concurrency slots (\`queue: max\`), so at most that many run at
|
|
169
|
+
# once and the rest wait without using runner minutes.
|
|
121
170
|
#
|
|
122
|
-
# ONE WORD, ONE MEANING — \`integrationMode\`
|
|
123
|
-
#
|
|
124
|
-
# * propose ⇒
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
# real cross-run serialiser: a leg that loses the claim race exits clean.
|
|
171
|
+
# ONE WORD, ONE MEANING — \`integrationMode\` is forwarded to every item run,
|
|
172
|
+
# which passes it to \`advance\` as \`--propose\`/\`--merge\`:
|
|
173
|
+
# * propose ⇒ each item opens its OWN PR; an item run can NEVER merge to main.
|
|
174
|
+
# * merge ⇒ each item lands on main. The LAND tail (rebase + CAS push) is
|
|
175
|
+
# serialised by the engine's \`mergeRetries\` CAS-retry loop in the
|
|
176
|
+
# apply job, NOT by a workflow shape: a non-fast-forward push
|
|
177
|
+
# re-rebases and retries, never a \`--force\`. A lost CAS does NOT
|
|
178
|
+
# re-run the gate (the gate ran in the agent job, minutes
|
|
179
|
+
# earlier); the apply phase reports every land whose tree differs
|
|
180
|
+
# from the gated tree ("landed without re-gate after N lost
|
|
181
|
+
# races") in its output and the landed commit's trailer.
|
|
182
|
+
# The CLAIM CAS, not the workflow, is the real cross-run serialiser: an item run
|
|
183
|
+
# that loses the claim race exits clean, and a duplicate run re-classifies at the
|
|
184
|
+
# fresh arbiter tip.
|
|
137
185
|
#
|
|
138
186
|
# CI runs IN-PLACE (the CI container IS the isolation): NO --isolated/--remote/
|
|
139
187
|
# registry (laptop-only affordances). The concurrency group below stops
|
|
140
188
|
# overlapping ticks of the same ref from colliding.
|
|
141
189
|
#
|
|
142
|
-
# SAFETY (US #9):
|
|
143
|
-
#
|
|
144
|
-
# its own triggers.
|
|
190
|
+
# SAFETY (US #9): no job here requests a \`workflows\` permission, so none can
|
|
191
|
+
# rewrite its own triggers.
|
|
145
192
|
|
|
146
193
|
name: advance-lifecycle
|
|
147
194
|
|
|
@@ -152,15 +199,18 @@ on:
|
|
|
152
199
|
# (here: hourly).
|
|
153
200
|
- cron: '0 * * * *'
|
|
154
201
|
push:
|
|
155
|
-
# On-answer-committed: a push that touches an answered
|
|
156
|
-
# (\`work/questions/**\`) re-runs the loop so the human's
|
|
157
|
-
# promptly.
|
|
202
|
+
# On-answer-committed: a push to the DEFAULT BRANCH that touches an answered
|
|
203
|
+
# question sidecar (\`work/questions/**\`) re-runs the loop so the human's
|
|
204
|
+
# answer is applied promptly. Pinned to main: a work-branch push touching a
|
|
205
|
+
# sidecar must never run the lifecycle on that branch.
|
|
206
|
+
branches:
|
|
207
|
+
- main
|
|
158
208
|
paths:
|
|
159
209
|
- 'work/questions/**'
|
|
160
210
|
workflow_dispatch:
|
|
161
211
|
inputs:
|
|
162
212
|
integrationMode:
|
|
163
|
-
description: "Integration mode
|
|
213
|
+
description: "Integration mode, forwarded to every item run as \`advance --propose\`/\`--merge\`: propose ⇒ one PR per item; merge ⇒ each item lands on main (the LAND tail is serialised by the engine's \`mergeRetries\` CAS-retry loop, not by the workflow shape)."
|
|
164
214
|
required: false
|
|
165
215
|
default: 'propose'
|
|
166
216
|
type: choice
|
|
@@ -176,10 +226,9 @@ on:
|
|
|
176
226
|
# Override an engine gate for THIS manual run only, riding the env layer of
|
|
177
227
|
# flag > env > per-repo > global > default. Modelled as \`type: choice\` with a
|
|
178
228
|
# BLANK first option (not \`type: boolean\`, which cannot represent "unset"):
|
|
179
|
-
# blank ⇒ emit NOTHING (the committed dorfl.json wins
|
|
180
|
-
#
|
|
181
|
-
# run
|
|
182
|
-
# string, so the per-job step below only writes when the input is non-blank.
|
|
229
|
+
# blank ⇒ emit NOTHING (the committed dorfl.json wins); a non-blank choice ⇒
|
|
230
|
+
# export the matching DORFL_* in \`enumerate\` and forward it to every item
|
|
231
|
+
# run (its lock, agent and apply jobs).
|
|
183
232
|
autoBuild:
|
|
184
233
|
description: 'One-shot override of the autoBuild gate (DORFL_AUTO_BUILD) for THIS dispatch run only. Blank ⇒ no override (config wins).'
|
|
185
234
|
required: false
|
|
@@ -224,126 +273,60 @@ concurrency:
|
|
|
224
273
|
group: advance-lifecycle-\${{ github.ref }}
|
|
225
274
|
cancel-in-progress: false
|
|
226
275
|
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
# This is the provider-agnostic hygiene sweep (\`gc --remote-branches\`) for
|
|
244
|
-
# out-of-band human/UI PR merges, whose work/<slug> branch nothing else deletes.
|
|
245
|
-
SWEEP_MERGED_BRANCHES: \${{ github.event.inputs.sweepMergedBranches || 'true' }}
|
|
246
|
-
|
|
247
|
-
# ── The engine GATE FAMILY is resolved FROM CONFIG, not carried here ─────────
|
|
248
|
-
# CI is NOT a special policy surface (ADR ci-config-policy-and-gate-family §5):
|
|
249
|
-
# it runs the SAME engine gates, resolved through flag > env > per-repo > global
|
|
250
|
-
# > default. The SAME dorfl.json the laptop uses applies here. This
|
|
251
|
-
# workflow emits NO DORFL_AUTO_BUILD / DORFL_AUTO_TASK /
|
|
252
|
-
# DORFL_OBSERVATION_TRIAGE / DORFL_SURFACE_BLOCKERS line on a
|
|
253
|
-
# SCHEDULE/PUSH tick, so the env layer carries NO defaults there — your committed
|
|
254
|
-
# dorfl.json wins (then the global config, then the strict built-in
|
|
255
|
-
# defaults autoBuild:false / autoTask:false / observationTriage:'off' /
|
|
256
|
-
# surfaceBlockers:false). The ONE exception is a manual \`workflow_dispatch\` where
|
|
257
|
-
# you fill a gate override input (above): the per-job step then exports that ONE
|
|
258
|
-
# DORFL_* for that run only (blank input ⇒ still nothing). The override
|
|
259
|
-
# MUST be wired into every job that resolves the gate family — including the
|
|
260
|
-
# \`enumerate\` scan (it gates the matrix pools via observationTriage/surfaceBlockers
|
|
261
|
-
# /autoTask/autoBuild), not just the agent-running jobs — or the override is inert
|
|
262
|
-
# for the very pools it targets. To enable CI autonomy durably, set the gate(s) in
|
|
263
|
-
# dorfl.json (applies everywhere) — NOT by re-running install-ci (ADR §6:
|
|
264
|
-
# install-ci is one-time).
|
|
276
|
+
# Nothing at workflow level: each job grants its own scopes. NO job requests a
|
|
277
|
+
# \`workflows\` permission (US #9).
|
|
278
|
+
permissions: {}
|
|
279
|
+
|
|
280
|
+
# ── The engine GATE FAMILY is resolved FROM CONFIG, not carried here ─────────
|
|
281
|
+
# CI is NOT a special policy surface (ADR ci-config-policy-and-gate-family §5):
|
|
282
|
+
# it runs the SAME engine gates, resolved through flag > env > per-repo > global
|
|
283
|
+
# > default. This workflow emits NO DORFL_AUTO_BUILD / DORFL_AUTO_TASK /
|
|
284
|
+
# DORFL_OBSERVATION_TRIAGE / DORFL_SURFACE_BLOCKERS line on a SCHEDULE/PUSH tick,
|
|
285
|
+
# so your committed dorfl.json wins (then the global config, then the strict
|
|
286
|
+
# built-in defaults autoBuild:false / autoTask:false / observationTriage:'off' /
|
|
287
|
+
# surfaceBlockers:false). The ONE exception is a manual \`workflow_dispatch\` where
|
|
288
|
+
# you fill a gate override input (above): \`enumerate\` exports it for its scan and
|
|
289
|
+
# \`dispatch\` forwards it to every item run. To enable CI autonomy durably, set
|
|
290
|
+
# the gate(s) in dorfl.json (applies everywhere) — NOT by re-running install-ci
|
|
291
|
+
# (ADR §6: install-ci is one-time).
|
|
265
292
|
|
|
266
293
|
jobs:
|
|
267
|
-
# ── ENUMERATE
|
|
268
|
-
#
|
|
269
|
-
#
|
|
270
|
-
#
|
|
271
|
-
# \`cwd.repo.
|
|
272
|
-
#
|
|
273
|
-
#
|
|
274
|
-
#
|
|
275
|
-
#
|
|
276
|
-
#
|
|
277
|
-
#
|
|
278
|
-
#
|
|
279
|
-
#
|
|
280
|
-
# becomes one matrix leg → one independent \`advance --propose\`. Skipped in merge
|
|
281
|
-
# mode. Inert with calm-default gates (empty triage/surface pools).
|
|
294
|
+
# ── ENUMERATE ───────────────────────────────────────────────────────────────
|
|
295
|
+
# List the eligible items. \`scan --json\` reports BOTH the registry/hub-mirror
|
|
296
|
+
# pool (\`repos[].items[]\`, \`repos[].specs[]\`, \`repos[].lifecycle\`) AND the
|
|
297
|
+
# in-place working checkout (\`cwd.repo.items[]\`, \`cwd.repo.specs[]\`,
|
|
298
|
+
# \`cwd.repo.lifecycle\`); CI runs IN-PLACE so the items live in the latter (a
|
|
299
|
+
# fresh runner has no registered mirror). \`jq\` unions + dedups the build/task
|
|
300
|
+
# pools AND the LIFECYCLE pools into a list of explicit \`task:<slug>\` /
|
|
301
|
+
# \`spec:<slug>\` / \`obs:<slug>\` ids (CI MUST use explicit prefixes): \`obs:\`
|
|
302
|
+
# (triage untriaged observations), surface \`task:\`/\`spec:\` (\`needsAnswers\`,
|
|
303
|
+
# no answered sidecar) AND apply \`task:\`/\`spec:\`/\`observation:\` (an answered
|
|
304
|
+
# sidecar — consume the committed answer, closing the on-answer
|
|
305
|
+
# \`push: work/questions/**\` loop). Read-only: it runs no agent and writes
|
|
306
|
+
# nothing.
|
|
282
307
|
enumerate:
|
|
283
308
|
runs-on: ubuntu-latest
|
|
309
|
+
permissions:
|
|
310
|
+
contents: read
|
|
284
311
|
outputs:
|
|
285
312
|
items: \${{ steps.scan.outputs.items }}
|
|
286
313
|
any: \${{ steps.scan.outputs.any }}
|
|
287
|
-
# Dynamic GitHub-backstop cap (spec
|
|
288
|
-
# \`graceful-pre-timeout-wip-checkpoint\`): the agent-leg jobs
|
|
289
|
-
# (advance-propose / advance-merge) read this as their
|
|
290
|
-
# \`timeout-minutes\` — replacing the retired static
|
|
291
|
-
# \`legTimeoutMinutes\`. Computed at run time from the committed
|
|
292
|
-
# \`dorfl.json\` (via \`dorfl config --json\`) as
|
|
293
|
-
# \`agentDeadlineMinutes + checkpointHeadroomMinutes\`, so an edit to
|
|
294
|
-
# \`dorfl.json\` reflects EVERYWHERE (the internal deadline the legs
|
|
295
|
-
# honour AND this GitHub cap) on the NEXT tick — no \`install-ci\`
|
|
296
|
-
# re-run, no baked-in YAML number.
|
|
297
|
-
githubTimeout: \${{ steps.timeout.outputs.githubTimeout }}
|
|
298
314
|
steps:
|
|
299
|
-
- uses:
|
|
315
|
+
- uses: ${pinnedUses(ACTION_PINS.checkout)}
|
|
300
316
|
with:
|
|
301
317
|
fetch-depth: 0
|
|
302
|
-
- uses:
|
|
303
|
-
|
|
304
|
-
# MUST run BEFORE \`scan\`: scan's matrix pools are gated by the SAME engine
|
|
305
|
-
# gate family (autoTask/autoBuild + lifecycle observationTriage/
|
|
306
|
-
# surfaceBlockers), so an override that does not reach this job produces an
|
|
307
|
-
# empty matrix and is silently inert. \`if:\` + the inner \`[ -n ... ]\` guard
|
|
308
|
-
# keep schedule/push (and a blank dispatch field) exporting NOTHING — an
|
|
309
|
-
# empty DORFL_* would make env-config coercion throw. The inputs reach the
|
|
310
|
-
# shell through \`env:\` as DATA, never as \`\${{ }}\` text spliced into the
|
|
311
|
-
# script (GitHub's script-injection guidance), and the \`case\` guard stops a
|
|
312
|
-
# value from appending its own lines to $GITHUB_ENV. Both only matter if an
|
|
313
|
-
# input ever stops being a fixed \`type: choice\`; for the choices below the
|
|
314
|
-
# step writes exactly what it always did.
|
|
315
|
-
if: \${{ github.event_name == 'workflow_dispatch' }}
|
|
316
|
-
env:
|
|
317
|
-
DISPATCH_AUTO_BUILD: \${{ github.event.inputs.autoBuild }}
|
|
318
|
-
DISPATCH_AUTO_TASK: \${{ github.event.inputs.autoTask }}
|
|
319
|
-
DISPATCH_OBSERVATION_TRIAGE: \${{ github.event.inputs.observationTriage }}
|
|
320
|
-
DISPATCH_SURFACE_BLOCKERS: \${{ github.event.inputs.surfaceBlockers }}
|
|
321
|
-
run: |
|
|
322
|
-
case "\${DISPATCH_AUTO_BUILD}\${DISPATCH_AUTO_TASK}\${DISPATCH_OBSERVATION_TRIAGE}\${DISPATCH_SURFACE_BLOCKERS}" in
|
|
323
|
-
*$'\\n'*|*$'\\r'*) echo "::error::a dispatch gate override contains a line break"; exit 1 ;;
|
|
324
|
-
esac
|
|
325
|
-
[ -n "\${DISPATCH_AUTO_BUILD}" ] && echo "DORFL_AUTO_BUILD=\${DISPATCH_AUTO_BUILD}" >> "$GITHUB_ENV"
|
|
326
|
-
[ -n "\${DISPATCH_AUTO_TASK}" ] && echo "DORFL_AUTO_TASK=\${DISPATCH_AUTO_TASK}" >> "$GITHUB_ENV"
|
|
327
|
-
[ -n "\${DISPATCH_OBSERVATION_TRIAGE}" ] && echo "DORFL_OBSERVATION_TRIAGE=\${DISPATCH_OBSERVATION_TRIAGE}" >> "$GITHUB_ENV"
|
|
328
|
-
[ -n "\${DISPATCH_SURFACE_BLOCKERS}" ] && echo "DORFL_SURFACE_BLOCKERS=\${DISPATCH_SURFACE_BLOCKERS}" >> "$GITHUB_ENV"
|
|
329
|
-
true
|
|
318
|
+
- uses: ${WRITER_SETUP_ACTION_USES}
|
|
319
|
+
${enumerateGate}
|
|
330
320
|
- id: scan
|
|
331
|
-
# Enumerate eligible items as namespaced ids, one
|
|
332
|
-
#
|
|
333
|
-
#
|
|
334
|
-
#
|
|
335
|
-
#
|
|
336
|
-
#
|
|
337
|
-
# \`
|
|
338
|
-
# (
|
|
339
|
-
#
|
|
340
|
-
# the apply rung too, so it MUST carry through here). CI runs IN-PLACE, so we use
|
|
341
|
-
# \`scan --here\`: it reports ONLY the cwd checkout (\`cwd.repo.*\`) and SKIPS
|
|
342
|
-
# the cross-repo registry loop (no N-mirror fetches; a fresh runner has no
|
|
343
|
-
# registered mirror anyway). The \`cwd\` arbiter is the checkout's own clone,
|
|
344
|
-
# so the fetch-first is a near-noop. \`jq\` still reads the (now-empty)
|
|
345
|
-
# \`repos[]\` branches harmlessly; we union + dedup so the matrix has one
|
|
346
|
-
# leg per item.
|
|
321
|
+
# Enumerate eligible items as namespaced ids, one item run per id.
|
|
322
|
+
# Eligible TASKS ⇒ \`task:<slug>\` (\`advance\` builds them); TASKABLE SPECS
|
|
323
|
+
# ⇒ \`spec:<slug>\` (\`advance\` auto-tasks them, capability B). LIFECYCLE
|
|
324
|
+
# pools: \`lifecycle.triage[]\` ⇒ \`obs:<slug>\`, \`lifecycle.surface[]\` +
|
|
325
|
+
# \`lifecycle.apply[]\` ⇒ \`.namespace + ":" + .slug\` (an answered
|
|
326
|
+
# observation sidecar is consumed by the apply rung too, so
|
|
327
|
+
# \`observation:\` MUST carry through here). \`scan --here\` reports ONLY
|
|
328
|
+
# the cwd checkout (\`cwd.repo.*\`) and skips the cross-repo registry
|
|
329
|
+
# loop; \`jq\` still reads the (now-empty) \`repos[]\` branches harmlessly.
|
|
347
330
|
run: |
|
|
348
331
|
items="$(dorfl scan --json --here \\
|
|
349
332
|
| jq -c '[(.repos[].items[]?, .cwd.repo.items[]?) | select(.eligibility.eligible == true) | "task:" + .slug] + [(.repos[].specs[]?, .cwd.repo.specs[]?) | select(.eligibility.eligible == true) | "spec:" + .slug] + [(.repos[].lifecycle.triage[]?, .cwd.repo.lifecycle.triage[]?) | "obs:" + .slug] + [(.repos[].lifecycle.surface[]?, .cwd.repo.lifecycle.surface[]?, .repos[].lifecycle.apply[]?, .cwd.repo.lifecycle.apply[]?) | .namespace + ":" + .slug] | unique')"
|
|
@@ -353,192 +336,76 @@ jobs:
|
|
|
353
336
|
else
|
|
354
337
|
echo "any=false" >> "$GITHUB_OUTPUT"
|
|
355
338
|
fi
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
echo "githubTimeout=\${cap}" >> "$GITHUB_OUTPUT"
|
|
372
|
-
|
|
373
|
-
# ── PROPOSE: a MATRIX of independent jobs (one PR per item) ──────────────────
|
|
374
|
-
# True parallelism — each item is an independent PR, so a matrix is the right
|
|
375
|
-
# parallel shape and \`-n\` is NOT needed. Each leg passes \`--propose\`, tying the
|
|
376
|
-
# integration mode to THIS shape: it sits at the top of the precedence chain, so
|
|
377
|
-
# the workflow mode always wins over the repo config default and a leg can NEVER
|
|
378
|
-
# merge to main. Explicit \`task:\`/\`spec:\` prefixes only, never bare.
|
|
379
|
-
advance-propose:
|
|
339
|
+
|
|
340
|
+
# ── DISPATCH: one dorfl-item-dispatch.yml run per item ──────────────────────
|
|
341
|
+
# \`actions: write\` and NOTHING else, NO checkout and NO setup: no repository
|
|
342
|
+
# code runs next to \`actions: write\`, only the runner's preinstalled \`gh\` and
|
|
343
|
+
# \`jq\`. Each item run joins the slot dorfl-slot-<index mod maxParallel>
|
|
344
|
+
# (\`queue: max\`), so at most maxParallel (install-ci --max-parallel) item runs
|
|
345
|
+
# execute at once: each spawns a full agent session, and an unbounded fan-out
|
|
346
|
+
# exhausts the model provider's rate limit and thrashes the arbiter-main CAS.
|
|
347
|
+
# An item whose \`dorfl-item <item>\` run has not completed yet is SKIPPED (a
|
|
348
|
+
# tick ends as soon as it has dispatched, so the next tick can come while
|
|
349
|
+
# earlier runs still wait in a slot); a duplicate that slips through a race is
|
|
350
|
+
# harmless (its lock job re-classifies at the fresh arbiter tip). Every value
|
|
351
|
+
# reaches the script through \`env:\`; the log lists each item, its slot and
|
|
352
|
+
# whether it was dispatched or skipped.
|
|
353
|
+
dispatch:
|
|
380
354
|
needs: enumerate
|
|
381
|
-
if: \${{
|
|
355
|
+
if: \${{ needs.enumerate.outputs.any == 'true' }}
|
|
382
356
|
runs-on: ubuntu-latest
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
# time from the committed \`dorfl.json\` as
|
|
386
|
-
# \`agentDeadlineMinutes + checkpointHeadroomMinutes\`. It is a STRICTLY
|
|
387
|
-
# LARGER backstop than the dorfl-internal deadline the leg self-stops at
|
|
388
|
-
# (which SAVES its WIP + auto-continues on the next tick), so the GitHub
|
|
389
|
-
# SIGKILL only fires if the graceful save itself wedges. Editing
|
|
390
|
-
# \`dorfl.json\` flips BOTH the internal deadline AND this cap on the
|
|
391
|
-
# next tick — no \`install-ci\` re-run, no baked-in YAML number.
|
|
392
|
-
# A job output is always a STRING; \`timeout-minutes\` needs an INTEGER, so
|
|
393
|
-
# coerce it with \`fromJson(...)\` (the same pattern the \`item\` matrix uses).
|
|
394
|
-
# A raw \`\${{ ... }}\` substitution makes GitHub reject it ("Unexpected
|
|
395
|
-
# value '45'").
|
|
396
|
-
timeout-minutes: \${{ fromJson(needs.enumerate.outputs.githubTimeout) }}
|
|
397
|
-
strategy:
|
|
398
|
-
# Independent PRs: one failing item must NOT cancel the others.
|
|
399
|
-
fail-fast: false
|
|
400
|
-
# Cap concurrent legs (config \`maxParallel\`, install-ci --max-parallel):
|
|
401
|
-
# each leg spawns a FULL agent session (build + Gate-2 review), so an
|
|
402
|
-
# unbounded fan-out over a large item set exhausts the model-provider API
|
|
403
|
-
# rate limit (429s that strand legs as transient-infra stuck) AND thrashes
|
|
404
|
-
# the arbiter-main CAS. A bounded fan-out drains steadily instead.
|
|
405
|
-
max-parallel: ${config.maxParallel}
|
|
406
|
-
matrix:
|
|
407
|
-
item: \${{ fromJson(needs.enumerate.outputs.items) }}
|
|
357
|
+
permissions:
|
|
358
|
+
actions: write
|
|
408
359
|
steps:
|
|
409
|
-
-
|
|
410
|
-
with:
|
|
411
|
-
fetch-depth: 0
|
|
412
|
-
- uses: ./.github/actions/dorfl-setup${setupWith}
|
|
413
|
-
- name: apply dispatch gate overrides (one-shot, this run only)
|
|
414
|
-
# Mirror of the enumerate-job override (see there): export each gate's
|
|
415
|
-
# DORFL_* ONLY on a workflow_dispatch with a non-blank input, so the
|
|
416
|
-
# one-shot override also reaches the \`advance\` leg that builds the item.
|
|
417
|
-
if: \${{ github.event_name == 'workflow_dispatch' }}
|
|
360
|
+
- name: dispatch one item run per item
|
|
418
361
|
env:
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
362
|
+
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
363
|
+
REPO: \${{ github.repository }}
|
|
364
|
+
DEFAULT_BRANCH: \${{ github.event.repository.default_branch || 'main' }}
|
|
365
|
+
ITEMS: \${{ needs.enumerate.outputs.items }}
|
|
366
|
+
MAX_PARALLEL: '${config.maxParallel}'
|
|
367
|
+
INTEGRATION_MODE: \${{ github.event.inputs.integrationMode || 'propose' }}
|
|
368
|
+
${dispatchGateEnv}
|
|
423
369
|
run: |
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
# \`GITHUB_TOKEN\` are \`github-actions[bot]\` and do NOT trigger further
|
|
441
|
-
# \`on: pull_request\` runs). Falls back to the auto \`GITHUB_TOKEN\` so it
|
|
442
|
-
# works zero-config; the job's \`pull-requests: write\` scopes that token.
|
|
443
|
-
env:
|
|
444
|
-
GH_TOKEN: \${{ secrets.DORFL_GH_TOKEN || secrets.GITHUB_TOKEN }}
|
|
445
|
-
# The item id is a slug read from work/ (frontmatter \`slug:\` or the
|
|
446
|
-
# file name). dorfl sanitises the slugs it mints, but a hand-written
|
|
447
|
-
# one is only checked when \`advance\` resolves it, so it reaches the
|
|
448
|
-
# shell as DATA through env, never as \`\${{ matrix.item }}\` text
|
|
449
|
-
# spliced into the script.
|
|
450
|
-
WORK_ITEM: \${{ matrix.item }}
|
|
451
|
-
# \`--watch\` streams the build agent's high-signal turns (assistant text,
|
|
452
|
-
# tool calls, finish) into THIS job log live, so the run shows the agent
|
|
453
|
-
# working instead of freezing after "Start work". A read-only observer (no
|
|
454
|
-
# outcome/gate/git effect). It fits because each matrix leg names ONE item
|
|
455
|
-
# (one pi session to tail) — the merge job is now a per-item matrix too
|
|
456
|
-
# (see below) and streams the same way.
|
|
457
|
-
run: dorfl advance "\${WORK_ITEM}" --propose --watch --arbiter origin
|
|
458
|
-
|
|
459
|
-
# ── MERGE: a MATRIX of independent jobs (parallel build/gate/review, serialised land) ──
|
|
460
|
-
# SPEC \`land-time-reverify-and-parallel-merge-ceiling\` (stories 4 + 6): each item
|
|
461
|
-
# gets its own leg; build/gate/review run concurrently across siblings; the LAND
|
|
462
|
-
# TAIL is serialised by the engine's \`mergeRetries\` CAS-retry loop — the
|
|
463
|
-
# git-alone floor — NOT by the workflow shape. A non-fast-forward push triggers
|
|
464
|
-
# re-rebase + re-gate + retry up to the resolved \`mergeRetries\` cap; never a
|
|
465
|
-
# \`--force\`. NO host-specific \`concurrency:\` group on this job (it would be
|
|
466
|
-
# load-bearing for cross-job safety, breaking the floor framing).
|
|
467
|
-
# The fully-autonomous-to-main path (this job + all gates on) is still a LOUD,
|
|
468
|
-
# NON-DEFAULT opt-in: the default integrationMode is \`propose\`, so reaching
|
|
469
|
-
# merge-to-main requires deliberately dispatching/pinning \`merge\`.
|
|
470
|
-
advance-merge:
|
|
471
|
-
needs: enumerate
|
|
472
|
-
if: \${{ (github.event.inputs.integrationMode || 'propose') == 'merge' && needs.enumerate.outputs.any == 'true' }}
|
|
370
|
+
${DISPATCH_SCRIPT}
|
|
371
|
+
|
|
372
|
+
# ── SURFACE MERGE QUESTIONS (no agent) ──────────────────────────────────────
|
|
373
|
+
# Ask a merge question (a \`kind: merge\` sidecar entry the human answers
|
|
374
|
+
# \`merge | hold | drop\`) for every unmerged \`work/task-<slug>\` branch with no
|
|
375
|
+
# open PR (an open PR already is the land decision), whose task rests in
|
|
376
|
+
# tasks/ready/ or tasks/backlog/, whose lock is free or kept by its finished
|
|
377
|
+
# propose build, and whose tip carries the done-move; then publish the
|
|
378
|
+
# questions to main. An answered \`merge\` is enumerated like any answered
|
|
379
|
+
# sidecar and lands through the item run's apply rung. Gated by the
|
|
380
|
+
# \`mergeQuestions\` config (off|ask, default ask): \`off\` surfaces nothing.
|
|
381
|
+
# Deterministic: it runs no agent, so the writer-role setup (Node and dorfl
|
|
382
|
+
# only). It fetches the arbiter before listing the branches. \`gh\` reads the
|
|
383
|
+
# open PRs with the built-in token (\`pull-requests: read\`); a failed PR
|
|
384
|
+
# listing fails the job loudly rather than skipping every branch silently.
|
|
385
|
+
surface-merge-questions:
|
|
473
386
|
runs-on: ubuntu-latest
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
timeout-minutes: \${{ fromJson(needs.enumerate.outputs.githubTimeout) }}
|
|
478
|
-
strategy:
|
|
479
|
-
# Independent landings: one failing item must NOT cancel the others; a
|
|
480
|
-
# loser of the CAS race re-rebases + re-gates + retries.
|
|
481
|
-
fail-fast: false
|
|
482
|
-
# Cap concurrent legs (config \`maxParallel\`, see advance-propose): a FULL
|
|
483
|
-
# agent session per leg, so bound the fan-out to avoid API-429s + CAS thrash.
|
|
484
|
-
max-parallel: ${config.maxParallel}
|
|
485
|
-
matrix:
|
|
486
|
-
item: \${{ fromJson(needs.enumerate.outputs.items) }}
|
|
387
|
+
permissions:
|
|
388
|
+
contents: write
|
|
389
|
+
pull-requests: read
|
|
487
390
|
steps:
|
|
488
|
-
- uses:
|
|
391
|
+
- uses: ${pinnedUses(ACTION_PINS.checkout)}
|
|
489
392
|
with:
|
|
490
393
|
fetch-depth: 0
|
|
491
|
-
- uses:
|
|
492
|
-
- name:
|
|
493
|
-
# Mirror of the enumerate-job override (see there): export each gate's
|
|
494
|
-
# DORFL_* ONLY on a workflow_dispatch with a non-blank input. The
|
|
495
|
-
# merge job re-scans the pool inside \`advance -n\`, so it needs the override
|
|
496
|
-
# too for the lifecycle/task pools to reflect it.
|
|
497
|
-
if: \${{ github.event_name == 'workflow_dispatch' }}
|
|
498
|
-
env:
|
|
499
|
-
DISPATCH_AUTO_BUILD: \${{ github.event.inputs.autoBuild }}
|
|
500
|
-
DISPATCH_AUTO_TASK: \${{ github.event.inputs.autoTask }}
|
|
501
|
-
DISPATCH_OBSERVATION_TRIAGE: \${{ github.event.inputs.observationTriage }}
|
|
502
|
-
DISPATCH_SURFACE_BLOCKERS: \${{ github.event.inputs.surfaceBlockers }}
|
|
503
|
-
run: |
|
|
504
|
-
case "\${DISPATCH_AUTO_BUILD}\${DISPATCH_AUTO_TASK}\${DISPATCH_OBSERVATION_TRIAGE}\${DISPATCH_SURFACE_BLOCKERS}" in
|
|
505
|
-
*$'\\n'*|*$'\\r'*) echo "::error::a dispatch gate override contains a line break"; exit 1 ;;
|
|
506
|
-
esac
|
|
507
|
-
[ -n "\${DISPATCH_AUTO_BUILD}" ] && echo "DORFL_AUTO_BUILD=\${DISPATCH_AUTO_BUILD}" >> "$GITHUB_ENV"
|
|
508
|
-
[ -n "\${DISPATCH_AUTO_TASK}" ] && echo "DORFL_AUTO_TASK=\${DISPATCH_AUTO_TASK}" >> "$GITHUB_ENV"
|
|
509
|
-
[ -n "\${DISPATCH_OBSERVATION_TRIAGE}" ] && echo "DORFL_OBSERVATION_TRIAGE=\${DISPATCH_OBSERVATION_TRIAGE}" >> "$GITHUB_ENV"
|
|
510
|
-
[ -n "\${DISPATCH_SURFACE_BLOCKERS}" ] && echo "DORFL_SURFACE_BLOCKERS=\${DISPATCH_SURFACE_BLOCKERS}" >> "$GITHUB_ENV"
|
|
511
|
-
true
|
|
512
|
-
- name: advance one item in-place (merge ⇒ rebase + CAS land on main)
|
|
513
|
-
# In-place (no --isolated/--remote). \`--merge\` ties the integration mode
|
|
514
|
-
# to THIS (matrix) shape: it sits at the top of the precedence chain, so
|
|
515
|
-
# the workflow mode always wins over the repo's \`dorfl.json\` default.
|
|
516
|
-
# Cross-job land safety comes from the engine's CAS-retry loop, not from
|
|
517
|
-
# this workflow's job shape. \`gh\` (merge / PR housekeeping) reads
|
|
518
|
-
# \`GH_TOKEN\` from the env.
|
|
394
|
+
- uses: ${WRITER_SETUP_ACTION_USES}
|
|
395
|
+
- name: surface merge questions for unmerged work/* branches (surface-merge-questions)
|
|
519
396
|
env:
|
|
520
|
-
GH_TOKEN: \${{ secrets.
|
|
521
|
-
|
|
522
|
-
# file name). dorfl sanitises the slugs it mints, but a hand-written
|
|
523
|
-
# one is only checked when \`advance\` resolves it, so it reaches the
|
|
524
|
-
# shell as DATA through env, never as \`\${{ matrix.item }}\` text
|
|
525
|
-
# spliced into the script.
|
|
526
|
-
WORK_ITEM: \${{ matrix.item }}
|
|
527
|
-
# \`--watch\` streams the build agent's high-signal turns into THIS job
|
|
528
|
-
# log live; each leg names ONE item, so it fits the same way it does on
|
|
529
|
-
# the propose legs.
|
|
530
|
-
run: dorfl advance "\${WORK_ITEM}" --merge --watch --arbiter origin
|
|
397
|
+
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
398
|
+
run: dorfl surface-merge-questions --arbiter origin
|
|
531
399
|
|
|
532
400
|
# ── REAP merged remote work/* branches (capability F, the hygiene sweep) ─────
|
|
533
401
|
# PRESERVED from the seed (NOT a separate gc-sweep workflow): the provider-
|
|
534
402
|
# agnostic counterpart of the worktree reaper. Deletes remote \`work/<slug>\`
|
|
535
403
|
# branches that are PROVABLY MERGED into \`origin/main\`, so an out-of-band
|
|
536
404
|
# human/UI PR merge stops leaving its branch lingering on the arbiter.
|
|
537
|
-
# Independent of the integration mode
|
|
538
|
-
# accumulate out-of-band-merged branches), so it runs on EVERY tick regardless
|
|
405
|
+
# Independent of the integration mode, so it runs on EVERY tick regardless
|
|
539
406
|
# of \`integrationMode\` — gated ONLY by the \`sweepMergedBranches\` opt-out (ON by
|
|
540
407
|
# default). It NEVER touches an un-merged/in-flight branch and NEVER \`--force\`s.
|
|
541
|
-
#
|
|
408
|
+
# It runs no agent: the writer-role setup (Node and dorfl only).
|
|
542
409
|
#
|
|
543
410
|
# OPTIONAL belt-and-suspenders for a GitHub arbiter: enable the repo-level
|
|
544
411
|
# "Automatically delete head branches" setting (\`delete_branch_on_merge\`), owned
|
|
@@ -548,11 +415,13 @@ jobs:
|
|
|
548
415
|
reap-merged-branches:
|
|
549
416
|
if: \${{ (github.event.inputs.sweepMergedBranches || 'true') == 'true' }}
|
|
550
417
|
runs-on: ubuntu-latest
|
|
418
|
+
permissions:
|
|
419
|
+
contents: write
|
|
551
420
|
steps:
|
|
552
|
-
- uses:
|
|
421
|
+
- uses: ${pinnedUses(ACTION_PINS.checkout)}
|
|
553
422
|
with:
|
|
554
423
|
fetch-depth: 0
|
|
555
|
-
- uses:
|
|
424
|
+
- uses: ${WRITER_SETUP_ACTION_USES}
|
|
556
425
|
- name: reap merged remote work/* branches + orphan sidecars (gc --remote-branches)
|
|
557
426
|
# Deletes ONLY branches provably merged into origin/main; reports
|
|
558
427
|
# deleted-vs-retained-with-reason. Safe to run every tick (idempotent).
|
|
@@ -606,14 +475,23 @@ export function validateAdvanceLifecycleWorkflow(
|
|
|
606
475
|
.filter((line) => !/^\s*#/.test(line))
|
|
607
476
|
.join('\n');
|
|
608
477
|
|
|
609
|
-
// ---
|
|
610
|
-
require('
|
|
611
|
-
|
|
612
|
-
), 'the
|
|
478
|
+
// --- The tick runs NO agent: it dispatches one item run per item ------------
|
|
479
|
+
require('dispatches-item-runs', /gh workflow run dorfl-item-dispatch\.yml\b/.test(
|
|
480
|
+
operative,
|
|
481
|
+
), 'the `dispatch` job must start one `dorfl-item-dispatch.yml` run per item ' +
|
|
482
|
+
'(`gh workflow run dorfl-item-dispatch.yml`), which calls dorfl-item.yml.');
|
|
483
|
+
require('no-agent-verb-in-tick', !/dorfl (?:advance|do|intake|run)\b/.test(
|
|
484
|
+
operative,
|
|
485
|
+
), 'the tick itself must run NO agent-spawning verb: `advance` runs inside ' +
|
|
486
|
+
'the per-item workflow (lock, agent, apply jobs).');
|
|
613
487
|
require('never-invokes-do', !/dorfl do\b/.test(
|
|
614
488
|
operative,
|
|
615
489
|
), 'the workflow must NEVER invoke `do` directly (CI always invokes `advance`, ' +
|
|
616
490
|
'ADR ci-config-policy-and-gate-family §1).');
|
|
491
|
+
require('no-matrix', !/\bstrategy:\s*[\s\S]*?matrix:/.test(
|
|
492
|
+
operative,
|
|
493
|
+
), 'no job may use a `strategy.matrix`: one item per workflow run (a matrix ' +
|
|
494
|
+
'shares one artifact namespace across items, decision 1).');
|
|
617
495
|
|
|
618
496
|
// --- Triggers: cron + workflow_dispatch + the on-answer-committed push -------
|
|
619
497
|
require('trigger-cron', /\bschedule:\s*[\s\S]*?-\s*cron:/.test(
|
|
@@ -632,62 +510,43 @@ export function validateAdvanceLifecycleWorkflow(
|
|
|
632
510
|
text,
|
|
633
511
|
), 'must trigger on-answer-committed (a push touching `work/questions/**`) — ' +
|
|
634
512
|
'the lifecycle answer loop.');
|
|
513
|
+
// ... on the DEFAULT BRANCH only: a work-branch push touching a sidecar must
|
|
514
|
+
// never run the lifecycle (observation
|
|
515
|
+
// `advance-lifecycle-push-trigger-lands-an-unreviewed-work-branch-on-main`).
|
|
516
|
+
require('push-pinned-to-main', /\bpush:\s*\n\s+(?:#[^\n]*\n\s+)*branches:\s*\n\s+-\s*main\b/.test(
|
|
517
|
+
text,
|
|
518
|
+
), 'the on-answer-committed `push` trigger must be pinned to the default ' +
|
|
519
|
+
'branch (`branches: [main]`).');
|
|
635
520
|
|
|
636
|
-
// --- integrationMode
|
|
521
|
+
// --- integrationMode is forwarded to every item run (one word) -------------
|
|
637
522
|
require('integration-mode-one-word', /integrationMode:/.test(text) &&
|
|
638
523
|
/github\.event\.inputs\.integrationMode/.test(
|
|
639
524
|
text,
|
|
640
|
-
), 'the dispatch input must be `integrationMode` (one word
|
|
641
|
-
'
|
|
642
|
-
|
|
643
|
-
// --- propose ⇒ a DYNAMIC matrix enumerated via `scan --json` ----------------
|
|
644
|
-
require('propose-matrix', /strategy:\s*[\s\S]*?matrix:/.test(
|
|
645
|
-
text,
|
|
646
|
-
), '`propose` mode must emit a MATRIX of jobs (`strategy.matrix`).');
|
|
647
|
-
require('propose-enumerates-via-scan', /dorfl scan --json/.test(
|
|
648
|
-
text,
|
|
649
|
-
), 'the matrix items must be ENUMERATED via the eligible-pool scan ' +
|
|
650
|
-
'(`dorfl scan --json`).');
|
|
651
|
-
require('propose-one-advance-per-item', /WORK_ITEM:\s*\$\{\{\s*matrix\.item\s*\}\}[\s\S]*?dorfl advance "\$\{WORK_ITEM\}"/.test(
|
|
652
|
-
text,
|
|
653
|
-
), 'each matrix leg must run one `dorfl advance <matrix item>` ' +
|
|
654
|
-
'(one PR per item), reading the item from the step env (`WORK_ITEM`).');
|
|
655
|
-
// The item id is a slug that may be hand-written (only minted slugs are
|
|
656
|
-
// sanitised), so it must reach the shell as DATA through `env:`, never as `${{ }}` text
|
|
657
|
-
// spliced into the `run:` script (GitHub Actions script injection).
|
|
658
|
-
require('matrix-item-not-spliced-into-run', !/dorfl advance "?\$\{\{/.test(
|
|
525
|
+
), 'the dispatch input must be `integrationMode` (one word, forwarded to ' +
|
|
526
|
+
'every item run as `--propose`/`--merge`).');
|
|
527
|
+
require('dispatch-forwards-integration-mode', /-f "integrationMode=\$\{INTEGRATION_MODE\}"/.test(
|
|
659
528
|
operative,
|
|
660
|
-
), 'the
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
// gate/review fan out; the LAND tail is serialised by the engine's
|
|
672
|
-
// `mergeRetries` CAS-retry loop (the git-alone floor), NOT the workflow.
|
|
673
|
-
require('merge-matrix', /advance-merge:[\s\S]*?strategy:\s*[\s\S]*?matrix:/.test(
|
|
674
|
-
text,
|
|
675
|
-
), 'the `merge` job must use a MATRIX (parallel build/gate/review per item; ' +
|
|
676
|
-
"the land tail is serialised by the engine's `mergeRetries` CAS-retry " +
|
|
677
|
-
"loop, not by the workflow's job shape).");
|
|
678
|
-
require('merge-leg-carries-merge-flag', /advance-merge:[\s\S]*?dorfl advance "\$\{WORK_ITEM\}"[^\n]*--merge\b/.test(
|
|
679
|
-
text,
|
|
680
|
-
), 'each `merge` matrix leg must pass `--merge` so the integration mode is ' +
|
|
681
|
-
'TIED to the matrix shape (a leg can never propose-only / desync from the ' +
|
|
682
|
-
'dispatch mode).');
|
|
683
|
-
// No host-specific cross-job serialiser on the merge job: a GitHub Actions
|
|
684
|
-
// `concurrency:` block there would make cross-job land safety depend on a host
|
|
685
|
-
// feature, breaking the git-alone-floor framing (Applied Answer q1).
|
|
686
|
-
require('merge-no-host-concurrency-serialiser', !/advance-merge:[\s\S]*?\n\s{4}concurrency:/.test(
|
|
529
|
+
), 'the `dispatch` job must forward `integrationMode` to each item run.');
|
|
530
|
+
require('dispatch-slot', /-f "slot=\$\{slot\}"/.test(operative) &&
|
|
531
|
+
/i % MAX_PARALLEL/.test(
|
|
532
|
+
operative,
|
|
533
|
+
), 'the `dispatch` job must give each item run a slot (index mod ' +
|
|
534
|
+
'maxParallel), the concurrency group that caps the parallelism.');
|
|
535
|
+
require('dispatch-skips-active-runs', /gh run list[^\n]*--workflow dorfl-item-dispatch\.yml[^\n]*displayTitle,status/.test(
|
|
536
|
+
operative,
|
|
537
|
+
), 'the `dispatch` job must list the non-completed `dorfl-item-dispatch.yml` ' +
|
|
538
|
+
'runs and skip an item whose `dorfl-item <item>` run is still active.');
|
|
539
|
+
require('dispatch-actions-write-only', /\n {2}dispatch:[\s\S]*?\n {4}permissions:\s*\n {6}actions: write\s*\n {4}steps:/.test(
|
|
687
540
|
text,
|
|
688
|
-
), 'the `
|
|
689
|
-
|
|
690
|
-
|
|
541
|
+
), 'the `dispatch` job must hold `actions: write` and nothing else.');
|
|
542
|
+
require('dispatch-no-checkout-no-setup', !/\n {2}dispatch:[\s\S]*?(?:\n {2}\S|$)/
|
|
543
|
+
.exec(operative)?.[0]
|
|
544
|
+
.match(/uses:/), 'the `dispatch` job must have NO checkout and NO setup ' +
|
|
545
|
+
'(no repository code runs next to `actions: write`).');
|
|
546
|
+
require('item-values-not-spliced-into-run', !/gh workflow run[^\n]*\$\{\{/.test(
|
|
547
|
+
operative,
|
|
548
|
+
), 'the item list and inputs must reach the dispatch script through `env:`, ' +
|
|
549
|
+
'never as `${{ }}` text in the `run:` script.');
|
|
691
550
|
|
|
692
551
|
// --- The DORFL_* gate family must NOT be carried as workflow env -----
|
|
693
552
|
// The workflow emits NO active gate env line for any of AUTO_BUILD / AUTO_TASK
|
|
@@ -695,56 +554,34 @@ export function validateAdvanceLifecycleWorkflow(
|
|
|
695
554
|
// override layer, NOT the carrier of defaults. Emitting any of them would FORCE
|
|
696
555
|
// env to win over the repo's own dorfl.json (the precedence is
|
|
697
556
|
// flag > env > per-repo > global > default), silently shadowing per-repo gate
|
|
698
|
-
// config in CI
|
|
699
|
-
// CI-SPECIFIC override adds the env var themselves (the opt-in CI override the
|
|
700
|
-
// env layer is FOR). Check the OPERATIVE (non-comment) lines so the explanatory
|
|
557
|
+
// config in CI. Check the OPERATIVE (non-comment) lines so the explanatory
|
|
701
558
|
// header comment that NAMES these keys is not a false positive.
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
'
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
), 'the workflow must NOT emit an `DORFL_OBSERVATION_TRIAGE:` env ' +
|
|
713
|
-
'assignment (env carries no defaults; resolved from per-repo config / built-in default).');
|
|
714
|
-
require('no-gate-env-surface-blockers', !/DORFL_SURFACE_BLOCKERS\s*:/.test(
|
|
715
|
-
operative,
|
|
716
|
-
), 'the workflow must NOT emit an `DORFL_SURFACE_BLOCKERS:` env ' +
|
|
717
|
-
'assignment (env carries no defaults; resolved from per-repo config / built-in default).');
|
|
718
|
-
// --- Gate-family DISPATCH OVERRIDES (one-shot, dispatch only) ---------------
|
|
719
|
-
// Each gate is exposed as a `workflow_dispatch` input AND its override is wired
|
|
720
|
-
// (as a guarded `$GITHUB_ENV` write, NOT a YAML `env:` key — so the
|
|
721
|
-
// `no-gate-env-*` invariants above still hold) into EVERY job that resolves the
|
|
722
|
-
// gate family: `enumerate` (it gates the matrix pools via `scan`), plus the two
|
|
723
|
-
// agent-running jobs. The enumerate wiring is the load-bearing one: without it a
|
|
724
|
-
// dispatch override of `observationTriage`/`surfaceBlockers`/`autoTask` produces
|
|
725
|
-
// an empty matrix and is silently inert (the bug this task's review caught).
|
|
726
|
-
for (const input of [
|
|
727
|
-
'autoBuild',
|
|
728
|
-
'autoTask',
|
|
729
|
-
'observationTriage',
|
|
730
|
-
'surfaceBlockers',
|
|
731
|
-
]) {
|
|
732
|
-
require(`dispatch-${input}-input`, new RegExp(
|
|
733
|
-
`workflow_dispatch:[\\s\\S]*?inputs:[\\s\\S]*?\\b${input}:`,
|
|
734
|
-
).test(
|
|
735
|
-
text,
|
|
736
|
-
), `the \`workflow_dispatch\` must carry a \`${input}\` gate-override input.`);
|
|
559
|
+
for (const [id, envVar] of [
|
|
560
|
+
['no-gate-env-auto-build', 'DORFL_AUTO_BUILD'],
|
|
561
|
+
['no-gate-env-auto-task', 'DORFL_AUTO_TASK'],
|
|
562
|
+
['no-gate-env-observation-triage', 'DORFL_OBSERVATION_TRIAGE'],
|
|
563
|
+
['no-gate-env-surface-blockers', 'DORFL_SURFACE_BLOCKERS'],
|
|
564
|
+
] as const) {
|
|
565
|
+
require(id, !new RegExp(`${envVar}\\s*:`).test(
|
|
566
|
+
operative,
|
|
567
|
+
), `the workflow must NOT emit an \`${envVar}:\` env assignment ` +
|
|
568
|
+
'(env carries no defaults; the gate is resolved from per-repo config / built-in default).');
|
|
737
569
|
}
|
|
570
|
+
// --- Gate-family DISPATCH OVERRIDES (one-shot, dispatch only) ---------------
|
|
571
|
+
// Each gate is a `workflow_dispatch` input, exported (a guarded `$GITHUB_ENV`
|
|
572
|
+
// write, NOT a YAML `env:` key) in `enumerate` BEFORE its scan, and forwarded
|
|
573
|
+
// by `dispatch` to every item run (whose lock, agent and apply jobs export it).
|
|
738
574
|
for (const [input, envVar] of [
|
|
739
575
|
['autoBuild', 'DORFL_AUTO_BUILD'],
|
|
740
576
|
['autoTask', 'DORFL_AUTO_TASK'],
|
|
741
577
|
['observationTriage', 'DORFL_OBSERVATION_TRIAGE'],
|
|
742
578
|
['surfaceBlockers', 'DORFL_SURFACE_BLOCKERS'],
|
|
743
579
|
] as const) {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
580
|
+
require(`dispatch-${input}-input`, new RegExp(
|
|
581
|
+
`workflow_dispatch:[\\s\\S]*?inputs:[\\s\\S]*?\\b${input}:`,
|
|
582
|
+
).test(
|
|
583
|
+
text,
|
|
584
|
+
), `the \`workflow_dispatch\` must carry a \`${input}\` gate-override input.`);
|
|
748
585
|
const dispatchVar = envVar.replace(/^DORFL_/, 'DISPATCH_');
|
|
749
586
|
const guardedWrite = new RegExp(
|
|
750
587
|
`${dispatchVar}:\\s*\\$\\{\\{ github\\.event\\.inputs\\.${input} \\}\\}` +
|
|
@@ -754,13 +591,19 @@ export function validateAdvanceLifecycleWorkflow(
|
|
|
754
591
|
text,
|
|
755
592
|
), `the \`${input}\` override must be a blank-guarded write of \`${envVar}\` ` +
|
|
756
593
|
'to `$GITHUB_ENV` (blank dispatch input / schedule / push emit nothing).');
|
|
594
|
+
require(`dispatch-forwards-${input}`, new RegExp(
|
|
595
|
+
`args\\+=\\(-f "${input}=\\$\\{${dispatchVar}\\}"\\)`,
|
|
596
|
+
).test(
|
|
597
|
+
operative,
|
|
598
|
+
), `the \`dispatch\` job must forward a non-blank \`${input}\` override to ` +
|
|
599
|
+
'each item run.');
|
|
757
600
|
}
|
|
758
601
|
// The ENUMERATE job MUST carry the override before its `scan` step — else the
|
|
759
|
-
//
|
|
602
|
+
// item list is built from the un-overridden gates and the override is inert.
|
|
760
603
|
require('enumerate-carries-gate-override', /enumerate:[\s\S]*?DORFL_OBSERVATION_TRIAGE=[\s\S]*?id: scan/.test(
|
|
761
604
|
text,
|
|
762
605
|
), 'the `enumerate` job must apply the dispatch gate override BEFORE its `scan` ' +
|
|
763
|
-
'step (scan gates the
|
|
606
|
+
'step (scan gates the item pools by the gate family; otherwise the ' +
|
|
764
607
|
'override is silently inert for the lifecycle/task pools).');
|
|
765
608
|
// The override must be GUARDED by the workflow_dispatch event so schedule/push
|
|
766
609
|
// runs never even enter the write step.
|
|
@@ -789,15 +632,9 @@ export function validateAdvanceLifecycleWorkflow(
|
|
|
789
632
|
text,
|
|
790
633
|
), 'the `sweepMergedBranches` dispatch input (capability F, opt-out) must be ' +
|
|
791
634
|
'preserved.');
|
|
792
|
-
// The ORPHAN-SIDECAR reap
|
|
793
|
-
// `
|
|
794
|
-
//
|
|
795
|
-
// FIRES on the cron tick (not behind an un-passed flag). The reap step that
|
|
796
|
-
// runs `gc --remote-branches` MUST be checked out WITH a working tree (the
|
|
797
|
-
// orphan sweep is working-tree based: it reads `work/questions/` + the
|
|
798
|
-
// lifecycle folders from the checkout). `actions/checkout` provides that, and
|
|
799
|
-
// the step's name/comment names the orphan-sidecar duty so the linkage is not
|
|
800
|
-
// silently lost on a future template edit.
|
|
635
|
+
// The ORPHAN-SIDECAR reap rides the SAME scheduled `dorfl gc
|
|
636
|
+
// --remote-branches` invocation, which reads the working tree, so the reap
|
|
637
|
+
// job must check one out, and its step must name the orphan-sidecar duty.
|
|
801
638
|
require('reap-checks-out-working-tree', /reap-merged-branches:[\s\S]*?uses:\s*actions\/checkout/.test(
|
|
802
639
|
text,
|
|
803
640
|
), 'the reap job must `actions/checkout` a working tree before ' +
|
|
@@ -808,14 +645,41 @@ export function validateAdvanceLifecycleWorkflow(
|
|
|
808
645
|
), 'the reap step (running `gc --remote-branches`) must name the ORPHAN ' +
|
|
809
646
|
'SIDECAR reap it ALSO performs (US #10), so the scheduled invocation that ' +
|
|
810
647
|
'fires it is visible and not silently dropped on a future edit.');
|
|
648
|
+
require('reap-writer-role', /reap-merged-branches:[\s\S]*?uses:\s*\.\/\.github\/actions\/dorfl-setup-writer\b/.test(
|
|
649
|
+
text,
|
|
650
|
+
), 'the reap job runs no agent: it must use the writer-role setup ' +
|
|
651
|
+
'(`./.github/actions/dorfl-setup-writer`: Node and dorfl only).');
|
|
652
|
+
|
|
653
|
+
// --- The merge-question writer job (no agent) -------------------------------
|
|
654
|
+
// Task `wire-merge-questions-into-the-advance-tick` (decision 5): surfacing is
|
|
655
|
+
// deterministic, so a no-agent writer job runs it; `enumerate` stays
|
|
656
|
+
// read-only.
|
|
657
|
+
require('surface-merge-questions-job', /\n {2}surface-merge-questions:\s*\n {4}runs-on: ubuntu-latest\s*\n {4}permissions:\s*\n {6}contents: write\s*\n {6}pull-requests: read\s*\n {4}steps:/.test(
|
|
658
|
+
text,
|
|
659
|
+
), 'the workflow must carry the `surface-merge-questions` job (contents: write, ' +
|
|
660
|
+
'pull-requests: read, no agent) that asks the merge questions.');
|
|
661
|
+
// Gate-3 (PR #459): `gh pr list` needs a credential, or every branch is
|
|
662
|
+
// skipped as `pr-state-unknown` on a GitHub arbiter. The built-in token
|
|
663
|
+
// suffices (a read), so the step carries `GH_TOKEN: secrets.GITHUB_TOKEN`.
|
|
664
|
+
require('surface-merge-questions-gh-token', /surface-merge-questions:[\s\S]*?env:\s*\n\s*GH_TOKEN: \$\{\{ secrets\.GITHUB_TOKEN \}\}\s*\n\s*run: dorfl surface-merge-questions\b/.test(
|
|
665
|
+
operative,
|
|
666
|
+
), 'the `surface-merge-questions` step must give `gh` a credential ' +
|
|
667
|
+
'(`env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}`), else the open-PR listing ' +
|
|
668
|
+
'fails and every branch is skipped.');
|
|
669
|
+
require('surface-merge-questions-runs-the-command', /surface-merge-questions:[\s\S]*?run: dorfl surface-merge-questions\b/.test(
|
|
670
|
+
operative,
|
|
671
|
+
), 'the `surface-merge-questions` job must run `dorfl surface-merge-questions`.');
|
|
672
|
+
require('surface-merge-questions-writer-role', /surface-merge-questions:[\s\S]*?uses:\s*\.\/\.github\/actions\/dorfl-setup-writer\b/.test(
|
|
673
|
+
text,
|
|
674
|
+
), 'the `surface-merge-questions` job runs no agent: it must use the ' +
|
|
675
|
+
'writer-role setup (`./.github/actions/dorfl-setup-writer`).');
|
|
811
676
|
|
|
812
677
|
// --- CI runs IN-PLACE: no isolation machinery ------------------------------
|
|
813
678
|
require('no-isolated-flag', !/--isolated\b/.test(
|
|
814
679
|
operative,
|
|
815
680
|
), 'CI runs IN-PLACE (the container IS the isolation): no `--isolated` flag.');
|
|
816
681
|
// Guard `--remote` the LAPTOP affordance, but NOT `--remote-branches` (the
|
|
817
|
-
// preserved capability-F `gc --remote-branches` sweep)
|
|
818
|
-
// for a following `-`/word char so the gc flag is not a false positive.
|
|
682
|
+
// preserved capability-F `gc --remote-branches` sweep).
|
|
819
683
|
require('no-remote-flag', !/--remote(?![-\w])/.test(
|
|
820
684
|
operative,
|
|
821
685
|
), 'CI runs IN-PLACE: no `--remote` flag (laptop-only affordance).');
|
|
@@ -825,7 +689,14 @@ export function validateAdvanceLifecycleWorkflow(
|
|
|
825
689
|
text,
|
|
826
690
|
), 'must carry a CI `concurrency.group` so overlapping ticks never collide.');
|
|
827
691
|
|
|
828
|
-
// ---
|
|
692
|
+
// --- Permissions: nothing at workflow level; NO `workflows` permission -----
|
|
693
|
+
require('workflow-permissions-empty', /^permissions: \{\}$/m.test(
|
|
694
|
+
text,
|
|
695
|
+
), 'the workflow must grant nothing at workflow level (`permissions: {}`); ' +
|
|
696
|
+
'each job grants its own scopes.');
|
|
697
|
+
require('enumerate-contents-read', /\n {2}enumerate:\s*\n {4}runs-on: ubuntu-latest\s*\n {4}permissions:\s*\n {6}contents: read\s*\n {4}outputs:/.test(
|
|
698
|
+
text,
|
|
699
|
+
), 'the `enumerate` job must hold `contents: read` only.');
|
|
829
700
|
require('no-workflows-permission', !/\bworkflows:\s*write\b/.test(
|
|
830
701
|
text,
|
|
831
702
|
), 'the running job must request NO `workflows` permission (US #9: it can ' +
|
|
@@ -835,96 +706,62 @@ export function validateAdvanceLifecycleWorkflow(
|
|
|
835
706
|
), 'no emitted job step may touch `.github/workflows/**` (US #9).');
|
|
836
707
|
|
|
837
708
|
// --- Explicit slug prefixes, never bare ------------------------------------
|
|
838
|
-
require('explicit-task-prefix', /"task:" \+ \.slug/.test(
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
), 'CI must use explicit `task:`/`spec:` slug prefixes, never bare (ADR ' +
|
|
709
|
+
require('explicit-task-prefix', /"task:" \+ \.slug/.test(
|
|
710
|
+
text,
|
|
711
|
+
), 'CI must use explicit `task:`/`spec:` slug prefixes, never bare (ADR ' +
|
|
842
712
|
'command-surface-and-journeys §3a).');
|
|
843
713
|
|
|
844
|
-
// --- The
|
|
714
|
+
// --- The `enumerate` list must UNION taskable specs -------------------------
|
|
845
715
|
// (`ci-propose-matrix-must-enumerate-sliceable-prds-not-only-slices`): a
|
|
846
|
-
// task-only `jq` would render `DORFL_AUTO_TASK: 'true'` dead on the
|
|
847
|
-
//
|
|
848
|
-
// must enumerate `spec:<slug>` ids from `scan --json`'s taskable-SPEC pool
|
|
849
|
-
// (`repos[].specs[]` + `cwd.repo.specs[]`) alongside the eligible-task legs.
|
|
716
|
+
// task-only `jq` would render `DORFL_AUTO_TASK: 'true'` dead on the hourly
|
|
717
|
+
// cron — a ready ungated SPEC would never become an item run.
|
|
850
718
|
require('propose-enumerates-taskable-specs', /"spec:" \+ \.slug/.test(text) &&
|
|
851
719
|
/\.specs\[\]/.test(
|
|
852
720
|
text,
|
|
853
|
-
), 'the
|
|
854
|
-
"
|
|
855
|
-
'
|
|
856
|
-
'
|
|
721
|
+
), 'the `enumerate` `jq` must union taskable specs into the item list as ' +
|
|
722
|
+
"`spec:<slug>` ids (read from `scan --json`'s `repos[].specs[]` + " +
|
|
723
|
+
'`cwd.repo.specs[]` pools), so a ready ungated SPEC becomes one auto-task ' +
|
|
724
|
+
'item run alongside the eligible-task items ' +
|
|
857
725
|
'(`ci-propose-matrix-must-enumerate-sliceable-prds-not-only-slices`).');
|
|
726
|
+
require('propose-enumerates-via-scan', /dorfl scan --json/.test(
|
|
727
|
+
text,
|
|
728
|
+
), 'the items must be ENUMERATED via the eligible-pool scan ' +
|
|
729
|
+
'(`dorfl scan --json`).');
|
|
858
730
|
|
|
859
|
-
// --- The
|
|
860
|
-
// (`ci-propose-matrix-enumerates-lifecycle-items`):
|
|
861
|
-
//
|
|
862
|
-
//
|
|
863
|
-
// all, so NO lifecycle rung (triage/surface/apply) ever gets a propose leg. The
|
|
864
|
-
// `jq` must enumerate the three lifecycle sub-pools from `scan --json`
|
|
865
|
-
// (`repos[].lifecycle.*` + `cwd.repo.lifecycle.*`): `triage[]` → `obs:<slug>`,
|
|
866
|
-
// and `surface[]`/`apply[]` → `.namespace + ":" + .slug`. Match all four shape
|
|
867
|
-
// signals so a regression that drops any one leg is caught.
|
|
731
|
+
// --- The `enumerate` list must UNION the LIFECYCLE pools --------------------
|
|
732
|
+
// (`ci-propose-matrix-enumerates-lifecycle-items`): `triage[]` → `obs:<slug>`,
|
|
733
|
+
// and `surface[]`/`apply[]` → `.namespace + ":" + .slug`, so the WHOLE
|
|
734
|
+
// answer-loop runs in propose mode too.
|
|
868
735
|
require('propose-enumerates-lifecycle-items', /"obs:" \+ \.slug/.test(text) &&
|
|
869
736
|
/\.lifecycle\.triage\[\]/.test(text) &&
|
|
870
737
|
/\.lifecycle\.surface\[\]/.test(text) &&
|
|
871
738
|
/\.lifecycle\.apply\[\]/.test(text) &&
|
|
872
739
|
/\.namespace \+ ":" \+ \.slug/.test(
|
|
873
740
|
text,
|
|
874
|
-
), 'the
|
|
875
|
-
"
|
|
876
|
-
'`cwd.repo.lifecycle.*`): `triage[]` as `obs:<slug
|
|
877
|
-
'`surface[]`/`apply[]` as `.namespace + ":" + .slug`
|
|
878
|
-
'`observation:` for an answered-observation apply leg) legs, ' +
|
|
879
|
-
'so the WHOLE answer-loop (triage + surface + apply) runs in propose mode, ' +
|
|
880
|
-
'not only in merge mode ' +
|
|
741
|
+
), 'the `enumerate` `jq` must union the LIFECYCLE pools into the item list ' +
|
|
742
|
+
"(read from `scan --json`'s `repos[].lifecycle.*` + " +
|
|
743
|
+
'`cwd.repo.lifecycle.*`): `triage[]` as `obs:<slug>`, and ' +
|
|
744
|
+
'`surface[]`/`apply[]` as `.namespace + ":" + .slug` ' +
|
|
881
745
|
'(`ci-propose-matrix-enumerates-lifecycle-items`).');
|
|
882
746
|
|
|
883
|
-
// ---
|
|
884
|
-
// The agent
|
|
885
|
-
//
|
|
886
|
-
//
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
), 'the `enumerate` job must declare a `githubTimeout` job OUTPUT (computed ' +
|
|
892
|
-
'from the committed `dorfl.json` as ' +
|
|
893
|
-
'`agentDeadlineMinutes + checkpointHeadroomMinutes`).');
|
|
894
|
-
require('enumerate-reads-dorfl-config-json', /dorfl config --json/.test(
|
|
895
|
-
text,
|
|
896
|
-
), 'the `enumerate` job must read the committed config via ' +
|
|
897
|
-
'`dorfl config --json` at run time (so an edit to `dorfl.json` ' +
|
|
898
|
-
'reflects on the next tick without an `install-ci` re-run).');
|
|
899
|
-
require('enumerate-computes-deadline-plus-headroom', /agentDeadlineMinutes \+ checkpointHeadroomMinutes/.test(
|
|
900
|
-
text,
|
|
901
|
-
), 'the `enumerate` job must compute `githubTimeout` as ' +
|
|
902
|
-
'`agentDeadlineMinutes + checkpointHeadroomMinutes` (the backstop is ' +
|
|
903
|
-
'DELIBERATELY head-room above the internal deadline).');
|
|
904
|
-
require('propose-uses-dynamic-github-timeout', /advance-propose:[\s\S]*?timeout-minutes:\s*\$\{\{\s*fromJson\(needs\.enumerate\.outputs\.githubTimeout\)\s*\}\}/.test(
|
|
905
|
-
text,
|
|
906
|
-
), 'the `advance-propose` job must set `timeout-minutes: ${{ ' +
|
|
907
|
-
'fromJson(needs.enumerate.outputs.githubTimeout) }}` (the DYNAMIC GitHub ' +
|
|
908
|
-
'backstop, fromJson-coerced to an integer, not a baked-in number).');
|
|
909
|
-
require('merge-uses-dynamic-github-timeout', /advance-merge:[\s\S]*?timeout-minutes:\s*\$\{\{\s*fromJson\(needs\.enumerate\.outputs\.githubTimeout\)\s*\}\}/.test(
|
|
910
|
-
text,
|
|
911
|
-
), 'the `advance-merge` job must set `timeout-minutes: ${{ ' +
|
|
912
|
-
'fromJson(needs.enumerate.outputs.githubTimeout) }}` (the DYNAMIC GitHub ' +
|
|
913
|
-
'backstop, fromJson-coerced to an integer).');
|
|
914
|
-
// A baked static `timeout-minutes: <number>` on an agent-leg job is the
|
|
915
|
-
// retired `legTimeoutMinutes` render — forbid it so a future edit cannot
|
|
916
|
-
// silently re-introduce the drift the dynamic backstop is fixing.
|
|
917
|
-
require('no-static-agent-leg-timeout', !/(?:advance-propose|advance-merge):[\s\S]*?timeout-minutes:\s*\d/.test(
|
|
918
|
-
text,
|
|
919
|
-
), 'no agent-leg job may set a baked-in numeric `timeout-minutes` ' +
|
|
920
|
-
'(the retired `legTimeoutMinutes` render); the GitHub backstop is ' +
|
|
921
|
-
'DYNAMIC (read from `dorfl.json` at run time).');
|
|
747
|
+
// --- No timeout is computed here: the item's lock job owns it ---------------
|
|
748
|
+
// The agent job's `timeout-minutes` is the lock job's `agentTimeoutMinutes`
|
|
749
|
+
// output (dorfl.json at the item's base), in dorfl-item.yml; no job here may
|
|
750
|
+
// bake a numeric `timeout-minutes` either (the retired `legTimeoutMinutes`).
|
|
751
|
+
require('no-static-timeout', !/timeout-minutes:\s*\d/.test(
|
|
752
|
+
operative,
|
|
753
|
+
), 'no job may set a baked-in numeric `timeout-minutes`: the agent timeout ' +
|
|
754
|
+
"is the item lock job's `agentTimeoutMinutes` (dorfl.json at the base).");
|
|
922
755
|
|
|
923
|
-
// --- Wires the
|
|
924
|
-
require('uses-
|
|
756
|
+
// --- Wires the writer-role setup action (no agent runs here) ---------------
|
|
757
|
+
require('uses-writer-setup-action', /uses:\s*\.\/\.github\/actions\/dorfl-setup-writer\b/.test(
|
|
925
758
|
text,
|
|
926
|
-
), '
|
|
927
|
-
'(`./.github/actions/dorfl-setup
|
|
759
|
+
), 'the jobs that set up dorfl here run no agent, so they must use the ' +
|
|
760
|
+
'writer-role setup (`./.github/actions/dorfl-setup-writer`).');
|
|
761
|
+
require('no-agent-setup-action', !/uses:\s*\.\/\.github\/actions\/dorfl-setup\s*$/m.test(
|
|
762
|
+
operative,
|
|
763
|
+
), 'no job here runs an agent, so none may use the agent-role setup ' +
|
|
764
|
+
'(`./.github/actions/dorfl-setup`: the harness and the provider key).');
|
|
928
765
|
|
|
929
766
|
return {ok: problems.length === 0, problems};
|
|
930
767
|
}
|