@outerlayer/cli 0.2.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/README.md +229 -60
  3. package/dist/agent-setup-IFH3FB5X.js +197 -0
  4. package/dist/build-TZUUTRGH.js +9 -0
  5. package/dist/build-info.json +1 -1
  6. package/dist/check-7MFH6HNC.js +94 -0
  7. package/dist/chunk-3TFPDVTI.js +94 -0
  8. package/dist/{chunk-4TNZMO7V.js → chunk-4C4THABW.js} +5566 -754
  9. package/dist/{chunk-R5KBGVII.js → chunk-5QRP3MVS.js} +30 -14
  10. package/dist/chunk-5Y3QQRUH.js +300 -0
  11. package/dist/chunk-774HEQL6.js +178 -0
  12. package/dist/{work-pr-cmd-AZQPWH4H.js → chunk-7WERLFVR.js} +9 -26
  13. package/dist/chunk-ABIWDUMS.js +104 -0
  14. package/dist/chunk-AUFG23AA.js +534 -0
  15. package/dist/chunk-B2G7JHHB.js +42 -0
  16. package/dist/chunk-BCJNHMZT.js +34 -0
  17. package/dist/chunk-BCLJSQCV.js +56 -0
  18. package/dist/chunk-BFESLKPP.js +61 -0
  19. package/dist/chunk-BJ3KTMDH.js +63 -0
  20. package/dist/chunk-BKO6JEZI.js +47 -0
  21. package/dist/chunk-BOLTI6LR.js +37 -0
  22. package/dist/chunk-CBPPJSAR.js +89 -0
  23. package/dist/chunk-DM3VFDS3.js +1461 -0
  24. package/dist/{chunk-65WXAANR.js → chunk-DVDEBNQJ.js} +17 -2
  25. package/dist/{chunk-TFUIDMOB.js → chunk-EABW6AJQ.js} +12 -1
  26. package/dist/{chunk-KFYJV2ZG.js → chunk-EMY4I27X.js} +1 -1
  27. package/dist/{chunk-NTTPJV35.js → chunk-F47JBIAW.js} +3 -1
  28. package/dist/chunk-F6GFZXZ3.js +60 -0
  29. package/dist/{chunk-YOOSBOKS.js → chunk-F7CU5ABH.js} +1 -1
  30. package/dist/{emit-cmd-TWSYYEZI.js → chunk-H5NCNVDA.js} +68 -17
  31. package/dist/chunk-HZRNMGLF.js +2233 -0
  32. package/dist/chunk-LF6MLJCY.js +38 -0
  33. package/dist/{chunk-U32VSRLO.js → chunk-LT5TZHYH.js} +95 -634
  34. package/dist/chunk-LYCDNMOH.js +83 -0
  35. package/dist/chunk-M5POMKKW.js +1051 -0
  36. package/dist/{mcp-install-cmd-FDQH6SEN.js → chunk-MQ3IPHIZ.js} +35 -18
  37. package/dist/{chunk-D77LS3UI.js → chunk-N5FOE5PS.js} +33 -4
  38. package/dist/chunk-N6LURUEF.js +64 -0
  39. package/dist/chunk-OGFGZQA3.js +23 -0
  40. package/dist/chunk-QDQEUVUF.js +9 -0
  41. package/dist/chunk-QOZKPLVJ.js +226 -0
  42. package/dist/chunk-QY22NZUW.js +1764 -0
  43. package/dist/chunk-RA3O54FC.js +30 -0
  44. package/dist/chunk-RFUPN6KX.js +71 -0
  45. package/dist/chunk-SY4GK4SP.js +55 -0
  46. package/dist/chunk-TBT347UY.js +41 -0
  47. package/dist/chunk-TWNWNS2Q.js +1084 -0
  48. package/dist/{chunk-NXLLURA4.js → chunk-UFFXNXLQ.js} +115 -71
  49. package/dist/chunk-USO2DKBD.js +421 -0
  50. package/dist/chunk-W4SQCOZU.js +206 -0
  51. package/dist/{chunk-Q6CL5THG.js → chunk-W5FVUZXV.js} +208 -61
  52. package/dist/chunk-WFJ65NUD.js +383 -0
  53. package/dist/{chunk-A3WLZX2F.js → chunk-WIOZAJ2W.js} +15 -2
  54. package/dist/chunk-WO2BXCTQ.js +83 -0
  55. package/dist/{chunk-I3ETLSNE.js → chunk-XCCLFVXM.js} +120 -11
  56. package/dist/{context-materialize-6WKT3RBQ.js → chunk-XDDW4FRS.js} +134 -26
  57. package/dist/chunk-Y7KJLXYN.js +232 -0
  58. package/dist/chunk-YFHIIH4B.js +102 -0
  59. package/dist/chunk-YYYXRJUW.js +101 -0
  60. package/dist/chunk-ZM2IMMYN.js +76 -0
  61. package/dist/chunk-ZMYPLFG3.js +71 -0
  62. package/dist/chunk-ZNA27WEV.js +470 -0
  63. package/dist/{cli-G7DILYJY.js → cli-W62AFRSW.js} +616 -878
  64. package/dist/{paths-D2VGWWFI.js → cli-build-K56DK4DS.js} +1 -1
  65. package/dist/config-XVJYZ4IQ.js +9 -0
  66. package/dist/connect-cmd-ZTJONP37.js +16 -0
  67. package/dist/context-adopt-UMA4O5NY.js +105 -0
  68. package/dist/context-materialize-G2FUFC72.js +19 -0
  69. package/dist/dist-44CQVWGT.js +6 -0
  70. package/dist/docker-NEGDLU6D.js +7 -0
  71. package/dist/doctor-53F2PYY7.js +43 -0
  72. package/dist/doctor-ZLGZYMDK.js +426 -0
  73. package/dist/{emit-artifact-cmd-UQVT3OKW.js → emit-artifact-cmd-AKZP7TMF.js} +87 -23
  74. package/dist/emit-cmd-DCERAU27.js +9 -0
  75. package/dist/emit-criteria-cmd-7MXQVCKD.js +166 -0
  76. package/dist/{emit-finding-cmd-FDOU2OPD.js → emit-finding-cmd-N2FTGCVC.js} +34 -31
  77. package/dist/{emit-result-cmd-H2T4C2K3.js → emit-result-cmd-4CPDBMN2.js} +34 -62
  78. package/dist/exec-client-4XGXV3XN.js +8 -0
  79. package/dist/guest-init-GENRHNP7.js +8 -0
  80. package/dist/hook-fast-EYENPK6F.js +9 -0
  81. package/dist/{hook-wrap-fast-KXYNX3AD.js → hook-wrap-fast-NPLHJXFK.js} +2 -2
  82. package/dist/host-key-KDYZ5ECC.js +7 -0
  83. package/dist/import-capture-cmd-AVZ4VIVJ.js +68 -0
  84. package/dist/{import-ruler-cmd-7LH2QOMG.js → import-ruler-cmd-GUHL6IY5.js} +1 -1
  85. package/dist/index.js +4 -4
  86. package/dist/init-55Y4LHWF.js +36 -0
  87. package/dist/init-XXEKWZQP.js +221 -0
  88. package/dist/init-cmd-WLST2F7G.js +135 -0
  89. package/dist/install-cmd-7WXOIFDO.js +55 -0
  90. package/dist/lima-XN65D7GN.js +55 -0
  91. package/dist/login-browser-7XGSV7MA.js +10 -0
  92. package/dist/{config-POF7DEQW.js → logout-cmd-QAUFDJ6J.js} +3 -1
  93. package/dist/{logs-TRNPQM42.js → logs-7RYUH5A6.js} +1 -1
  94. package/dist/loop-JFIBP4B7.js +43 -0
  95. package/dist/machine-ZBPT2J3R.js +35 -0
  96. package/dist/mcp-install-cmd-RLK4SO3N.js +9 -0
  97. package/dist/{mcp-serve-cmd-57EZZOTL.js → mcp-serve-cmd-N6UD2KBX.js} +20 -9
  98. package/dist/paths-OYKMVYJP.js +6 -0
  99. package/dist/{pidfile-PTW76F56.js → pidfile-UZRH774M.js} +2 -3
  100. package/dist/real-deps-SU24ZA2K.js +21 -0
  101. package/dist/relay-L76HDX72.js +46 -0
  102. package/dist/settings-J2652U5N.js +7 -0
  103. package/dist/starter-pack-LIZYMKYQ.js +10 -0
  104. package/dist/{status-I27IST4L.js → status-UJXWZRN5.js} +30 -13
  105. package/dist/{statusline-fast-3C5OXHDD.js → statusline-fast-SVTMBMD7.js} +3 -2
  106. package/dist/sync-cmd-4G4A7EVN.js +28 -0
  107. package/dist/version-BWM6VLDI.js +6 -0
  108. package/dist/{watch-3DTPJETH.js → watch-7SKJKGAA.js} +19 -8
  109. package/dist/{work-claim-cmd-5AUNCLS2.js → work-claim-cmd-5F7VZ42N.js} +34 -21
  110. package/dist/work-cmd-QAUZ7MTD.js +17 -0
  111. package/dist/work-comment-cmd-BNCSBTLH.js +106 -0
  112. package/dist/{work-launch-LT663PB3.js → work-launch-YI4CJDAP.js} +1 -1
  113. package/dist/work-open-pr-cmd-VFYZHIIW.js +156 -0
  114. package/dist/work-pr-cmd-PV32ZLSI.js +16 -0
  115. package/package.json +13 -3
  116. package/skill-pack/maintained/amend/SKILL.md +104 -0
  117. package/skill-pack/maintained/emitting-evidence/SKILL.md +108 -0
  118. package/skill-pack/maintained/emitting-evidence/references/agents-snippet.md +20 -0
  119. package/skill-pack/maintained/outerlayer/SKILL.md +55 -0
  120. package/skill-pack/maintained/reporting-findings/SKILL.md +148 -0
  121. package/skill-pack/template/build/SKILL.md +193 -0
  122. package/skill-pack/template/build/references/agent-briefs.md +243 -0
  123. package/skill-pack/template/build/references/criteria-judge.md +91 -0
  124. package/skill-pack/template/build/references/evidence.md +42 -0
  125. package/skill-pack/template/build/references/release.md +74 -0
  126. package/skill-pack/template/build/references/review-briefs.md +275 -0
  127. package/skill-pack/template/build/references/review-loop.md +158 -0
  128. package/skill-pack/template/build/scripts/record-criteria.mjs +235 -0
  129. package/skill-pack/template/spec/SKILL.md +84 -0
  130. package/skill-pack/template/writing-specs/SKILL.md +134 -0
  131. package/dist/chunk-DCNOXRMV.js +0 -589
  132. package/dist/chunk-JJP7YLMN.js +0 -25
  133. package/dist/chunk-OZ7C3XUE.js +0 -34
  134. package/dist/chunk-WQ6VGRGZ.js +0 -150
  135. package/dist/hook-fast-J5LCDHUJ.js +0 -8
  136. package/dist/import-capture-cmd-EUMBGIV3.js +0 -176
  137. package/dist/init-PTBITAUO.js +0 -103
  138. package/dist/login-cmd-IRX6LZT7.js +0 -62
  139. package/dist/loop-ASZRX3CZ.js +0 -648
  140. package/dist/sync-cmd-BBAUZ5JD.js +0 -17
  141. package/dist/work-cmd-2P4BVX47.js +0 -18
