@@ -174,6 +174,114 @@ 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 mismatch/live/failure
193+ | |
194+ artifacts absent artifacts preserved
195+ ```
196+
197+ The exported client API and machine CLI share these tagged result schemas:
198+
199+ ``` typescript
200+ type ExitEvidenceTail =
201+ | { _tag: " present" ; lastLines: string [] }
202+ | { _tag: " unavailable" }
203+
204+ type ExitEvidenceResult =
205+ | {
206+ _tag: " snapshot"
207+ snapshot: {
208+ name: string
209+ generation: string
210+ status: " exited" | " vanished"
211+ exitCode: number | null
212+ stream: " combined"
213+ tail: ExitEvidenceTail
214+ }
215+ }
216+ | {
217+ _tag: " unavailable"
218+ reason:
219+ | " missing"
220+ | " running"
221+ | " busy"
222+ | " generation-unavailable"
223+ | " invalid-metadata"
224+ }
225+
226+ type RemoveGenerationResult =
227+ | { _tag: " removed" }
228+ | { _tag: " missing" }
229+ | { _tag: " generation-mismatch" }
230+ | { _tag: " not-terminal" }
231+ | { _tag: " invalid-metadata" }
232+ | { _tag: " busy" }
233+ ` ` `
234+
235+ Snapshot and remove accept an exact filename-safe stable id, never a mutable
236+ display-name reference. The generation is an opaque, nonempty token returned by
237+ the snapshot; callers compare or return it without interpreting its format.
238+ ` exited ` carries the recorded integer exit code, while ` vanished ` has a null
239+ exit code because no terminal exit record exists. ` tail .present ` is the exact
240+ persisted combined-stream line array, including an empty array;
241+ ` tail .unavailable ` means no retained tail was persisted.
242+
243+ The evidence metadata reader opens the metadata path read-only, nonblocking,
244+ and without following symlinks. It requires a regular file no larger than 1
245+ MiB and a JSON object with these structural constraints:
246+
247+ | Field | Constraint |
248+ | --- | --- |
249+ | ` generation ` | required nonempty string |
250+ | ` daemonPid ` | optional positive integer |
251+ | ` exitedAt ` , ` exitCode ` | both absent, or nonempty string plus integer |
252+ | ` lastLines ` | optional array of at most 200 strings |
253+
254+ Unknown compatible fields are ignored. Malformed JSON, oversized or nonregular
255+ artifacts, symlinks, invalid field types, mixed terminal fields, and over-bound
256+ tails produce ` invalid -metadata ` ; they are neither truncated nor treated as
257+ missing. A legacy record without ` generation ` is explicitly
258+ ` generation -unavailable ` for snapshot and ` generation -mismatch ` for removal.
259+
260+ Snapshot holds the stable-id creation lock across metadata validation and the
261+ daemon-generation liveness check. Conditional removal serializes event and
262+ creation mutation, then applies this order:
263+
264+ 1. read and structurally validate metadata;
265+ 2. compare the retained opaque generation with the expected generation;
266+ 3. refuse a live matching generation;
267+ 4. re-read, revalidate, and compare the generation;
268+ 5. remove socket, pid, event, and recovery-revision artifacts;
269+ 6. remove metadata last, preserving evidence if an earlier cleanup step fails;
270+ 7. release both locks.
271+
272+ Missing artifacts are idempotent. Contention and semantic refusal return tagged
273+ results. Argument, metadata-cleanup I/O, and output-transport failures remain
274+ operational errors rather than success-shaped results. The CLI output law is:
275+
276+ | CLI outcome | stdout | stderr | exit status |
277+ | --- | --- | --- | --- |
278+ | semantic snapshot/remove result | exactly one tagged JSON document | empty | 0 |
279+ | argument or operational failure | no success JSON | diagnostic | nonzero |
280+
281+ ` evidence snapshot --id ` and ` evidence remove --id --expected -generation `
282+ provide leaf-specific help and completion schemas; snapshot never advertises
283+ the remove-only generation option (R11, R12).
284+
177285### Live registry recovery
178286
179287A supporting daemon may publish an opaque recovery capability only when it can
@@ -220,6 +328,7 @@ input, resize, and multi-client geometry without mocks.
220328| 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) |
221329| 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) |
222330| 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) |
331+ | 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) |
223332
224333` node scripts /verify -docs .ts --vrs -only ` validates this two-document shape,
225334sequential requirement IDs, links, and complete requirement references.
0 commit comments