@jr2/orchestrator 0.5.1 → 0.6.0

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 (185) hide show
  1. package/docs/agents-block.md +2 -2
  2. package/docs/concepts/agents-and-turns.md +56 -20
  3. package/docs/concepts/architecture.md +30 -17
  4. package/docs/concepts/charts.md +6 -3
  5. package/docs/concepts/converge.md +3 -0
  6. package/docs/concepts/effects.md +86 -31
  7. package/docs/concepts/machines-and-workflows.md +9 -0
  8. package/docs/concepts/pools-and-sources.md +6 -6
  9. package/docs/concepts/secrets-and-trust.md +126 -42
  10. package/docs/concepts/the-menu.md +3 -0
  11. package/docs/concepts/workspaces.md +129 -51
  12. package/docs/get-started/an-agent-in-a-sandbox.md +21 -19
  13. package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
  14. package/docs/get-started/install.md +4 -0
  15. package/docs/guides/add-tools-to-the-sandbox.md +17 -4
  16. package/docs/guides/bind-open-parts.md +5 -4
  17. package/docs/guides/cut-a-worktree.md +560 -0
  18. package/docs/guides/fix-a-failed-converge.md +1 -1
  19. package/docs/guides/let-an-agent-write-a-chart.md +5 -1
  20. package/docs/guides/look-inside-a-sandbox.md +18 -15
  21. package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
  22. package/docs/guides/push-and-sign-a-branch.md +67 -21
  23. package/docs/guides/run-a-command-or-call-a-url.md +84 -41
  24. package/docs/guides/run-one-worker-per-item.md +19 -18
  25. package/docs/guides/set-an-agents-access-and-dials.md +67 -22
  26. package/docs/guides/size-a-workspace.md +19 -6
  27. package/docs/guides/use-a-hosted-provider.md +6 -0
  28. package/docs/guides/watch-a-run.md +47 -24
  29. package/docs/guides/write-a-workflow.md +1 -1
  30. package/docs/llms.txt +33 -12
  31. package/docs/reference/authoring-surface/agent.md +34 -11
  32. package/docs/reference/authoring-surface/answer.md +19 -3
  33. package/docs/reference/authoring-surface/arg.md +27 -5
  34. package/docs/reference/authoring-surface/branchname.md +15 -0
  35. package/docs/reference/authoring-surface/command.md +36 -21
  36. package/docs/reference/authoring-surface/fail.md +17 -4
  37. package/docs/reference/authoring-surface/file.md +36 -16
  38. package/docs/reference/authoring-surface/http.md +108 -8
  39. package/docs/reference/authoring-surface/isready.md +14 -0
  40. package/docs/reference/authoring-surface/jr2setup.md +1 -1
  41. package/docs/reference/authoring-surface/open.md +36 -5
  42. package/docs/reference/authoring-surface/paramsof.md +22 -4
  43. package/docs/reference/authoring-surface/pool.md +4 -4
  44. package/docs/reference/authoring-surface/push.md +30 -12
  45. package/docs/reference/authoring-surface/readyof.md +21 -0
  46. package/docs/reference/authoring-surface/secret.md +25 -4
  47. package/docs/reference/authoring-surface/source.md +3 -3
  48. package/docs/reference/authoring-surface/type-agentdefinition.md +2 -2
  49. package/docs/reference/authoring-surface/type-agentslots.md +4 -1
  50. package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
  51. package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
  52. package/docs/reference/authoring-surface/type-bindactors.md +14 -0
  53. package/docs/reference/authoring-surface/type-binding.md +5 -6
  54. package/docs/reference/authoring-surface/type-commandinput.md +4 -3
  55. package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
  56. package/docs/reference/authoring-surface/type-commandoutput.md +22 -4
  57. package/docs/reference/authoring-surface/type-customize.md +4 -2
  58. package/docs/reference/authoring-surface/type-dispatchoutput.md +8 -1
  59. package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
  60. package/docs/reference/authoring-surface/type-filebody.md +4 -8
  61. package/docs/reference/authoring-surface/type-framepath.md +14 -0
  62. package/docs/reference/authoring-surface/type-hostinjectedinput.md +2 -4
  63. package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
  64. package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
  65. package/docs/reference/authoring-surface/type-httpoutput.md +7 -1
  66. package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
  67. package/docs/reference/authoring-surface/type-namestart.md +77 -0
  68. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
  69. package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
  70. package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
  71. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
  72. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
  73. package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
  74. package/docs/reference/authoring-surface/type-poolspec.md +2 -2
  75. package/docs/reference/authoring-surface/type-pushinput.md +22 -3
  76. package/docs/reference/authoring-surface/type-pushoutput.md +32 -7
  77. package/docs/reference/authoring-surface/type-reposlot.md +5 -5
  78. package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
  79. package/docs/reference/authoring-surface/type-slothandles.md +5 -13
  80. package/docs/reference/authoring-surface/type-sourcespec.md +10 -10
  81. package/docs/reference/authoring-surface/type-workspaced.md +29 -8
  82. package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
  83. package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
  84. package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
  85. package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
  86. package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
  87. package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
  88. package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
  89. package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
  90. package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
  91. package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
  92. package/docs/reference/authoring-surface/workspace.md +65 -37
  93. package/docs/reference/authoring-surface/worktree.md +71 -0
  94. package/docs/reference/authoring-surface.md +30 -10
  95. package/docs/reference/cli.md +1 -1
  96. package/docs/reference/config.md +13 -4
  97. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
  98. package/docs/reference/decisions/0002-duplex-streaming-actor.md +17 -10
  99. package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
  100. package/docs/reference/decisions/0005-sandbox-pod-composition.md +29 -19
  101. package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
  102. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +30 -30
  103. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +35 -17
  104. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +8 -2
  105. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
  106. package/docs/reference/decisions/0017-pool-over-a-source-port.md +6 -4
  107. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +21 -9
  108. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +2 -1
  109. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +8 -9
  110. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +62 -65
  111. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +9 -1
  112. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +13 -12
  113. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +4 -1
  114. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +18 -19
  115. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +8 -8
  116. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +4 -4
  117. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
  118. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
  119. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
  120. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +24 -24
  121. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +5 -3
  122. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +110 -67
  123. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +101 -74
  124. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +5 -6
  125. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +2 -0
  126. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +181 -153
  127. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +74 -63
  128. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +31 -20
  129. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
  130. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +295 -0
  131. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
  132. package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
  133. package/docs/reference/decisions.md +3 -0
  134. package/docs/reference/glossary.md +51 -12
  135. package/docs/reference/http-api.md +45 -7
  136. package/docs/reference/machines.md +0 -1
  137. package/docs/why.md +5 -4
  138. package/package.json +3 -3
  139. package/src/actor.ts +161 -35
  140. package/src/agent.ts +6 -6
  141. package/src/ambient.ts +3 -5
  142. package/src/chart.ts +186 -19
  143. package/src/charts.ts +9 -2
  144. package/src/config.ts +16 -6
  145. package/src/custodian.lua +17 -5
  146. package/src/custodian.ts +251 -77
  147. package/src/customize.ts +11 -3
  148. package/src/effect-checks.ts +50 -3
  149. package/src/effect.ts +343 -127
  150. package/src/effector-wire.ts +6 -3
  151. package/src/fail.ts +17 -4
  152. package/src/harness-client.ts +74 -31
  153. package/src/harness-env.ts +33 -0
  154. package/src/held-secrets.ts +125 -16
  155. package/src/holds.ts +3 -0
  156. package/src/http-effect.ts +88 -32
  157. package/src/http.ts +56 -17
  158. package/src/index.ts +15 -1
  159. package/src/internal.ts +1 -0
  160. package/src/kube-client.ts +6 -3
  161. package/src/machine-doc.ts +3 -4
  162. package/src/model-keys.ts +24 -0
  163. package/src/names.ts +19 -6
  164. package/src/notices.ts +443 -54
  165. package/src/open-body.ts +85 -19
  166. package/src/open.ts +36 -5
  167. package/src/parts.ts +40 -28
  168. package/src/party.ts +33 -0
  169. package/src/pick.ts +41 -7
  170. package/src/pool.ts +44 -23
  171. package/src/registration.ts +53 -11
  172. package/src/repo-identity.ts +7 -3
  173. package/src/repos.ts +20 -2
  174. package/src/run-host.ts +106 -20
  175. package/src/sandbox-effect.ts +217 -66
  176. package/src/sandbox-kube.ts +63 -16
  177. package/src/secret-placement.ts +9 -2
  178. package/src/setup.ts +86 -6
  179. package/src/tokens.ts +107 -32
  180. package/src/vocabulary.ts +23 -4
  181. package/src/wire.ts +10 -10
  182. package/src/workspace.ts +264 -253
  183. package/src/worktree.ts +249 -0
  184. package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
  185. package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