package/CHANGELOG.md CHANGED
@@ -1,5 +1,137 @@
1
1
  # @outerlayer/cli
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - a5cda3d: `import capture` and `context emit` help list all four maintained skills, and `mcp serve` no longer suggests the hidden `--api-key` flag when it finds no API key.
8
+ - b150a61: `emit artifact` reads a JUnit XML file bound with `--for` as test results, and a repeatable `--test "<name>[=path:line]"` selects the tests that prove the criterion and gives a location the file lacks.
9
+ - 32850dd: When a build's command exits 0, `outerlayer runner` reads the work item's evidence verdict before it releases the claim. A build whose item still has a failing check, or a required check with no result, is released `incomplete`, with a reason naming each check, instead of `ok`. A verdict that is not passing is read again every 30 seconds for up to 5 minutes; a passing one is believed at once. The report hook's `OUTERLAYER_OUTCOME` is the outcome the release carries. Runner protocol 12.
10
+
11
+ ## 0.4.0
12
+
13
+ ### Minor Changes
14
+
15
+ - 1cffcc2: `outerlayer connect` now checks that the repository is linked to the factory it chose. When it is not, `connect` saves the connection as before, prints the factory's Work page link where the setup panel links a repository, and exits 2. `init` reports its connect step as done with `linked: false` and repeats the link under "Next", and still exits 0. A factory key skips the check. `connect` also takes `--json`, which writes one document with `linked` and, when it is false, `linkUrl`.
16
+ - ecc937a: Add `outerlayer emit criteria <file> [--item <number>]`, which records a work item's acceptance criteria as one list. The list is validated before anything is sent: an empty list, a repeated id or a reserved id is refused. The newest recorded list is what the item's Criteria tab, its count and its status read, and criteria written in an issue body are no longer read. Once an item has a list, only a person can replace it.
17
+ - 6bde8e9: `outerlayer init` now sets up Claude Code in the repository: it installs the starter skills and a new `outerlayer` skill, writes `.outerlayer/config.json` when there is none, runs `outerlayer context emit`, and adds the OuterLayer MCP server to `.mcp.json`. It asks nothing and changes nothing you wrote. It skips this in a repository whose context comes from a control plane, and `--local` skips it too.
18
+ - 69e695a: Adds `builtin:vm`, a third built-in runner backend. On a Linux x86_64 host where the runner's user can use `/dev/kvm`, each build runs in a Firecracker microVM made from the recipe's image. The microVM has no network device. It reaches the runner's tunnel over vsock, and it has the vCPUs, memory and disk that `runner.build` sets.
19
+
20
+ The guest kernel and helper are not published yet, and the hashes pinned in the CLI are placeholders. Until they are published, `outerlayer runner init` does not name `builtin:vm` on any host, and `outerlayer doctor` fails `builtin:vm` hooks with that reason. After the first publish, `runner init` names `builtin:vm` on a Linux x86_64 host where the user can use `/dev/kvm`. A host without usable KVM keeps `builtin:container` or `builtin:process`, unchanged. `outerlayer doctor` adds a `KVM` check, which names the group to join, and a `MicroVM memory` check, which warns when `runner.concurrency` times `runner.build.memory` is more than the host's memory.
21
+
22
+ The first microVM build downloads Firecracker, a guest kernel and a guest helper from github.com, and checks each against a sha256 pinned in the CLI.
23
+
24
+ - b678856: A runner now keeps itself at the CLI version its gateway names. When it has no build in flight, it installs that version beside its current one, checks the version's tarball and provenance against this repository's release workflow, and switches to it. If the new version fails its own checks, the runner goes back to the previous version. Turn this off with `runner.autoUpdate: false`. Run `outerlayer runner install` once on an existing host to move it onto the install layout.
25
+
26
+ ### Patch Changes
27
+
28
+ - 48352ce: The starter `build` skill now records the item's acceptance criteria at intake with `outerlayer emit criteria`. It reads an `## Acceptance criteria` section with ids, or a bullet list under an `Acceptance:` line and adds `AC-<item number>-NN` ids. A list a person already recorded is never replaced. The criteria judge reads the recorded list from the build's state file.
29
+ - 93695bc: `init` ends by saying to commit and push `.outerlayer/` when its agent setup step left files in the repository. The factory reads the policy from the default branch, so no check runs on the repository's pull requests until it is there. The line is left out with `--local`, outside a repository, and under `--json`.
30
+ - d7d56d3: The microVM build disk's copy step can now be supplied by the host that builds the disk. The runner still copies with GNU `cp --sparse=always` by default, so builds on Linux are unchanged.
31
+ - e1a440f: A `builtin:vm` build runs Docker when its recipe asks for it. A recipe asks by listing the `ghcr.io/devcontainers/features/docker-in-docker` feature, at any tag or digest. The microVM then starts its own Docker daemon, and the agent user can run `docker` before the clone starts. Image pulls go through the runner's tunnel like the rest of the build's traffic. Containers the build starts have no route out. If the daemon does not answer within 60 seconds, provisioning fails and names the daemon. Its reason includes the end of the daemon's log. A recipe that does not list the feature runs no daemon. The build's recorded recipe report says whether Docker was asked for.
32
+
33
+ ## 0.3.0
34
+
35
+ ### Minor Changes
36
+
37
+ - 9b8ebd0: Adds `outerlayer work open-pr --title <text> --body-file <path>`. A host build sends it to the gateway, which opens or edits the item's pull request as the factory's GitHub App. A person's own session runs `gh` under their login and declares the pull request on the item.
38
+
39
+ The runner now speaks protocol 4. A claim names the branch the build works on, and the runner checks that branch out in the provisioned workdir before the command starts. It also sets `OUTERLAYER_BRANCH` for every hook and the command. A container build gets `OUTERLAYER_BRANCH` but not the checkout, so it checks the branch out itself. The runner skips a claim the gateway refuses, such as a pull request from a fork, and logs the refusal code. A gateway from this release refuses a runner below protocol 4 with `runner_upgrade_required`, so upgrade runners to this version.
40
+
41
+ - e377274: `outerlayer login` signs in to your account through the browser: it opens a page where you approve the CLI, then saves a token for your account. It chooses no organization or factory, so commands that need a factory tell you to run `outerlayer connect`. `outerlayer logout` revokes the token and removes it. A factory key, from a flag, `OUTERLAYER_API_KEY` or the config file, always wins over the login, so CI and runners keep acting as their key. Use `--no-browser` to print the link instead, `--no-input` to skip waiting on a person, and `--check` to finish a login that was still waiting.
42
+ - 39f75bc: Adds `outerlayer connect`, which chooses the factory a repository's work goes to. It picks by itself when only one answer is possible, says what it picked and why, and saves the choice in the repository's git directory, shared by every worktree and never committed. With no terminal it exits 1 and lists the `--factory <org>/<factory>` values to pass.
43
+
44
+ `outerlayer init` now connects the repository, installs the hooks and runs `doctor`. It never signs in: with no login it skips the connect step and says to run `outerlayer login`. `init --local` is the old behavior, with no connect step and no network call. `init --json` prints one document naming the outcome of each step.
45
+
46
+ A login now takes its factory from the repository it runs in, not from `appId` in the config file. `--app-id` and `OUTERLAYER_APP_ID` still win. A factory key is unchanged. Run `outerlayer connect` once in each repository that used a login's saved factory.
47
+
48
+ - 91c2778: A container build of a governed repository now starts with its control plane's context in the checkout: `AGENTS.md`, skills and validators, at the control plane's current head. The runner reads the control plane on the host with the host's own git login, and the build receives files, never a credential. A host whose git login cannot read the control plane fails the build at provision with `context_unavailable`, and `outerlayer doctor` checks this on a container host.
49
+
50
+ The runner now speaks protocol 9, because it asks the gateway which repository governs a build's repository. A gateway from this release still accepts a runner at protocol 8.
51
+
52
+ - 3294997: `outerlayer doctor` now asks the gateway three questions. Does the saved credential work? Can it use the factory chosen for this repository? Is the repository linked to that factory? A login and a factory key are each checked the way a command would use them, and a factory key wins over a login. With no credential, the three checks are skipped and doctor makes no network call. The GitHub App check runs only when the credential check passes. Fix hints no longer name `--api-key`, `--root` or `npx @outerlayer/cli`.
53
+ - 89a20a5: `outerlayer doctor` adds one check per repository connected to your factory, once `outerlayer login` has saved a factory key. It warns when the GitHub App installation has not accepted a permission builds need, naming each one and the installation's settings page, and when the default branch does not require a pull request. It reads `GET /v1/repositories/access` and changes nothing.
54
+ - b0519e1: `outerlayer emit finding` now records a problem you hit in the factory. It takes `--category` (`context`, `flaky-test`, `tooling`, `environment`, `defect` or `other`) and drops `--subject`, `--kind`, `--severity`, `--verdict`, `--fixed` and `--source`; each removed flag is refused by name. A `context` finding names the instruction with `--rule-path` and `--rule-relation` (`wrong`, `broken` or `missing`), and quotes it with `--rule-quote` unless the relation is `missing`.
55
+
56
+ `outerlayer emit findings <file>` takes `"schemaVersion": 2` files. A `schemaVersion: 1` file is refused. The gateway refuses a `schemaVersion: 1` batch with a message to upgrade the CLI.
57
+
58
+ `outerlayer init --template default` and `outerlayer context emit` install a new maintained `reporting-findings` skill that tells agents when to record a finding.
59
+
60
+ - 6bddf79: Uploads now carry the session's token counts so far, split per model, speed and input size, so the gateway can price the whole session itself. Each turn's usage also names its 5-minute cache writes and the request's speed. The gateway stores its own price and keeps the cost the CLI worked out only for a turn it has no price for.
61
+
62
+ The CLI's local cost estimate now prices a turn as unknown when the model has no rate for a token class the turn used, such as cache reads. It used to price that class at zero, so the status line showed a lower cost than the true one.
63
+
64
+ - e3f8d26: The runner sends a heartbeat at the end of every poll — its CLI version, poll interval, slots in use and total, and the result of that poll's own request for work. A key without the new `hosts.ingest` permission still polls and claims normally; the heartbeat is refused, logged once, then every tenth poll while it continues, and the host stays off `GET /v1/hosts` until the key is edited to add it.
65
+ - 9de5518: A build the runner starts now holds an item key, not the runner's own key. The claim response carries the key, and the runner passes it as `OUTERLAYER_API_KEY` to the hooks, the command, the sync step and the cleanup hook. It acts only on the claimed item, and only while the claim is live. The runner keeps its own key for renewing and releasing the claim and for heartbeats, and it runs the final sync under the item key before it releases. The runner protocol is now 2, so a gateway that predates the item key does not return one. The runner then releases the claim without starting the build and says so in its log.
66
+ - ea3130e: `outerlayer runner image --repository <owner/name>` builds the image a repository's builds run in, from the devcontainer file on its default branch. It ignores every setting that reaches the host or widens the container's privileges, and lists each one it ignored. It adds this CLI and a pinned Claude Code to the image, reuses the image until the recipe or the tools change, and keeps the newest three. `outerlayer doctor` on a runner host now checks each fetched repository's recipe, the devcontainer CLI, and whether the pinned Claude Code version is behind. The CLI now depends on `@devcontainers/cli`.
67
+ - 1834f72: A build's commits are now authored by the GitHub App's bot account, and credit the member who asked for the build. The claim response carries `commitAuthor` and, when the requester signed in with GitHub, `coAuthor`. The runner sets `GIT_AUTHOR_NAME`, `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_NAME` and `GIT_COMMITTER_EMAIL` for every process an attempt starts, so a host's own git identity no longer authors a commit.
68
+
69
+ For a co-author, the runner writes a hooks directory into the build's directory and points git at it through `GIT_CONFIG_COUNT`, after any entries the host already passes. Each hook runs the repository's own hook of the same name with the same arguments, input and exit status. `prepare-commit-msg` then adds one `Co-authored-by` line. A build run through an `exec` gets the bot as author and no co-author line.
70
+
71
+ The release records `environment.commitIdentity`. The runner now speaks protocol 8. A gateway that predates the field refuses a release that names it, and the runner sends the release again without it.
72
+
73
+ - c2032f3: A runner whose provision and cleanup hooks are `builtin:container` now runs each build in its own hardened container: no network but loopback, no Linux capabilities, a read-only root filesystem and one volume. The build reaches public addresses only through a tunnel the runner serves, which refuses private, link-local, shared-address and the host's own addresses and records every host the build reached. The runner config gains a `build` block (`memory`, `cpus`, `pids`, `disk`, `variables`), the image's tools layer gains an unprivileged user, `gh` and `tini`, and every release now carries an `environment` object recording what the attempt ran in. The runner protocol is now 2.
74
+ - ca1cc05: A container build no longer holds a credential that can write to its repository. The runner serves the build a local git remote and holds the repository tokens itself. It asks the gateway for a read token before it provisions anything, and for a push token on the build's first push. It renews each token within ten minutes of expiry and revokes every one when the attempt ends. The build's `gh` holds only the read token.
75
+
76
+ The runner forwards a push only when every ref it updates is the branch the claim names. On a branch outside `outerlayer/`, it forwards only creating the branch or a fast-forward. `GH_TOKEN`, `GITHUB_TOKEN`, `GH_ENTERPRISE_TOKEN` and `GITHUB_ENTERPRISE_TOKEN` in `runner.build.variables` are no longer passed into a build. The runner logs each name it skips.
77
+
78
+ A container build now needs a gateway with a GitHub App configured. Without one, every claim ends as `failed` at provision with the reason `repository_tokens_unavailable`. The release records which kinds of token the build used as `environment.credentials`.
79
+
80
+ The runner now speaks protocol 6, because the release carries `credentials` and the runner calls the token route. A gateway that has not been upgraded refuses a release that names `credentials`, so upgrade the gateway first.
81
+
82
+ - 3a32b01: A build the runner provisions with `builtin:container` or `builtin:process` no longer holds the item key or Claude's credential. The runner serves two built-in destinations, `gateway` and `claude`. The build's `OUTERLAYER_URL` and `ANTHROPIC_BASE_URL` point at local addresses, and its `OUTERLAYER_API_KEY` and Claude credential variable hold placeholders. The runner sets the item key and the host's credential on each request it forwards.
83
+
84
+ The runner reads `CLAUDE_CODE_OAUTH_TOKEN`, or else `ANTHROPIC_API_KEY`, from its own environment. A host that sets neither now ends each attempt as `failed` at `provision` with the reason `agent_credential_missing`, so set one before upgrading. Remove `CLAUDE_CODE_OAUTH_TOKEN` from `runner.build.variables`: a name there now gets a placeholder. A host may list at most 18 destinations, down from 20, and may not name one `gateway` or `claude`.
85
+
86
+ - e07a51f: A host's own hooks can now report how their builds are isolated, and `outerlayer runner init` names the hooks that fit the machine. A provision hook can write `isolation`, `exec`, `broker` and `recipe` to `provision.out`. It receives the build's directory, the repository, the recipe commit and the four limits. The new `builtin:process` hooks run builds as processes on machines that cannot isolate them, through the same tunnel, and record `shared-user`. `runner init` names `builtin:container` on Linux or WSL2 with Docker Engine 26 or later, and `builtin:process` elsewhere, and no longer writes hook scripts. `outerlayer doctor` says what a host's builds get. A host can list credentialed services in `runner.build.destinations`. The runner forwards a build's requests to them over HTTPS with the host's header in place of any credential the build sent, so the secret never enters the build, and the release names the destinations a build called. The runner protocol is 4.
87
+ - ad6979c: `outerlayer runner check` now exits 1, before the host takes work, when an enabled queue's command is still the placeholder `runner init` wrote, when built-in hooks find neither `CLAUDE_CODE_OAUTH_TOKEN` nor `ANTHROPIC_API_KEY` set, and when the key lacks `work.claim`. Each message names the queue, `claude setup-token` or the permission.
88
+
89
+ On a runner host, `outerlayer doctor` lists the laptop checks as skipped with one line that says why, adds a `Claude credential` check that names the variable and never its value, and names a missing permission instead of calling the key revoked or expired. Its GitHub App hint says whether the App's own settings must add a permission or an installation owner must accept it.
90
+
91
+ A container build fetches the image recipe of a private repository with the claim's read token, so the host needs no git login. `outerlayer runner image` exits 1 with one line naming the repository when its fetch fails. `outerlayer work build` warns when the gateway says the request cannot earn repository tokens.
92
+
93
+ - b2a18b7: `outerlayer runner init` no longer chooses builds without isolation for you. On a machine that cannot isolate a build, it refuses and says what isolation needs; pass `--allow-process-builds` to run builds as processes under your user instead. On a Mac with no terminal to ask, it refuses and names `--vm`. A host can now list the public hosts its builds may reach in `runner.build.allowHosts`: the tunnel refuses every other name before resolving it, and records the refusal with the range `not-allowed`.
94
+ - 3019459: A runner key now works only from the host that first used it, and the runner signs every request it makes. The runner makes an Ed25519 host key in `~/.outerlayer/runner/host-key.pem` the first time it starts. Its first claim binds the key's public half to the runner key. After that the gateway refuses any request made with that runner key unless the host key signed it.
95
+
96
+ The runner speaks protocol 7, and a gateway from this release refuses a runner below it with `runner_upgrade_required`, so upgrade every runner. To move a runner key to a new host, a member clears its binding on the factory's API keys page, and the new host's next claim binds its own key.
97
+
98
+ `outerlayer doctor` says whether the runner key is bound to this host, bound to another, or not yet bound. A refused signature is reported with its cause: a stale one names the host's clock. `outerlayer runner init` prints the host key's fingerprint.
99
+
100
+ - 85fa250: On macOS, `outerlayer runner init` now offers to create a Linux VM with Lima and run the runner inside it, so builds run in containers and record `container`. `--vm` answers yes and `--no-vm` answers no; a run with no terminal answers no. The VM holds the runner key and the host key, and starts at login through a launch agent and a systemd service. The Mac's config loses `apiKey` and `runner` and gains a `runnerVm` note.
101
+
102
+ `outerlayer doctor` on a Mac with process hooks now says builds are not isolated and names `outerlayer runner init --vm`. With the runner in a VM, it reports the VM's state and how to start it.
103
+
104
+ - 365dab4: The runner and `outerlayer work claim` now send the runner protocol they were built with, as `runnerProtocol`, on every claim, and the runner sends it on every heartbeat. A gateway that needs a newer protocol refuses the claim with `426 runner_upgrade_required`. The runner then logs the minimum protocol and a link to the upgrade steps, and keeps polling so it takes work again once it is upgraded and restarted.
105
+ - 02f7990: `outerlayer init --template default` installs a starter skill pack into `.outerlayer/skills/`. The templates `spec`, `writing-specs` and `build` are copied once and never overwritten. The maintained skills `emitting-evidence` and `amend` are rewritten from the installed CLI on every `outerlayer context emit`, and each carries the CLI version on its first line. `init --template default` also writes a starter `.outerlayer/policy.yaml` unless one exists.
106
+
107
+ `outerlayer context emit --check` now reports drift, and exits 1, when a maintained skill differs from the installed CLI's copy, for example after a CLI upgrade. `outerlayer import capture` rewrites both maintained skills instead of refusing to overwrite a skill with local edits.
108
+
109
+ - 1177caa: `outerlayer work build` takes a Linear or Jira issue key, not only a GitHub issue number: `outerlayer work build --issue ENG-123 --tracker linear`. `--tracker` names which tracker a keyed issue belongs to, required only when a factory has connected more than one. `--repo` is the only way to attach a repository to a keyed issue. A new `--retry <count>` option sends a build request again, up to 4 more times, after any 5xx or 429 answer, including a 5xx from a proxy in front of the gateway. A retry after such an answer can record the request twice. It never resends after a network error or a 4xx other than 429.
110
+ - 852946a: Add `outerlayer work build`, which asks a host to build a work item. `outerlayer work add` alone no longer queues an item for a host. A session launched with `OUTERLAYER_WORK` now holds a 20-minute lease on its item, renewed by a new `PostToolUse` hook and released when the session ends; run `outerlayer init` to add the hook to an existing install.
111
+ - b52a19e: `outerlayer work build` is the one command that starts work on an item. `work build --issue <n>` asks a host to build it. `work build --issue <n> --local` puts it on Work for you to build, and prints the `OUTERLAYER_WORK=<n> claude` line that starts your session on it. `outerlayer work add` is removed; use `work build --local` where you used it.
112
+ - cfddc47: Adds `outerlayer work comment` and `outerlayer work threads`. A work item's review, and each acceptance criterion's own proof, live on comment threads now: a person's pass or fail, an agent's attach or ready. `outerlayer emit`'s per-artifact verdict option is removed — record it as a comment instead.
113
+
114
+ ### Patch Changes
115
+
116
+ - 9787d1c: The runner now sends the step an attempt ended in, its exit code, and a one-line reason from `$OUTERLAYER_JOB_DIR/reason` when it releases a work item claim. The reason comes only from a file the host's own cleanup hook writes, scrubbed and cut to 200 characters before it leaves the machine.
117
+ - 22ce869: The status line now keeps the session cost Claude Code reports, and `sync` and the daemon send it with each Claude Code session as `totals.agentReportedCostUsd`. The daily pricing check compares it with the cost the gateway stored.
118
+ - ac57469: Every command on a runner host signs its requests with the host key when it uses the saved runner key. `outerlayer doctor`, `work status`, `sync`, `emit` and the other commands no longer fail with `runner_key_signature_required` once the runner key is bound to the host.
119
+ - 1ba39dc: A container build now starts on the branch its claim names, by the rule a host build uses, before its lifecycle commands run. The checkout is a partial clone that keeps every commit, so a merged `outerlayer/` branch starts from the default branch's head. The build image also writes Claude Code managed settings that run `outerlayer hook`, so the sessions a build starts are linked to the item and uploaded by its sync. The runner copies the build's hook error log into the job directory before it removes the container.
120
+ - 4677cd6: A container or process build can run `yarn install`. The runner now also sets `YARN_HTTPS_PROXY` and `YARN_HTTP_PROXY`, because Yarn 2 and later ignore `HTTPS_PROXY` and could not reach the registry from a build.
121
+ - 1e2f5e3: `outerlayer doctor` checks that a container host can read the control plane of every repository the runner builds. With no `repos.include`, which is how `runner init` leaves the config, it checks every repository connected to the factory instead of nothing.
122
+ - e4f5e1e: `outerlayer sync` no longer writes `~/.outerlayer/spool/session-repos.json`. Nothing reads that file, so it is safe to delete.
123
+ - 0248261: Hooks written by `outerlayer init` run the CLI through `node`, so they keep working when a copy of the CLI loses its execute bit. Run `outerlayer init` again to rewrite hooks written by an earlier version. `outerlayer doctor` now fails "Hooks installed" when a hook runs the CLI script directly and the script is not executable.
124
+ - f90e444: Help and error text use the product's own words. Every `--app-id` option is described as the factory id, no text calls the Work page "Floor", and a key bound to another factory is refused with "bound to a different factory". The package description names only commands the CLI has.
125
+ - e78d9a9: The cost estimate charges 1-hour cache writes at the model's published 1-hour rate. It also charges a request whose total input exceeds a model's long-request threshold at the higher rates for every token, and charges fast mode at its multiplier for models that have one, such as Opus 5.5.
126
+ - 0db0425: The provision hook written by `outerlayer runner init` clones the repository the work item records (`specRepository` in `outerlayer work status --json`) instead of reading it off the tracker key. A Linear or Jira item, whose key such as `ENG-123` names no repository, now clones the right one. An item that records no repository fails its job with one line saying so, rather than trying to clone a URL that does not exist. `runner init` never overwrites a hook, so delete an existing `provision.sh` written by an earlier version before running it again, or make the same edit by hand.
127
+ - 112813e: No change in behavior. The committed `.outerlayer-pin.json` is still written and read the same way; the server no longer compares sessions against it.
128
+ - 02db30e: The `reporting-findings` skill that `outerlayer init` installs now describes the moments it applies to: writing a "Factory problems" section, a build or review report, or noticing a stale instruction, a flaky test or a misbehaving gate. Its batch shape is spelled out so `outerlayer emit findings` files can be written from the skill alone.
129
+ - 4c5a9ea: `outerlayer emit <name>` from inside a recorded session can now record checks on the work item the session was launched for, stored as the session's own. A session still cannot clear a person's fail. The help text and README say so.
130
+ - 7720377: `outerlayer sync` no longer guesses a price for a model name it doesn't recognize exactly, case-insensitively, or as a listed variant (a provider prefix, a region prefix, a date suffix, a `-vN:M` suffix, or a `[1m]` suffix). A name like `claude-opus-4-9` now shows the existing unknown-model warning and stores an unpriced cost, instead of silently pricing as `claude-opus-4` — a different model, at a different rate.
131
+ - 07c2785: `outerlayer work comment` now says that a person's `fail` on the item's general thread is what hands the item to an agent, and that a plain comment or a criterion `fail` no longer does. The README describes the item page in place of the removed Review tab.
132
+ - d15ccf4: `outerlayer work comment` and `outerlayer work threads` print a failed request as a one-line error and exit 1, as the other `work` commands do, instead of a stack trace.
133
+ - dec3f5b: `outerlayer work list --claimed` lists only the work items a host holds right now. Each claimed row shows which host holds it, as which kind of claim, since when and until when. With `--json`, each row carries its `claim` object.
134
+
3
135
  ## 0.2.0
