Files
portal/docs/lint.md
T
Bendik Aagaard LynghaugandClaude Fable 5.1 96b3746643
Test / test (pull_request) Successful in 1m7s
One binary: the lint and the trainer are commands of portal
portal serve runs a site; portal lint is what iris was; portal plan,
build, report and the rest are what portal-trainer was. One source at
one version, so the three never disagree about what content means.
Both histories are merged in (subtree). The release attaches the bare
binary for a content repo's CI. The trainer's plans, intakes, engines
and reference prompt come along; its runs stay in the old repo.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GT37Z1Xtfd9pUuQtMTg6Yt
2026-09-30 15:29:30 +02:00

2.4 KiB

The lint

The iris over a site. A content push is an incoming wormhole; nothing comes through until it checks out.

portal lint --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

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:

      - run: |
          curl -sfL "https://project.uhhm.no/uhhm/portal/releases/download/$PORTAL_RELEASE/portal" -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 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.