@@ -53,16 +53,20 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
53
53
  token being an Effect secret it holds. A Sandbox Effector — the binary with `JR2_EFFECTOR_WORK` set, a read-only view
54
54
  of the Agent's `/work` — runs an `http` whose body is a Worktree file, `push` and a credentialed `command` (ADR-0067);
55
55
  the Instance Effector refuses the last two, and a file body, as a defect, and a Sandbox Effector refuses a `dispatch`.
56
- Its env names where its mount holds the rest: `JR2_EFFECTOR_REPOS` (the node's Repo caches, read-only),
57
- `JR2_EFFECTOR_GIT` (the push credentials: one directory per `git.credentials` entry the Sandbox's pushes spend, the
58
- entry's own Secret as the operator projects it — `entry.json`, what the entry is; `identity` and `known_hosts` for an
59
- ssh key; `password` for a token; `sign.identity` or `sign.private.asc` for the signing key; see the Sandbox Effector's
60
- objects below), `JR2_EFFECTOR_SCRATCH` (its own writable scratch). What jr2 guarantees is stated per kind: for `push`
61
- and a file body, git never takes config or hooks from the Agent's repository and no program the Agent's files name
62
- runs — the clean-repository procedure of ADR-0067 and the opener below. jr2's own programs (`git`, `ssh`,
63
- `ssh-keygen`, `gpg`, `gpg-agent`) run by an absolute path found in one fixed PATH, `/usr/local/bin:/usr/bin:/bin`,
64
- never by the PATH the image set: an image that puts a directory of its own first does not choose which `git` runs. For
65
- a credentialed `command` the Agent's files are the author's choice to run, and the claims are the next bullet's.
56
+ Under Repo Slots each of the three needs a cut's settled row in the run's ledger, because a path alone does not name
57
+ the target of a credential: the Orchestrator reads the row before it starts the Effect, and with no row it sends
58
+ nothing and settles the kind's own outcome
59
+ ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)). Its env names where its mount holds the
60
+ rest: `JR2_EFFECTOR_REPOS` (the node's Repo caches, read-only), `JR2_EFFECTOR_GIT` (the push credentials: one
61
+ directory per `git.credentials` entry the Sandbox's pushes spend, the entry's own Secret as the operator projects it —
62
+ `entry.json`, what the entry is; `identity` and `known_hosts` for an ssh key; `password` for a token; `sign.identity`
63
+ or `sign.private.asc` for the signing key; see the Sandbox Effector's objects below), `JR2_EFFECTOR_SCRATCH` (its own
64
+ writable scratch). What jr2 guarantees is stated per kind: for `push` and a file body, git never takes config or hooks
65
+ from the Agent's repository and no program the Agent's files name runs — the clean-repository procedure of ADR-0067
66
+ and the opener below. jr2's own programs (`git`, `ssh`, `ssh-keygen`, `gpg`, `gpg-agent`) run by an absolute path
67
+ found in one fixed PATH, `/usr/local/bin:/usr/bin:/bin`, never by the PATH the image set: an image that puts a
68
+ directory of its own first does not choose which `git` runs. For a credentialed `command` the Agent's files are the
69
+ author's choice to run, and the claims are the next bullet's.
66
70
  - **A credentialed `command`'s child is confined with Landlock.** The child runs as the Effector's own uid in the
