name: CURABIS task-state check # 2026-08-03 - scope correction after an audit found the header overstated # this check's actual guarantee. # # What this DOES enforce deterministically: checklist ORDER in the PR body # ("## CURABIS Task State") - a later stage cannot be checked while an # earlier one isn't. It runs on every push/edit, needs no AI session to # execute, and cannot be talked out of failing. # # What this does NOT enforce, and never has: that a checked box corresponds # to a real event (a red test that actually ran, a review that actually # happened). A session or a rushed developer can check every box in perfect # order having done none of the underlying work, and this Action passes. # The order check catches a narrower, still-real failure mode (a later # stage claimed before an earlier one) - it is not proof the trail is true. # # This ALSO does not enforce anything by itself unless a human has # separately added it as a required status check in the repo's branch # protection settings (curabis-standard.agent.md documents this as a # one-time manual step - it cannot be automated by file deployment). Absent # that, a failing run just shows as a red X someone can ignore and merge # past. Rømer's inspection round has a station that checks whether branch # protection is actually configured this way - see roemer.agent.md station 14. # # This check ONLY covers the AppSource track (PR body checklists). The PTE # track (BC task comments) has NO equivalent deterministic backstop - only # Smiley's Close-gate self-verification and al-review's "state trail # complete?" checklist item, both of which are an AI session re-reading its # own/BC's history, not an independent script. That is a known, accepted # gap, not an oversight - see task-state-lives-in-the-mandatory-artifact.md. # # Silently passes (does nothing) if the "## CURABIS Task State" section is # absent - this check only applies to PRs that opted into the state trail; # it must never block an unrelated PR (docs fix, infra change, etc.). on: pull_request: types: [opened, edited, synchronize, reopened] jobs: check-task-state-order: runs-on: ubuntu-latest steps: - name: Validate CURABIS Task State checklist order uses: actions/github-script@v7 with: script: | const body = context.payload.pull_request.body || ""; const heading = "## CURABIS Task State"; const headingIdx = body.indexOf(heading); if (headingIdx === -1) { console.log("No '## CURABIS Task State' section found - not a state-tracked PR, skipping."); return; } // Take everything after the heading up to the next "## " heading (or end of body). const rest = body.slice(headingIdx + heading.length); const nextHeadingIdx = rest.search(/\n##\s/); const section = nextHeadingIdx === -1 ? rest : rest.slice(0, nextHeadingIdx); const lineRe = /^-\s*\[( |x|X)\]\s*(.+)$/gm; const items = []; let m; while ((m = lineRe.exec(section)) !== null) { items.push({ checked: m[1].toLowerCase() === "x", label: m[2].trim() }); } if (items.length === 0) { core.setFailed( "Found a '## CURABIS Task State' heading but no checklist lines under it " + "(expected '- [ ] ...' / '- [x] ...'). Either add the checklist or remove the heading." ); return; } // Valid order is monotonic: once an item is unchecked, every item after it // must also be unchecked. A checked item after an unchecked one means a // later stage was marked done while an earlier one wasn't. let seenUnchecked = false; let brokenAt = -1; for (let i = 0; i < items.length; i++) { if (!items[i].checked) { seenUnchecked = true; } else if (seenUnchecked) { brokenAt = i; break; } } if (brokenAt !== -1) { const lines = items.map((it, i) => ` ${i === brokenAt ? ">>" : " "} [${it.checked ? "x" : " "}] ${it.label}` ).join("\n"); core.setFailed( "CURABIS Task State checklist is out of order - a later stage is checked " + "while an earlier one is not. A stage cannot be marked done before the ones " + "before it. Offending line marked with '>>':\n\n" + lines ); return; } console.log(`CURABIS Task State checklist order OK (${items.filter(i => i.checked).length}/${items.length} checked).`);