How a run moves · 6

The workflow, step by step

workflow.yaml is the source of truth for order: 15 steps, each with prerequisites, exit predicates and a closed list of blockers. The skills are prose about the same pipeline; when they disagree, the YAML wins because the YAML is what the gate reads.

The DAG

Arrows mean "must be closed (or blocked) first". After build the pipeline fans out into four independent checks and joins again at publish-preview.

Two profiles exist. site runs everything. app excludes scaffold and flows — an application brings its own build and its Playwright suite replaces the frozen flow.

How a step is written

Every step has the same five keys. Here is mwg-install, the shortest one that shows all of them:

- id: mwg-install
  title: Modern Web Guidance is installed and pinned
  scope: site
  actor: orchestrator
  requires: [repo]
  exit:
    - kind: file
      glob: ".agents/skills/modern-web-guidance/SKILL.md"
      min_count: 1
    - kind: json_field
      file: "skills-lock.json"
      pointer: "/skills/modern-web-guidance/version"
      why: "an unpinned skill silently changes what 'passed' means"
  blockers: []

The why lines are for the reader, not the gate — but they are the best short explanation of each check, and the tables below quote them.

Setup: steps 1–4

StepActorCloses whenArtefact and its author
1 preflight
The host can finish this build
orchestratordoctor.mjs exits 0: Node ≥ 22.12, Chrome found, gh and wrangler name an account, operator attestation under a year old.preflight.json, written by the doctor even on failure — so the preflight-failed blocker has evidence to stand on.
2 brief
One action, an audience, proof
site-briefbrief.md exists; required fields non-empty; exactly one primary action.brief.md, by the skill's interview.
3 repo
A private repo with an initial commit
orchestratorSite dir is a repo root, a commit exists, a remote is set, and gh reports it private. Unconfirmable is refused.The repo, by git and gh.
4 mwg-install
Guidance installed and pinned
orchestratorThe skill file exists under .agents/skills/ and skills-lock.json pins its version.mwg-install.mjs install, which vendors the guidance and writes the lock with a tree hash.

Design and build: steps 5–7

StepActorCloses whenArtefact and its author
5 design
A per-site design system, contrast holds
design-systemsite-context.json names the Claude Design project (Claude Design is Anthropic's design tool; a project there is the source the tokens are exported from — tokens come before the scaffold because the scaffold consumes them); that project was looked up since the tokens last changed; src/styles/tokens.css exists; every declared text/background pair reaches 4.5:1 in both colour schemes. No large-text escape.tokens.css by the skill; the pairs and project id via site-context.mjs design.
6 scaffold
The project builds
site-scaffoldnpm run build exits 0.The Astro project; routes declared via site-context.mjs routes.
7 build
Pages implemented, guidance consulted, no runtime errors
orchestratorThe ledger proves a guidance search before every component's first write, and nine tools pass on every declared route: axe-routes (both schemes), a11y (skip link via real Enter and Tab, motion pausable, fragments resolve, no placeholder-only labels), console-clean, seo --build, forms (autocomplete tokens), fonts (no FOIT), bfcache (one real back-navigation restored), markup, content (text walls, DE+EN spelling, readability ratchet, article dates).The pages, by the agent; the ledger, by the hooks.

Step 7 is where most refusals happen, and each predicate prints a line per finding. Treat the FAIL lines as the to-do list and re-run done build until it closes.

Checks: steps 8–12

StepActorCloses whenArtefact and its author
8 security
The built site defends itself
orchestratorsecurity.mjs: 19 checks on _headers and dist/ — CSP without unsafe-inline, HSTS, nosniff, referrer and permissions policies, no secrets, SRI, no sourcemaps, and more. deps.mjs: no high or critical advisory. supply-chain.mjs: lifecycle scripts off, lockfile URLs on the public registry, signatures verified._headers, by the scaffold. Reports "declared, not proven live" — never "secure".
9 flows
The critical flow works and is frozen
orchestratorA flows/*.cli.mjs script exists and replays green against the predicate's own server. Any budget line it prints is judged: INP ≤ 200 ms, zero layout shift, a busy state within 300 ms of any wait over 300 ms.The flow script, by the agent using flow-kit.mjs.
10 ux-review
Nothing manipulative ships
ux-reviewux-review.json gives a verdict with a ≥ 20-character reason for all 31 mandatory patterns, carries the catalogue fingerprint, covers every declared route, is newer than the build, and a scanner finds no signature the review calls absent.ux-review.json, by the skill. Corroborated, not trusted.
11 commercial-review
Someone would pay for this
commercial-juryThree distinctly named blind scorers — separate agent sessions or people that have not seen the brief or each other's scores — the identical 11-dimension rubric, integer scores 1–5, median per dimension ≥ 3, filed after the last change; plus an owner sign-off.Ballots via jury.mjs record; sign-off via site-step sign.
12 ship-check
The floor holds, measured not asserted
orchestratorA recorded measurement: 5 Lighthouse runs, median within budgets.json, spread ≤ 5 points.measure.mjs, which refuses fewer runs, a wide spread, or a Lighthouse other than the pinned 12.2.1.

Publish: steps 13–15

StepActorCloses whenArtefact and its author
13 publish-preview
Reviewable at a URL, invisible to search
site-publishThe preview URL is observed to send x-robots-tag: noindex and a content-security-policy — a declared header the host silently drops is what step 8 cannot see. Every licensable asset in dist/ maps to a licences.json entry with source, licence and proof.Headers recorded by publish.mjs probe; URL via site-context.mjs urls.
14 legal-check
Required legal elements present
legal-checklegal.mjs: 23 checks on the live preview — Impressum by legal form, Datenschutz naming every observed and declared processor, zero cookies or storage before consent, reject as prominent as accept and re-observed, no fonts.gstatic.com, BFSG, AI Act, PAngV, § 312k. Plus a legal-reviewer sign-off.Declarations via site-context.mjs hosting|processor|bfsg|legal|jurisdiction. Reports "present" — never "compliant".
15 publish-live
Launched on its own domain
site-publishlive.mjs: DNS resolves, noindex gone, sitemap and robots served, and the receipt is newer than the newest file under src/, public/, dist/. seo.mjs: self-referential canonicals, Open Graph, JSON-LD, every link resolves. dns.mjs: SPF, DMARC and MX match the declared mail posture, no dangling CNAME, sane CAA, registration not lapsing.The receipt, by verify-receipt.mjs audit; mail posture via site-context.mjs mail.

Blockers: the only legal way around a step

A blocker (that is: a recorded, validated reason a step cannot be done) is the machine's escape hatch, and it is narrow by design. Each step lists which blockers it accepts; eight steps accept none and can only be done.

BlockerOn stepValidator requires
preflight-failedpreflightpreflight.json records pass: false
design-source-externaldesign--evidence pointing at an existing file (the agreed style guide)
mwg-not-applicablebuild--evidence: the written file that is genuinely not a component
advisory-unfixablesecurity--evidence: which advisory, and why the exposure does not apply
flow-not-applicableflows--evidence: the site genuinely has no interactive flow
no-ai-featurelegal-checksite-context.json does not declare an AI feature
no-custom-domain-yetpublish-live--evidence: the domain is not registered or delegated yet

Every blocker also needs --reason of at least 20 characters. A blocked step counts as satisfied for the steps after it, and shows as BLK in status. A refused blocker halts the run like any other refusal.