@awebai/oats 0.24.12 → 0.25.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 (52) hide show
  1. package/bin/oats.mjs +936 -2822
  2. package/docs/capabilities.md +136 -323
  3. package/docs/configuration.md +68 -533
  4. package/docs/design/2026-09-16-fresh-operator-walkthrough.md +19 -14
  5. package/docs/design/2026-09-16-portable-migration-evidence.md +2 -0
  6. package/docs/design/2026-09-16-portable-onboarding.md +4 -2
  7. package/docs/design/2026-09-20-redesign-program-board.md +1 -1
  8. package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +2 -0
  9. package/docs/design/2026-09-23-simplified-workspace-model.md +711 -0
  10. package/docs/design/2026-09-23-workspace-module-contracts.md +309 -0
  11. package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +65 -0
  12. package/docs/design/README.md +20 -8
  13. package/docs/design/operations-contract.md +1 -0
  14. package/docs/design/package-engine-contract.md +1 -1
  15. package/docs/design/package-runtime-api.md +1 -1
  16. package/docs/desktop-cli-api.md +386 -6
  17. package/docs/desktop-succession.md +3 -4
  18. package/docs/first-team.md +107 -224
  19. package/docs/implementation.md +6 -4
  20. package/docs/integrations.md +45 -44
  21. package/docs/knowledge-capability-authoring.md +10 -4
  22. package/docs/knowledge-migration.md +5 -4
  23. package/docs/knowledge-reference/package-craft.md +11 -3
  24. package/docs/knowledge.md +24 -8
  25. package/docs/layers.md +3 -3
  26. package/docs/oats-local.schema.json +50 -0
  27. package/docs/oats-membership.schema.json +23 -0
  28. package/docs/oats-workspace.schema.json +133 -48
  29. package/docs/official-marketplace.md +9 -6
  30. package/docs/packages.md +229 -440
  31. package/docs/rebuild-to-v2.md +233 -0
  32. package/docs/release-notes/v0.24.13.md +51 -0
  33. package/docs/release-notes/v0.25.0.md +99 -0
  34. package/docs/soul.schema.json +41 -68
  35. package/docs/souls-and-instances.md +175 -108
  36. package/docs/workspace-adoption.md +70 -345
  37. package/docs/workspaces.md +429 -119
  38. package/lib/core.mjs +419 -55
  39. package/lib/instance-resolution.mjs +312 -0
  40. package/lib/materialize.mjs +580 -0
  41. package/lib/packages.mjs +501 -1273
  42. package/lib/remote.mjs +639 -0
  43. package/lib/resolve.mjs +576 -0
  44. package/lib/schedule.mjs +194 -34
  45. package/lib/workspace.mjs +635 -0
  46. package/package.json +1 -1
  47. package/lib/portable-migration-artifacts.mjs +0 -135
  48. package/lib/portable-migration-evidence.mjs +0 -305
  49. package/lib/portable-migration-store.mjs +0 -199
  50. package/lib/portable-migration.mjs +0 -104
  51. package/lib/portable-onboarding-acceptance.mjs +0 -66
  52. package/lib/setup-expert-source.mjs +0 -100
