@awebai/oats 0.24.7 → 0.24.9

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.
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { isAbsolute, resolve } from 'node:path';
3
3
  import { fs, join, dirname, safePath, readJSON, save, atomic, tree, materialize, digest, hash, withLock, oats, command, fail, relPath } from './io.mjs';
4
- import { loadSource, loadStatus, saveStatus, updateStatus, capture, input, markerPath, homeSource } from './sources.mjs';
4
+ import { loadSource, loadStatus, saveStatus, updateStatus, capture, input, markerPath, homeSource, settleRetiredSchedule } from './sources.mjs';
5
5
  import {capturedSource,qualifyCapturedWorker,assertCapturedRun,capturedScaffold,retainCapturedWorkerCustody,assertCapturedWorkerHome,capturedStart} from './captured-worker.mjs';
6
6
  import { metadata, splitRef } from './config.mjs';
7
7
  import { stageBase, validateBase, allowedChanges, verifyGitScope, gitPublish, directoryPublish, journalPath, baseLock, recoveryStage, reconcileDirectoryIntent, gitRecoveryState } from './stores.mjs';
@@ -63,7 +63,10 @@ export function runSource(source,{noLaunch=false,manual=false,capturedInvocation
63
63
  const previous=status.pendingRejudgment?readRun(source,status.pendingRejudgment):null;
64
64
  if(previous) checkRecoveryGuards(source,previous);
65
65
  const ids=previous?previous.inputs:status.captured.inputs.filter(id=>!status.processed.includes(id));
66
- if(!ids.length) return status.finalCaptureUncertified?{status:'source-unavailable',processedCapturedInput:true,finalCaptureComplete:false}:{status:'empty',processed:true};
66
+ if(!ids.length) {
67
+ if(status.retired && !status.finalCaptureUncertified) {updateStatus(source,current=>{current.auto=false;});settleRetiredSchedule(source);}
68
+ return status.finalCaptureUncertified?{status:'source-unavailable',processedCapturedInput:true,finalCaptureComplete:false}:{status:'empty',processed:true};
69
+ }
67
70
  const selected=[];let bytes=0;
68
71
  for(const id of ids) {const n=Buffer.byteLength(JSON.stringify(input(source,id)));if(selected.length && bytes+n>192000) break;selected.push(id);bytes+=n;}
69
72
  if(!source.decl.owns.length) fail('E_OWNER','source has evidence but owns no destination; retained for explicit ownership routing');
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "capability": "oats.okf",
3
3
  "command": "okf",
4
- "version": "2.1.2",
4
+ "version": "2.1.3",
5
5
  "compatibility": {
6
6
  "oats": ">=0.24.4"
7
7
  },
@@ -62,7 +62,16 @@
62
62
  "setting state-dir must be a normalized absolute host path",
63
63
  "setting harvest-runtime is required (pi, claude or codex)",
64
64
  "setting harvest-runtime must be pi, claude or codex",
65
- "setting harvest-model must be null or a non-empty string"
65
+ "setting harvest-model must be null or a non-empty string",
66
+ "check action is not an admitted knowledge operation",
67
+ "bound runtime bindings file is missing or invalid",
68
+ "more than 64 git knowledge bases declared",
69
+ "declared knowledge base could not be staged from its git source",
70
+ "declared knowledge base is not a validated knowledge tree",
71
+ "knowledge base owner or remote custody requirement not met",
72
+ "staged git source does not match the declared knowledge base",
73
+ "harvest runtime command is not installed on this host",
74
+ "harvest runtime could not be qualified against the accepted bases"
66
75
  ]
67
76
  },
68
77
  "inject": "injects/okf.md",
@@ -76,13 +85,17 @@
76
85
  "command": "bin/oats-okf.mjs spawn",
77
86
  "required": true,
78
87
  "inputs": {
79
- "sourceReceipt": { "version": 1 }
88
+ "sourceReceipt": {
89
+ "version": 1
90
+ }
80
91
  }
81
92
  },
82
93
  "retire": {
83
94
  "command": "bin/oats-okf.mjs retire",
84
95
  "inputs": {
85
- "sourceReceipt": { "version": 1 }
96
+ "sourceReceipt": {
97
+ "version": 1
98
+ }
86
99
  }
87
100
  }
88
101
  },
@@ -99,8 +112,18 @@
99
112
  "context": "home",
100
113
  "description": "Capture this source into durable custody and request an independent worker.",
101
114
  "args": [
102
- {"name": "native-request", "flag": "--native-request", "required": false, "description": "Captured operations require an explicit absolute backend-only native request JSON; provider owns the worker task."},
103
- {"name": "worker-mode", "flag": "--worker-mode", "required": false, "description": "Captured worker mode: prepare or launch (default launch); legacy behavior is unchanged when absent."}
115
+ {
116
+ "name": "native-request",
117
+ "flag": "--native-request",
118
+ "required": false,
119
+ "description": "Captured operations require an explicit absolute backend-only native request JSON; provider owns the worker task."
120
+ },
121
+ {
122
+ "name": "worker-mode",
123
+ "flag": "--worker-mode",
124
+ "required": false,
125
+ "description": "Captured worker mode: prepare or launch (default launch); legacy behavior is unchanged when absent."
126
+ }
104
127
  ]
105
128
  }
106
129
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Purpose:** the one accurate view of every work stream in the redesign, what is on main, what is in flight, who owns it, and what blocks it. Lead: `oats-expert` (redesign lead). Updated whenever anything merges, is returned, or reality changes. Older per-lane boards are superseded by this file.
4
4
 