4
136
 
5
137
  ### Minor Changes
package/README.md CHANGED
@@ -13,8 +13,9 @@ network, and when, is below.
13
13
  Launch a session naming the work it's for, then sync:
14
14
 
15
15
  ```
16
- npx @outerlayer/cli init # install the capture hooks
17
- npx @outerlayer/cli work add --issue 42 # create the work item; prints its number
16
+ npx @outerlayer/cli login # sign in, once per machine
17
+ npx @outerlayer/cli init # connect this repository to a factory, install the capture hooks
18
+ npx @outerlayer/cli work build --issue 42 --local # create the work item; prints its number
18
19
  OUTERLAYER_WORK=7 claude # launch a session naming that number
19
20
  npx @outerlayer/cli sync --dry-run # see exactly what would leave your machine
20
21
  npx @outerlayer/cli sync # upload sessions launched that way
@@ -23,7 +24,7 @@ npx @outerlayer/cli sync # upload sessions launched tha
23
24
  ## Privacy, stated plainly
24
25
 
25
26
  **A session uploads only when you launch it with `OUTERLAYER_WORK` naming
26
- the work item it's for.** Create the item first with `outerlayer work add`,
27
+ the work item it's for.** Create the item first with `outerlayer work build --local`,
27
28
  which prints its factory-scoped number, then set that number before
28
29
  starting your agent — `OUTERLAYER_WORK=7 claude`. The session-start hook
29
30
  then records the launch and the session uploads whole, from its first turn,
@@ -39,7 +40,7 @@ and never upload.
39
40
  sessions.** It runs when you run it. The hooks also fire `outerlayer sync
