# The lint The iris over a site. A content push is an incoming wormhole; nothing comes through until it checks out. ``` portal iris --path questions # a local checkout portal lint --repo https://project.uhhm.no/uhhm/questions portal lint replay --path questions --nats nats://127.0.0.1:14222 ``` `portal iris` is the lint's name, after the iris that keeps bad content out of the portal; `portal lint` runs the same thing, so workflows that already call it keep working. It was its own binary, iris, until portal 0.6: the same code, released as portal's twin. Now it is the `portal` binary's `lint` command, so a content repo's CI downloads the release and runs it: ```yaml - run: | curl -sfL "https://project.uhhm.no/uhhm/portal/releases/download/$PORTAL_RELEASE/portal-x86_64-linux" -o portal chmod +x portal && ./portal lint --path questions ``` `check` loads a content repo exactly the way the running site does, validates it (every action lands on a page, every transition is one the state machine allows, every recorded bucket is read back somewhere), and then holds it to the business needs the repo declares in `needs.yaml`, when it has one: - a path of submissions gets each kind of visitor's answer into the bucket it names, within a submission and required-field budget; - the handling group holds a desk over that bucket, and every finished state is reachable through the buttons it (and any `also_moved_by` group) offers; no record strands on the way; - every stage the business named is a state of the machine. A need marked `planned` warns instead of failing. Exit status is the verdict, so a content repo's CI is one line. Beyond the verdict, `check` exports what a trainer needs to grade the wording: `--needs-tasks` writes the personas as typed choice tasks (`--skim` for headings only), `--needs-score` grades an engine's answers, and `--needs-sim` writes a resolved model of the whole site. `portal lint replay` runs simulated cases through the site's real aggregate engine on a throwaway JetStream and reports every bucket by state; `--report-only` takes the same scorecard from a live site's buckets. See [Training a site](training.md) for what sits around those. ## Matching the site The lint is the site: `portal lint` and `portal serve` are one binary built from one source, so a content shape the site reads is a shape the lint knows, at the same version. A content repo pins `PORTAL_RELEASE` to what its site runs and lints with that release's `portal`. The `IRIS_RELEASE` pins from before portal 0.6 keep working against the iris releases that exist; nothing new is published there.