67
71
  Effector's container, where the placement key, every placed secret and the push and signing keys are files that uid
68
72
  can read, and its program is often the Agent's code (`npm run deploy`, `make`, `./scripts/release.sh`, a tool's
@@ -74,28 +78,34 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
74
78
  The thread is locked from the probe to the `execve`, so the domain is the one `execve` keeps. Landlock is an
75
79
  allow-list the kernel enforces and every descendant inherits, and a confined process cannot ptrace a process outside
76
80
  its domain, so `/proc/<effector>/mem` and `/proc/<effector>/environ` are closed too. The ruleset handles every
77
- filesystem right of ABI 1, and `REFER` and `TRUNCATE` when the kernel offers ABI 2 and 3. It allows: read and execute
78
- on the image's system paths — every top-level directory the image holds that is not a denied name, whole, so the rules
79
- follow the image and break on no layout (the usual `/usr`, `/bin`, `/etc`, `/opt`, and a tool tree of the image's own
80
- such as `/google-cloud-sdk` or `/nix` alike; a link such as `/lib -> usr/lib` is followed when what it names holds
81
- none of the Effector's own paths); read and write on the devices (`/dev/null`, `/dev/zero`, `/dev/full`,
82
- `/dev/random`, `/dev/urandom`, `/dev/tty`) and the Effect's own scratch, one per Effect (the checkout, `$HOME` and
83
- `$TMPDIR`); read on each secret file the command names, by `secret("x")` and `.path` alike. The denied names — the
84
- kernel and process pseudo-filesystems (`/proc`, `/sys`, the rest of `/dev`), the runtime tree where jr2 mounts the
85
- Effector's own files (`/run`, the key and config under `/run/jr2/effector`), and the volatile and per-user trees that
86
- hold no tool an image ships (`/home`, `/root`, `/var`, `/srv`, `/mnt`, `/media`, `/lost+found`) — are denied, and so
87
- is every path named in the Effector's own deny list: the secrets the command does not name, the push credentials
88
- (`@git`), the Worktree (`/work`), the node caches (`/repos`), the rest of its scratch (another Effect's, and a push's
89
- copied keys). Every jr2 mount in the Effector's container that holds a secret or the Agent's files is under a denied
90
- name (`/run`: the key, the config, the secrets, `@git`) or in that deny list (`/work`, `/repos`, the scratch). The two
91
- others hold only public data and the child may read them: the runtime volume at `/opt/jr2`, which holds the programs
92
- jr2 ships, and the instance's caBundle ConfigMap at `/etc/jr2/effector-ca` (ADR-0020). So a jr2 mount that holds a
93
- secret goes under `/run/jr2`; one under `/etc`, `/opt` or a new top-level name is readable by the child. A top-level
94
- directory that holds one of the Effector's own paths is allowed one level down at a time around it, so a mount placed
95
- inside one stays denied. Network is not restricted. Any failure before `execve` ends the confiner with exit 126 and
96
- its reason on stderr: nothing runs unconfined. The design needs ABI 1; measured from a hardened non-root pod (every
81
+ filesystem right of ABI 1, `REFER` (ABI 2) and `TRUNCATE` (ABI 3). At ABI 6 it also sets the scopes of abstract Unix
82
+ sockets and of signals: the child cannot connect to an abstract socket that a process outside its domain made, or
83
+ signal such a process. It allows: read and execute on the image's system paths — every top-level directory the image
84
+ holds that is not a denied name, whole, so the rules follow the image and break on no layout (the usual `/usr`,
85
+ `/bin`, `/etc`, `/opt`, and a tool tree of the image's own such as `/google-cloud-sdk` or `/nix` alike; a link such as
86
+ `/lib -> usr/lib` gets no rule, and works through the rule on what it names, because the kernel checks the path that a
87
+ link resolves to; the confiner opens each rule's path with no link on it, and refuses to start when one is there);
88
+ read and write on the devices (`/dev/null`, `/dev/zero`, `/dev/full`, `/dev/random`, `/dev/urandom`, `/dev/tty`) and
89
+ the Effect's own scratch, one per Effect (the checkout, `$HOME` and `$TMPDIR`); read on each secret file the command
90
+ names, by `secret("x")` and `.path` alike. The denied names — the kernel and process pseudo-filesystems (`/proc`,
91
+ `/sys`, the rest of `/dev`), the runtime tree where jr2 mounts the Effector's own files (`/run`, the key and config
92
+ under `/run/jr2/effector`), and the volatile and per-user trees that hold no tool an image ships (`/home`, `/root`,
93
+ `/var`, `/srv`, `/mnt`, `/media`, `/lost+found`) — are denied, and so is every path named in the Effector's own deny
94
+ list: the secrets the command does not name, the push credentials (`@git`), the Worktree (`/work`), the node caches
95
+ (`/repos`), the rest of its scratch (another Effect's, and a push's copied keys). Every jr2 mount in the Effector's
96
+ container that holds a secret or the Agent's files is under a denied name (`/run`: the key, the config, the secrets,
97
+ `@git`) or in that deny list (`/work`, `/repos`, the scratch). The two others hold only public data and the child may
98
+ read them: the runtime volume at `/opt/jr2`, which holds the programs jr2 ships, and the instance's caBundle ConfigMap
99
+ at `/etc/jr2/effector-ca` (ADR-0020). So a jr2 mount that holds a secret goes under `/run/jr2`; one under `/etc`,
100
+ `/opt` or a new top-level name is readable by the child. A top-level directory that holds one of the Effector's own
101
+ paths is allowed one level down at a time around it, so a mount placed inside one stays denied. Network is not
102
+ restricted. Any failure before `execve` ends the confiner with exit 126 and its reason on stderr: nothing runs
103
+ unconfined. The design needs ABI 3, the minimum of jr2 (ADR-0077); measured from a hardened non-root pod (every
97
104
  capability dropped, `RuntimeDefault` seccomp): kind on WSL2 6.6 — ABI 3; the home cluster's arm64 nodes (6.8) — ABI 4;
