@@ -174,6 +174,119 @@ Explicit lifecycle commands and `gc` own mutation. Cleanup is authorized by the
174174observed generation; removal wins over late daemon finalization, and permanent
175175respawn cannot overwrite a replacement (R03, R10).
176176
177+ ### Retained exit evidence
178+
179+ An evidence consumer separates observation from cleanup (R12):
180+
181+ ``` text
182+ exact stable id
183+ |
184+ v
185+ bounded snapshot -- opaque generation --> durable consumer action
186+ |
187+ v
188+ conditional same-generation remove
189+ |
190+ +-------------------+--------------------+
191+ | | |
192+ removed semantic refusal cleanup I/O failure
193+ | | |
194+ artifacts absent artifacts untouched metadata retained;
195+ cleanup may be partial
196+ ```
197+
198+ The exported client API and machine CLI share these tagged result schemas:
199+
200+ ``` typescript
201+ type ExitEvidenceTail =
202+ | { _tag: " present" ; lastLines: string [] }
203+ | { _tag: " unavailable" }
204+
205+ type ExitEvidenceResult =
206+ | {
207+ _tag: " snapshot"
208+ snapshot: {
209+ name: string
210+ generation: string
211+ status: " exited" | " vanished"
212+ exitCode: number | null
213+ stream: " combined"
214+ tail: ExitEvidenceTail
215+ }
216+ }
217+ | {
218+ _tag: " unavailable"
219+ reason:
220+ | " missing"
221+ | " running"
222+ | " busy"
223+ | " generation-unavailable"
224+ | " invalid-metadata"
225+ }
226+
227+ type RemoveGenerationResult =
228+ | { _tag: " removed" }
229+ | { _tag: " missing" }
230+ | { _tag: " generation-mismatch" }
231+ | { _tag: " not-terminal" }
232+ | { _tag: " invalid-metadata" }
233+ | { _tag: " busy" }
234+ ` ` `
235+
236+ Snapshot and remove accept an exact filename-safe stable id, never a mutable
237+ display-name reference. The generation is an opaque, nonempty token returned by
238+ the snapshot; callers compare or return it without interpreting its format.
239+ ` exited ` carries the recorded integer exit code, while ` vanished ` has a null
240+ exit code because no terminal exit record exists. ` tail .present ` is the exact
241+ persisted combined-stream line array, including an empty array;
242+ ` tail .unavailable ` means no retained tail was persisted.
243+
244+ The evidence metadata reader opens the metadata path read-only, nonblocking,
245+ and without following symlinks. It requires a regular file no larger than 1
246+ MiB and a JSON object with these structural constraints:
247+
248+ | Field | Constraint |
249+ | --- | --- |
250+ | ` generation ` | required nonempty string |
251+ | ` daemonPid ` | optional positive integer |
252+ | ` exitedAt ` , ` exitCode ` | both absent, or nonempty string plus integer |
253+ | ` lastLines ` | optional array of at most 200 strings |
254+
255+ Unknown compatible fields are ignored. Malformed JSON, oversized or nonregular
256+ artifacts, symlinks, invalid field types, mixed terminal fields, and over-bound
257+ tails produce ` invalid -metadata ` ; they are neither truncated nor treated as
258+ missing. A legacy record without ` generation ` is explicitly
259+ ` generation -unavailable ` for snapshot and ` generation -mismatch ` for removal.
260+
261+ Snapshot holds the stable-id creation lock across metadata validation and the
262+ daemon-generation liveness check. Conditional removal serializes event and
263+ creation mutation, then applies this order:
264+
265+ 1. read and structurally validate metadata;
266+ 2. compare the retained opaque generation with the expected generation;
267+ 3. refuse a live matching generation;
268+ 4. re-read, revalidate, and compare the generation;
269+ 5. remove socket, pid, event, and recovery-revision artifacts;
270+ 6. remove metadata last, preserving evidence if an earlier cleanup step fails;
271+ 7. release both locks.
272+
273+ Steps 5 and 6 are not transactional. If step 5 fails, auxiliary artifacts
274+ removed earlier in the sequence remain absent, while metadata remains as
275+ retryable exact-generation evidence. No rollback is attempted. Missing
276+ artifacts are idempotent. Contention and semantic refusal happen before cleanup
277+ mutation and return tagged results. Argument, metadata-cleanup I/O, and
278+ output-transport failures remain operational errors rather than success-shaped
279+ results. The CLI output law is:
280+
281+ | CLI outcome | stdout | stderr | exit status |
282+ | --- | --- | --- | --- |
283+ | semantic snapshot/remove result | exactly one tagged JSON document | empty | 0 |
284+ | argument or operational failure | no success JSON | diagnostic | nonzero |
285+
286+ ` evidence snapshot --id ` and ` evidence remove --id --expected -generation `
287+ provide leaf-specific help and completion schemas; snapshot never advertises
288+ the remove-only generation option (R11, R12).
289+
177290### Live registry recovery
178291
179292A supporting daemon may publish an opaque recovery capability only when it can
@@ -220,6 +333,7 @@ input, resize, and multi-client geometry without mocks.
220333| R09 | [sessions](../../src/sessions.ts), [server](../../src/server.ts), [recovery](../../src/recovery.ts), [CLI](../../src/cli.ts) | [root](../../tests/pty-root.test.ts), [display name](../../tests/display-name.test.ts), [status](../../tests/stats-cli.test.ts), [list purity](../../tests/list-purity.test.ts), [recovery](../../tests/recovery.test.ts) |
221334| R10 | [sessions](../../src/sessions.ts), [events](../../src/events.ts), [recovery](../../src/recovery.ts), [protocol](../../src/protocol.ts) | [atomic writes](../../tests/atomic-writes.test.ts), [metadata events](../../tests/metadata-events.test.ts), [events](../../tests/events.test.ts), [recovery](../../tests/recovery.test.ts), [disk layout](../../tests/disk-layout-docs.test.ts) |
222335| R11 | [CLI](../../src/cli.ts), [client API](../../src/client-api.ts), [remote](../../src/remote.ts), [testing API](../../src/testing/index.ts) | [help](../../tests/help.test.ts), [completions](../../tests/completions.test.ts), [remote](../../tests/remote-fabric.test.ts), [screenshots](../../tests/screenshot.test.ts), [keys](../../tests/keys.test.ts) |
336+ | R12 | [sessions](../../src/sessions.ts), [server](../../src/server.ts), [client API](../../src/client-api.ts), [CLI](../../src/cli.ts), [completions](../../src/completions.ts) | [exit evidence](../../tests/exit-reap.test.ts), [generation guard](../../tests/gc-generation-guard.test.ts), [immediate reuse](../../tests/rm-immediate-reuse.test.ts), [help](../../tests/help.test.ts), [completions](../../tests/completions.test.ts), [security](../../tests/security-fixes.test.ts) |
223337
224338` node scripts /verify -docs .ts --vrs -only ` validates this two-document shape,
225339sequential requirement IDs, links, and complete requirement references.
0 commit comments