@@ -0,0 +1,233 @@
1
+ # Rebuilding a 0.24.x deployment for the workspace model (0.25)
2
+
3
+ The workspace model ([workspaces.md](workspaces.md)) is a **clean v2**: no
4
+ converter, no dual-schema reader, no `oats migrate`. This guide is what ships
5
+ instead (decision 15 of `workspace-model-v2`). It is short because the new
6
+ surface is small: three shared files, one local file, one command.
7
+
8
+ ## 0. 0.24.x keeps working
9
+
10
+ A 0.24.x kernel keeps spawning 0.24.x deployments indefinitely. Nothing forces
11
+ the move: install 0.25 when you are ready to rebuild, not before. A 0.25 kernel
12
+ reads only v2 files — a 0.24 `oats-workspace.yaml` (`schemaVersion: 1`), a
13
+ `soul.yaml` with `requires:`/`source:`, an `oats.yaml`, an `oats-config.yaml` or
14
+ a lock v1/v2 is an error **naming the schema** (`E_WORKSPACE_SCHEMA "… reads
15
+ schemaVersion 2 only; found 1"`, `E_LOCK_SCHEMA`), never a silent fallback.
16
+ Keep the 0.24 kernel installed until the last 0.24 deployment you care about is
17
+ rebuilt; the two do not share files.
18
+
19
+ ## 1. Decide the one workspace
20
+
21
+ One workspace per organisation. Pick the repo that **hosts**
22
+ `oats-workspace.yaml` (a dedicated `agents` repo is common; any member can host
23
+ it). Decide the team labels you want (`global`, `engineering`, …) — labels
24
+ organise and may add defaults; they never gate anything.
25
+
26
+ **If any member is private, host the workspace file in a private repo that is
27
+ not a public member.** The workspace file names every member, so whoever can
28
+ read it sees the member list: a public host would publish the private repo's
29
+ name; hosting inside the private member hides the workspace from public
30
+ contributors entirely. A dedicated private repo (`<org>/workspace`) is the
31
+ honest shape. Public contributors who can read a public member but not the
32
+ host still get that member's souls through the standalone case (`from: here`
33
+ capabilities plus `oats.core`), so a public soul stays usable.
34
+
35
+ Two teams that need two different messaging identities (an open-source team
36
+ and a hosted-operations team, say) stay in ONE workspace: `team:` is a label,
37
+ and the provider payload is addressed by label under `messaging.byTeam` (§2).
38
+
39
+ ## 2. Write `oats-workspace.yaml` v2 in the host repo
40
+
41
+ Start from the 0.24 file and rewrite it:
42
+
43
+ | 0.24 | v2 |
44
+ |---|---|
45
+ | `schemaVersion: 1` | `schemaVersion: 2` |
46
+ | `members: [{ source: git:… }]` | `members: [git:…]` — plain refs, **no** `@revision` |
47
+ | `imports:` of your **own** repos' souls | delete — member souls are discovered by convention |
48
+ | `imports:` of a **stranger's** soul (with `revision`) | `external: [{ source: git:<repo>@<full OID>, soul: <path> }]` |
49
+ | `teams: { private: per-human }` (the messaging payload) | `messaging: { private: per-human }`; `teams:` now declares **labels** |
50
+ | `defaults.knowledge: { capability, source }` | `defaults.knowledge: { <cap>: { from: package } }` (one entry, or `none`) |
51
+ | per-soul `stores.<x>.inherit` | `stores: { <name>: git:<repo> }` once, here |
52
+ | `catalog:` | delete (bare versions use the official catalog; `OATS_PACKAGE_CATALOG` overrides) |
53
+ | — | `packages: { <id>: <version> \| git:<repo>@<ref> }` — every version your souls used to carry in `source:` lines, **once** |
54
+ | — | `defaults.capabilities: { oats.core: { from: package } }` and whatever every soul should get |
55
+
56
+ ```yaml
57
+ schemaVersion: 2
58
+ name: acme
59
+ members:
60
+ - git:github.com/acme/agents
61
+ - git:github.com/acme/platform
62
+ packages:
63
+ oats.framework: v1.1.3
64
+ oats.okf: v2.1.3
65
+ oats.aweb: v1.11.2
66
+ teams:
67
+ global: { description: Org-wide }
68
+ engineering: { description: Platform }
69
+ defaults:
70
+ capabilities: { oats.core: { from: package } }
71
+ knowledge: { oats.okf: { from: package } }
72
+ messaging: { oats.aweb: { from: package } }
73
+ tasks: none
74
+ stores:
75
+ org: git:github.com/acme/knowledge
76
+ messaging:
77
+ private: per-human
78
+ ```
79
+
80
+ No absolute paths anywhere (they belong in `oats-local.yaml`). `from:` values
81
+ that name a repo are **canonical keys** — `github.com/acme/agents`, not
82
+ `git:github.com/acme/agents` and not `https://…`.
83
+
84
+ ## 3. Add `oats-membership.yaml` to every member (replaces `oats.yaml`)
85
+
86
+ ```yaml
87
+ schemaVersion: 2
88
+ workspace: git:github.com/acme/agents
89
+ team: engineering # optional default label for this repo's souls/capabilities
90
+ ```
91
+
92
+ Delete `oats.yaml`. Its `exports:` lists are gone: every `souls/*/soul.yaml` and
93
+ `capabilities/*/oats.json` is discoverable; add `private: true` to the ones that
94
+ should stay internal. The host repo backlinks to itself like any member.
95
+
96
+ ## 4. Edit every `soul.yaml` to v2
97
+
98
+ | 0.24 | v2 |
99
+ |---|---|
100
+ | `schemaVersion: 1` | `schemaVersion: 2` |
101
+ | `requires.knowledge: { capability: oats.okf, source: git:…@v2.1.3#oats-package }` | `capabilities: { oats.okf: { from: package } }` — or nothing, if the workspace default already says so |
102
+ | `requires.capabilities.<cap>: { source: git:… }` | `<cap>: { from: package }` (published) or `<cap>: { from: here }` / `{ from: <repo key> }` (a member capability) |
103
+ | `source: repo:…` / `path:` | `{ from: here }` |
104
+ | `defaults.capabilities` | fold into `capabilities:`; use `off` to remove a workspace default |
105
+ | `stores.inherit` | delete (stores are declared once in the workspace) |
106
+ | `imports` | delete |
107
+ | `kind`, `type`, `repo`, `runtime`, `model`, `launch-config` | delete — model/runtime/launch config are spawn-time choices; `team:` replaces `type:` as the grouping |
108
+ | `knowledge:` / `messaging:` payload | keep as is (opaque provider payload); `none` empties the slot |
109
+ | — | `compatibility: { <cap>: ">=x.y" }` if you want a floor |
110
+
111
+ ```yaml
112
+ schemaVersion: 2
113
+ name: release-manager
114
+ description: Cuts, verifies and announces releases.
115
+ work: worktree
116
+ team: engineering
117
+ capabilities:
118
+ acme-release-tooling: { from: here }
119
+ knowledge:
120
+ owns: release-manager
121
+ reads: [platform-engineer]
122
+ messaging:
123
+ channels: [acme-eng]
124
+ ```
125
+
126
+ `name` must equal the soul's directory name; `name`, `description` and `work`
127
+ are required. Capabilities the repo exports live at
128
+ `capabilities/<name>/oats.json` — the manifest is unchanged; you may add
129
+ `private: true` / `team:`.
130
+
131
+ ## 5. Write `oats-local.yaml` on each machine
132
+
133
+ ```
134
+ ~/acme-workspace/ # the taught convention: "<name>-workspace"
135
+ ├── oats-local.yaml
136
+ ├── agents/ # instance homes
137
+ └── platform/ # member clones, only where someone works IN them
138
+ ```
139
+
140
+ ```yaml
141
+ schemaVersion: 2
142
+ workspace: git:github.com/acme/agents
143
+ settings: # what used to be `settings:` under capabilities.layers.* in oats-config.yaml
144
+ oats.okf:
145
+ bindings-file: /Users/ana/.oats/okf-bindings.json
146
+ state-dir: /Users/ana/.oats/okf
147
+ souls:
148
+ disabled: [data-analyst]
149
+ ```
150
+
151
+ Move host paths from `oats-config.yaml` `settings:` here; the `souls:` blocks of
152
+ `oats-config.yaml` become `--provider` flags at spawn (step 8). Delete
153
+ `oats-config.yaml`; it is not read. Do not commit `oats-local.yaml`.
154
+ (`oats onboard <dir> --workspace <repo ref>` writes a minimal `oats-local.yaml`
155
+ and runs the first `sync` for you; add `settings:` afterwards.)
156
+
157
+ ## 6. `oats sync`
158
+
159
+ From the deployment directory:
160
+
161
+ ```
162
+ oats sync
163
+ ```
164
+
165
+ It confirms every member (fix any `no-backlink` / `backlink-elsewhere` /
166
+ `cannot-read` row before going on), resolves `packages:` to commits, writes
167
+ `oats-lock.json` (lockfileVersion 3) and asks for executable approval once per
168
+ package version. The 0.24 lock is not read; delete it (`E_LOCK_SCHEMA` names
169
+ it if you leave it in the way).
170
+
171
+ ## 7. Approve packages
172
+
173
+ Approval is **per package version, once, in the lock** — no `oats trust`, no
174
+ per-capability approval, no per-operator trust list. `oats sync` on a terminal
175
+ prints every executable (`commands.*` and `hooks.*.command` targets of every
176
+ capability the package provides) and asks `approve <id> <version>? [y/N]`.
177
+ Declined or non-interactive → exit `2`, the lock records the entry
178
+ unapproved, and spawns of souls using it are refused (`E_PACKAGE_UNAPPROVED`)
179
+ until you run `oats sync` in a terminal and say yes. Member capabilities need no
180
+ approval: membership is the trust.
181
+
182
+ ## 8. Re-take a retained messaging seat with `spawn --provider`
183
+
184
+ In 0.24, an instance-specific messaging identity (a retained seat) was pinned in
185
+ `oats-config.yaml` under `souls:`. That home is gone; the fact belongs to the
186
+ **spawn**:
187
+
188
+ ```bash
189
+ oats spawn release-manager --purpose seat --provider oats.aweb identity.source=retained:release-seat
190
+ ```
191
+
192
+ `--provider <cap> key=value` is repeatable; dotted keys nest. The payload is
193
+ merged after the soul's `messaging:` and the machine's `settings.oats.aweb`, and
194
+ recorded in `instance.json.providers.oats.aweb`, so exactly one instance holds
195
+ the seat while other instances of the soul mint fresh identities. Consult your
196
+ messaging capability's documentation for the exact key it reads. The Desktop's
197
+ confirmed apply carries the same map.
198
+
199
+ ## 9. Spawn, and check drift
200
+
201
+ ```bash
202
+ oats souls # every non-private soul of every confirmed member, with origin and team
203
+ oats capabilities # every capability, member (origin: member <key> @ <commit>) or package (package <id> v<ver>)
204
+ oats spawn <soul> --preview # modules[] with from/commit/changedSince, team, resolution revision
205
+ oats spawn <soul> --purpose x
206
+ oats status # per instance: modules … [member moved since (now @ …)] / [capability no longer present]
207
+ ```
208
+
209
+ ## What disappears
210
+
211
+ | Gone | Replaced by |
212
+ |---|---|
213
+ | `oats-config.yaml` (and the laptop/workspace/repo config chain, `agent-types`, `capabilities.layers`/`additive`, `souls:`, adopted config templates) | `oats-workspace.yaml` defaults + `soul.yaml` `capabilities:`; `oats-local.yaml` for host settings; `spawn --provider` for per-instance facts |
214
+ | `oats.yaml` | `oats-membership.yaml` |
215
+ | `.agents/capabilities/installed/` and `owned/` | nothing is installed; `<instance>/.oats/modules/<cap>/` per instance; member capabilities under `<repo>/capabilities/` |
216
+ | `oats init`, `oats use`, `oats install`, `oats restore`, `oats trust`, `oats list`, `oats catalog`, `oats remove`, `oats migrate`, `oats config` | `oats sync`, `oats package add \| remove`, `oats workspace status`, `oats capabilities`, `oats souls` — each removed verb answers `E_UNKNOWN_COMMAND` naming its replacement |
217
+ | lock v1 / v2 | lock v3 (`packages` only, with `url`, `capabilities`, `approved`) |
218
+ | per-soul `source: git:…@v#…`, `repo:`, `path:` | `from: here \| <repo key> \| package` + `packages:` in the workspace |
219
+ | `imports:` of member souls, `exports:` lists | discovery by convention; `private: true` |
220
+ | `stores.<x>.inherit` | `stores:` in the workspace |
221
+ | `teams:` as the messaging payload | `messaging:`; `teams:` are labels |
222
+ | `@revision` on members | none — members are latest; frozen content is a package |
223
+ | ambient-skill exclusion at launch | the harness starts normally; capability skills are copied to `.agents/skills/<cap>/` |
224
+
225
+ ## What is kept
226
+
227
+ Kernel-neutral provider payloads and the `binding` contract; per-version
228
+ executable approval (now in the lock); spawn preview / confirmed apply
229
+ (`decision.revision`, now binding the resolution revision) and idempotency;
230
+ retirement and retention; the official catalog; the canonical-plus-alias
231
+ instance construction (`CLAUDE.md → AGENTS.md`, `.claude/skills →
232
+ ../.agents/skills`); every published Desktop CLI contract, extended as described
233
+ in [desktop-cli-api.md](desktop-cli-api.md#workspace-model-workspaceapi-2).
@@ -0,0 +1,51 @@
1
+ # OATS v0.24.13 — schedule history API 3 and the Desktop schedules table
2
+
3
+ Kernel/Pi/Desktop **0.24.13**. Tag `v0.24.13` → the commit carrying these
4
+ notes; the version-bump commit lands after the tag. Consumers gate on
5
+ `oats version --json` `features[]` names and API integers — never on the version.
6
+
7
+ ## Kernel — `schedule-read-2` (`scheduleHistoryApi: 3`; `scheduleApi` stays 2)
8
+
9
+ `oats schedule list|show` history had four defects found by the Desktop
10
+ engineer's exact-source review; each is closed under a new advertised name.
11
+
12
+ - **A run's identity is when it was scheduled and started, never its outcome.**
13
+ `runId = sha256(scheduledFor|startedAt|attemptId)[0:24]`; a run's later facts
14
+ update its one row; `transitions[]` keeps the outcome sequence
15
+ (`["started","unknown","ended"]` is one run, not three); `settled`,
16
+ `recordedAt`. Pre-API-3 rows are returned `legacy: true` with `runId: null`
17
+ and are never merged.
18
+ - **Bounded, descriptor-safe state.** Both scope files are `lstat`ed (regular
19
+ file only), opened `O_NOFOLLOW|O_NONBLOCK`, `fstat`-verified (dev+ino) and
20
+ read whole **only within a 1 MiB budget** — over budget is a typed
21
+ `E_SCHEDULE_STATE_OVERSIZE` refusal, never truncated JSON. `list` carries
22
+ `integrity.sources[]`; history is capped at 50 rows at read
23
+ (`history: {status, stored, truncated}`); one job's corrupt history or bad
24
+ identity is its own row and never fails the others.
25
+ - **Subject truth.** `list`/`show` echo the resolved `scope` and canonical `id`;
26
+ a definition whose own `id` differs from its key → `E_SCHEDULE_IDENTITY`; ids
27
+ are validated before any read. Typed refusal details travel through the CLI's
28
+ JSON failure.
29
+ - **Session provenance, never a transcript.** The `transcript` pointer named a
30
+ reader that does not exist and is gone. Every run carries
31
+ `session: {instance|null, home|null, incarnation|null, server|null, delivery}`
32
+ — what the recorder knew at write time. A read-only transcript verb is a
33
+ separate seam (K12), not implied by this API.
34
+
35
+ ## Desktop
36
+
37
+ - **Schedules** view: the compact table (enabled, schedule, target, cadence,
38
+ next run, last reported outcome, actions) with **Recent runs** for the
39
+ workspace (≤50), read once on entry and on explicit Refresh — no polling.
40
+ History enables nothing; `ended` is not success and `delivered` is not
41
+ consumption; missing time facts are shown as unreported. Session provenance
42
+ is displayed with a precise unavailable reason — no transcript, terminal or
43
+ link. Remote history and editing a captured wake are shown as honest
44
+ limitations. Gated on `scheduleHistoryApi === 3` and `schedule-read-2`.
45
+ - **Security**: the command-running `GET /api/schedules` is removed (405, no
46
+ command, no default workspace); legacy POST `list|show` aliases run through
47
+ the same strict admission, budgets and two coalesced read slots.
48
+
49
+ ## Upgrade
50
+
51
+ `npm i -g @awebai/oats@0.24.13`, then `oats doctor`.
@@ -0,0 +1,99 @@
1
+ # OATS v0.25.0 — the workspace model (breaking)
2
+
3
+ Kernel/Pi **0.25.0**. Tag `v0.25.0` → the commit carrying these notes; the
4
+ version-bump commit lands after the tag. Consumers gate on `oats version --json`
5
+ `features[]` names and API integers — never on the version.
6
+
7
+ **This is the 0.25 line: a new deployment model, not a patch to the old one.**
8
+ 0.24.x keeps spawning 0.24.x deployments; there is no converter. The rebuild
9
+ guide is `docs/rebuild-to-v2.md`. Design: `docs/design/2026-09-23-simplified-workspace-model.md`;
10
+ decision record (26 decisions): `agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md`;
11
+ normative module contracts: `docs/design/2026-09-23-workspace-module-contracts.md`.
12
+
13
+ ## The one idea
14
+
15
+ An organisation has **one workspace**: a Git repo hosting `oats-workspace.yaml`
16
+ that lists its **member repositories**, pins its **packages**, names its
17
+ **teams** and declares **defaults**. Every member carries an
18
+ `oats-membership.yaml` back-link; the handshake is confirmed over the Git
19
+ remotes in the operator's own access context and **membership is the trust**.
20
+ A member exports **souls** (`souls/<name>/soul.yaml`) and **capabilities**
21
+ (`capabilities/<name>/oats.json`) at its latest commit; a soul says where each
22
+ capability comes from (`from: <member repo> | here | package`). Packages are
23
+ consumed only through `packages:` + `oats-lock.json` v3, with **per-version
24
+ executable approval**. A spawn **resolves** the soul (`lib/resolve.mjs`) and
25
+ **materializes** every capability whole into the instance
26
+ (`<home>/.oats/modules/<cap>/`, skills under `<home>/.agents/skills/<cap>/`);
27
+ the harness starts normally. Nothing is installed anywhere.
28
+
29
+ ## Kernel — `workspace-v2` (`workspaceApi: 2`), `instance-modules`, `spawn-provider-payload`
30
+
31
+ - **Declarations** (`docs/*.schema.json`, all `schemaVersion: 2`):
32
+ `oats-workspace.yaml` (members, packages, teams, defaults incl. `byTeam`,
33
+ stores, `messaging` incl. `byTeam.<label>`, external souls),
34
+ `oats-membership.yaml` (`workspace`, `team?`), `soul.yaml` v2
35
+ (`capabilities: { <cap>: { from } | off }`, `team`, `private`,
36
+ `compatibility`, slot payloads), `oats-local.yaml` (the ONLY per-machine
37
+ file: `workspace:` or `standalone:` ref, `clones`, `settings.<cap>`,
38
+ `souls.disabled`).
39
+ - **`oats sync`** — discover → confirm membership → resolve packages → write
40
+ `oats-lock.json` v3; unapproved executables listed, exit 2 non-TTY / prompt
41
+ on a TTY. `oats package add|remove`, `oats workspace status`,
42
+ `oats capabilities`, `oats souls` (origin + team columns).
43
+ - **`oats spawn`** — discovers and resolves over the remotes, fetches the soul
44
+ from its member repo (refreshed per commit), materializes, launches.
45
+ `--preview` works before any apply and reports `modules[]` (with
46
+ `changedSince`), `team`, `resolution`, `workspace`, `soulFetched`; the
47
+ decision binds the resolution revision (a member that moved between preview
48
+ and apply → `E_DECISION_STALE`). `--provider <cap> k=v` records a per-spawn
49
+ provider payload (`instance.json.providers`) — the third payload home after
50
+ the soul and `oats-local.yaml settings`.
51
+ - **`instance.json`** carries `modules{}` (name → from/commit/digest),
52
+ `providers{}`, `workspace{ key, commit, resolution, standalone, soul }`,
53
+ `capabilities[]`.
54
+ - **`oats status`** shows drift per instance (`member moved since …`,
55
+ `capability no longer present`); `--json` `instances[].modules[]`.
56
+ - **`oats onboard [<dir>] --workspace <ref>`** — writes `oats-local.yaml`,
57
+ runs the sync path, prints the taught layout and the **hosting rule** for
58
+ mixed public/private organisations (`onboardApi: 2`). Creates no soul,
59
+ spawns nothing.
60
+ - **Standalone case** — a member whose workspace cannot be read (access
61
+ failure only, never network/timeout) still offers its souls with `from: here`
62
+ capabilities **plus `oats.core`** from the official catalog through the
63
+ operator's own lock; marked `standalone: true` in sync/status/spawn output.
64
+ - **Teams** are labels; per-team provider payload is `messaging.byTeam.<label>`
65
+ (merged by the kernel, stripped before the provider). A store names a
66
+ repository; the root inside it is the provider's binding key.
67
+ - **Scheduled spawns** on a workspace deployment materialize exactly like
68
+ `oats spawn` (the scheduler delegates to the CLI).
69
+ - **Security** (found by adversarial review, all pinned): remote tree names
70
+ fsck'd (no traversal), tag naming a blob refused as a commit, symlinks in
71
+ fetched trees refused except the soul's `CLAUDE.md → AGENTS.md` alias,
72
+ `__proto__`/`constructor`/`prototype` refused at every payload layer and in
73
+ `--provider` flags, `byTeam` reserved outside `workspace.messaging`, package
74
+ executables gated on per-version approval, atomic staging with full
75
+ rollback (no half homes).
76
+
77
+ ## Removed
78
+
79
+ `oats init`, `use`, `install`, `trust`, `list`, `remove`, `migrate`, `catalog`,
80
+ `update`, `config`, `inject` → typed `E_UNKNOWN_COMMAND` naming the
81
+ replacement. `oats-config.yaml` is no longer read as configuration (a leftover
82
+ one is harmless); the `installed/` tier, `oats.yaml`, `lockfileVersion` 2,
83
+ per-soul `source: git:…` lines, `stores.inherit`, `imports`. The `catalog`
84
+ feature name is gone from `version --json`. `docs/configuration.md` now
85
+ describes `oats-local.yaml` only.
86
+
87
+ ## Desktop
88
+
89
+ The Desktop server's `oats catalog` reader now surfaces `E_USAGE` (the verb is
90
+ removed); the Capabilities view's package acquisition flow will follow the new
91
+ DTOs in a later release (Phase F). Everything else in the Desktop is unchanged.
92
+
93
+ ## Known follow-ups
94
+
95
+ - The classic (no `oats-local.yaml`) spawn path still runs the pre-v2 compose
96
+ in `lib/core.mjs`; folding it into the one pipeline removes the remaining v1
97
+ modules (residue table in `docs/design/2026-09-23-workspace-v2-implementation-plan.md`).
98
+ - The OATS framework repositories themselves convert to the model (six expert
99
+ souls, `oats.core`/`oats.setup` rewritten for the new architecture) in 0.26.
@@ -1,82 +1,55 @@
1
1
  {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://oats.dev/schemas/soul-v1.json",
4
- "title": "Portable soul declaration v1",
5
- "description": "Authored shape only. The shared source codec, provider codec and preparation transaction enforce source semantics, containment, requirements and readiness.",
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://oats.dev/schemas/soul-v2.json",
4
+ "title": "Soul declaration v2 (soul.yaml)",
5
+ "description": "Authored shape only. A soul names each capability WITH WHERE IT COMES FROM (`from: here | <repo key> | package`) — a location, never a version. Provider payloads (knowledge, messaging, tasks) are opaque to the kernel; `none` empties the slot. Membership, privacy and team semantics are enforced by discovery/resolution, not here.",
6
6
  "type": "object",
7
- "required": ["schemaVersion", "name"],
7
+ "required": ["schemaVersion", "name", "description", "work"],
8
8
  "additionalProperties": false,
9
9
  "properties": {
10
- "schemaVersion": { "const": 1 },
11
- "name": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
10
+ "schemaVersion": { "const": 2 },
11
+ "name": { "$ref": "#/$defs/slug" },
12
12
  "description": { "type": "string" },
13
- "requires": { "$ref": "#/$defs/requirements" },
14
- "defaults": { "$ref": "#/$defs/defaults" },
15
- "knowledge": {
13
+ "work": { "enum": ["worktree", "checkout", "directory", "workspace"] },
14
+ "team": { "$ref": "#/$defs/label", "description": "Team label; overrides the repo's default from oats-membership.yaml." },
15
+ "private": { "type": "boolean", "description": "true → not discoverable in the workspace; usable only from its own repo." },
16
+ "capabilities": {
16
17
  "type": "object",
17
- "required": ["contract", "version", "payload"],
18
- "additionalProperties": false,
19
- "properties": {
20
- "contract": { "type": "string", "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]*$" },
21
- "version": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 },
22
- "payload": {}
23
- }
24
- },
25
- "teams": {
26
- "type": "array", "uniqueItems": true,
27
- "items": { "type": "string", "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]*$" }
18
+ "propertyNames": { "$ref": "#/$defs/capabilityName" },
19
+ "additionalProperties": { "$ref": "#/$defs/capabilityChoice" }
28
20
  },
29
- "resources": {
30
- "type": "array", "uniqueItems": true,
31
- "items": { "type": "string", "pattern": "^(?:\\.|(?![A-Za-z]:)(?!.*(?:^|/)\\.{1,2}(?:/|$))[^/\\\\\u0000]+(?:/[^/\\\\\u0000]+)*)$" }
32
- },
33
- "work": { "enum": ["worktree", "checkout", "attached", "workspace", "directory"] },
34
- "runtime": { "enum": ["pi", "claude", "codex"] },
35
- "model": { "type": "string", "minLength": 1 },
36
- "launch-config": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$" },
37
- "backend": { "enum": ["tmux", "herdr"] },
38
- "yolo": { "type": "boolean" }
21
+ "knowledge": { "$ref": "#/$defs/slotPayload" },
22
+ "messaging": { "$ref": "#/$defs/slotPayload" },
23
+ "tasks": { "$ref": "#/$defs/slotPayload" },
24
+ "compatibility": {
25
+ "type": "object",
26
+ "propertyNames": { "$ref": "#/$defs/capabilityName" },
27
+ "additionalProperties": { "type": "string", "minLength": 1 },
28
+ "description": "Optional FLOORS on package versions (semver ranges) — constraints, not sources."
29
+ }
39
30
  },
40
31
  "$defs": {
41
- "source": { "type": "string", "pattern": "^(git|repo|path):.+$" },
42
- "capability": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]*$" },
43
- "selection": {
44
- "type": "object", "required": ["source"], "additionalProperties": false,
45
- "properties": { "source": { "$ref": "#/$defs/source" }, "settings": { "type": "object" } }
46
- },
47
- "provider": {
48
- "type": "object", "required": ["capability", "source"], "additionalProperties": false,
49
- "properties": {
50
- "capability": { "$ref": "#/$defs/capability" },
51
- "source": { "$ref": "#/$defs/source" },
52
- "settings": { "type": "object" }
53
- }
32
+ "slug": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
33
+ "label": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]*$" },
34
+ "capabilityName": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]*$" },
35
+ "repoKey": { "type": "string", "pattern": "^[^\\s@/][^\\s@]*/[^\\s@]+$" },
36
+ "fromLocation": {
37
+ "anyOf": [
38
+ { "const": "package" },
39
+ { "const": "here" },
40
+ { "$ref": "#/$defs/repoKey" }
41
+ ]
54
42
  },