98
- spark (6.11, arm64) — ABI 5.
105
+ spark (6.11, arm64) — ABI 5. Landlock is a requirement of jr2
106
+ ([ADR-0077](0077-jr2-requires-landlock-on-every-sandbox-node.md)). The confiner is shared: the Harness runs each
107
+ process it starts in `/work` through the same program, with its own rules
108
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)).
99
109
  - **One child cannot reach another.** Three rules, in the Effector:
100
110
  1. **The subreaper.** The Effector sets `PR_SET_CHILD_SUBREAPER` when it starts, so a descendant whose parent ends is
101
111
  reparented to the Effector, never to the container's init. Every child an Effect starts runs in its own session and
@@ -137,16 +147,16 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
137
147
  3. **A private TMPDIR.** Each credentialed child gets `TMPDIR` set to a directory of its Effect's own scratch, which
138
148
  goes when the outcome is read, and `/tmp` is not in the ruleset: a child neither reads nor writes it, so what one
139
149
  child writes no other child reads.
140
- - **Fail-closed at the pod, not at `jr2 up`.** A Sandbox Effector probes Landlock when it starts
141
- (`landlock_create_ruleset` with `LANDLOCK_CREATE_RULESET_VERSION`). When its Workspace holds a credentialed command
142
- (`effector.json`'s `commands`, which `jr2 up` writes) and the probe fails, it does not become ready: it ends, and its
143
- termination message — written to `/dev/termination-log`, the container's `FallbackToLogsOnError` the fallback — says
144
- `landlock: unavailable on node <node> (<why>)`, with the node from the downward API (`NODE_NAME`). The operator copies
145
- the Effector container's restarts and last end onto the Sandbox's status (`status.effector`), and the provision fails
146
- on an Effector that ended before the pod was Ready, in those words, before any Turn. A Workspace that holds no
147
- credentialed command starts on such a node and refuses a command start (400, with the same reason). No `jr2 up` probe:
148
- one probe Job reports one node, which says nothing in a mixed cluster, and in a uniform one the provision says the
149
- same one step later; a Node's `kernelVersion` does not say whether `landlock` is in its LSM list.
150
+ - **Fail-closed at `jr2 up` and at the pod.** Landlock is a requirement of jr2 (ADR-0077): `jr2 up` refuses to converge
151
+ while a Sandbox node lacks it, as the Repo cache agent on that node reports, and a Sandbox Effector probes it when it
152
+ starts (`landlock_create_ruleset` with `LANDLOCK_CREATE_RULESET_VERSION`), whatever its Workspace holds. The Instance
153
+ Effector confines nothing and does not probe. When the probe fails, the Sandbox Effector does not become ready: it
154
+ ends, and its termination message — written to `/dev/termination-log`, the container's `FallbackToLogsOnError` the
155
+ fallback — says `landlock: unavailable on node <node> (<why>)`, with the node from the downward API (`NODE_NAME`). The
156
+ operator copies the Sandbox Effector container's restarts and last end onto the Sandbox's status (`status.effector`),
157
+ and the provision fails on an Effector that ended before the pod was Ready, in those words, before any Turn. The pod's
158
+ probe is the backstop for a node that joins after the converge. A Node's `kernelVersion` does not say whether
159
+ `landlock` is in its LSM list, so only a probe on the node can tell.
150
160
  - **The wire.** `POST /effects/<id>` starts an Effect and answers at once (202, `state: running`). A second `POST` with
151
161
  the same id starts nothing and attaches (200). The answer to a `push` start names the commit it runs against
152
162
  (`pinned`), read before the answer, so the Orchestrator writes it to the ledger's intent row. `GET /effects/<id>`
@@ -274,8 +284,8 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
274
284
  - `unreachable`: nothing was sent — the name did not resolve, the connection or TLS failed.
275
285
  - `refused`: the Effector refused it and sent nothing — a path segment that is `.`, `..` or empty (an Agent's param
276
286
  can cause it, so it is output, not a fault), or a host and secrets that no Effect placed there names. The
277
- Orchestrator settles an `http` start longer than 4 MiB `refused` the same way, before it sends anything; `refused`
278
- is an `http` outcome only.
287
+ Orchestrator settles an `http` start longer than 4 MiB `refused` the same way, before it sends anything, and a file
288
+ body with no cut's row (ADR-0075); a `push` and a `command` have no `refused`.
279
289
  - `unknown`: the connection was made and no answer came (the timeout, a reset): the host may have acted.
280
290
 
281
291
  Each outcome other than `response` carries a `reason` in words. A pick that runs an `http` Effect is answered, by
@@ -402,36 +412,37 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
402
412
  echo, a side channel. The claim is that an Effector does not return a secret it holds, in the forms it sent or in
403
413
  common encodings.
404
414
  - **What a credentialed command's child can still do.** It reads the secret the command names — the author put it there,
405
- and a file the Agent wrote that the argv runs reads it too — and egress is open, so the claim is the three of
406
- ADR-0067: the Agent never reads or spends the secret from its own container, the tool sees a pinned commit and not the
407
- live Worktree, and the output is scrubbed. It shares the Effector's uid, so it can signal the Effector (a fault of its
408
- own run, not a leak) and connect to its port, where nothing without the key is accepted. It cannot outlive its Effect
409
- (rule 1), share a temporary tree with another child (rule 3), or be alive beside a `gpg-agent` (rule 2).
415
+ and a file the Agent wrote that the argv runs reads it too — and egress is open but for the cloud metadata addresses
416
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)), so the claim is the three
417
+ of ADR-0067: the Agent never reads or spends the secret from its own container, the tool sees a pinned commit and not
418
+ the live Worktree, and the output is scrubbed. It shares the Effector's uid, so it can signal the Effector (a fault of
419
+ its own run, not a leak) and connect to its port, where nothing without the key is accepted. It cannot outlive its
420
+ Effect (rule 1), share a temporary tree with another child (rule 3), or be alive beside a `gpg-agent` (rule 2).
410
421
  - **A long GPG push delays a concurrent credentialed command** by its duration, against that command's timeout, and the
