Files
portal/docs/lint.md
T

95 lines
4.6 KiB
Markdown
Raw Normal View History

# 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, and each
working stage can follow the one before it;
- across each bucket, whichever need brought a record in: every state
is somewhere a record can get to, no state a record gets to holds it
with no way on (before done or after), every declared move is a
button, a visitor's own link or a clock (a move nobody offers warns),
no desk is gated to one group on the page and another on its list,
and every mail goes out from a state records reach and names only
fields the forms collect.
Every finding says what to change and where: the missing
`{ from, to, label }` and the desk page it goes on, the group that
already has the button, or the edge aggregates.yaml lacks. A schema
error no longer hides the needs findings - both are printed, so one
round can fix them together.
`--findings <out.jsonl>` writes the same findings one JSON object per
line: `id`, `level` (`fail`/`warn`), `code`, `scope` (`need`,
`bucket`, or `repo` for a schema error), `need` (the need's id, or the
bucket's name), `subject` (the state, the move as `from>to`, or the
page), `message` and `fix`. The file is written empty when the content
is clean. `id` hashes scope, need, code and subject and nothing of the
wording, so the same gap keeps its id from one run to the next: a
trainer can tell what a round closed, what is still open and what it
broke. The codes:
| code | |
|---|---|
| `schema` | content the site would refuse to load |
| `need-duplicate`, `persona-unknown-need` | needs.yaml itself |
| `entry-missing`, `entry-gated`, `entry-no-responsible` | the page a visitor arrives on |
| `path-missing`, `path-closed`, `path-over-budget`, `path-no-action` | getting the answer recorded |
| `machine-missing`, `desk-missing`, `desk-gated-twice` | who works the bucket |
| `done-unknown`, `done-unreachable`, `success-not-done` | finishing a case |
| `stage-unknown`, `stage-unreachable`, `stage-order` | the business's own stages |
| `state-unreached`, `strand`, `edge-unoffered` | every state and move of the bucket |
| `mail-unreachable`, `mail-unknown-field` | what a state mails |
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.