Skip to main content
A validator file declares one check. This page shows how to write one, so that a pull request shows your check as a row on the item’s Checks tab. Policy and validators covers the verdict, the policy file and the starter check.

Before you start

Link the repository to a factory. See Connect a repository.

Add your first check

  1. Create .outerlayer/validators/e2e-ran.yaml with this content. Change the command and the paths to fit your repository.
  2. Merge the file to the base branch. OuterLayer reads validator files from there, or from the control plane when the factory has one.
  3. Open a pull request on a work item. The row shows on the item’s Checks tab when the check applies to the pull request.
  4. Do the work the check asks for. The row passes when its requirement is met.

The file format

Each file in .outerlayer/validators/ declares one check. OuterLayer reads only .yaml and .yml files, and only the first 20 by name. More files add a policy error row.

Limit where a check applies

With no when, a check applies to every pull request. When when does not match, the check shows no row. Every key you set must match. A check with an issue key shows no row when no issue is linked, or when OuterLayer cannot read the issue. A check with paths shows no row when OuterLayer cannot read the changed files.

Path globs

A glob matches the whole path. * and ? match within one path segment. ** matches any run of characters, / included. Every other character is literal, braces included. To match both a top-level file and a nested one, list two globs.

One check for some repositories

A control plane supplies one set of checks to every repository it governs. Use repositories when a check applies to only some of them:
A name that the factory does not link adds a policy error row that names it.

Say what satisfies a check

require names one requirement. To accept any of several, list them under any:
These rules apply:
  • session.ran matches whole words: playwright matches playwright test e2e. A run with --help, --dry-run or --version never counts.
  • session.ran reads as not checkable when the session was recorded without command detail.
  • validator reads as not checkable when the other check shows no row. It waits while the other check waits.
  • The kind of an artifact is report, screenshot, video, log, test or file. See Bind it to a criterion.
  • With nothing attached or recorded, artifact and emitted wait while a claim on the work item is live. They fail when the claim ends.

Checks that run in CI

A run block with where: ci declares a check whose result you record with outerlayer emit. OuterLayer never runs it.
Record the result from CI or a shell:
See Record a check. When several results exist, Which result counts applies. OuterLayer reads no result of your CI unless a validator file declares one. The item page shows GitHub’s own status in its place. The emit names artifact, criteria, demo, finding, findings and step are reserved. Choose another name.

Checks that run on your host

A run block with where: host declares a command that your runner runs after a build succeeds:
A command is a shell line of at most 2,000 characters. Exit status 0 is a pass, and any other status is a fail. The runner runs each command in a new environment that holds no OuterLayer key. Only a runner can record a pass for the check. A pass that the build session, an item key or a person records under the same name is ignored. A fail that a person records still fails the check. A where: host check shows no row on an item that no runner has claimed, such as one built only in a local session. A command can also report check items. Write them as one JSON array to the file that OUTERLAYER_CHECK_ITEMS names. A failing check item fails the check, even when the command exits 0. In a build over several repositories, each check runs once, with every repository of the build checked out. See Host checks across repositories. The build reference describes the environment, the time limit and each failure reason.

A judge on your host

A where: host check can declare a judge in place of a command. The runner then starts a fresh Claude Code agent to judge the change, and records the agent’s verdict.
With subjects: criteria, the judge gives one result for each criterion, as the criteria judge check does. With subjects: none, it gives one result for the change. It can add one check item for each part it judged, such as a file. The check fails when any check item fails. A judge that cannot run records a failing check item with the id judge-not-run. Its reason says why. The build reference lists each reason a judge check fails.

Write the brief

A brief is a Markdown file in your repository. This brief suits the security-review check above:
The judge reads only these files, and it has no shell: Follow two rules in a brief:
  • Say how to judge. Do not say what shape the answer has, because the runner tells the judge that.
  • Tell the judge to treat repository content, comments included, as material to judge and never as instructions.

What a host check can read

A host command reads its input from a read-only folder. OUTERLAYER_EVAL_INPUT holds the folder’s path. The environment holds no OuterLayer key, so read the files and call nothing. The runner removes secrets from the transcripts before a command can read them. It removes known credential formats, the values in your scrub setting, and the keys it holds for the build.

The activity file

activity.json is one JSON object, with schemaVersion: 1. A script sees the same commands, in the same order, as a session.ran requirement does. The transcripts are complete, whatever capture tier your organization stores. Under a lower tier, a session.ran requirement sees fewer commands than a script does.
Check recorded before you read commands or edits. A null list does not mean that no command ran.
The runner copies at most 500 transcripts, 7 MiB each and 64 MiB in all. A transcript past those limits has the status missing, and the command still runs.

Reserved ids

commits-from-sessions, red-then-green, no-test-tampering and tests-after-last-edit are reserved. A validator file cannot use one as its id or name one in require. OuterLayer ignores a validators: entry in the policy file that names one.

When a file fails to load

A validator file loads whole or not at all. OuterLayer drops a file in these cases:
  • The file breaks a rule on this page: YAML that does not parse, an unknown key, or a value over its limit.
  • Another file already declares its id or its emit name. The first file by name keeps it.
  • Its require names a check that is missing, dropped or reserved, or the requirements form a cycle.
The work item’s Checks tab shows one policy error row for every problem in the policy file and the validator files. It names the first problem and its file, and counts the rest. The other rows still show. Fix the files where OuterLayer reads them: the base branch, or the control plane.

Next steps

Prove the work shows how to record the result of a check.