411
422
  reverse. Rejected: refusing a credentialed command beside a GPG-signing push at `jr2 up` — the hole is open only while
412
423
  a GPG push signs, so the rule is that the two never overlap; and a second container per credentialed placement — its
413
424
  child would still share a uid with that container's Effector and could spend that container's key, so it would need
414
425
  Landlock anyway, plus a key, a Secret, a port and a NetworkPolicy per placement.
415
- - **A node with no Landlock runs no credentialed command.** Its Sandboxes that hold one fail at provision, naming the
416
- node; the fix is a kernel with `landlock` in its LSM list. The home cluster's nodes are arm64, so the Effector binary
417
- and the Kit images need `linux/arm64` builds there.
426
+ - **A node with no Landlock runs no Sandbox** (ADR-0077). A Sandbox placed there fails at provision, naming the node;
427
+ the fix is a kernel with `landlock` in its LSM list. The home cluster's nodes are arm64, so the Effector binary and
428
+ the Kit images need `linux/arm64` builds there.
418
429
  - An `unknown` outcome is the Machine's to route. jr2 sends an `http` call again only when the author marked it
419
430
  `idempotent` ([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)).
420
431
  - The Effector's Go tests run Landlock for real on the host's kernel: a child that reads the key, an unnamed secret or
421
432
  `@git`, or writes the Worktree, ends with a permission error and an answer with no secret in it; one that reads its
