@awebai/oats 0.41.0 → 0.42.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.
@@ -0,0 +1,398 @@
1
+ # OATS 0.42.0
2
+
3
+ ## Added
4
+
5
+ - **A capability can declare the home state it owns:
6
+ `retirement.disposable.home`**
7
+ ([#410](https://github.com/awebai/oats/issues/410)). The meaning is:
8
+ provider-owned state that is not the instance's work, left in place until
9
+ the home is removed, and not copied to recovery. The one exception is the
10
+ preservation of a failed spawn in directory mode, which copies the whole
11
+ home, declared entries included
12
+ ([#598](https://github.com/awebai/oats/issues/598)). A provider declares the
13
+ top-level entries of the instance home that hold its own state, such as an
14
+ identity directory with a signing key:
15
+
16
+ ```json
17
+ "retirement": { "disposable": { "home": [".aw", ".oats-aweb", ".aweb-identity", ".aweb-identity-*"] } }
18
+ ```
19
+
20
+ Each entry is one hidden top-level name (`.name`) or a prefix
21
+ (`.prefix-*`, every top-level entry whose name starts with the text before
22
+ `*`). Matching entries are left out of the home fingerprint, the home copy
23
+ and its verification, so a change to them alone causes no copy. Exclusion
24
+ means "not copied" and nothing more: the entries stay in the home until the
25
+ home is removed, retire hooks still see them, and an incomplete cleanup
26
+ keeps the home with them. The receipt names what was left out in
27
+ `workRecovery.notCopied` (`[{scope, path, owner}]`: names and owners only),
28
+ and the summary prints `not copied: .aw, .oats-aweb (oats.aweb)`. A
29
+ capability that declares nothing has its home state copied, once. This
30
+ kernel reads the field. See
31
+ [capabilities](../capabilities.md#manifest).
32
+
33
+ ## Changed
34
+
35
+ - **Desktop has a theme picker (#602).** The theme button and
36
+ Ctrl+Shift+Space (⇧⌘Space on macOS) open a theme picker in the middle of
37
+ the window: the four themes, the current one marked. Choose one with a
38
+ click, Enter or Space; Escape closes it with nothing changed. The shortcut
39
+ works while a terminal has focus too. The theme button now opens the picker;
40
+ cycling between themes stays in the command palette (**Theme: cycle…**). On
41
+ Omarchy, Super+Ctrl+Shift+Space stays Omarchy's own theme menu (it changes
42
+ the computer's theme, which This computer follows); Ctrl+Shift+Space is its
43
+ in-app counterpart and opens Desktop's picker. A shortcut you had already set
44
+ to the same keys keeps them; the shortcuts editor names the clash.
45
+ - **The Desktop accepts OATS CLIs `>=0.25.8 <0.43.0`**, so it runs against
46
+ this release's kernel; the Desktop 0.41.x refuses a 0.42 CLI.
47
+ - **One retire writes one recovery**
48
+ ([#480](https://github.com/awebai/oats/issues/480),
49
+ [#544](https://github.com/awebai/oats/issues/544)). A retire whose hooks
50
+ changed the home or the work wrote a second recovery directory with a full
51
+ copy of both. It now adds to the recovery it already wrote, and copies again
52
+ only the part that changed:
53
+
54
+ ```text
55
+ <recovery>/
56
+ recovery.json
57
+ home/ the home before the retire hooks
58
+ repo/ or work/ the work before the retire hooks
59
+ after-hooks/
60
+ home/ the home again, only if a hook changed home bytes
61
+ repo/ or work/ the work again, unless it is proven unchanged
62
+ ```
63
+
64
+ The work is copied again unless it is proven unchanged: unchanged means
65
+ that everything a copy of it would carry is equal byte for byte, and
66
+ anything that cannot be compared exactly counts as changed. In worktree
67
+ mode that is what a work copy holds beside the files (status, branch and
68
+ commit; the index's entries, resolve-undo records and permission bits;
69
+ what it copies from the Git directories, with their permission bits: an
70
+ operation in progress, `info/attributes` and the stash's log; tags,
71
+ stash, exclude rules and status settings) and the bytes and permission
72
+ bits of its files. Every part is compared as its bytes, never as
73
+ decoded text, so two states that differ only in bytes that are not valid
74
+ UTF-8 (in a ref name, a path, a file name or the target of a symbolic
75
+ link) are two states. A recovery still cannot hold a file whose name is
76
+ not valid UTF-8: its copy fails and the retire refuses, as before.
77
+ A status with the same rows is not taken as proof, so a retire
78
+ hook that rewrites a file that was already modified has its bytes
79
+ preserved, whether or not it also writes the home
80
+ ([#600](https://github.com/awebai/oats/issues/600)). For that proof a
81
+ retire reads the worktree's files once before the hooks, when there is
82
+ something to preserve, and at most once more after them. A tag or a stash
83
+ made in the worktree's repository while the hooks run adds a copy
84
+ attempt. Two things are not compared, and the proof claims no byte
85
+ equality beyond them: the index's derived data (its stat cache and
86
+ extensions), a deliberate semantic exception; and the repository the
87
+ worktree belongs to (its objects, its other branches, the settings a clone
88
+ of it is served under), a custody boundary. It holds because of 0.41's
89
+ retirement ([#640](https://github.com/awebai/oats/issues/640)): the
90
+ repository survives the retire, no retire deletes a branch, and a commit
91
+ of the worktree that no ref reaches is preserved before the worktree is
92
+ removed.
93
+ A worktree that holds a repository (a directory under it with a `.git`
94
+ entry of any kind, a dangling symbolic link included; a repository made
95
+ by `git init` or a clone, a submodule, or a linked worktree of another
96
+ repository) is never proven unchanged: its work is copied again after
97
+ the hooks, whatever they did, and `afterHooks.work` then says that a copy
98
+ was made, not that a hook changed the work. That copy can fail where the
99
+ first did not, and the retire then refuses after the hooks with the home,
100
+ the work and the recovery written before them kept. A worktree with a
101
+ `.git` entry under it that OATS cannot read as a repository (a dangling
102
+ symbolic link, or a `.git` it cannot test) is never home-only, and no
103
+ class is added for it: a change to the home alone can now cause a
104
+ work-copy attempt before the hooks that OATS 0.41 did not make, and any
105
+ failure of that attempt can refuse the retirement, before any retire hook
106
+ runs and with the home and the work kept; a launched instance's session
107
+ has been stopped by then, as the refusal says. Of a nested repository's
108
+ branches other than the one `HEAD` is on, the copy holds the commits and
109
+ not the names: `git fsck --unreachable` in the copy lists them, and a
110
+ commit kept without a name is lost to `git gc` in the copy. Of its stash
111
+ the copy holds the latest entry; of its remote-tracking refs, notes and other
112
+ namespaces only what a branch or a tag reaches; a repository inside it
113
+ comes along as plain files.
114
+ A snapshot that holds the home only also gets the work under
115
+ `after-hooks/` when the hooks did not move it but something beyond the
116
+ home is there to preserve after them. The home's comparison holds every
117
+ entry its copy carries, the kernel's own records included
118
+ (`.oats-events.jsonl`, the `.oats-stop*.json` and `.oats-restart.json`
119
+ receipts, `.oats-agents-md.*.previous`, `.claude/settings.json`, and every
120
+ field of `instance.json`): a hook that writes one of them has the home
121
+ copied again. The home is not copied again because the work is, so when a
122
+ hook moved only the work those records are as of the pre-hook snapshot.
123
+ A home copy is still verified against the digest a spawn baseline uses,
124
+ which passes over those records: the verification does not prove they
125
+ were preserved ([#645](https://github.com/awebai/oats/issues/645)).
126
+
127
+ Each part under `after-hooks/` is whole and verified, verified
128
+ before the home is removed; `after-hooks/home/` exists only when the
129
+ home's own bytes moved. The pre-hook snapshot is never rewritten. An
130
+ instance that had nothing to preserve before the hooks still gets its one
131
+ recovery after them, with no `after-hooks/`. `recovery.json` gains `phase`:
132
+ `"before-hooks"` when the pre-hook snapshot is written, `"complete"` once
133
+ the post-hook check has concluded. A retried retire, after incomplete
134
+ cleanup or after a refused or interrupted attempt, writes its own recovery
135
+ and never touches an earlier one; a directory left at `before-hooks` is a
136
+ complete, verified pre-hook snapshot. The summary prints one "has been
137
+ preserved" block, with what was copied from the home and an `after the
138
+ retire hooks: … copied again under after-hooks/` line when it applies. See
139
+ [souls and instances](../souls-and-instances.md#retire).
140
+ - **A socket, a FIFO or a device file in a worktree refuses the retire.**
141
+ In worktree mode, when a retire has something to preserve before its
142
+ hooks run, it now reads the whole worktree, and an entry that is not a
143
+ file, a directory or a symbolic link cannot be read or copied. Git prints
144
+ no status row for such an entry. Before, a retire refused it only inside
145
+ a directory Git reports as ignored, or when it copied the work; a retire
146
+ that preserved only the home went through. Now it refuses with `E_WORK_INSPECTION_FAILED` before
147
+ any retire hook runs: no recovery was written and nothing was deleted,
148
+ however often it is retried. The message names the entry and says what to
149
+ do: safely stop the process or resource that owns it, or move the entry
150
+ elsewhere, then retry (it may be a live endpoint, so the advice is not to
151
+ delete it).
152
+ `--json` keeps the code; the `message` field is the new sentence, also for
153
+ the cases that were already refused. Those are refused where they were,
154
+ at the first inspection: an entry inside a directory Git reports as
155
+ ignored whole, in the `work/` of a directory instance, or in the home
156
+ itself. That inspection runs before the retire stops the session, so
157
+ there the session is still running, and the message ends after saying
158
+ what to do. Everywhere else in a worktree the refusal comes from the new
159
+ read, after the retire has stopped the session of a launched instance:
160
+ the instance's session is stopped, the instance is not retired, and its
161
+ home is kept; the message says so, and says that this retire stopped no
162
+ session when the instance had none to stop. To continue, deal with the
163
+ entry and run
164
+ `oats retire <instance>` again, or start the session again in the same
165
+ home with `oats session start --home <abs>`. A self-retire (`--self`) is
166
+ refused the same way by its detached completion, which records the
167
+ refusal beside the home; after that only `oats retire <instance>`
168
+ continues, and `oats session start` refuses until it has. Of the refusals
169
+ from the new read, only that of `--self --keep-dir` comes with the session
170
+ still running. When a retire hook leaves such an
171
+ entry behind, the hooks have run: the retire refuses
172
+ (`E_WORK_INSPECTION_FAILED`, or `E_WORK_PRESERVATION_FAILED` when the hook
173
+ also moved the Git state) and the home, the work and the recovery written
174
+ before the hooks are kept.
175
+ - **A file in a worktree that cannot be read refuses a retire that
176
+ preserved only the home.** In worktree mode, when a retire has something
177
+ to preserve before its hooks run, it reads every file of the worktree,
178
+ also what is nothing to preserve: a tracked file that is unchanged, and a
179
+ file under a work root a capability declared disposable
180
+ (`retirement.disposable.work`). A file without read
181
+ permission, or one over 2 GiB (which a single read cannot take), refuses
182
+ the retire with `E_WORK_INSPECTION_FAILED` before any retire hook runs,
183
+ with or without `--force`: no recovery was written and nothing was
184
+ deleted. Before, a retire whose snapshot held the home only did not read
185
+ the work and went through. The message is `could not read the worktree at
186
+ <work>: <reason>`: for a file without read permission the reason names the
187
+ file; for a file over 2 GiB it gives the size, and
188
+ `find <work> -type f -size +2147483647c` finds the file. To continue, move
189
+ the file out of the worktree or make it readable, then run
190
+ `oats retire <instance>` again. As for a socket or a FIFO that the new
191
+ read meets, the session of a launched instance has already been stopped by
192
+ then, the instance is not
193
+ retired, and its home is kept.
194
+ - **A worktree that cannot be proven unchanged is copied before the hooks,
195
+ and a retire of one that preserved only the home can now be refused.**
196
+ A worktree is not provable when it holds a repository (a `.git` entry of
197
+ any kind under it, a dangling symbolic link included), or when a read of
198
+ its Git state fails while `git status` works (for example
199
+ `info/attributes` or the stash's log cannot be read). Such a worktree
200
+ always has its work in the pre-hook snapshot and is copied again after
201
+ the hooks; with nothing to preserve it retires as before. The newly
202
+ refused case: a retire whose pre-hook snapshot would have held the home
203
+ only, whose worktree has a state read that fails or a `.git` entry under
204
+ it that OATS cannot read as a repository, and whose copy cannot be made
205
+ either. It refuses with `E_WORK_PRESERVATION_FAILED` before any
206
+ retire hook runs: no recovery was written and nothing was deleted; the
207
+ session of a launched instance has already been stopped, the instance is
208
+ not retired, and its home and work are kept. Before, that retire went
209
+ through and the work was never read. A retire hook can leave the worktree
210
+ in that state too: the work is then copied under `after-hooks/`, also when
211
+ the pre-hook snapshot held the home only, and when that copy cannot be
212
+ made the retire refuses with `E_WORK_PRESERVATION_FAILED` after the hooks
213
+ have run, with the home, the work and the recovery written before the
214
+ hooks kept. Before, that retire went through as well.
215
+ - **A retire refused by the copy made before its hooks says how far it
216
+ got.** The message of every `E_WORK_PRESERVATION_FAILED` raised by that
217
+ copy, whatever its cause (a recovery that could not be verified, an entry
218
+ that cannot be copied), now ends with: no retire hook has run, no recovery
219
+ was written and nothing was deleted, the instance is not retired, its home
220
+ and work are kept, and either "its session has been stopped" or "this
221
+ retire stopped no session". `--json` keeps the code and `details`; the
222
+ `message` field is longer. A refusal after the hooks is unchanged.
223
+ - **A retire that already refused an entry it cannot read now refuses it
224
+ with another error code.** A worktree that the retire copies before its
225
+ hooks (it has work to preserve) and that holds an entry that is not a
226
+ file, a directory or a symbolic link was refused with
227
+ `E_WORK_PRESERVATION_FAILED`, when the copy met the entry. It is now
228
+ refused earlier, when the worktree is read, with
229
+ `E_WORK_INSPECTION_FAILED`; the message names the entry's path and says
230
+ what to do. A worktree that cannot be proven unchanged is copied without
231
+ that read, and keeps `E_WORK_PRESERVATION_FAILED`. The home is kept in
232
+ both.
233
+ - **`oats retire --json` no longer emits `workRecoveries`.** The receipt has
234
+ one `workRecovery`, which keeps `path`, `classes`, `bytes`, `outputs` and
235
+ `repoCopy` and gains `home` (what was copied from the home), `notCopied`
236
+ and `afterHooks: {home, work}` (which parts are under `after-hooks/`).
237
+ A reader that consumes the receipt should read `workRecovery`, and keep
238
+ accepting `workRecoveries[]` from an older kernel on a server. The
239
+ Desktop's reader of the receipt is unchanged. See
240
+ [the CLI API](../desktop-cli-api.md#retire).
241
+ - **`oats retire <instance> --plan` says what a retire would copy.** Two
242
+ `notes` are added: where changed home files are copied, with the declared
243
+ entries that are not copied, and whether the work is copied too (worktree
244
+ and directory modes). No other key changes, and `planRevision` is
245
+ unaffected.
246
+ - **A forced removal past incomplete cleanup no longer leaves a copy of
247
+ declared state in recovery.** `oats retire <instance> --force` on a home
248
+ whose cleanup cannot finish removes the home after preserving its work.
249
+ Entries declared in `retirement.disposable.home` go with the home; the
250
+ recovery holds no copy of them.
251
+ - **A malformed `retirement` is refused where the manifest is read.** Member
252
+ discovery (`E_WORKSPACE_SCHEMA`), package manifests (`E_PACKAGE_MANIFEST`)
253
+ and the kernel loader refuse the same manifest with the same JSON pointer.
254
+ `retirement` must contain a `disposable` map with only `home` and `work`,
255
+ each an array of strings. A `home` entry must be one hidden top-level name
256
+ (`^\.[A-Za-z0-9_][A-Za-z0-9._-]*$`) or a prefix
257
+ (`^\.[A-Za-z0-9_][A-Za-z0-9._-]*-\*$`). Refused: anything else (`notes`,
258
+ `.aw/keys`, `.a*`); an exact name that is `.oats`, `.agents` or `.claude`,
259
+ or that starts with `.oats-events`, `.oats-stop`, `.oats-restart`,
260
+ `.oats-rollback`, `.oats-agents-md`, `.oats-start` or `.oats-attachments`
261
+ (the entries the kernel itself writes in a home); and a prefix that starts
262
+ with `.oats-`. An exact name such as `.oats-aweb` is allowed. The published
263
+ [manifest schema](../capability-manifest.schema.json) states the same
264
+ rules.
265
+
266
+ **This is stricter than before for `home`.** Before this release `home`
267
+ entries were not checked against a grammar; an entry outside the grammar
268
+ now refuses the whole manifest. For a home whose module copy
269
+ (`<home>/.oats/modules/<capability>/oats.json`) carries such an entry, the
270
+ commands that load the home's module manifests stop: starting or
271
+ restarting its session, and capability commands run from the home.
272
+ `oats inspect` reports it as a `module-manifest-invalid` problem instead of
273
+ failing. Retiring such a home still works, because retire runs the hooks
274
+ recorded in `instance.json` at spawn and does not load the manifests. No
275
+ capability shipped in this repository declares `retirement`.
276
+
277
+ - **A pane's environment is its tmux session's, `PATH` included, whoever
278
+ opens its window** ([#616](https://github.com/awebai/oats/issues/616)).
279
+ The tmux client that creates an agent's window, or respawns its pane in a
280
+ restart in place, now runs with only `LANG`, `LC_ALL` and `LC_CTYPE`, for
281
+ every creator: an instance, the Desktop, an operator's shell. tmux then gives
282
+ the pane its session's `PATH`, or the server's global one, instead of the
283
+ creating process's. Before, a pane's `PATH` was the `PATH` of each `oats
284
+ spawn` or `oats session start`, while the rest of its environment was the
285
+ server's: a version-manager wrapper (an Omarchy `~/.local/bin/claude`,
286
+ which runs `mise x claude -- claude`) then saw a `PATH` without the
287
+ variables that interpret it, resolved its own name again and looped. A
288
+ `PATH` and its companion state now come from one environment. OATS does not
289
+ certify that environment: an existing or external server, a session's own
290
+ `PATH` and a launch configuration's `PATH` are used as they are. The tmux
291
+ client is run by the absolute `tmux` the creating process's own `PATH`
292
+ finds. See [execution targets](../execution-targets.md#the-servers-start-environment).
293
+ - **The harness is looked up on the `PATH` its pane runs it with**
294
+ ([#616](https://github.com/awebai/oats/issues/616)). A spawn or a start
295
+ that looks its executable up by name (the harness's, or a launch
296
+ configuration's bare `executable`) uses, in this order: a declared absolute
297
+ `executable` as it is; the launch configuration's own `PATH`; otherwise the
298
+ session's or the server's `PATH`, never the creating process's. It looks
299
+ once before anything is created, against the `PATH` it expects, and again
300
+ on the session it actually gets (another creator may have started the
301
+ server, a session may override `PATH`): found elsewhere, that executable is
302
+ launched and recorded; not found, the spawn is rolled back. With no server
303
+ running, a start under a new selection looks it up on the `PATH` of the
304
+ environment the server will be created with. A start that reuses the
305
+ home's recorded pane looks on that pane's own session, wherever it is
306
+ recorded, never on the OATS server. A session that
307
+ clears `PATH`, a `PATH` the strict reader cannot read and a server with no
308
+ `PATH` are refused, never substituted. The refusal is
309
+ `E_HARNESS_UNAVAILABLE` (`E_LAUNCH_EXECUTABLE` for a configuration's
310
+ declared name); its message names where it looked and the remedies
311
+ (declare `executable`, set `PATH` in the launch configuration, or start the
312
+ server from your own shell) and prints no `PATH`. A recorded executable,
313
+ reused by a plain start, is not looked up again.
314
+ - **When OATS starts the `oats` tmux server, it gives it your login
315
+ environment** ([#616](https://github.com/awebai/oats/issues/616)). Whoever
316
+ runs the command that starts it (an instance, the Desktop, an operator's
317
+ shell, a schedule runner), OATS runs your login shell from the password
318
+ database (bash, zsh or fish) as `-l -i -c` from a fixed seed (your `HOME`,
319
+ `USER`, `LOGNAME` and `SHELL`, a minimal `PATH`, `TERM=dumb`, the creator's
320
+ locale, and the session variables `SSH_AUTH_SOCK`, `DISPLAY`,
321
+ `WAYLAND_DISPLAY`, `XDG_RUNTIME_DIR` and `DBUS_SESSION_BUS_ADDRESS` from
322
+ the creator when it is no instance, the server an instance's home records,
323
+ or your user session), and starts the server with the environment that
324
+ shell sets up. Nothing else of the creator, so no agent identity,
325
+ credential or harness variable, reaches the server, except your OATS
326
+ configuration: the `OATS_` and `PI_AGENTS_` variables that are not the
327
+ kernel's (`OATS_HOME_DIR`, `OATS_TMUX_SESSION`, …) stay the creator's (an
328
+ instance's: its recorded server's), as they were before. The functions
329
+ bash exports (`BASH_FUNC_<name>%%`) are dropped. The login shell is run
330
+ only for a process whose `HOME` is your home directory; one that set
331
+ another `HOME` takes the fallback. The answer comes as one frame on the
332
+ shell's stdout, marked with a random value made for each reading (never a
333
+ file, and not a descriptor of its own, which bash 5.3 started `-l -i` closes
334
+ before the emitter runs); everything else the shell prints is discarded,
335
+ and a missing, cut or repeated frame is no answer; it is accepted only
336
+ whole (exit 0, at most 1 MiB, one JSON object of text, `HOME` and `PATH`
337
+ present) and never evaluated; the read is bounded at 5 s and the
338
+ shell's process group is killed at the deadline. Before, the server got the
339
+ creating process's environment (an instance: a copy of its recorded
340
+ server's). See [execution targets](../execution-targets.md#the-servers-start-environment).
341
+
342
+ ## Fixed
343
+
344
+ - **No classic scrollbars in the Desktop on Linux.** Chromium on Linux drew a
345
+ classic scrollbar, arrows included, under the terminal tabs and beside a
346
+ terminal. The tab strips still scroll (the wheel, and to show the selected
347
+ tab) without a scrollbar, like the other tab bars, and a terminal's pane no
348
+ longer scrolls: the terminal scrolls itself. macOS showed neither.
349
+ - **A copy keeps the target of a symbolic link as its bytes.** A recovery,
350
+ and every other tree the kernel copies (a skill at spawn), recreates a
351
+ symbolic link whose target is not valid UTF-8 with the target's bytes.
352
+ Before, each such byte became a replacement character, and the link in the
353
+ copy named another path. A target that is valid UTF-8 is recreated as
354
+ before.
355
+
356
+ ## Notes
357
+
358
+ - **No manual step for the `oats` server any more.** 0.41.0 asked you to
359
+ start it from your own shell before anything else did. Now, when OATS
360
+ starts it (after a reboot, or when its last session ends), it reads your
361
+ login environment itself. When that cannot be read, OATS says so in one
362
+ line on stderr, with no value, and falls back: a command run outside every
363
+ instance uses its own environment, as before; an instance uses a copy of
364
+ the server its home records, or is refused. A fallback is degraded: it is
365
+ not a login environment and does not prove a working agent socket, and it
366
+ passes the creator's environment on as it is, so a creator whose `PATH`
367
+ names a version manager's directories without the variables that interpret
368
+ them can still make a wrapper such as Omarchy's loop. Only a login
369
+ environment that OATS read prevents that.
370
+ - **A server you or a service started is used as it is.** A server you start
371
+ yourself (`tmux -L oats new-session …`) keeps your shell's environment; one
372
+ a service manager starts (`systemd-run`, a unit, launchd) gets only that
373
+ manager's environment, and OATS does not change or certify it. A server
374
+ that is already running is not restarted: the login environment applies the
375
+ next time OATS starts the server.
376
+ - **The pane rule applies to every window created after the upgrade, on
377
+ whatever server is running, for every creator: instances, the Desktop and
378
+ an operator's shell.** No pane is killed or migrated, and a running agent
379
+ keeps its environment. This is a behaviour change: a new pane's `PATH` is
380
+ now the server's (or its session's), no longer the creating process's, and
381
+ a harness found only on the creating process's `PATH` is refused with
382
+ `E_HARNESS_UNAVAILABLE`. On a server a service manager started
383
+ (`systemd-run`, a unit, launchd), whose `PATH` is only that manager's, a
384
+ spawn or start by bare harness name, an instance's included, is refused
385
+ until you act. To give the panes another `PATH`, set it on the server
386
+ (`tmux -L oats set-environment -g PATH "$PATH"`: windows created from then
387
+ on take it), declare the executable or a `PATH` in the launch
388
+ configuration, or let OATS start the server (it then gets your login
389
+ environment) or start it from your own shell.
390
+
391
+ - **Instances spawned before the upgrade retire with one copy, and still
392
+ copy provider state.** The declaration is recorded at spawn in the home's
393
+ retirement baseline, and retire reads it from there only. A home spawned
394
+ before its capability declared the entries gains no exclusion from a
395
+ package update: its home is copied whole, once. An instance spawned after
396
+ the capability declares them leaves them out.
397
+ - Existing recovery directories are not read, listed or deleted by this
398
+ release.