55
- "requiredProvider": { "anyOf": [{ "const": "any" }, { "$ref": "#/$defs/provider" }] },
56
- "defaultProvider": { "anyOf": [{ "const": "none" }, { "$ref": "#/$defs/provider" }] },
57
- "requirements": {
58
- "type": "object", "additionalProperties": false,
59
- "properties": {
60
- "capabilities": {
61
- "type": "object", "additionalProperties": false,
62
- "patternProperties": { "^[a-z0-9][a-z0-9._-]*$": { "$ref": "#/$defs/selection" } }
63
- },
64
- "knowledge": { "$ref": "#/$defs/requiredProvider" },
65
- "messaging": { "$ref": "#/$defs/requiredProvider" },
66
- "tasks": { "$ref": "#/$defs/requiredProvider" }
67
- }
43
+ "capabilityRef": {
44
+ "type": "object",
45
+ "required": ["from"],
46
+ "additionalProperties": false,
47
+ "properties": { "from": { "$ref": "#/$defs/fromLocation" } }
68
48
  },
69
- "defaults": {
70
- "type": "object", "additionalProperties": false,
71
- "properties": {
72
- "capabilities": {
73
- "type": "object", "additionalProperties": false,
74
- "patternProperties": { "^[a-z0-9][a-z0-9._-]*$": { "anyOf": [{ "$ref": "#/$defs/selection" }, { "const": false }] } }
75
- },
76
- "knowledge": { "$ref": "#/$defs/defaultProvider" },
77
- "messaging": { "$ref": "#/$defs/defaultProvider" },
78
- "tasks": { "$ref": "#/$defs/defaultProvider" }
79
- }
49
+ "capabilityChoice": { "anyOf": [{ "$ref": "#/$defs/capabilityRef" }, { "const": "off" }] },
50
+ "slotPayload": {
51
+ "anyOf": [{ "const": "none" }, { "type": "object" }],
52
+ "description": "Opaque provider payload, or `none` to leave the slot empty."
80
53
  }
81
54
  }
82
55
  }