> ## Documentation Index
> Fetch the complete documentation index at: https://docs-stg.outerlayer.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Prove the work

> Record a work item's criteria, attach the artifacts and test results that prove them, and record named checks and code reviews.

A work item is proven by three things recorded on it: its acceptance criteria, the [artifacts](/concepts#how-the-work-is-judged) bound to them, and its named checks. This page shows how to record each one. [Policy and validators](/policy-and-validators) decides which of them count toward the verdict.

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/outerlayer-stg/vtkewxrpx_NzS0BQ/images/evidence-flow-light.svg?fit=max&auto=format&n=vtkewxrpx_NzS0BQ&q=85&s=efa2239d42a0693885d212e595a20d5e" alt="How evidence becomes a verdict. emit criteria records the criteria list. emit artifact with --for attaches proof to one criterion. emit with a check's name, a host check, or the judge records a check with its check items. The policy and validators, from the base branch or the control plane, judge all three into one verdict: pass, flagged or waiting. A person's review is not a check and stays separate. The Criteria tab shows each criterion with its proof and check items, the Checks tab shows each check and its failing check items, and the evidence page shows the criteria, proof and cost. On GitHub, when Post session summaries on pull requests is on, the evidence comment shows the checks and the review, and the OuterLayer evidence check reads both." width="660" height="548" data-path="images/evidence-flow-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/outerlayer-stg/vtkewxrpx_NzS0BQ/images/evidence-flow-dark.svg?fit=max&auto=format&n=vtkewxrpx_NzS0BQ&q=85&s=ce52b843e01490d77cc1a7b4f54433c7" alt="How evidence becomes a verdict. emit criteria records the criteria list. emit artifact with --for attaches proof to one criterion. emit with a check's name, a host check, or the judge records a check with its check items. The policy and validators, from the base branch or the control plane, judge all three into one verdict: pass, flagged or waiting. A person's review is not a check and stays separate. The Criteria tab shows each criterion with its proof and check items, the Checks tab shows each check and its failing check items, and the evidence page shows the criteria, proof and cost. On GitHub, when Post session summaries on pull requests is on, the evidence comment shows the checks and the review, and the OuterLayer evidence check reads both." width="660" height="548" data-path="images/evidence-flow-dark.svg" />
</Frame>

## Record the criteria

An item's acceptance criteria are one list recorded on the item. The **Criteria** tab, its count, the item's status and the criteria judge read this list. Nothing reads criteria out of the issue.

The `/build` skill records the list for you. It reads the issue's criteria in any form the issue writes them. It keeps each one's text exactly as written, and records the list before it writes code:

* When every criterion in the issue has an id that fits the [id rules](/reference/cli-emit#outerlayer-emit-criteria), the build keeps those ids. Otherwise it numbers the criteria `AC-<item number>-01`, `-02` and so on.
* When the issue holds no criteria, the build drafts them from the issue's description. The Criteria tab then says the build drafted the list.
* A criterion gets a declared proof only when the issue states one, as in `(proof: screenshot)`.

To record the list yourself:

1. Write the criteria to a file:

   ```json theme={"system"}
   {
     "criteria": [
       { "id": "AC-412-01", "text": "Given a signed-in user, when they log out, then the session ends.", "proof": null },
       { "id": "AC-412-02", "text": "Login redirects to the page the user asked for.", "proof": "screenshot" }
     ]
   }
   ```

2. Record it on the item:

   ```bash theme={"system"}
   outerlayer emit criteria criteria.json --item 412
   ```

`proof` names the artifact kind that proves the criterion. `null` means any artifact bound to the id proves it. The command also reads an acceptance file that ends in `.md`.

**Only a person can replace a recorded list.** An agent session can record an item's first list, but not a replacement. A drafted list is no exception. Run the command with your own login, outside the agent session. To let agent sessions replace a list too, set [`criteria.replace: anyone`](/policy-and-validators#the-policy-file).

The id rules, the acceptance file format and the exit codes are in [`outerlayer emit criteria`](/reference/cli-emit#outerlayer-emit-criteria).

## Attach an artifact

Capture the state first, then emit it:

```bash theme={"system"}
outerlayer emit artifact ./after.png --caption "The settings page renders the new toggle"
```

Write the caption as one present-tense sentence with no secrets. The file's extension sets its kind; you cannot set it yourself:

| File type | Kind |
| - | - |
| `png`, `jpg`, `jpeg` | screenshot |
| `webm`, `mp4` | video |
| `html`, `htm`, `pdf` | report |
| `txt`, `log` | log |
| `xml` with a `<testsuites>` or `<testsuite>` root | test |
| any other `xml`, and anything else | file |

Keep files small: video is the kind most likely to hit the size cap. The caps and every flag rule are in [`outerlayer emit artifact`](/reference/cli-emit#outerlayer-emit-artifact).

### Where it lands

**Inside a recorded session**, the file is queued, and the next `outerlayer sync` uploads it with the session and prints its link. A session not launched with `OUTERLAYER_WORK` refuses the emit, because nothing it produces may leave the machine.

**Outside a session**, it uploads at once. It attaches to the first of these that exists:

1. The pull request `--pr` names.
2. In GitHub Actions, the pull request the run is for.
3. The current git checkout.

With nothing to attach to, the command refuses. On other CI systems, pass `--pr`.

## Bind it to a criterion

Pass the id of a recorded criterion to `--for`. The **Criteria** tab then shows the artifact on that criterion's row:

```bash theme={"system"}
outerlayer emit artifact ./after.png --caption "…" --for AC-412-02
```

The artifact's kind must match the proof the criterion declares. A screenshot never satisfies a criterion that asks for a video.

## Prove a criterion with test results

A criterion can declare `"proof": "test"`. Bind JUnit XML results to it with `--for`. Vitest, Jest, pytest, Go (through `gotestsum`), JUnit and RSpec can all write that format. Another test runner can prove a criterion too, if it writes JUnit XML. Its documentation says whether it does, and where it writes the file. Some write it by default; others need a reporter or logger turned on.

```bash theme={"system"}
outerlayer emit artifact results.xml --caption "Auth tests pass" --for AC-412-01
```

Every test in the file is bound. To bind only some, name each one with `--test`. Add `=<path>:<line>` to say where a test is written:

```bash theme={"system"}
outerlayer emit artifact results.xml --caption "Expired links are refused" --for AC-412-01 \
  --test "rejects an expired link=src/auth/link.test.ts:42"
```

The rules for `--test`, and for which XML files are accepted, are in [`outerlayer emit artifact`](/reference/cli-emit#outerlayer-emit-artifact).

A test's location comes from the testcase's own `file` and `line` attributes first, then from `--test`. A test with neither shows by name, with no link. Vitest and jest-junit write `file` only with their `addFileAttribute` option. pytest writes it only under `junit_family=xunit1`.

The criterion is proven when every bound test passed, at least one passed, and no test you named with `--test` was skipped. A failed or errored test leaves it unproven. A skipped test you did not name neither proves nor fails it. When you emit again after a fix, retire the old results with [`--replaces`](#replace-an-earlier-artifact), or the old failure still counts.

Results prove a criterion only at the head commit of a pull request linked to the item. In GitHub Actions, the command records the pull request's head commit, not the merge commit the checkout holds. On other CI, check out the pull request's head commit before you run the tests. Results from an earlier commit show as out of date on the Criteria tab and prove nothing until you emit again at the head.

To require that results came from CI, set [`criteria.test_proof: ci`](/policy-and-validators#the-policy-file) in the policy file.

On the Criteria tab, each bound test links to its file and line at the commit that ran it. A failed test is marked as failed.

## Record a check

A named check is one pass or fail recorded on a work item under a name you choose, such as `ui-review` or `e2e`. Record one from CI or a terminal with [`outerlayer emit <name>`](/reference/cli-emit#outerlayer-emit-a-named-check).

A check counts only once a validator in `.outerlayer/validators/` declares its name. Until then it is stored, but shown nowhere and blocks nothing. See [Checks that run in CI](/write-a-validator#checks-that-run-in-ci).

Say what is wrong in one sentence:

```bash theme={"system"}
outerlayer emit ui-review --result fail --item 412 \
  --body "The button should be the destructive red, not grey."
```

Read the sentence from a file, or from standard input with `-`:

```bash theme={"system"}
echo "The button should be the destructive red, not grey." \
  | outerlayer emit ui-review --result fail --item 412 --body-file -
```

Record a pass once the work is right. `--result` defaults to `pass`:

```bash theme={"system"}
outerlayer emit ui-review --item 412
```

A check with a run to point at carries its URL instead of a sentence:

```bash theme={"system"}
outerlayer emit e2e --result pass --item 412 --link "$RUN_URL"
```

### Record check items

A check can carry [check items](/policy-and-validators#check-items): one result for each thing it judged. Write them as a JSON array:

```json theme={"system"}
[
  { "id": "AC-412-03", "criterion": "AC-412-03", "result": "fail", "reason": "The e2e test never filters by a label." },
  { "id": "AC-412-04", "criterion": "AC-412-04", "result": "pass" }
]
```

Then record the check with `--items`:

```bash theme={"system"}
outerlayer emit e2e --item 412 --items check-items.json --link "$RUN_URL"
```

With no `--result`, a failing check item fails the check, and a check with none passes. With no `--body`, the check's sentence names each failing check item. A check item with a `criterion` shows on that criterion's row on the **Criteria** tab.

### Which result counts

These rules apply to every named check, whoever records it:

* **The latest result counts.** A check is recorded on the work item, not on one pull request. The newest result under a name counts for every pull request the item has.
* **A person's fail stands over a later pass from a machine.** Only a person can record over a person's fail. A session or a machine key is refused. A pass that a runner's host check records does not undo it either.
* **A missing result waits while a claim is live.** A check that a validator file requires reads as waiting while a runner or a session holds a claim on the item. Once the claim is released or expires, a missing result fails.
* **`--item` is always required**, because the check belongs to the item.
* **A fail needs a link or a sentence.** A pass needs neither. A sentence made from failing check items counts.
* **A failing check item rules out a pass.** `--result pass` with one is refused before anything is sent.
* **A session records checks on its own item.** Inside a recorded session, a check on the item that session was launched for is stored as the session's.
* **Your key decides who recorded the check, not the request.** A key bound to you records as you. A key with no person behind it, such as one for CI, records as a machine. Inside a recorded session the session records it, even with your own key.
* **Every row says who recorded it.** The Checks tab and the pull request comment name CI, a machine key, the person, the build session, or the runner that ran it.

A review of the whole item is not a named check. Record it on the item page, or with `outerlayer work comment`. See [Review and merge](/review-the-work#threads-and-waiting-on). The Quality chart on the Metrics → Overview dashboard reads reviews only, never named checks.

## Record a code review

The starter [code review check](/policy-and-validators#the-code-review-check) passes when a pass is recorded under `code-review` for the work item. The `/build` skill records it for you. To record one yourself:

1. Emit the review's report as an artifact.
2. Run `outerlayer sync`, and take the report's deep link from the line it prints for the report.
3. Record the verdict with that link as its proof.

```bash theme={"system"}
outerlayer emit artifact review.html --caption "Review of the change and what it found"
outerlayer sync
outerlayer emit code-review --item 412 --result pass --link <the report's deep link>
```

If `sync` printed no line for the report, another sync already uploaded it. Run `outerlayer sync --link <the artifact id>`, with the id `emit artifact` printed.

Record `--result fail` when the review left something unfixed. A session, CI or a person can record it.

## Replace an earlier artifact

The command prints an id. Pass it to `--replaces` to correct that artifact:

```bash theme={"system"}
outerlayer emit artifact ./after-v2.png --caption "…" --replaces <id>
```

A retired artifact that had uploaded is hidden as superseded, but keeps its file and link. One still queued never uploads. A failed emit retires nothing. To retire several at once, see [`--replaces`](/reference/cli-emit#outerlayer-emit-artifact).

## Find it afterwards

Once it uploads, the artifact appears on the work item's page:

* on its criterion's row on **Criteria**, when it is bound to one;
* beside the check that names it on **Checks**;
* otherwise under **Other artifacts** on **Checks**.

To get its link:

* **From a session**, run `outerlayer sync`. It prints a line for each artifact it uploads, ending with the link. `outerlayer sync --json` lists them under `uploadedArtifacts`.
* **Outside a session**, `outerlayer emit artifact` prints the link on the line after the id.
* **After another sync uploaded it**, run `outerlayer sync --link <artifact id>`, with the id `emit artifact` printed. It prints the link from the record the upload left on this machine and sends nothing.
* **From any machine**, the `list_artifacts` tool of the [MCP server](/mcp) lists a work item's artifacts with their links.

The link also appears in the pull request's evidence comment, if there is one. The id `emit artifact` prints is not part of the link.

An artifact emitted in a session on a work item reaches the evidence comment of the item's pull request about a minute after it uploads. If the item has no pull request yet, it reaches the comment once the pull request is opened and declared on the item.

The pull request's [evidence page](/share-evidence) shows it too, under its criterion, and can be made readable without signing in.

## Permissions

Recording criteria, artifacts and checks needs **Emit evidence**. See [API keys](/api-keys).

A browser `outerlayer login` holds **Emit evidence** only when your role can create factory keys. The owner, admin and write roles can. Otherwise, use a factory key bound to you.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.