Gates & gate-waive
Whether a gate runs is the lever file's business. When it runs is the engine's phase map, frozen into each run when it begins.
plan: none
code: phpcs, phpstan, config_clean
test: phpunit, mutation, playwright, coverage, rendered_check, config_clean
complete: everything above, plus wiki_freshThe gates
| Gate | What passes |
|---|---|
phpcs |
Drupal and DrupalPractice standards over the analysis paths. |
phpstan |
Static analysis at the configured level; max is what Droost itself runs at. |
phpunit |
The suite named by the repo's phpunit.xml. An empty suite is a labeled pass that hardens the moment a first test exists; no phpunit.xml refuses before spawning anything. |
config_clean |
A fresh drush config:export would change nothing in the tracked tree. |
rendered_check |
A real sub-request to each route in routes (default /) returns success. Artifacts are truth. |
wiki_fresh |
The project's own documentation still verifies against the code. Due only at complete. |
playwright |
node_modules/.bin/playwright test exits zero. Without Playwright installed, tool-missing. |
mutation, coverage |
Infection MSI and coverage floors, when enabled. |
custom.* |
Your own commands (semgrep, behat, anything): exit zero passes; a command the shell cannot find is tool-missing, which blocks. |
Honest degradation
A gate that cannot run never passes by accident. Tool missing outranks everything and blocks. Config missing is named as such. A diff with nothing analysable reports a labeled "nothing to analyse", which is a pass that says why. Against a plain checkout with no site, the site-dependent gates report skipped, no site with the reason recorded, and the report says exactly which checks it could not perform.
The feedback loop
A blocking gate opens a bounded loop rather than ending the run: one attempt plus max_gate_retries retries per blocking gate, with the cause fixed between invocations. When the budget is spent the phase is recorded failed and the run stops; drush droost:workflow:reset archives the record and clears the way.
Waiving a gate
Sometimes an optional gate is wrong for one run: a Playwright suite that is not installed on this machine yet, a coverage floor a spike cannot meet. The answer is a scoped waiver, not a lever edit.
drush droost:workflow:gate-waive playwright "not installed on this machine yet; tracked in #42"Waivers are the operator's, scoped and recorded
A waiver applies to one gate for the current run only. It needs a reason; an empty one is refused, as is an unknown gate. The mandatory trio (phpcs, phpstan, phpunit) cannot be waived at all. A waived gate is not executed and is recorded as waived by the operator: <reason> rather than passed, so the report never claims a check that did not run. It is CLI-only: there is no MCP surface for it, so an agent can never waive its own gates. The run record and drush droost:workflow:report carry the waiver.
This exists because two live-agent evals reached for it before it did. In one, the operator was handed a wall-level bypass because nothing narrower existed, and the record could not say what had actually been relaxed. droost/workflow 0.6.3 shipped the scoped waiver between the two rounds that showed the gap.