422
433
  named secret from env and from its file succeeds; a child of the child is confined too; a failing probe stops the
423
- start of a Workspace that holds a credentialed command, with the node in the reason. And the three rules of "One child
424
- cannot reach another" run with real processes: a `setsid` escapee, and an escapee of that escapee, is dead once its
425
- command has settled; a live command's daemon, under its keeper, lives through the settle of another Effect beside it;
426
- the keeper ends what its child left and answers the child's own end; a command started during a GPG push runs only
427
- after the agent is gone, and the reverse, with no sample of that Effector's processes seeing both alive — the
428
- command's child matched to its scratch, and the agent known from a witness in place of `gpg-agent` that tells the test
429
- before the launcher runs and, after it exits, which processes the agent left, so an agent that lives less than one
430
- sample is never missed; a waiter past its timeout settles timed out and never runs late; a child's `TMPDIR` write is
431
- invisible to the next child, and `/tmp` refuses both; a signed DELETE ends a credentialed command's group, `exited`
432
- with SIGTERM, a program that catches SIGTERM gives its code beside the signal, a child that ignores SIGTERM is killed
433
- past the grace (`exited` with SIGKILL), an unsigned DELETE stops nothing, a DELETE on a push or an `http` path answers
434
- 405, and a command start that arrives after a DELETE of its id runs nothing, while a push or an `http` start does.
434
+ start of the Effector, with the node in the reason. And the three rules of "One child cannot reach another" run with
435
+ real processes: a `setsid` escapee, and an escapee of that escapee, is dead once its command has settled; a live
436
+ command's daemon, under its keeper, lives through the settle of another Effect beside it; the keeper ends what its
437
+ child left and answers the child's own end; a command started during a GPG push runs only after the agent is gone, and
438
+ the reverse, with no sample of that Effector's processes seeing both alive — the command's child matched to its
439
+ scratch, and the agent known from a witness in place of `gpg-agent` that tells the test before the launcher runs and,
440
+ after it exits, which processes the agent left, so an agent that lives less than one sample is never missed; a waiter
441
+ past its timeout settles timed out and never runs late; a child's `TMPDIR` write is invisible to the next child, and
442
+ `/tmp` refuses both; a signed DELETE ends a credentialed command's group, `exited` with SIGTERM, a program that
443
+ catches SIGTERM gives its code beside the signal, a child that ignores SIGTERM is killed past the grace (`exited` with
444
+ SIGKILL), an unsigned DELETE stops nothing, a DELETE on a push or an `http` path answers 405, and a command start that
445
+ arrives after a DELETE of its id runs nothing, while a push or an `http` start does.
435
446
  - The `@kind` tier carries the proofs a unit test cannot: an echo server in the seed namespace answers each header and