40
41
  --quiet` in the background after each agent turn and when a session ends, at
41
42
  most once every five minutes, as soon as `outerlayer login` has saved
42
- credentials. So a launched session uploads while it is still running, not
43
+ credentials and `outerlayer connect` has chosen the repository's factory. So a launched session uploads while it is still running, not
43
44
  only once it is over. Set `"autoSync": false` in `~/.outerlayer/config.json`
44
45
  to leave every automatic upload — the background sync and the daemon's
45
46
  streaming below — to your own command.
@@ -58,16 +59,26 @@ along before that sync ever runs.
58
59
  These commands also talk to your workspace. None of them sends session
59
60
  content:
60
61
 
61
- - **`outerlayer work add`, `remove`, `status`, `list`, `link-session`, `pr`,
62
- `claim`, `renew`, `release`** read and write your Floor. The session-start
62
+ - **`outerlayer work build`, `remove`, `status`, `list`, `link-session`, `pr`,
63
+ `claim`, `renew`, `release`, `comment`, `threads`** read and write your factory's work items. The session-start
63
64
  hook spawns `outerlayer work link-session` when you launch a session with
64
65
  `OUTERLAYER_WORK`, naming the item and the session id — the item itself
65
- must already exist, created ahead of time with `outerlayer work add`.
66
+ must already exist, created ahead of time with `outerlayer work build --local`.
66
67
  `outerlayer work pr` sends the session id and the pull request number and
67
- repository — never the session's own content. `outerlayer work claim`
68
+ repository — never the session's own content. `outerlayer work open-pr`
69
+ sends the pull request's title and the text of the body file you name. In a
70
+ host build, which holds an item key, it goes to the gateway, which opens or
71
+ edits the pull request as the factory's GitHub App on the branch the claim
72
+ named, and no `gh` command runs. In your own session it runs `gh pr create`
73
+ or `gh pr edit` under your login, then sends the pull request number and
74
+ repository the way `work pr` does. `outerlayer work claim`
68
75
  records a lease for this host before it starts working on an item, so two
69
76
  hosts never build the same piece of work at once; `renew` extends it and
70
- `release` marks it done. A runner key needs `git.read` and `work.read` to
77
+ `release` marks it done. `outerlayer work comment` posts one comment on a
78
+ work item's general thread or a criterion thread — the detected session id
79
+ rides along the same way it does for `emit`, only ever narrowing what the
80
+ comment may do; `outerlayer work threads` reads them back. A runner key
81
+ needs `git.read` and `work.read` to
71
82
  list and read items, and `work.claim` to claim, renew or release — `work.claim`
72
83
  is not granted to a dashboard role by default, since a claim is a host's
73
84
  lease, not a person's.
@@ -75,9 +86,159 @@ content:
75
86
  items the same way `work claim`/`renew`/`release` do, on the schedule its
76
87
  config sets — it sends nothing about the job it runs beyond that; the
77
88
  agent it starts is a separate process with its own credentials, from the
78
- `runner` block, never the copy-out daemon's. `runner check` makes one
79
- list call to confirm the key works and sends nothing else; `runner init`,
80
- `runner status` and `runner logs` make no network call at all.
89
+ `runner` block, never the copy-out daemon's. It also sends a heartbeat at
90
+ the end of every poll: the host, the CLI version, the poll interval, the
91
+ slots in use and total, and the result of that poll's own request for
92
+ work — success, or the error code and message the platform returned.
93
+ When a build's command exits 0, it reads that work item once more, for its
94
+ evidence verdict, before releasing it: it sends nothing but the request.
95
+ Before a build's command starts, the runner also runs `git ls-remote` and
96
+ `git fetch` in the job's workdir, with the host's own git credentials, to
97
+ put it on the branch the claim named. Nothing about the job leaves the host
98
+ that way.
99
+ When the gateway names a higher CLI version than the one running,
100
+ `outerlayer runner start` also calls the public npm registry
101
+ (`registry.npmjs.org`) for `@outerlayer/cli` only: that version's metadata
102
+ at once, and once no build is in flight, its tarball and its provenance
103
+ attestation, plus an `npm install` and an `npm audit signatures` run
104
+ against that same registry. The calls carry no credentials and nothing
105
+ about the host or the factory. Set `runner.autoUpdate` to `false` to stop
106
+ them.
107
+ `runner check` makes a list call and a host-key lookup to confirm the key
108
+ works and sends nothing else; `runner status` and `runner logs` make no network call at all.
109
+ On a runner host, any command that uses the saved runner key signs each
110
+ request it makes with the host key, the way the runner does. The signature
111
+ covers the request's method, path, query and body, and adds nothing else
112
+ to it.
113
+ - **`outerlayer runner init`** makes no network call, except `runner init --vm`
114
+ on a Mac. That runs `limactl`, and Lima downloads a Linux VM image. Inside
115
+ the VM, `apt` downloads Node and `npm` downloads this CLI. Your runner key goes to the VM on the standard
116
+ input of a `limactl shell` command and is never a file or an argument on the
117
+ Mac.
118
+ - **`outerlayer login`**, with no key on stdin, signs in to your account by
119
+ browser approval. It sends your dashboard a one-time public key and this
120
+ machine's host name, then polls for the answer. The answer is a token for
121
+ your account, encrypted to that public key, so only this process can read
122
+ it. It comes with the gateway address and your email, and no factory
123
+ or organization. The token is saved to `~/.outerlayer/config.json` and
124
+ never printed. If this machine held a token from an earlier login, login
125
+ asks the dashboard to revoke it. A key piped on stdin makes no network
126
+ call.
127
+ - **`outerlayer connect`**, and the first step of `outerlayer init`, send a
128
+ request with your login's token: `GET /v1/me/factories?repository=<host/owner/name>`,
129
+ naming this checkout's remote. The gateway answers with the factories you can
130
+ use, and whether this repository is linked to each. Once a factory is chosen,
131
+ a second request, `GET /v1/apps/<factory id>/git/links`, asks which
132
+ repositories that factory has linked, to say whether this one is among them.
133
+ Nothing else leaves the machine. The choice is saved in the repository's git directory, at
134
+ `outerlayer/connection.json`, which git never tracks. With a factory key it
135
+ makes no network call. `outerlayer init --local` makes none either.
136
+ - **`outerlayer logout`** sends the saved token to your dashboard
137
+ (`POST /api/cli/logout`) so it is revoked, then deletes it from the config
138
+ file. It sends nothing else.
139
+ - **`outerlayer runner image`** runs `git fetch` against the repository's
140
+ remote, `https://github.com/<owner>/<name>.git` unless `--remote` names
141
+ another, with the host's own git credentials. It sends nothing about the
142
+ host. Docker pulls base images, features and Claude Code's npm package
143
+ from registries while the image builds, with the host's own `docker
144
+ login`. The tools layer also downloads the pinned `gh` and `tini` releases
145
+ from GitHub, checking each against a digest. The image build runs outside
146
+ any work item and sends nothing to your factory.
147
+ - **`outerlayer runner start` with `builtin:vm` hooks** runs each build in its
148
+ own Firecracker microVM, which has no network device. The guest kernel and
149
+ helper are not published yet, so no host is offered `builtin:vm` and
150
+ `outerlayer doctor` fails it. Once they are, the first microVM
151
+ build downloads a pinned Firecracker release, a guest kernel and a guest
152
+ helper from github.com, and checks each against a sha256 pinned in the CLI;
153
+ the requests send nothing about the host. The build reaches the network only
154
+ through the same tunnel a container build uses, which the runner serves on
155
+ Unix sockets and the guest reaches over vsock. The guest's exec agent accepts
156
+ connections from the host only, and the runner runs every step of the build
157
+ through it, including the build's command (`outerlayer vm-exec` on the host).
158
+ - **`outerlayer runner start` with `builtin:container` hooks** runs each
159
+ build in its own Docker container, which has no network but loopback. The
160
+ build reaches the network only through a tunnel the runner serves on a
161
+ Unix socket, which the container's own `outerlayer broker-relay` process
162
+ reaches from loopback. The tunnel refuses loopback, private, link-local,
163
+ shared-address, multicast and unique-local addresses and the host's own,
164
+ and it terminates no TLS. The tunnel also serves the build a local git
165
+ remote. Git in the container still shows
166
+ `https://github.com/<owner>/<name>.git` and reaches the runner instead,
167
+ which forwards only git's fetch and push requests and Git LFS batch calls
168
+ to github.com. For the item's repository the runner adds a GitHub App
169
+ token it asked your gateway for (`POST /v1/work-items/{id}/claim/tokens`):
170
+ a read token for a fetch, and a push token for a push, which it forwards
171
+ only when every ref the push updates is the branch the claim names. Any
172
+ other repository is forwarded with no credential. Before it forwards a
173
+ push to a branch outside `outerlayer/`, the runner runs `git fetch` and
174
+ `git receive-pack` in a bare mirror it keeps under its own directory, with
175
+ the read token, to check that the push moves the branch forward. The
176
+ build's `gh` holds the read token in a directory the runner owns and
177
+ nothing else. When the attempt ends, the runner revokes every token it was
178
+ issued with `DELETE https://api.github.com/installation/token`, sent with
179
+ the token it revokes. The release for the attempt then lists the hosts the
180
+ build reached, each address the tunnel refused with its range, the names,
181
+ never the values, of the host variables the runner's config passed in, the
182
+ names of the destinations it called, which kinds of token the build used,
183
+ and whether its commits were authored by the GitHub App's bot account and
184
+ credited the member who asked for the build. The runner sets that author in
185
+ the build's environment and adds the `Co-authored-by` line with git hooks it
186
+ writes into the build's own directory.
187
+ - **`outerlayer runner start` with `builtin:container` hooks** also asks your
188
+ gateway which repository governs the item's repository (`GET
189
+ /v1/context/source`, signed with the host key), after the recipe's
190
+ lifecycle commands. When one does, the runner runs `git fetch` against that
191
+ control plane on the host, with the host's own git credentials, and copies
192
+ its context into the checkout through the build's directory. The build
193
+ receives files and no credential for the control plane. The runner deletes
194
+ the staged copy before the command starts. A host that cannot read the
195
+ control plane fails the build at provision.
196
+ - **`outerlayer runner start` with `builtin:process` hooks** runs each build
197
+ as a process under the runner's own user, with no isolation, and the
198
+ attempt records `shared-user`. The runner clones the item's repository
199
+ from `https://github.com/<owner>/<name>.git` with the host's own git
200
+ credentials. It serves the same tunnel on a loopback port, which the
201
+ build reaches through its proxy settings, and the release lists the same
202
+ hosts and refusals.
203
+ - **`outerlayer runner start` with your own executable hooks** runs
204
+ `git ls-remote` against `https://github.com/<owner>/<name>.git`, with the
205
+ host's own git credentials, before each provision hook, to tell the hook
206
+ the commit the default branch points at. It sends nothing about the host,
207
+ and a host whose git cannot reach the remote loses only that hint.
208
+ - **`outerlayer runner start` with `build.destinations`** serves each
209
+ destination the config lists on a local address, and forwards each request
210
+ a build sends there to the destination's upstream, over HTTPS, with the
211
+ header the config names in place of any credential the build sent. The
212
+ header's secret is read from the runner's own environment.
213
+ It is never in a build's environment or files, and the release names a
214
+ destination and nothing more.
215
+ - **`outerlayer runner start` with `builtin:vm`, `builtin:container` or
216
+ `builtin:process` hooks** also serves two built-in destinations, `gateway` and `claude`, to
217
+ each build. The gateway destination forwards a build's requests to your
218
+ gateway with the attempt's item key, and the `claude` destination forwards
219
+ them to `https://api.anthropic.com` with the host's `CLAUDE_CODE_OAUTH_TOKEN`
220
+ or `ANTHROPIC_API_KEY`, read from the runner's own environment. A build holds a placeholder for each and never the value, and the
221
+ release names the two destinations and nothing more. A build under
222
+ `builtin:process` runs as the runner's user and can still read the runner's
223
+ environment and the attempt's job file.
224
+ - **`outerlayer doctor`**, once a factory key is saved (`echo "$KEY" | outerlayer login`),
225
+ asks your gateway (`GET /v1/repositories/access`, with that key) which
226
+ GitHub App permissions each connected repository's installation has not
227
+ accepted, and whether each default branch requires a pull request. The
228
+ request carries nothing about the host, and the gateway stores nothing.
229
+ - **`outerlayer runner check`** also asks your gateway (`GET /v1/runner/host-key`,
230
+ with the runner key, signed with the host key) whether the key holds
231
+ `work.claim`, so a key that cannot claim is named before the host takes work.
232
+ The request carries nothing about the host beyond the signature the key's
233
+ own requests already carry, and the gateway stores nothing.
234
+ - **`outerlayer doctor`** on a runner host, one with a `runner` block in its
235
+ config, also asks the npm registry for the latest Claude Code version, to
236
+ say whether the version the runner's images pin is behind. The request
237
+ names the package and nothing about the host. On a host with
238
+ `builtin:container` hooks it also asks your gateway which repository governs
239
+ each repository in `repos.include` (`GET /v1/context/source`), then runs
240
+ `git ls-remote` against that control plane with the host's own git
241
+ credentials, to say whether the host can read it.
81
242
  - **`outerlayer emit artifact`** uploads the file you name — a screenshot, a