5
- **Last update:** 2026-09-21 19:25Z (earlier stamps reading 2026-09-22 were a lead clock error — the work happened 2026-09-21; corrected on Antares's observation) · S8: 1a/1b/3/4/6a/7a MERGED · K1+K4 MERGED · engineer → 2a · lead → P1 Decision, K3, K5; tag 0.24.7 once 2a lands
5
+ **Last update:** 2026-09-22 17:30Z · main `1b8349e5` · Slice 5 ✅ (PR71) · security follow-up ✅ (PR73) · K5 pins ✅ (PR74/75) · **K6b ✅ (PR76: `spawnPreviewApi 2` / `spawn-preview-2` — side-effect-free preview, `--agents-root`, `--expect-decision`/`E_DECISION_STALE`, bounded preflight)** · **PR77 ✅ killGroup pid-0 guard (HIGH; engineer-found)** · OKF 2.1.3 ✅ · engineer → **6b READ wiring** (API 2 gate) → 6b apply companion (proposal) → 7b → 8 · open contracts: K11 admission, attach-knowledge (OKF node refs), auto-PR (P1 write approval), branch enumeration · **0.24.9** after 6b read (release-notes file FIRST)
6
6
 
7
7
  Legend: ✅ on main/published · 🔄 in flight (PR/branch) · 🟡 preserved, not adopted · ⬜ not started · ⛔ blocked
8
8
 
@@ -17,7 +17,7 @@ Legend: ✅ on main/published · 🔄 in flight (PR/branch) · 🟡 preserved, n
17
17
  | S5 | Official marketplace = reviewed list in oats repo | ✅ D4 merged · ✅ `oats.framework` 1.1.1 listed (`oats.core`, `oats.setup`, `oats.knowledge-theory` aliases) | M | Desktop view → S8 |
18
18
  | S6 | Five expert souls created in the oats repo (`souls/<name>/`) | ✅ five + `oats-setup-expert` on main, all declaring `oats.core`, exported + imported | M, L, lead | legacy `agents/` cutover after S7 proof |
19
19
  | S7 | Centralised per-soul knowledge in `oats-knowledge` (migration + PR-only learning) | ✅ **MIGRATED — PR #2 merged → main `7148a36`, 58 concepts / 35k words from 399 legacy** (kernel 26, expert 18, desktop 13, assistant 1, market-research chartered empty); option A roster; theory + no-code-teaching rule; two workflow passes (24 agents) + lead review; validator 58/0/0; ownership 14/14 · ⏸ harvest OFF until new souls run from the base (human) | lead | next: legacy `agents/` decommission with the `~/OATS` cutover |
20
- | S8 | Desktop parity with the redesign (Redesign v3, aweb palette, discovery-first control panel) | 🔄 **STARTED 2026-09-22**: existing `oats-desktop-engineer-1` retrofitted with an aweb identity (spawn hook replayed, `capabilityMeta` persisted, session reloaded); redesign artefacts copied into its home; brief sent (f16eed3f): merge main (0.24.5) first, then PR slices — shell/palette/sidebar → right panel follows selection → Souls view → **Capabilities view incl. official catalog (human's required feature; server-side via kernel catalog API, never auto-acquire/auto-trust)** → Knowledge/Tasks adapters → remainder. Lead reviews/merges each slice. **Decisions 2026-09-22:** human's later palette/row instructions supersede the HTML where they conflict; grouping is agent-group (cross-repo clusters), not repo; kernel seam `oats catalog --json` merged (PR46 `524180b7`, ships 0.24.6); the Juan-host fresh-engineer plan is superseded — this instance owns S8. | oats-desktop-engineer-1, lead | per-slice PRs | ✅ **Slice 3 MERGED** (PR54; Souls + Sources on K4 soulsApi 1; 1503/1503) · ✅ **K1 MERGED** (PR53; `oats instance git|diff`, instanceGitApi 1; DTO in docs/desktop-cli-api.md) · ✅ **K4 MERGED** (PR52; `inspect --json` soulsApi 1: declarations/provenance/readiness/sources; onboard records provenance; DTO in docs/desktop-cli-api.md) · ✅ **Slice 7a MERGED** (PR51; frame 07 Active overview on /api/panel; anonymous groups; 1466/1466) · ✅ **Slice 6a MERGED** (PR50; Spawn modal frame 02 on existing seams; launchConfig restored; 1435/1435) · ✅ **Slice 4 MERGED** (PR49; Capabilities view: official catalog via `oats catalog` + classic inventory; 1386/1386; server boundary reviewed) · ✅ **Slice 1b MERGED** (PR48; contextual panel + focus mode; 1238/1238) · ✅ **Slice 1a MERGED `508c4b5f`** (PR45; 273/273 real jsdom; gates green) → v0.24.6. **Plan + seams recorded: [`docs/design/2026-09-22-desktop-parity-seams.md`](2026-09-22-desktop-parity-seams.md)** — slices 1a–8, seams K1–K8/P1, five policy decisions **DECIDED 2026-09-22 under delegation** — see `decisions/desktop-parity-lifecycle-and-policy-decisions.md`.
20
+ | S8 | Desktop parity with the redesign (Redesign v3, aweb palette, discovery-first control panel) | 🔄 **STARTED 2026-09-22**: existing `oats-desktop-engineer-1` retrofitted with an aweb identity (spawn hook replayed, `capabilityMeta` persisted, session reloaded); redesign artefacts copied into its home; brief sent (f16eed3f): merge main (0.24.5) first, then PR slices — shell/palette/sidebar → right panel follows selection → Souls view → **Capabilities view incl. official catalog (human's required feature; server-side via kernel catalog API, never auto-acquire/auto-trust)** → Knowledge/Tasks adapters → remainder. Lead reviews/merges each slice. **Decisions 2026-09-22:** human's later palette/row instructions supersede the HTML where they conflict; grouping is agent-group (cross-repo clusters), not repo; kernel seam `oats catalog --json` merged (PR46 `524180b7`, ships 0.24.6); the Juan-host fresh-engineer plan is superseded — this instance owns S8. | oats-desktop-engineer-1, lead | per-slice PRs | ✅ **Slice 2b MERGED** (PR65; Connections + PR card; 1654/1654) · ✅ **K5+K6 MERGED** (PR63; readiness quartet/signatures/enforced child policy; spawn --preview/--base/@native-default) · ✅ **K7+K8 MERGED** (PR64; typed instance events; schedule recentRuns) · ✅ **K3 MERGED** (PR62; `instance stop --plan|--apply`, `retire --plan`, retention-by-default retire with re-home) · ✅ baseline flakes fixed (PR61) · ✅ **P1 decided + K1 `remote` MERGED** (PR60) · ✅ **Components (frame 10, existing data) MERGED** (PR59; 1606/1606) · ✅ **Slice 2a MERGED** (PR57; Git panel on K1; legacy gitState collector removed) · ✅ **v0.24.7 tagged/published** (tag → `c923d81b`) · ✅ **Slice 3 MERGED** (PR54; Souls + Sources on K4 soulsApi 1; 1503/1503) · ✅ **K1 MERGED** (PR53; `oats instance git|diff`, instanceGitApi 1; DTO in docs/desktop-cli-api.md) · ✅ **K4 MERGED** (PR52; `inspect --json` soulsApi 1: declarations/provenance/readiness/sources; onboard records provenance; DTO in docs/desktop-cli-api.md) · ✅ **Slice 7a MERGED** (PR51; frame 07 Active overview on /api/panel; anonymous groups; 1466/1466) · ✅ **Slice 6a MERGED** (PR50; Spawn modal frame 02 on existing seams; launchConfig restored; 1435/1435) · ✅ **Slice 4 MERGED** (PR49; Capabilities view: official catalog via `oats catalog` + classic inventory; 1386/1386; server boundary reviewed) · ✅ **Slice 1b MERGED** (PR48; contextual panel + focus mode; 1238/1238) · ✅ **Slice 1a MERGED `508c4b5f`** (PR45; 273/273 real jsdom; gates green) → v0.24.6. **Plan + seams recorded: [`docs/design/2026-09-22-desktop-parity-seams.md`](2026-09-22-desktop-parity-seams.md)** — slices 1a–8, seams K1–K8/P1, five policy decisions **DECIDED 2026-09-22 under delegation** — see `decisions/desktop-parity-lifecycle-and-policy-decisions.md`.
21
21
 
22
22
  ## S1 — Knowledge capability contract rework
23
23
  - ✅ Provider-neutral contract, binding wire v1, helper/input contract, retained execution: OATS 0.24.0 + OKF 2.1.0 (f20f8e57) published.
@@ -34,9 +34,9 @@ Envelope `{schemaVersion:1, ok, result|error}` unchanged. Requests address a ser
34
34
  - **K2 review-thread delivery**: explicit, confirmed send of selected threads to the exact home's session input with receipt (`delivered | refused | unknown`); delivered ≠ consumed. Typed producer events for commit / branch-renamed / PR-updated / review-request; **no prose parsing** to infer actions.
35
35
  - **K3 lifecycle plan/apply**: read-only plan (runtime activity, children, worktree dirt, branch + open PRs, retention per artefact, per-option allowed/default/reason, warnings, blockers) → apply with plan revision + idempotency key, revalidated under the lifecycle lock; per-target `completed | retained | partial | unknown`. Today there is **no standalone stop**, and retire removes owned worktrees; the design's default Remove retains worktree/branch/PR.
36
36
  - **K4 souls/sources enumeration** (✅ MERGED PR52 as additive `inspect --json` `soulsApi:1`, not a new command — see docs/desktop-cli-api.md): qualified list of imported editions + authored local souls with identity/source/revision/requirements/declarations/editability; readiness separate from launchability and adoption; no renderer YAML.
37
- - **K5 readiness quartet**: `installed | trusted | configured | enrolled`, each `pass | fail | unknown | not-applicable` with items (subject, requiredness, reason, producer, evidence, remedy); trust separates artifact approval from `signature {verified|unsigned|unknown|invalid, signer}`; policy view returns **enforced** child-spawn/worktree permissions with origins; native config items report labels/scope state, never secrets; unknown ≠ granted.
38
- - **K6 spawn preview/apply** (add, 2026-09-22 from 6a review: an explicit `model: {kind: "native-default"}` request field — today an omitted model inherits the configured/soul model and there is no force-native override; 6a renders that control disabled until K6): kernel returns suggestions, canonical worktree/home/branch/base OID, resolved knowledge refs, enforced child policy, auto-PR policy, readiness, typed field problems; apply revalidates and captures; no Desktop-derived paths or branches.
39
- - **K7 activity feed**: bounded typed events per instance with provenance; "waiting on you" only from a producer that reports it.
37
+ - **K5 readiness quartet** (✅ MERGED PR63 — `oats readiness`): `installed | trusted | configured | enrolled`, each `pass | fail | unknown | not-applicable` with items (subject, requiredness, reason, producer, evidence, remedy); trust separates artifact approval from `signature {verified|unsigned|unknown|invalid, signer}`; policy view returns **enforced** child-spawn/worktree permissions with origins; native config items report labels/scope state, never secrets; unknown ≠ granted.
38
+ - **K6 spawn preview/apply** (✅ MERGED PR63 — `oats spawn --preview`, `--base`, `--model @native-default`) (add, 2026-09-22 from 6a review: an explicit `model: {kind: "native-default"}` request field — today an omitted model inherits the configured/soul model and there is no force-native override; 6a renders that control disabled until K6): kernel returns suggestions, canonical worktree/home/branch/base OID, resolved knowledge refs, enforced child policy, auto-PR policy, readiness, typed field problems; apply revalidates and captures; no Desktop-derived paths or branches.
39
+ - **K7 activity feed** (✅ MERGED PR64 — `oats instance events`): bounded typed events per instance with provenance; "waiting on you" only from a producer that reports it.
40
40
  - **K8 schedule run history** + captured-policy-preserving edit contract; transcript access via the owning CLI/provider.
41
41
 
42
42
  ## Decisions — DECIDED 2026-09-22 (lead, delegated by the human)
@@ -55,4 +55,4 @@ Also to confirm: prototype "Gemini" runtime is illustrative (not an OATS runtime
55
55
 
56
56
  ## Ownership
57
57
 
58
- Lead: K1, K4, K5 (readiness/policy shape), K6 preview/apply plumbing, K7/K8 projections, dispatch contract for additive-capability views — proposed as Decisions where they change contracts, implemented in small PRs otherwise. P1: a new capability (`oats.git`/GitHub backend) — spec'd as a Decision with the human; implementation lane TBD. Desktop engineer: all slices, Desktop IPC review items (detach, open-in-editor).
58
+ Lead: K1, K4, K5 (readiness/policy shape), K6 preview/apply plumbing, K7/K8 projections, dispatch contract for additive-capability views — proposed as Decisions where they change contracts, implemented in small PRs otherwise. P1: **forge connections are ADE/workstation integrations, not capabilities** — **Decision ACCEPTED** (`agents/oats-expert/soul/knowledge/decisions/p1-forge-connection-is-an-ade-integration.md`). Desktop *Connections* surface (GitHub card: status / Connect = `gh auth login --web` in an owned pane / Disconnect), PR card read by the Desktop server via fixed-argv `gh pr view … --json` at the existing guarded boundary, typed states (available / no-pull-request / not-connected / cli-not-installed / unsupported-forge / no-remote / unavailable), remote workspaces refuse; OATS never holds a token. No kernel dispatch contract. Kernel: K1 gains `remote {name,url,host,path}` (lead). Slice **2b** = Connections + PR card (engineer; lead security gate before wiring). Auto-PR: ADE-owned, off, per spawn, after K6. Desktop engineer: all slices, Desktop IPC review items (detach, open-in-editor).
@@ -53,8 +53,9 @@ declares**, parsed by the kernel — a consumer never parses YAML and never
53
53
  infers a field that is not there:
54
54
 
55
55
  - `soulsApi: 1`
56
- - `declarations: { requires, defaults, knowledge, teams, resources }` — each
57
- the declared object, or `null` when the section is absent.
56
+ - `declarations: { requires, defaults, knowledge, teams, resources, children }` — each
57
+ the declared object, or `null` when the section is absent (`children`
58
+ since 0.24.8: `{spawn: boolean}`, see readiness policy).
58
59
  - `provenance: { kind, source, revision, path, workspaceRevision } | null` —
59
60
  where this soul copy came from, as recorded by the kernel when it created
60
61
  it (`oats onboard` records `packaged-definition` or
@@ -105,6 +106,7 @@ un-materialized tree → `E_NO_WORKTREE`.
105
106
  "recorded":{"branch":"feat/x","repo":"/abs/repo","drift":true},
106
107
  "upstream":{"ref":"origin/feat/y","ahead":1,"behind":0},
107
108
  "base":{"ref":"origin/main","source":"origin/HEAD","mergeBase":"<oid>","ahead":2,"behind":0},
109
+ "remote":{"name":"origin","url":"git@github.com:acme/one.git","host":"github.com","path":"acme/one","source":"branch-upstream|origin"},
108
110
  "summary":{"changed":1,"renamed":1,"copied":0,"unmerged":0,"untracked":1},
109
111
  "files":[{"id":"<24 hex>","kind":"renamed","xy":"R.","submodule":false,"score":"R100","path":"src/new.txt","origPath":"src/old.txt"}],
110
112
  "notes":[]}
@@ -119,6 +121,11 @@ un-materialized tree → `E_NO_WORKTREE`.
119
121
  unmerged | untracked. Ignored files are not listed.
120
122
  - `files[].id` is **opaque**, minted under (`revision`, `indexRevision`). It is
121
123
  the only way to ask for a diff.
124
+ - `remote` (0.24.8+): the branch's configured remote (`source: branch-upstream`),
125
+ else `origin`, else `null` — never invented. `host`/`path` are **parsed** from
126
+ the URL (ssh/https forms; `.git` stripped) so an ADE can choose a forge backend
127
+ and a `owner/repo` **without running Git**; a local path has `host: null`. No
128
+ network, no forge knowledge in the kernel.
122
129
 
123
130
  `oats instance diff <instance> --file <id> --revision <rev> [--index-revision <idx>] --json`
124
131
  returns a bounded unified diff:
@@ -147,7 +154,389 @@ returns a bounded unified diff:
147
154
  render a diff against a tree that is not the one on screen. A path in
148
155
  `--file` is `E_BAD_ARGS`.
149
156
 
150
- No GitHub/PR data here: that is a capability seam (see the `oats.git` decision).
157
+ No forge (PR/checks/reviews) data here: forge connections are an ADE/workstation integration (P1 decision), read by the Desktop server through the forge's own CLI; the kernel only reports the instance's `remote` so the ADE can pick a backend.
158
+
159
+ ## Instance events (`oats instance events`, `eventsApi: 1`, OATS 0.24.8+) — K7
160
+
161
+ Typed lifecycle events per instance, **written by the kernel action that made
162
+ them true**, with the receipt it produced. Nothing is inferred from
163
+ transcripts, TASK/STATE files or prose. Append-only, two logs: `<home>/.oats-events.jsonl`
164
+ and `<workspace>/.agents/events/<agent>--<instance>.jsonl` (survives the
165
+ home's removal, so a retired instance's `retired` event is still readable).
166
+
167
+ `oats instance events <instance> [--limit <n>] [--since <iso>] [--home <abs>] [--dir <d>] --json`
168
+
169
+ ```json
170
+ {"eventsApi":1,"instance":"dev-1","home":"/abs/home","count":7,"returned":7,"truncated":false,
171
+ "events":[{"eventsApi":1,"at":"<iso>","instance":"dev-1","home":"/abs/home","producer":"kernel","kind":"spawned","data":{"agent":"dev","work":"worktree","branch":"agents/dev-1","runtime":"claude","model":null,"parentInstance":null,"relation":null,"launched":true}},
172
+ {"…":"launched | restarted | stopped | stop-refused | retire-planned | worktree-retained | worktree-removed | branch-deleted | retired | child-spawn-refused"}],
173
+ "lastEvent":{"kind":"stopped","at":"<iso>","producer":"kernel"},
174
+ "waitingOnYou":null,
175
+ "notes":["…"]}
176
+ ```
177
+
178
+ - `kind` is a closed set (unknown kinds are refused at write). `producer` is
179
+ `kernel` for lifecycle facts; a capability may append its own events with
180
+ its id as producer (the write API is `appendEvent`, not the renderer).
181
+ - **`waitingOnYou` is `null` unless a producer reported it** (`data.waitingOnYou:
182
+ true` with a `reason`). `null` means *unknown*, not "not waiting". Today no
183
+ kernel path claims it; the Active overview keeps rendering unknown until a
184
+ producer (a messaging or review capability) does.
185
+ - Window is bounded (`--limit`, default 200; `truncated` says so). A torn line
186
+ appears as `kind: "unreadable"` rather than vanishing.
187
+
188
+ ## Schedule run history (`scheduleApi: 2`, OATS 0.24.8+) — K8
189
+
190
+ `oats schedule show|list --json` entries gain **`recentRuns`**: the last 50
191
+ settled runs (newest first) — every `lastRun` the scheduler recorded once its
192
+ outcome settled (`ended | stopped | blocked | invalid | delivered | skipped |
193
+ unknown …`, never `active`/`starting`), exactly as the producer wrote it,
194
+ deduplicated per run. Where the run launched or targeted an instance, a
195
+ `transcript: {instance, home, kind: "session"}` pointer says which home's
196
+ session to open (the existing `oats session` surface); the kernel does not
197
+ copy transcripts. `nextRun`/`lastRun`/`executionStatus` are unchanged. The
198
+ Schedules view (frame 08) renders `recentRuns` as the recent-runs list and the
199
+ transcript pointer as the handoff; captured-policy definitions are preserved
200
+ as they are (definition fields are untouched by this addition).
201
+
202
+ ## Spawn preview (`oats spawn … --preview`, `spawnPreviewApi: 1`, OATS 0.24.8+)
203
+
204
+ The Spawn modal's fields are backed by the kernel's own decision, taken **before
205
+ any side effect**: `oats spawn <agent> [same flags as a real spawn] --preview --json`
206
+ runs every preflight a spawn runs (placement, composition, resources,
207
+ executable, runtime packages, child-spawn policy) and returns what the spawn
208
+ *would* do — then returns without creating a home, branch or worktree.
209
+
210
+ ```json
211
+ {"spawnPreviewApi":1,"preview":true,"agent":"dev","kind":"persistent","instance":"dev-fix-login","home":"/abs/agents/dev/instances/dev-fix-login",
212
+ "repo":"/abs/repo","work":"worktree","runtime":"claude","model":"opus","modelSource":"soul","launchConfig":null,"yolo":false,"backend":"tmux",
213
+ "branch":"agents/dev-fix-login","base":{"ref":"HEAD","oid":"<oid>"},"worktree":"/abs/agents/dev/instances/dev-fix-login/work",
214
+ "relation":null,"parentInstance":null,"policy":{"childSpawns":{"allowed":true,"origin":{"kind":"default","detail":"…"}}},
215
+ "executable":"/abs/bin/claude","capabilities":["oats.core"],"skills":["oats-operate","oats-souls"],"task":"…"}
216
+ ```
217
+
218
+ - **Name / work area**: `instance` is the canonical name (`<agent>-<purpose>`,
219
+ de-duplicated with `-2`, `-3`…); `home` and `worktree` are the canonical
220
+ paths. The renderer never derives paths.
221
+ - **Branch / base** (worktree mode): `branch` defaults to `agents/<instance>`
222
+ (`--branch <name>` overrides; validated); `base` is `--base <ref>` resolved
223
+ to its commit oid (default `HEAD`). `E_BRANCH_EXISTS` and `E_BASE_UNKNOWN`
224
+ are refused in preview and in apply, before anything exists. Apply creates
225
+ the worktree **from that exact oid**.
226
+ - **Model**: `model`/`modelSource` are the resolved selection. Omitting
227
+ `--model` **inherits** the launch configuration's or soul's preference;
228
+ `--model @native-default` is the explicit "use the runtime's own default"
229
+ (`modelSource: "native default (explicit)"`). These are different requests
230
+ and the UI must not relabel one as the other.
231
+ - **Policy**: `policy.childSpawns` is what this instance will record (soul
232
+ declaration / spawn option / default), enforced later by the spawn route for
233
+ its children (see readiness).
234
+ - **Apply** = the same command without `--preview`; the same inputs yield the
235
+ same decisions (instance, branch, base oid). If the world moved between
236
+ preview and apply (name taken, branch created, base gone) the apply refuses
237
+ with the same typed codes — the preview is a statement, not a reservation.
238
+ - Not in preview (later K6 follow-ups): attach-knowledge refs from the
239
+ knowledge provider (05 excluded, attach stays), auto-PR intent (ADE-owned,
240
+ P1).
241
+
242
+ ### Preview API 2 (0.24.9+, feature `spawn-preview-2`) — the safe-mode fence
243
+
244
+ **API 1 previews wrote before they returned** (a refused child spawn appended an
245
+ event to the parent; a Herdr backend could be started; an unknown soul could be
246
+ imported from an importable def). A consumer must therefore gate on
247
+ **`spawnPreviewApi === 2` AND `features.includes("spawn-preview-2")`** — API 1 is
248
+ the pre-fix marker and is never accepted for dispatch.
249
+
250
+ - **No writes, success or refusal.** A preview appends no event, starts no
251
+ daemon (`backendStatus {name, installed, started:false}` reports what it
252
+ observed), and never creates/updates a soul (`E_SOUL_UNKNOWN` instead of an
253
+ import; `--instructions-file`/`--def-file` refused with `E_BAD_ARGS`). Test:
254
+ the deployment tree is byte-identical after a success, a refusal and an
255
+ unknown-soul preview.
256
+ - **Exact root**: `spawn <soul> --agents-root <abs>` binds the soul to that root
257
+ (as inspect/readiness take it) — no team-soul / capability-agent / importable-
258
+ def fallback; mismatch → `E_SOUL_UNKNOWN`. The preview echoes
259
+ `subject {soul, agentsRoot|null, dir|null}` **as given, byte-exact**.
260
+ - **Decision binding**: `decision {instance, home, branch, base{ref,oid},
261
+ revision}` (24-hex). Apply with `spawn … --expect-decision <revision>`: the
262
+ kernel recomputes name/home/branch/base under the same placement path and
263
+ refuses **`E_DECISION_STALE`** with `details.decision` (the fresh one) on ANY
264
+ drift — no auto-suffix, no silent re-base, nothing created. A GUI re-previews
265
+ and re-confirms; it never second-guesses names or paths. Without the flag the
266
+ CLI keeps its legacy auto-suffix for humans.
267
+ - **Bounded preflight**: every native probe a preview runs (`pi --list-models`,
268
+ `pi list`, `claude plugin list`) shares ONE budget (20 s default), runs in its
269
+ own process group and is group-killed on timeout; `preflight {status:
270
+ complete|timeout, budgetMs, elapsedMs}` says which. A hanging runtime cannot
271
+ hang a preview.
272
+ - Still absent (named follow-ups, not parity-done): attach-knowledge node refs
273
+ (provider contract), auto-PR (P1/ADE write approval), branch enumeration
274
+ (producer seam).
275
+
276
+ ## Readiness quartet, signatures, enforced policy (`oats readiness`, `readinessApi: 1`, OATS 0.24.8+)
277
+
278
+ `oats readiness [--soul <name>] [--home <abs>] [--verify-signatures] [--policy] [--dir <d>] --json`
279
+ is the first-run readiness view (frame 09) and the Capabilities readiness rows
280
+ (frame 04). Every fact is derived from the **same** data `oats inspect` reports
281
+ — never a second opinion — and rolled into four checks:
282
+
283
+ ```json
284
+ {"readinessApi":1,"subject":{"kind":"soul","name":"dev"},"at":"<iso>",
285
+ "checks":{
286
+ "installed": {"status":"pass","items":[{"subject":"oats.core","status":"pass","required":true,"reason":null,"producer":"oats list","evidence":{"version":"1.1.3","integrity":"sha256-…","origin":"installed"},"remedy":null}]},
287
+ "trusted": {"status":"fail","items":[{"subject":"oats.core","status":"fail","required":true,"reason":"executable surface not approved","producer":"artifact approval","evidence":{"integrity":"sha256-…"},"remedy":"oats trust oats.core",
288
+ "signature":{"status":"unknown","signer":null,"reason":"signature verification needs a network fetch; pass --verify-signatures"}}]},
289
+ "configured":{"status":"pass","items":[{"subject":"oats.core activation","status":"pass","required":true,"producer":"oats-config.yaml","evidence":{"target":"declared","level":"/abs"},"remedy":null}]},
290
+ "enrolled": {"status":"not-applicable","items":[{"subject":"workspace membership","status":"not-applicable","required":false,"producer":"oats.yaml","reason":"standalone deployment: no workspace declared in oats.yaml"}]}},
291
+ "summary":{"ready":false,"required":3,"pass":2,"fail":1,"unknown":0},
292
+ "notes":["…"]}
293
+ ```
294
+
295
+ - Each check is `pass | fail | unknown | not-applicable`; items carry
296
+ `subject, status, required, reason, producer, evidence, remedy`. **"Ready" is
297
+ `summary.ready`**: every *required* item passes (or is not-applicable) and
298
+ there is at least one required item — never inferred from an empty set.
299
+ - **`installed`**: artifact present, locked, integrity matches. **`trusted`**:
300
+ executable approval of the exact artifact (`oats trust`). Separately,
301
+ `signature {status: verified | unsigned | unknown | invalid | not-applicable,
302
+ signer: {id, label} | null, reason}` — the source commit's **verified Git
303
+ signature**, named signer or nothing. It is `unknown` unless
304
+ `--verify-signatures` (a network fetch of that one commit; `git log %G?`);
305
+ a catalog URL, repository owner or byte hash is never a signer. Render
306
+ "Trusted · signed by <label>" only for `verified`.
307
+ - **`configured`**: activation for the subject, runtime-package requirements
308
+ (`missingRequires`), runtime-settings problems. **`enrolled`**: workspace
309
+ **member admission** (decision §3) — `not-applicable` for a standalone
310
+ deployment (no `workspace:` in `oats.yaml`), `unknown` until admission is
311
+ verified against the workspace observation, `pass`/`fail` when it is. Never
312
+ login, never team registration; "Skip" leaves it not-applicable, never pass.
313
+ - Subject: `--soul <name>` scopes required items to the soul's declared
314
+ requirements; without it, to the scope's active capabilities.
315
+
316
+ `--policy` adds the **enforced** policy view with origins:
317
+
318
+ ```json
319
+ "policy":{"childSpawns":{"allowed":false,"enforced":true,"origin":{"kind":"soul","detail":"children.spawn: false in soul.yaml"}},
320
+ "worktrees":{"allowed":true,"mode":"worktree","enforced":true,"origin":{"kind":"work-mode","detail":"work: worktree"}}}
321
+ ```
322
+
323
+ `childSpawns` is **enforced by the spawn route**: `soul.yaml` may declare
324
+ `children: {spawn: false}`; `oats spawn --allow-child-spawns | --no-child-spawns`
325
+ overrides per spawn; the result is recorded in `instance.json`
326
+ `policy.childSpawns {allowed, origin}`. A spawn with `--parent <p>` (or
327
+ `--relation child --relative-to <p>`) under a parent whose recorded policy is
328
+ off refuses **`E_CHILD_SPAWNS_DISABLED`** (`details.parent`, `details.policy`)
329
+ before anything is created. Absent policy (pre-0.24.8 instances) = allowed,
330
+ reported as `origin.kind: "default"`. With `--home <abs>` the policy is the
331
+ instance's recorded (enforced) one; with only `--soul` it is the declaration
332
+ (`enforced: false`). It is a lifecycle-authority claim, not an OS sandbox —
333
+ the UI says so.
334
+
335
+ ### Slice-5 producer pins (0.24.9+; all additive — gate items on field presence)
336
+
337
+ - **`configured` is EFFECTIVE activation.** `activation.enabled` is the resolved
338
+ verdict for the subject; a capability *declared* for the soul but disabled is
339
+ `fail` with reason `declared for soul <n> but disabled (…)`. Declaration is
340
+ never activation.
341
+ - **Trust is not-applicable for data-only capabilities.** The inspect row now
342
+ carries `health.executableSurface` (manifest commands/hooks/launch env — what
343
+ `oats trust` approves). No surface → `trusted` item `not-applicable`, reason
344
+ `no executable surface`, whatever the lock records. This is why a fresh
345
+ `oats trust <package> --all-capabilities` "skipped" `oats.core` and readiness
346
+ still said fail before 0.24.9.
347
+ - **Typed linkage on every item**: `capability {id, level, scope}` and
348
+ `origin {kind: requires|declares|default|inventory, target}`; plus
349
+ `summary.byCapability[] {capability, origin, required, checks{installed,
350
+ trusted, configured, enrolled}, ownReady, ready}` — the SAME items regrouped,
351
+ no second observation. `ownReady` is the capability's own four verdicts;
352
+ `ready` is `ownReady` AND no **subject-level blocker** — items that belong to
353
+ no capability (workspace membership, soul declarations) are listed in
354
+ `summary.subjectBlockers[] {check, subject, status}` and block every row.
355
+ A per-capability row never says ready while the subject is blocked, and a
356
+ row's verdict is never promoted to the subject's `summary.ready`. Render
357
+ per-capability rows from this; never parse subjects.
358
+ - **Selector echo**: `subject.selector` = the arguments **as given, byte-exact,
359
+ no realpath** — `{kind:"scope", dir|null}` · `{kind:"soul", soul,
360
+ agentsRoot|null, dir|null}` · `{kind:"home", home, soul, agentsRoot|null}`.
361
+ Compare with what you sent, byte for byte; never filesystem-normalize a
362
+ response path. The canonical scope is `subject.context` (may differ from
363
+ `dir`, e.g. `/var` vs `/private/var` on macOS).
364
+ - **Unreadable member document** (`oats.yaml` unreadable, or `workspace:`
365
+ present but not a mapping) → `enrolled` item `unknown` with
366
+ `evidence.file`, never `not-applicable`. A declared backlink stays `unknown`
367
+ with reason `reciprocal admission not observed …` until the CLI fetches the
368
+ workspace's members (K11).
369
+ - **Captured homes refuse**: `readiness --home <captured>` →
370
+ `E_UNSUPPORTED_MODE` (`details.captured: true`) before any current-config
371
+ interpretation.
372
+ - **`--agents-root <abs>`** is accepted with `--soul` (and with `--home`), as
373
+ inspect takes it — pin the exact root you admitted.
374
+ - **Signature verification (feature `readiness-verify`)**: `--verify-signatures`
375
+ is bounded custody — **one total budget per readiness read** (120 s default)
376
+ shared by every capability's fetch and verify (an exhausted budget refuses the
377
+ remaining capabilities with `budget-exhausted`, no fetch), each Git child in
378
+ its own process group and the **whole group** SIGKILLed on timeout or failure,
379
+ scratch repositories removed on normal exit and on SIGINT/SIGTERM/SIGHUP, `GIT_CONFIG_GLOBAL
380
+ =/dev/null` + no system config + no prompts/askpass, **only https/ssh**
381
+ transports. `signature.failure` is `null` or `{code}` from the closed set
382
+ `transport-not-allowed | fetch-failed | fetch-timeout | budget-exhausted |
383
+ verifier-failed | verifier-timeout | cannot-check`; `signature.reason` is a
384
+ fixed sentence, **never stderr**. Gate the *Verify signatures…* action on the
385
+ feature name; keep it an explicit user action.
386
+
387
+ ## Lifecycle plans — Stop and Remove (`lifecycleApi: 1`, OATS 0.24.8+)
388
+
389
+ The Desktop's Stop and Remove confirmations render **plans**: a read-only
390
+ statement of what the action would touch, with the facts a human needs, and a
391
+ `planRevision` hashed from the facts that make the action safe. Apply carries
392
+ the revision back; if reality moved, apply **refuses with the fresh plan**
393
+ (`E_PLAN_STALE`, `details.plan`) instead of acting on a world the human did
394
+ not see. An `idempotencyKey` makes a retried apply return the first receipt.
395
+
396
+ ### `oats instance stop <instance> --plan [--no-recursive] [--home <abs>] [--dir <d>] --json`
397
+
398
+ ```json
399
+ {"lifecycleApi":1,"action":"stop","instance":"dev-1","home":"/abs/home","recursive":true,"at":"<iso>",
400
+ "targets":[{"instance":"dev-1-child","agent":"dev","home":"/abs/child","depth":1,"workMode":"worktree","launched":true,
401
+ "session":{"state":"unknown","present":true,"backend":"tmux","established":true},
402
+ "work":{"observed":true,"revision":"<oid>","branch":"feat/x","detached":false,"drift":false,"changed":2,"untracked":1,"upstream":{"ref":null,"ahead":null,"behind":null},"base":{"ref":"origin/main","ahead":1,"behind":0},"remote":{"host":"github.com","path":"acme/one"}},
403
+ "retiring":false,"stopPending":false,"midTask":true}],
404
+ "skipped":[],"planRevision":"<24 hex>","notes":[]}
405
+ ```
406
+
407
+ - `targets` are the instance's **recorded descendants deepest-first, then the
408
+ instance** (recorded parentage — `parentInstance` — is the only relation the
409
+ kernel knows). `--no-recursive` lists them under `skipped` instead.
410
+ - `session.state` is the backend's word: `shell`/`stopped`/`not-launched` are
411
+ idle; `unknown` means a non-shell process is running whose identity tmux
412
+ cannot name (the ordinary state of a launched harness). If the state **could
413
+ not be established**, `established:false`, `present:null`,
414
+ `state:"unestablished"`, with a `reason` — render that as unknown, never as
415
+ idle.
416
+ - `work` is K1's observation (`observed:false` with a `reason` when there is no
417
+ work tree or it cannot be read — not "clean").
418
+ - `midTask` is **reported** activity: `true` (running session or dirty work),
419
+ `false` (established idle and observed clean), or `"unknown"`.
420
+
421
+ ### `oats instance stop <instance> --apply --plan-revision <rev> --idempotency-key <key> [--no-recursive] [--grace-ms <n>] --json`
422
+
423
+ Quiesces each target (SIGTERM to the harness processes, bounded wait, **never
424
+ escalated**), children first, under a per-home stop marker; retains home, work
425
+ tree, transcript and launch configuration so `oats session restart` brings the
426
+ instance back. Refuses `E_PLAN_STALE` (fresh plan attached),
427
+ `E_INSTANCE_RETIRING`, `E_LIFECYCLE_BUSY`.
428
+
429
+ ```json
430
+ {"lifecycleApi":1,"action":"stop","instance":"dev-1","home":"/abs/home","idempotencyKey":"k","planRevision":"<rev>","at":"<iso>",
431
+ "ok":false,"results":[{"instance":"dev-1-child","home":"/abs/child","ok":false,"code":"E_SESSION_STOP_FAILED","message":"…still running after 1500 ms; nothing was escalated","stillRunning":[4242]},
432
+ {"instance":"dev-1","home":"/abs/home","ok":true,"stopped":true,"alreadyIdle":false,"state":"shell"}],
433
+ "retained":["home","work","transcript","launch"],"replayed":false}
434
+ ```
435
+
436
+ `ok:false` means at least one target is still running; the receipt says which
437
+ pid. Nothing was killed harder. A replay (`replayed:true`) is the recorded
438
+ receipt for that key, not a second action.
439
+
440
+ ### `oats retire <instance> --plan [--home <abs>] [--dir <d>] --json`
441
+
442
+ What Remove would touch, with the design's defaults. Read-only.
443
+
444
+ ```json
445
+ {"lifecycleApi":1,"action":"retire","instance":"dev-1","home":"/abs/home","at":"<iso>",
446
+ "facts":{"session":{…},"work":{…K1 summary…},"workMode":"worktree","repo":"/abs/repo","recordedBranch":"agents/dev-1",
447
+ "children":[{"instance":"dev-1-child","agent":"dev","home":"/abs/child","session":{…}}],"pullRequest":"unknown"},
448
+ "defaults":{"retainWorktree":true,"deleteBranch":false,"stopChildren":true,"retainChildren":true},
449
+ "planRevision":"<24 hex>","notes":["the worktree is on feat/x, not the recorded agents/dev-1; branch actions use the worktree's branch", "…"]}
450
+ ```
451
+
452
+ `pullRequest` is **always `"unknown"` from the kernel**: forge facts belong to
453
+ the ADE's connection (P1). Branch actions use the **worktree's** branch
454
+ (`facts.work.branch`), never `recordedBranch`.
455
+
456
+ ### `oats retire <instance> [--discard-worktree] [--delete-branch] --json` — retention is the default (K3b)
457
+
458
+ Plain `retire` now **retains** a worktree-mode instance's work: the worktree
459
+ cannot stay under the removed home, so it is **re-homed** with
460
+ `git worktree move` to `<workspace>/.agents/worktrees/<repo>/<branch>` (a
461
+ `-2`, `-3` suffix if taken; detached → `detached-<oid12>`), with staged,
462
+ unstaged and untracked state intact, and the repository knows the new
463
+ location. The receipt says so:
464
+
465
+ ```json
466
+ {"retired":"dev-1","retention":{"worktree":"retained","movedTo":"/ws/.agents/worktrees/repo/feat-x","branch":"feat/x","detachedAt":null,"recordedBranch":"agents/dev-1"},
467
+ "worktreeRemoved":false,"branchDeleted":false, "workRecovery":{…}}
468
+ ```
469
+
470
+ - `--discard-worktree` restores removal (`retention.worktree: "removed"`).
471
+ - `--delete-branch` deletes the **worktree's verified branch**
472
+ (`retention.branchDeleted`), never the recorded spawn name, and implies
473
+ discarding the worktree (a checked-out branch cannot be deleted).
474
+ - A failed move keeps the home and refuses `E_WORK_PRESERVATION_FAILED` —
475
+ nothing is lost; retry or pass `--discard-worktree`.
476
+ - Non-worktree modes report `retention: null`. Quarantine/rollback paths keep
477
+ their removal semantics.
478
+ - The Remove dialog's "also delete worktree / branch" checkboxes map to these
479
+ two flags; the kernel never touches a PR.
480
+ - **Guarded apply** (what a GUI sends): `oats retire <i> --plan-revision <rev>
481
+ --idempotency-key <key> [--discard-worktree] [--delete-branch] --json`. The
482
+ revision is revalidated against a fresh plan first — facts moved →
483
+ `E_PLAN_STALE` with `details.plan` (re-render, re-confirm; nothing retired);
484
+ a repeated key **replays** the recorded receipt (`replayed: true`, JSON-v1
485
+ envelope) instead of retiring twice. A first retire prints its raw receipt
486
+ (pre-existing shape) with `planRevision`/`idempotencyKey`/`replayed:false`
487
+ added. Mint the key server-side per confirmation intent and keep it for that
488
+ intent's retries.
489
+ - **Children first, kernel-owned.** The plan's `facts.children` are stopped
490
+ by the kernel before retirement (bounded SIGTERM, never escalated) and
491
+ retained; the receipt lists `childrenStopped[]`. A child still running
492
+ after the grace **refuses the whole retirement** — `E_CHILDREN_RUNNING`
493
+ with `details.childrenStopped` (pids) and `details.plan`; nothing retired.
494
+ - **Branch deletion is bound to the confirmed branch.** The kernel re-verifies
495
+ the worktree's branch at the moment of deletion, after hooks (which may
496
+ mutate the tree); a mismatch deletes nothing and reports
497
+ `retention.branchDeletionSkipped {expected, actual, reason}`.
498
+ - **Ambiguous parentage is reported, never acted on.** Recorded parentage is
499
+ a bare name; if a child's parent name resolves to several homes under the
500
+ root, that child appears under `ambiguous[]` — `plan.ambiguous` on a stop
501
+ plan, `plan.facts.ambiguous` on a retire plan — with the reason, and is
502
+ excluded from `targets`/`children`.
503
+ - **Stop replay horizon**: stop receipts are stored **per idempotency key**
504
+ (`<home>/.oats-stop-receipt.<key>.json`); any earlier key replays its own
505
+ receipt for as long as the home exists. Retire receipts live beside the
506
+ instances directory and replay after the home is gone.
507
+
508
+ ### Feature advertisement — gate every new command on the probe
509
+
510
+ `oats version --json` `features` now lists: `catalog`, `instance-git`,
511
+ `instance-git-remote`, `souls-declarations`, `lifecycle-plans`,
512
+ `retire-retention`, `readiness`, `spawn-preview`, `instance-events`,
513
+ `schedule-history`, and carries the API integers (`instanceGitApi`, `soulsApi`,
514
+ `lifecycleApi`, `readinessApi`, `spawnPreviewApi`, `eventsApi`,
515
+ `scheduleHistoryApi`). **Gate on these, never on a version string and never by
516
+ optimistic invocation**: an older CLI ignores an unknown `--plan` on `retire`
517
+ and *retires*. Absent feature → the view is unavailable.
518
+
519
+ ## Instruction refresh (`oats session recompose`, feature `session-recompose`, OATS 0.24.8+)
520
+
521
+ A live instance's composed `AGENTS.md` is generated at spawn and outranks any
522
+ mail or tracked file *in the running context*. When a soul changes (a role or
523
+ budget amendment) and a respawn is not possible or wanted, an operator
524
+ refreshes the home in place:
525
+
526
+ - `oats session recompose --home <abs> [--dry-run] --json` → `{home, instance,
527
+ agent, soulDir, contextDir, changed, dryRun, blocks[{source,file}], previous,
528
+ note}`. Same composer spawn used, the home's own `soul` link and recorded
529
+ context/work mode. `changed:false` is a no-op (no receipt). On change the
530
+ prior text is retained as `previous` (`<home>/.oats-agents-md.<stamp>.previous`),
531
+ `instance.json` gains `instructions[]`/`recomposedAt`, and a `recomposed`
532
+ event is appended.
533
+ - **Nothing is signalled or restarted** — the harness re-reads on its own
534
+ schedule; the receipt's `note` says so. Refuses a retiring home
535
+ (`E_INSTANCE_RETIRING`), captured incarnations and capability-defined souls
536
+ (`E_UNSUPPORTED_MODE`: those are refreshed by a new resolution / package).
537
+ - Gate on `features.includes("session-recompose")`. It is an **operator
538
+ action** (the human or the instance's parent), never something a Desktop
539
+ poll or an agent runs on itself.
151
540
 
152
541
  ## Mutations exposed to Desktop v1
153
542
 
@@ -1,6 +1,6 @@
1
1
  # OATS v0.24.7 — instance Git observation, soul declarations, and no hollow agents
2
2
 
3
- Kernel/Pi/Desktop **0.24.7**. Two additive read-only contracts for the Desktop
3
+ Kernel/Pi/Desktop **0.24.7**. Tag `v0.24.7` → commit `c923d81b` (this notes commit; the last code change is `912aaee0`, PR #57). The npm tarball's `gitHead` is `c923d81b`; the version-bump commit lands after the tag (PR #58). A from-the-tag verifier should expect `gitHead == c923d81b`. Two additive read-only contracts for the Desktop
4
4
  (`instanceGitApi: 1`, `soulsApi: 1`), one spawn refusal that closes a
5
5
  first-team-path defect, and four Desktop parity slices.
6
6
 
@@ -53,7 +53,7 @@ first-team-path defect, and four Desktop parity slices.
53
53
  which ran Git against every instance tree on every roster poll without helper
54
54
  controls and substituted healthy zeros on failure — is **removed**. Desktop
55
55
  Git reads are the K1 route only. GitHub/PR card is *unavailable* pending the
56
- `oats.git` decision.
56
+ P1 decision: forge connections are an ADE/workstation integration, not a capability (working names `oats.git`/`oats.forge` retired).
57
57
  - **3** Souls + Sources on K4: declarations, recorded provenance (`null` renders
58
58
  *Unrecorded*, never *Local*), "Not declared" vs "Not reported" distinguished,
59
59
  sources-installed ≠ Ready.