436
447
  the url in every encoding, and the run's snapshot, its feed and the answer the model reads must hold no form of the
437
448
  value — the step decodes every base64 and percent-encoded run it reads, so a leak of what the Effector built
@@ -60,8 +60,10 @@ their own maintainers, and the escapes shipped in systems that used them were in
60
60
  `gate`, `fail`, a generic action's name or an integer, because those are the kit's; the Gate itself is lent as
61
61
  `gate: true`, and the Part that fails the run as `fail: true`
62
62
  ([ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md)), because the Machine
63
- decides which ends a Chart can choose. The guard `jr2.failure`, which tells a Failure from a defect on an `onError`,
64
- is in every Chart, as `jr2.sentBy` is: it is a predicate and gives no capability.
63
+ decides which ends a Chart can choose. The guards `jr2.failure`, which tells a Failure from a defect on an `onError`,
64
+ and `jr2.ready`, which says that a cut settled `ready`
65
+ ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)), are in every Chart, as `jr2.sentBy` is:
66
+ each is a predicate and gives no capability.
65
67
  - **Constants for authority, references for data.** `invoke.src`, guard and action `type`s, delay names and event names
66
68
  are constants, checked against the lent set and the Chart's own declarations at admission. Data flows through
67
69
  references: a placeholder `{{context.ticket.title}}`, or `{ "$ref": "event.summary" }`, in an invoke's `input`, an
@@ -130,11 +132,14 @@ their own maintainers, and the escapes shipped in systems that used them were in
130
132
  briefing names the lent Agents, and a body that allows no model has NO `agents` in its grammar and a briefing that
131
133
  says so: a planner reads a field it cannot fill as one to fill (measured: eight of ten first Charts minted under a
132
134
  `never` model, and read "must not have additional properties" for it), and a Chart that brings `agents` anyway reads
133
- at the door why there is none. And the Agent's surface MARKS a pick whose parameter is a Chart with that grammar's
134
- briefing (`SurfaceEvent.briefing`, by schema identity), so the Harness says the text in the Turn part, right before
135
- "Allowed now", on every Turn where that pick is Allowed, and on a restatement that opens it
136
- ([ADR-0062](0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md)). Nowhere else: the standing Briefing stays
137
- Chart-agnostic, the tools block stays the same bytes on every Turn
135
+ at the door why there is none. The briefing of a body that lends an Effect says how a Chart works under Repo Slots:
136
+ work happens in a Worktree; a lent cut takes a `path` of `/work/<slot>/<name>` and a `branch`, and `<name>` is not the
137
+ branch; its `onDone` has an edge with the guard `jr2.ready` and an edge with no guard, for a refused cut; and that one
138
+ literal path is the `cwd` of each Turn and the `worktree` of each Effect there. And the Agent's surface MARKS a pick
139
+ whose parameter is a Chart with that grammar's briefing (`SurfaceEvent.briefing`, by schema identity), so the Harness
140
+ says the text in the Turn part, right before "Allowed now", on every Turn where that pick is Allowed, and on a
141
+ restatement that opens it ([ADR-0062](0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md)). Nowhere else: the
142
+ standing Briefing stays Chart-agnostic, the tools block stays the same bytes on every Turn
138
143
  ([ADR-0029](0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md)), and a seat whose Menu
139
144
  holds no Chart pick reads not a word of it. The cost is the author's to scope: a seat whose Menu holds a Chart pick
140
145
  carries the grammar as cached tool parameters on every Turn, so an author who wants a planner that only sometimes
@@ -206,13 +211,14 @@ their own maintainers, and the escapes shipped in systems that used them were in
206
211
  above. The minted Agents: every model against the allowed list and the placed keys, every Workspace access against the
207
212
  allowed set. The structure: no `always` cycle without a guard, through parallel regions and history too; no `onDone`
208
213
  on an Agent invoke, because a Turn never settles by itself, it ends on a pick in `on`; no Agent state that nothing can