82
243
  recording, a report, a log — along with its caption. With no recorded
83
244
  session to attach it to, it uploads immediately, anchored to a pull request
@@ -88,9 +249,14 @@ content:
88
249
  route. Run from CI or a plain shell, where there is no session, it sends
89
250
  as it always has.
90
251
  - **`outerlayer emit finding`** and **`outerlayer emit findings <file>`** send
91
- one finding, or a whole batch of them, for a work item — the same
92
- anchoring as `emit <name>` (`--item`, or the recorded session's own item),
93
- except a session may record findings on the item it was launched for.
252
+ one finding, or a whole batch of them, for a work item — a problem an agent
253
+ hit in the factory. The anchoring is the same as `emit <name>` (`--item`, or
254
+ the recorded session's own item).
255
+ - **`outerlayer emit criteria <file>`** sends the acceptance criteria of a work
256
+ item as one list (`POST /v1/criteria`): each criterion's id, text and
257
+ declared proof kind, from the JSON file you name. The anchoring is the same
258
+ as `emit finding`. Nothing but the file's content and the item number is
259
+ sent.
94
260
  - **`outerlayer mcp serve`** is the stdio MCP server your editor spawns. It
95
261
  forwards every JSON-RPC message the editor sends to the gateway and returns
96
262
  the reply.
@@ -122,9 +288,9 @@ space, past both stubs, so the same test records every program each run
122
288
  starts and asserts none of them fetches over the network.
123
289
 
124
290
  Failures the hook cannot show you — a refused `OUTERLAYER_WORK` value, a
125
- `work add` that could not reach the Floor — are appended to
291
+ `work link-session` that could not reach the factory — are appended to
126
292
  `~/.outerlayer/spool/hook-errors.log`, and the next session start says so.
127
- The addition retries a gateway it cannot reach a few times, with backoff,
293
+ The link retries a gateway it cannot reach a few times, with backoff,
128
294
  before it gives up.
129
295
 
130
296
  - **The tier is applied before anything leaves.** The default tier is
@@ -142,37 +308,43 @@ before it gives up.
142
308
  | Command | What it does |
143
309
  |---|---|
144
310
  | `outerlayer sync` | Upload sessions launched with `OUTERLAYER_WORK` to your OuterLayer cloud workspace (incremental — only what's new since the last sync). Tier-gated client-side (`--tier metrics\|redacted\|full`, default `full`); `--dry-run` prints exactly what would leave the machine; `--all` re-sends everything (idempotent server-side). Credentials come from `outerlayer login`, `OUTERLAYER_*` env vars, or `--url/--app-id`. |
145
- | `outerlayer login [--url] [--app-id]` | Save the cloud URL, app id, and API key to `~/.outerlayer/config.json` once. The key is read from stdin (`echo "$KEY" \| outerlayer login …`) or a prompt with echo off; it is never a flag. `--no-input` refuses to prompt. |
146
- | `outerlayer init` | Install the capture hooks and the status-line segment. `--json` for scripts. Run through `npx`, it copies the CLI to `~/.outerlayer/cli` and points the hooks at that copy, so they survive npm clearing its cache; `npx @outerlayer/cli@latest init` upgrades it. From any other install, the hooks run that install. Claude Code deletes transcripts after ~30 days; run `outerlayer daemon` separately to mirror them first — `init` does not start it for you. |
311
+ | `outerlayer login [--dashboard] [--no-browser] [--check <id>]` | Sign in to your account by browser approval: prints an approve link and a code, a signed-in person approves it, and a token for their account is saved to `~/.outerlayer/config.json` without ever being shown. It chooses no factory; `outerlayer connect` does. Without a terminal it prints one JSON document and exits 75; `--check <id>` finishes it (0 signed in, 75 still waiting, 1 denied or expired). A factory key piped on stdin (`echo "$KEY" \| outerlayer login --url … --app-id …`) is saved as before; it is never a flag, and it always wins over the login. `--no-input` refuses to wait on a person. |
312
+ | `outerlayer logout` | Revoke this machine's login on the dashboard and delete it from the config file. A saved factory key and factory are left alone. If the dashboard cannot be reached it still deletes the local copy, exits 1, and says the token stays valid until revoked on `/profile/security`. |
313
+ | `outerlayer connect [--factory <org>/<factory>] [--json]` | Choose the factory this repository's work goes to, and say what it chose and why. With a login it lists the factories you can use and takes the first rule that leaves one answer: `--factory`, the factory this repository is already connected to, the one factory it is linked to, the one factory you can use; on a terminal it then asks, and with no terminal it exits 1 listing each `--factory` value. The choice is saved in the repository's git directory, shared by every worktree and never committed. A factory key already belongs to one factory, so `connect` changes nothing for it. It never links a repository. Once a factory is chosen it asks that factory whether this repository is linked. If not, it still saves the choice, prints the factory's Work page link to stderr and exits 2. `--json` writes one JSON document to stdout. |
314
+ | `outerlayer init` | Connect this repository (`outerlayer connect`), install the capture hooks and the status-line segment, and run `doctor`. It never signs in: without a login it skips the connect step and says to run `outerlayer login`. `--local` installs the hooks only, with no network call. `--json` prints one document naming each step's outcome. Run through `npx`, it copies the CLI to `~/.outerlayer/cli` and points the hooks at that copy, so they survive npm clearing its cache; `npx @outerlayer/cli@latest init --local` upgrades it. From any other install, the hooks run that install. Claude Code deletes transcripts after ~30 days; run `outerlayer daemon` separately to mirror them first — `init` does not start it for you. |
147
315
  | `outerlayer daemon` | Run the copy-out daemon in the foreground (`--once` for a single sweep, which uploads nothing). Once cloud credentials exist, it also streams a launched session's new turns as they land — see Privacy, above. `outerlayer watch` is the former name and still works, with a warning. |
148
- | `outerlayer doctor` | Check the installation: hooks, status-line freshness, and sync health. `--json` prints the checks and a summary for scripts. |
316
+ | `outerlayer doctor` | Check the installation: hooks, status-line freshness, sync health and, with a factory key, the GitHub App access builds need. `--json` prints the checks and a summary for scripts. |
149
317
  | `outerlayer context emit [--check]` | Compile `.outerlayer/` into each configured target tool's native files (targets come from `.outerlayer/config.json`). `--check` computes outputs and diffs against disk without writing (CI mode). Bare `outerlayer emit` with no name still compiles, with a deprecation warning. |
150
318
  | `outerlayer import ruler` | Port a `.ruler/` tree ([Ruler](https://github.com/intellectronica/ruler)) into the equivalent `.outerlayer/` tree — mostly a rename; never overwrites an existing `.outerlayer/`. |
151
319
  | `outerlayer hooks wrap` / `outerlayer hooks unwrap` | Auto-wrap (or undo wrapping) `PreToolUse`/`PostToolUse` hooks for execution evidence — one spawn per firing. |
152
- | `outerlayer emit artifact <file> --caption <text> [--for <criterion-id>] [--pr <n>]` | Upload a proof artifact — screenshot, recording, report, or log — with its caption. Inside a recorded session it spools locally and ships on the next `sync`; otherwise it uploads immediately, anchored to a pull request or the current checkout. `--replaces` retires artifacts an earlier run uploaded. |
153
- | `outerlayer emit <name> --result <pass\|fail> [--link <url>] [--body <text>\|--body-file <path>] --item <number>` | Record one named check's outcome on a work item. A check that ran carries the run URL as its proof (`--link`); a judgment you are making yourself carries one sentence (`--body`, or `--body-file` to read it from a file — `-` reads standard input). A `fail` needs at least one of the two; a `pass` needs neither. `--item` names the work item by the number printed when the item was created — always required, in or out of a recorded session; from inside a session it can only name an item that session was NOT launched for (a session cannot record a check on its own item). Prints the recorded check's id. Who recorded it comes from your API key, never from what you send. |
154
- | `outerlayer emit artifact-review --result <pass\|fail> --artifact <id> [--body <text>\|--body-file <path>]` | Record a person's own pass or fail on one artifact — evidence already emitted, bound to a criterion. `--artifact` names it and replaces `--item`; the gateway resolves the work item from the artifact's own pull request. `--link` is refused. A `fail` reads its sentence from `--body`/`--body-file`, or from standard input when neither is given. Refused from inside a recorded session — an artifact verdict is a person's act, the same rule the gateway enforces. Prints the recorded verdict's id. |
155
- | `outerlayer emit finding --id <id> --subject <change\|context> --title <text> --file <path> --kind <behavior\|hygiene\|proof> --verdict <confirmed\|refuted\|unverified> --source <implementer\|reviewer\|refuter\|gate> --where <label> [--item <number>] …` | Record one finding — what was found wrong about the change (`--subject change`) or about a rule it ran on (`--subject context`, which then needs `--rule-path`, `--rule-quote` and `--rule-relation` together — all three are required, not just `--rule-path`). Validated against the same contract the gateway checks before anything is sent. `--item` names the work item; without it, inside a recorded session, the item that session was launched for is used automatically — unlike `emit <name>`, a session may record findings on its own item. Re-emitting the same `--id` on the item replaces that finding. |
156
- | `outerlayer emit findings <file> [--item <number>]` | Record a whole batch at once, read from a `FindingBatch` JSON file (`-` for standard input) — the same shape and validation as `emit finding`, one record per subject/title/file/kind/verdict/source/where. Anchored the same way: `--item`, else a recorded session's own item. |
320
+ | `outerlayer emit artifact <file> --caption <text> [--for <criterion-id>] [--pr <n>]` | Upload a proof artifact — screenshot, recording, report, or log — with its caption. Inside a recorded session it spools locally and ships on the next `sync`; otherwise it uploads immediately, anchored to a pull request or the current checkout. `--replaces` retires artifacts an earlier run uploaded. A JUnit XML file bound with `--for` proves a criterion that declares `test` proof; `--test <name[=path:line]>` binds only the named tests. |
321
+ | `outerlayer emit <name> --result <pass\|fail> [--link <url>] [--body <text>\|--body-file <path>] --item <number>` | Record one named check's outcome on a work item. A check that ran carries the run URL as its proof (`--link`); a judgment you are making yourself carries one sentence (`--body`, or `--body-file` to read it from a file — `-` reads standard input). A `fail` needs at least one of the two; a `pass` needs neither. `--item` names the work item by the number printed when the item was created — always required, in or out of a recorded session; a session records checks on its own item like any other, stored as the session's, but cannot clear a person's fail. Prints the recorded check's id. Who recorded it comes from your API key, never from what you send. |
322
+ | `outerlayer work comment --item <n> [--criterion <id>] --body <text> [--artifact <id>] [--pass\|--fail\|--attach\|--ready]` | Post one comment on a work item's general thread, or on one acceptance criterion's own thread with `--criterion`. `--pass`/`--fail` need a person's own credential and apply to the whole item, so they work on the general thread only and the gateway refuses them with `--criterion`; `--attach` makes the named artifact that criterion's current proof; `--ready` hands a thread back to a person. The detected session id rides along automatically, only ever narrowing what the comment may do. |
323
+ | `outerlayer work threads --item <n>` | Read a work item's threads back — the general thread first, then one per criterion with proof or a comment, each with its `waitingOn`. |
324
+ | `outerlayer emit finding --id <id> --category <context\|flaky-test\|tooling\|environment\|defect\|other> --title <text> --file <path> --where <label> [--line <n>] [--item <number>] …` | Record one problem you hit in the factory. `--category` is one of `context` (an instruction that is wrong, broken or missing), `flaky-test`, `tooling`, `environment`, `defect` (a bug outside your own change) or `other`. A `context` finding also needs `--rule-path` and `--rule-relation` (`wrong`, `broken` or `missing`), and `--rule-quote` unless the relation is `missing`. Findings are informational; nothing reads one to pass or fail the work. `--item` names the work item; without it, inside a recorded session, the item that session was launched for is used automatically. Re-emitting the same `--id` on the item replaces that finding. |
325
+ | `outerlayer emit findings <file> [--item <number>]` | Record a whole batch at once, read from a JSON file (`-` for standard input) with `"schemaVersion": 2` — the same shape and validation as `emit finding`, one record per id, category, title, file, line, rule and where. Anchored the same way: `--item`, else a recorded session's own item. |
326
+ | `outerlayer emit criteria <file> [--item <number>]` | Record a work item's acceptance criteria as one list, read from a JSON file (`-` for standard input): `{"criteria": [{"id": "AC-1", "text": "Given …", "proof": null}]}`. An `id` is 1 to 64 characters of `A-Z a-z 0-9 . _ : -`, the same id `emit artifact --for` binds evidence to. `proof` is `video`, `screenshot`, `report`, `log`, `test`, `file` or `null` (any bound artifact proves it). The list holds 1 to 200 criteria, each `text` at most 2,000 characters, with no id twice. It is validated before anything is sent. This list is the only source for the item's Criteria tab. Once an item has a list, only a person can replace it: run the command outside an agent session, with your own login. Anchored the same way as `emit findings`: `--item`, else a recorded session's own item. |
157
327
  | `outerlayer mcp install [--transport stdio\|http] [--url] [--name] [--command]` | Write (or update) an `mcpServers` entry in `.mcp.json` for the OuterLayer gateway. Default `stdio`: the client spawns `outerlayer mcp serve`, which reads the API key from `~/.outerlayer/config.json` (or `OUTERLAYER_API_KEY`) each time it connects, so a reconnect picks up a newly saved or rotated key. `--transport http` writes a direct `POST /v1/mcp` entry referencing `${OUTERLAYER_API_KEY}`, resolved by the client from the environment it was launched with. Never writes an API key. Pass `--url`/`--app-id` for self-host. |
158
328
  | `outerlayer mcp serve [--url] [--app-id]` | Stdio MCP server bridging stdin/stdout JSON-RPC to the gateway's `POST /v1/mcp`. What the stdio `.mcp.json` entry runs; exits 1 with a clear message when no API key is configured. |
159
- | `outerlayer work add --issue <n>\|--pr <n> [--repo] [--note]` | Records that a source — a recorded session, a CI run, or the API key's bound member — is working on an issue or pull request. The only way an item becomes visible on the Floor. |
160
- | `outerlayer work remove --issue <n>\|--pr <n> --reason <text> [--repo]` | Withdraws the caller's own addition, recording the reason. Never deletes anything; the item leaves the Floor only once no live addition remains on it. Removing again is a no-op that still succeeds. |
329
+ | `outerlayer work build --issue <n\|KEY>\|--item <n> [--tracker linear\|jira] [--local] [--repo] [--note] [--retry <count>]` | Starts work on an item and puts it on the Work page. `--issue` takes a GitHub issue number or a Linear or Jira key such as `ENG-123`; the gateway verifies a key against the factory's tracker connection before adding it. `--tracker` is needed only when both Linear and Jira are connected. A key gets a repository only from `--repo`, never from the checkout. Without `--local`, records a build request that a host takes when it has a free slot. With `--local` (issue only), no host takes it: it prints the item's number and the line that starts your own session on it, such as `OUTERLAYER_WORK=7 claude`. `--retry` resends after a 5xx or 429 answer, up to that many more times. |
330
+ | `outerlayer work remove --issue <n>\|--pr <n> --reason <text> [--repo]` | Withdraws the caller's own addition, recording the reason. Never deletes anything; the item leaves the Work page only once no live addition remains on it. Removing again is a no-op that still succeeds. |
161
331
  | `outerlayer work status --issue <n>\|--pr <n> [--repo]` | Shows one item's stage, section, gate ledger, linked pull requests and sessions, and its additions. |
162
- | `outerlayer work list [--stage] [--section] [--repo] [--unclaimed] [--startable] [--needs amend]` | Lists live work items for the current factory, filterable by stage, section, claim state and whether an open fail is waiting for an answer. |
332
+ | `outerlayer work list [--stage] [--section] [--repo] [--claimed] [--unclaimed] [--startable] [--needs amend]` | Lists live work items for the current factory, filterable by stage, section, claim state, startability and whether a comment thread is waiting on an agent. |
163
333
  | `outerlayer work pr <n> [--repo] [--session-id]` | Run inside a session on a work item: declares that pull request `<n>` in the checkout's repository belongs to that item. Idempotent — declaring the same pull request twice is a no-op. Refuses when the session is on no item, or the repository is not connected. |
164
- | `outerlayer work claim --item <n>\|--issue <n> --kind implement\|amend [--repo] [--host] [--seconds]` | Records a lease for this host (default: its own hostname), so two hosts never build the same item at once. A live lease already held by a different host is refused; claiming again under the same host extends it. Lease length defaults to 900 seconds, the server's own cap. |
165
- | `outerlayer work renew --item <n>\|--issue <n> [--repo] [--host] [--seconds]` | Extends this host's own live lease. Refused if the lease has expired, or if a different host holds it. |
334
+ | `outerlayer work open-pr --title <text> --body-file <path> [--repo] [--session-id] [--json]` | Opens the pull request for the work item this session is on, or edits the one already open on its branch. A host build, which holds an item key, sends it to the gateway, which opens it as the factory's GitHub App from the branch the claim named; no `gh` command runs. Your own session runs `gh pr create` or `gh pr edit` under your login and then declares the pull request on the item. Run it again after changing the title or description. |
335
+ | `outerlayer work claim --item <n>\|--issue <n> --kind implement\|amend [--repo] [--host] [--seconds]` | Records a lease for this host (default: its own hostname), so two hosts never build the same item at once. The lease belongs to the runner key that claimed it: a live lease held by a different key is refused, even under the same host name, and claiming again with the same key extends it. The first claim creates the host key (`host-key.pem` beside the config file) and binds the runner key to it; from then on every request made with that key is signed. Lease length defaults to 900 seconds, the server's own cap. |
336
+ | `outerlayer work renew --item <n>\|--issue <n> [--repo] [--host] [--seconds]` | Extends this host's own live lease. Refused if the lease has expired, or if a different runner key holds it (403 `claim_not_held`). |
166
337
  | `outerlayer work release --item <n>\|--issue <n> [--repo] [--host] [--outcome]` | Marks this host's lease released, optionally recording how the attempt ended. Releasing an already-released lease is a no-op that returns the recorded release time and outcome. |
167
- | `outerlayer runner init [--config <path>]` | Writes a `runner` block with defaults and three example hooks, keeping every key the config already had. Refuses rather than overwriting an existing block. |
338
+ | `outerlayer runner init [--vm\|--no-vm] [--config <path>]` | Writes a `runner` block with defaults, keeping every key the config already had. It names `builtin:vm` on a Linux machine where the user can use `/dev/kvm` and Docker Engine can build images once the guest kernel and helper are published (not yet), `builtin:container` on that machine until then, `builtin:container` on any other machine with Docker Engine, and `builtin:process` on one without, and writes no hook script. Refuses rather than overwriting an existing block. On macOS it asks whether to create a Linux VM with Lima, installs Docker Engine and the runner in it, and starts both at login; `--vm` answers yes and `--no-vm` answers no, and a run with no terminal answers no. The runner key and the host key then live only in the VM: the Mac's config loses `apiKey` and `runner` and gains a `runnerVm` note, which `outerlayer doctor` reads. |
168
339
  | `outerlayer runner check [--config <path>]` | Validates the config, the hooks and the key, prints the settings the runner would use, and exits non-zero on a problem. Takes no work and writes no pid file. |
169
340
  | `outerlayer runner start [--config <path>]` | Runs the loop: claim work, run it, sync, clean up, report, release with an outcome. Repeat. Reads the `runner` block from the config file (default `~/.outerlayer/config.json`). Refuses to start on a bad config, an unrunnable hook, or a refused key. |
341
+ | `outerlayer runner image --repository <owner/name> [--remote <url>] [--config <path>] [--json]` | Builds the image a repository's builds run in. Fetches the head of the default branch into the runner's cache, builds its `.devcontainer/devcontainer.json` with the devcontainer CLI, ignores every setting that reaches the host or widens the container's privileges, and adds this CLI and a pinned Claude Code. Reuses the image until the recipe or the tools change, keeps the newest three, and writes `recipe.json` beside the image record. Refuses a Compose recipe (`recipe_needs_docker`) and a repository with only named configurations (`recipe_ambiguous`). |
170
342
  | `outerlayer runner stop [--config <path>] [--now]` | Takes no more work and waits for running jobs to finish, naming each every five seconds. `--now` ends them immediately with outcome `stopped`. Ctrl-C on a foreground runner drains; a second one stops now. |
171
343
  | `outerlayer runner status [--config <path>] [--recent] [--json]` | A header naming the runner's own state, then a row per running job — item, queue, stage, elapsed, started, lease, job directory. `--recent` adds finished jobs, newest first, capped at 20, with their outcomes. |
172
344
  | `outerlayer runner logs <item> [--config <path>] [-f]` | Prints the newest attempt's log for one item, following it with `-f`. |
173
345
 
174
346
  The `work` commands need an API key that carries `work.insert` to add,
175
- remove, and declare a pull request (`pr`), and `work.read` to look an item up
347
+ remove, and declare a pull request (`pr`, `open-pr`), and `work.read` to look an item up
176
348
  — which `remove` and `status` both do before they act. Tick both when you
177
349
  mint the key, in Settings → API keys. Claiming, renewing or releasing a
178
350
  lease needs `work.claim`. Withdrawing an addition somebody else made
@@ -203,10 +375,9 @@ Record a pass once the work is right:
203
375
  outerlayer emit code-review --result pass --item 412
204
376
  ```
205
377
 
206
- `--item` is always required, in or out of a recorded session. A session
207
- records a check only on an item it was NOT launched for, by naming it with
208
- `--item`; a check on the item the session itself was launched for needs a
209
- machine key (an API key run outside the recorded session) or CI.
378
+ `--item` is always required, in or out of a recorded session. Inside a
379
+ recorded session, the check is stored as the session's, on the item it was
380
+ launched for or any other. A session cannot record over a person's fail.
210
381
 
211
382
  Three things to know. Who recorded a check is decided by the API key you
212
383
  used, never by anything in the request. A key bound to a membership records
@@ -216,30 +387,28 @@ because a fail with neither is something nobody can act on. And a check only
216
387
  holds a gate once a validator in `.outerlayer/validators/` declares its emit
217
388
  name; without one the check is recorded and read back, and blocks nothing.
218
389
 
219
- A review of the work itself is different: it is recorded from the Review
220
- tab on the work item page, not the CLI, and holds every pull request of the
221
- item until a person records a pass — no validator declaration needed.
222
- `outerlayer emit` refuses `work-review` outright; recording it anywhere but
223
- the work item page is not supported. The gateway refuses a review, and an
224
- artifact verdict, on an item that is closed or shipped, has no evaluation
225
- yet, or whose evaluation is still waiting on a session link, with the code
226
- `item_not_reviewable`. A recorded pass carries the head commit of every
227
- pull request the item had at the time; once one of them moves, that pass
228
- stops counting and a fresh review is needed. A repository can require a
229
- current pass before the evidence check completes by setting the base
230
- branch's `review` policy key to `required` (default `optional`, alongside
231
- `merge_gate`) — the check then stays in progress, not failed, until a
232
- current pass exists.
233
-
234
- `outerlayer emit artifact-review` sits between the two: a person's own pass
235
- or fail, like a review, but on one piece of evidence rather than the whole
236
- item. A fail here holds the item the same way a review's fail does, and
237
- counts the work as bad on Quality once, however many artifacts carry one —
238
- it also lists the item on a queue the API exposes, naming the artifact and
239
- why it failed, so a host with a session linked to the item can answer it
240
- with a replacement, or the fail's own recorder can pass over it directly.
241
- The item's own pass stays available only once every required artifact has
242
- a pass and no fail is still open.
390
+ A review of the work itself is different: a person records it as a `pass`
391
+ or `fail` on a work item's general thread (the item page, or `outerlayer
392
+ work comment` on the general thread), never as a named check — no validator declaration needed. The gateway refuses a
393
+ pass or fail on an item that is closed or shipped, has no evaluation yet,
394
+ or whose evaluation is still waiting on a session link, with the code
395
+ `not_reviewable`. A recorded pass on the general thread carries the head
396
+ commit of every pull request the item had at the time; once one of them
397
+ moves, that pass stops counting and a fresh review is needed. A repository
398
+ can require a current pass before the evidence check completes by setting
399
+ the base branch's `review` policy key to `required` (default `optional`,
400
+ alongside `merge_gate`) — the check then stays in progress, not failed,
401
+ until a current pass exists.
402
+
403
+ A criterion thread is scoped to one acceptance criterion rather than the
404
+ whole item: an agent attaches proof with `outerlayer work comment
405
+ --criterion <id> --artifact <id> --attach`, and a person leaves a note on
406
+ it with a plain comment. A pass or fail is a verdict on the whole item, so
407
+ the gateway refuses one on a criterion thread. A fail on any thread counts
408
+ the work as bad on Quality once, however many threads carry one — a fail on
409
+ the general thread also lists the item on the amend queue `outerlayer
410
+ runner start` reads, naming the thread waiting on an agent, until a
411
+ person's next pass or fail clears it.
243
412
 
244
413
  Every command accepts `--no-color`, and color is off on its own when stdout is not a terminal, when `NO_COLOR` is set, or when `TERM` is `dumb`. `FORCE_COLOR=1` turns it on for a pipe. Commands that print anything accept `--json`.
245
414