Skip to main content
/ The workflow

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.

text
plan:     none
code:     phpcs, phpstan, config_clean
test:     phpunit, mutation, playwright, coverage, rendered_check, config_clean
complete: everything above, plus wiki_fresh

The 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.

bash
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.

Verified against droost 2.0.0-alpha4 · workflow 0.6.9 · 2026-09-03