209
- end; no parallel state at or below an Agent's. An Agent state of a Chart needs no `agent.fault` route. A state of a
210
- Machine needs one ([ADR-0074](0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md)). An
211
- `agent.fault` that no transition of the Chart takes goes, unchanged, to the Machine that invoked the Open body, and
212
- that Machine's `agent.fault` route takes it, as it takes the fault of an Agent of its own. So a Machine that invokes
213
- an Open body that can run an Agent needs the route too, on that state or above it. A route in the Chart stays legal,
214
- and it can read `{{event.reason}}`. The wrapper is the right place: a Chart under a body that lends no `fail` has no
215
- correct end for a fault, because a final state means `done`
214
+ end; no parallel state at or below an Agent's; no Agent in two states that can be active together. An Agent state of a
215
+ Chart needs no `agent.fault` route. A state of a Machine needs one
216
+ ([ADR-0074](0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md)). An `agent.fault` that no
217
+ transition of the Chart takes goes, unchanged, to the Machine that invoked the Open body, and that Machine's
218
+ `agent.fault` route takes it, as it takes the fault of an Agent of its own. So a Machine that invokes an Open body
219
+ that can run an Agent needs the route too, on that state or above it. A route in the Chart stays legal, and it can
220
+ read `{{event.reason}}`. The wrapper is the right place: a Chart under a body that lends no `fail` has no correct end
221
+ for a fault, because a final state means `done`
216
222
  ([ADR-0073](0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md)) and a state that waits
217
223
  holds the run until the wall clock. So the briefing does not teach the route, and the grammar's fragment does not show
218
224
  it. And two checks that stand in for the types a Chart does not have. **Dataflow**: every reference a Chart makes,
@@ -224,12 +230,17 @@ their own maintainers, and the escapes shipped in systems that used them were in
224
230
  is refused. The fields a Chart declares are its types. **Lent input shapes**: an Effect carries an `input` schema and
225
231
  an Agent's Turn input is a fixed key set (`prompt`, `cwd`, `continue`, `model`, `thinkingLevel`, no placement), so the
226
232
  literal parts of a Chart's `input` objects are validated at the door and the resolved object again at invoke time,
227
- where a failure is that invoke's error and never a mid-Turn surprise. Last, the door compiles the Chart as the body
228
- would: `jr2Setup`'s own refusals (less its `agent.fault` refusal), and the load warnings a hand-written Machine gets
229
- (an unentered mode), are refusal lines for a Chart, because a planner reads a report and nobody reads a warning. A
230
- refusal is the body's OUTPUT, `{ outcome: "refused", report }`, routed by the wrapper's `onDone` with a guard on
231
- `outcome`, so a wrapper whose `onDone` returns to the planning state hands it to the planner as the output link in its
232
- Chain ([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)): "Chart refused:
233
+ where a failure is that invoke's error and never a mid-Turn surprise. Then the rules of a Worktree (ADR-0075). Under
234
+ Repo Slots, a Turn's `cwd` and an Effect's `worktree` are required, and the door refuses a `cwd` that is a slot's
235
+ folder. A cut lent to a Chart must state `expect`, which is static, so a Chart cannot change it. The door refuses
236
+ `expect: "existing"` beside a lent force push, because together they let a Chart force-push any existing branch that
237
+ `default/` does not hold. A Chart's payload has no `pattern`, so a bad branch name reaches the cut, and the cut
238
+ refuses it. Last, the door compiles the Chart as the body would: `jr2Setup`'s own refusals (less its `agent.fault`
239
+ refusal), and the load warnings a hand-written Machine gets (an unentered mode), are refusal lines for a Chart,
240
+ because a planner reads a report and nobody reads a warning. A refusal is the body's OUTPUT,
241
+ `{ outcome: "refused", report }`, routed by the wrapper's `onDone` with a guard on `outcome`, so a wrapper whose
242
+ `onDone` returns to the planning state hands it to the planner as the output link in its Chain
243
+ ([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)): "Chart refused:
233
244
  `src: "linter"` is not a lent actor; lent: coder, reviewer, push", and the planner answers with a corrected Chart. It
234
245
  is not the body's error, because a child's error is a defect that never reaches the model (ADR-0067, which stands); at
235
246
  the HTTP door it is 422 with the same report. No new mechanism: an Agent reads its children's outputs through the
@@ -39,7 +39,7 @@ comment "the run faults rather than lie about a decision", and that run ended `d
39
39
  the run holds then, and nor does a Workspace that was stopped in `placing`
40
40
  ([ADR-0064](0064-a-workspace-waits-for-capacity.md)): a parent that invokes the same Machine again gives the new
41
41
  Workspace the same invoke id, and so the same Sandbox name. That new Workspace takes over the Sandbox and its
42
- Worktree, as a restore does, with the work of the failed attempt still in it. To keep the work for a person to read,
42
+ Worktrees, as a restore does, with the work of the failed attempt still in it. To keep the work for a person to read,
43
43
  the Machine waits at a Gate before it fails.
44
44
  - **`jr2 run` exits 0 only for `done`.** It exits 1 for every other end: `failed`, `error`, `cancelled`, `lost`,
45
45
  `drifted`, and a run that was parked before it settled. Exit code 2 stays the CLI's code for a usage error. The status