@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
package/docs/packages.md CHANGED
@@ -1,478 +1,267 @@
1
- # Distribution packages — capabilities, config templates, and host requirements
1
+ # Packages — the versioned tier
2
2
 
3
- An **OATS distribution package** is the acquire, update, and review unit above
4
- capabilities. It is *transport*, not the installed entity. A package is one
5
- `oats-package.json` at a package root that declares one or more **capabilities**
6
- and, optionally, one or more reference **config templates**.
3
+ A **package** is a place to fetch capabilities from *with a version attached*.
4
+ It is one of the two kinds of capability source in the
5
+ [workspace model](workspaces.md); the other — a member repo — is never
6
+ versioned. Nothing is installed: a package is resolved to an exact commit by
7
+ `oats sync`, recorded in `oats-lock.json`, approved once per version, and
8
+ **copied whole into each instance at spawn** (`<home>/.oats/modules/<cap>/`).
7
9
 
8
- The [official marketplace policy](official-marketplace.md) explains how packages
9
- join the reviewed catalog and how entries are updated or removed. Discoverable,
10
- installed and approved are separate states; a listing never grants executable trust.
10
+ Ground truth: [`oats-package.schema.json`](oats-package.schema.json) (the
11
+ package manifest), [`oats-lock-v3.schema.json`](oats-lock-v3.schema.json) (the
12
+ lock), and the module contract
13
+ [design/2026-09-23-workspace-module-contracts.md §4](design/2026-09-23-workspace-module-contracts.md).
11
14
 
12
- Acquisition stages the package in a temporary transaction directory, validates
13
- the whole selected payload, **materializes each declared capability** into
14
- `.agents/capabilities/installed/<id>/`, writes the exact lock, and discards the
15
- staging directory. There is no persistent package store. The engine side
16
- (acquisition, materialization, lock, per-capability trust) has its own contract
17
- in [`design/package-engine-contract.md`](design/package-engine-contract.md);
18
- this document covers the config side — adopting templates, whole-workspace
19
- reconciliation, and consented host-requirement installs.
15
+ ## What a package is
20
16
 
21
- A Git repository **contains** a package rather than being one. Which directory
22
- holds it is part of the source contract:
17
+ A Git repository **contains** a package at `oats-package/`:
23
18
 
24
- ```bash
25
- oats install git:github.com/org/repo@v1.0.0 # → repo's oats-package/ (the DEFAULT)
26
- oats install git:github.com/org/repo@v1.0.0#dist/oats # → repo's dist/oats/
27
- oats install git:github.com/org/repo@v1.0.0#. # → the repository ROOT
28
- oats install /repo/custom-root # local: that EXACT directory
29
19
  ```
30
-
31
- Official examples, scaffolds, and conventions use `oats-package/`. Catalog
32
- entries carry their own `path`. Local paths take no fragment and never apply the
33
- default. Only the selected subtree is installed and hashed, so repository docs,
34
- CI configuration, owner souls, and sibling packages stay outside the package's
35
- payload and integrity. One repository may ship several packages at different
36
- paths. The lock pins the selected root in its own `path` field, and only an
37
- explicit `oats update <package>` may move it. A catalog lock with an explicit
38
- selector (`catalog:oats.aweb@v1.8.0`) keeps that selector on a plain update;
39
- to advance it to another published ref, give the spec or `--to`:
40
- `oats update oats.aweb oats.aweb@v1.10.1` or `oats update oats.aweb --to v1.10.1`
41
- (same transactional path, approvals invalidated, then `oats trust`). See
42
- [`design/package-engine-contract.md` §1.1](design/package-engine-contract.md).
43
-
44
- Ground truth for the contract: [`oats-package.schema.json`](oats-package.schema.json),
45
- [`oats-lock.schema.json`](oats-lock.schema.json), and
46
- [`design/package-engine-contract.md`](design/package-engine-contract.md).
47
-
48
- ## Package is transport; capability is the installed entity
49
-
50
- Installing a package materializes **every** capability it exports. Each installed
51
- capability is a self-contained, independently hashable directory at
52
- `.agents/capabilities/installed/<capability-id>/`, containing that capability's
53
- own `oats.json`, skills, injections, commands, hooks, and any runtime closure.
54
- That directory is where you inspect installed behavior, and it is the only thing
55
- executable trust binds to.
56
-
57
- Every package must export at least one capability. Config-only and empty
58
- packages are rejected. A capability ID is unique at a scope, so two packages may
59
- not both supply the same capability there.
60
-
61
- ```text
62
- <scope>/
63
- oats-config.yaml # zero or one active config
64
- oats-lock.json # committed provenance
65
- .agents/
66
- capabilities/
67
- owned/<capability-id>/ # authored source; committed
68
- installed/<capability-id>/ # materialized artifact; gitignored
69
- config-templates/
70
- adopted/<package-id>/<template-name>/
71
- oats-config.yaml # the exact adopted base; commit-safe
72
- adoption.json # source/version/commit/path/hash
20
+ <repo>/
21
+ └── oats-package/
22
+ ├── oats-package.json # { "package": "acme.tools", "version": "0.4.0", "capabilities": ["capabilities/acme-lint", "capabilities/acme-deploy"] }
23
+ └── capabilities/
24
+ ├── acme-lint/oats.json # ordinary capability manifests (docs/capabilities.md)
25
+ └── acme-deploy/
26
+ ├── oats.json
27
+ └── bin/acme-deploy.mjs # an executable → approved once per version
73
28
  ```
74
29
 
75
- At a Git-backed scope, OATS keeps `.agents/capabilities/.gitignore` ignoring only
76
- `installed/`. Authored `owned/` capabilities and everything under
77
- `.agents/config-templates/adopted/` are meant to be reviewed and committed, so
78
- they are never ignored. Non-Git scopes use the same layout without pretending
79
- Git owns their durability.
30
+ `oats-package.json` must declare `package` and `capabilities` (a list of
31
+ directories relative to the package root, each holding an `oats.json`). A
32
+ directory entry need not equal the capability's name
33
+ (`capabilities/oats-okf` → capability `oats.okf`). A package declaring one
34
+ capability name twice, a listed directory without a manifest, or a manifest
35
+ without `capability` is `E_PACKAGE_MANIFEST`. Catalog entries may name another
36
+ `path` than `oats-package`; a `git:` ref always reads `oats-package/`.
80
37
 
81
- ## Package config templates (`oats init --package`)
38
+ ## Declaring packages — two forms, in one place
82
39
 
83
- A **config template** is a complete reference `oats-config.yaml` a package ships,
84
- named in `oats-package.json` under `configTemplates`. It is a recommended
85
- starting point, not installed policy. Adopting one is explicit and always
86
- separate from installing capabilities:
40
+ The workspace file's `packages:` map is the **only** list of versions in the
41
+ whole organisation:
87
42
 
88
- ```bash
89
- oats init --package example.engineering # official catalog id (latest)
90
- oats init --package example.engineering@1.2.0 # catalog id + pinned selector
91
- oats init --package ../engineering-oats --config minimal # local path + named template
92
- oats init --package https://example.invalid/pkg.git # git URL (default branch)
43
+ ```yaml
44
+ packages:
45
+ oats.framework: v1.1.3 # bare version → the official catalog
46
+ oats.okf: v2.1.3
47
+ acme.tools: git:github.com/acme/tools@v0.4.0 # direct ref: git:<repo>@<tag or full OID>
93
48
  ```
94
49
 
95
- `oats install <package>` never adopts a template — it materializes capabilities
96
- and reports available templates as optional follow-ups. Only `oats init --package`
97
- (and the guided `oats config adopt`) adopt one.
98
-
99
- New packages ship templates under a `config-templates/` directory and name them
100
- with the manifest's `configTemplates` map. Each package must also give every
101
- capability a dedicated self-contained root. The legacy `configs` manifest
102
- spelling and a `.` (package-root) capability root stay readable only so
103
- already-published tags remain consumable — new authoring never emits them.
104
-
105
- Behavior:
106
-
107
- - **Preview and validation first.** The template must be valid against the
108
- config schema. Every `from: installed` capability it references must be
109
- supplied by the package or its dependency closure. Layer bindings must agree
110
- with the capability manifests. Agent types must be syntactically valid. No
111
- path — injection overrides, work-mode setup scripts — may escape the target
112
- scope. A failing template is never written, and the scope is left untouched.
113
- - **Default selection.** A template marked `"default": true` is chosen when
114
- `--config` is omitted. A single template is chosen implicitly. Several
115
- unmarked templates require `--config <name>`, and refusing to guess is the
116
- point.
117
- - **Overwrite refusal.** `oats init --package` refuses when an `oats-config.yaml`
118
- already exists at the scope. Use `oats config adopt` to switch an existing
119
- scope to another template.
120
- - **The adopted base is recorded.** Adoption writes the exact template as a
121
- commit-safe base under `.agents/config-templates/adopted/<package>/<template>/`,
122
- alongside an `adoption.json` recording source, version, commit, path, and hash.
123
- Commit it — `oats config diff` and `oats config sync` compare against it. For a
124
- local `path:` source, `adoption.json` records `source: null` with
125
- `localSource: true`, so no absolute machine path leaks into the committed
126
- metadata; the exact source stays only in the authoritative lock.
127
-
128
- ### Your config is yours (adopter sovereignty)
129
-
130
- The adopted config is an **ordinary scoped config**. It is not live inheritance
131
- and not ambient package policy. `oats use`, `oats type`, `oats inject eject`, and
132
- hand edits keep their meaning, and package updates never rewrite it or the
133
- adopted base. Every capability an installed package exports stays individually
134
- addressable, so you may
135
-
136
- - **retarget** a capability from global to an agent type or soul
137
- (`oats use example.review --type reviewers`);
138
- - **disable** something the template enabled
139
- (`oats use example.review --global --disable`, or `knowledge: none` for a
140
- layer);
141
- - **re-set settings** per family (`oats use example.review --soul dev
142
- --settings depth=high`);
143
- - **replace** an exclusive-layer provider with another capability; and
144
- - **override from a nested repository** — a closer repo's `oats-config.yaml`
145
- wins per the normal cascade:
146
-
147
- ```yaml
148
- # member-repo/oats-config.yaml — this repo opts out of the workspace default
149
- name: member
150
- capabilities:
151
- layers:
152
- knowledge: none
153
- ```
154
-
155
- Nothing a package ships is mandatory. Every copied setting is fully locally
156
- editable, and the resolved local config is always authoritative.
157
-
158
- ### Guided template sync (`oats config diff | sync | adopt`)
159
-
160
- Your config and a package's template drift as you edit locally and as the
161
- package updates. Three commands manage that, and all three share one three-way
162
- comparison — the recorded **adopted base**, your current local
163
- `oats-config.yaml`, and the selected template read from the currently locked
164
- package.
50
+ - **Bare version** (`v2.1.3`, `2.1.3`, `1.0.0-rc.1`): the id is looked up in
51
+ the official catalog — `package-catalog.json` in the `oats` repo, or the file
52
+ named by `OATS_PACKAGE_CATALOG` — which supplies the repo url, the tag
53
+ convention (`v2.1.3` or `oats-framework/v1.1.3`) and the payload path. An id
54
+ the catalog does not know is `E_PACKAGE_MISSING` ("use `git:<repo>@<ref>` for
55
+ a package outside the catalog"). The catalog is the reviewed marketplace
56
+ ([official-marketplace.md](official-marketplace.md)) and the only way a
57
+ package becomes pinnable *by id*.
58
+ - **`git:<repo>@<ref>`**: `<repo>` is any repo ref the kernel understands
59
+ (`github.com/org/repo`, `https://…`, `git@host:…`, `/abs/bare.git`,
60
+ `file:///…`); `<ref>` is a tag name or a full 40-hex commit. The package is
61
+ read at `oats-package/`.
62
+
63
+ There is no third form; `lib/packages.mjs#classifyPackageValue` is the one
64
+ grammar, used by workspace validation and by `sync`. A `<ref>` (or catalog ref)
65
+ that resolves to a **branch** is refused: `E_PACKAGE_INTEGRITY { why: "branch" }`
66
+ — versions are immutable.
67
+
68
+ Souls never name versions. A soul says `acme-deploy: { from: package }`; which
69
+ package provides `acme-deploy`, and at which version, is the workspace's
70
+ decision recorded in the lock.
71
+
72
+ ## `oats sync` — the one command for the common path
165
73
 
166
- ```bash
167
- oats config diff # report only; nothing is written
168
- oats config sync # apply upstream changes; keep local edits
169
- oats config sync --accept <id>=local # resolve one conflict region in favor of local
170
- oats config sync --accept <id>=package # resolve one conflict region in favor of the template
171
- oats config sync --reset --yes # discard local changes; take the template verbatim
172
- oats config adopt other.package --config default # switch to a different base
74
+ ```
75
+ $ oats sync
76
+ workspace acme (github.com/acme/agents @ 3f2a9c1e)
77
+ members agents ✓↔ (@ 3f2a9c1e) platform ✓↔ (@ 77c0a1b2) tools ✓↔ (@ 47f4b816) billing ✗ (no-backlink)
78
+ packages acme.tools 0.4.0 ✓ (approval needed) oats.framework 1.1.3 ✓ (approved) oats.okf 2.1.3 ✓ (approved)
79
+ changed acme.tools — → 0.4.0 (@ 47f4b816)
80
+ souls 7 discovered (6 members, 1 external, 0 disabled here) · 1 private (platform-reviewer, platform only)
81
+ teams engineering 4 souls, 3 capabilities · global 2 souls, 2 capabilities · unassigned 1 soul
82
+
83
+ acme.tools 0.4.0 @ 47f4b816 needs executable approval (2 executables, digest sha256-7923…):
84
+ acme-deploy: command apply → bin/acme-deploy.mjs
85
+ acme-deploy: command plan → bin/acme-deploy.mjs
86
+ approve acme.tools 0.4.0? [y/N]
173
87
  ```
174
88
 
175
- - **`oats config diff`** reports how your config, the adopted base, and the
176
- package's current template differ. It classifies each region as
177
- upstream-only, local-only, or a conflict, and writes nothing.
178
- - **`oats config sync`** applies upstream-only changes and keeps local-only
179
- edits. It presents the complete plan before touching anything, preserves the
180
- untouched bytes, comments, order, and formatting of your file, and advances
181
- the adopted base only after a successful write. A recoverable `.bak` backup
182
- survives the run.
183
- - **Conflicts require an explicit choice.** A region changed both locally and
184
- upstream is a conflict. `oats config sync` never picks a side for you.
185
- Interactively it prompts per region. Noninteractively (or with `--json`) it
186
- fails with `E_SYNC_AMBIGUOUS` unless you pass `--accept <regionId>=local` or
187
- `--accept <regionId>=package` for each one.
188
- - **`oats config sync --reset`** is the exact-template replacement path. It
189
- previews every local change region it will discard, backs up the current
190
- config, then replaces both the config and the adopted-base metadata. It
191
- demands strong confirmation interactively, and `--yes` to accept the loss
192
- noninteractively.
193
- - **`oats config adopt <package> --config <name>`** switches the one local config
194
- to a different base. It rebases your config against the new template rather
195
- than creating a second config, and exactly one adopted base remains afterward.
196
-
197
- ## Workspace reconciliation (bare `oats install`)
198
-
199
- At a config scope that declares `team:`, bare `oats install` reconciles the whole
200
- workspace instead of only the ancestor chain:
201
-
202
- 1. prints the chosen boundary **before any network or host work**;
203
- 2. restores the boundary scope's locked graph;
204
- 3. discovers descendant scopes containing `oats-config.yaml` or `oats-lock.json`,
205
- in deterministic path order, pruning `.git`, generated stores (`.agents/`),
206
- dependency/vendor directories (`node_modules`, `vendor`, virtualenvs), agent
207
- instances/worktrees, `local-agents/`, **package payload** (below), and
208
- **nested team boundaries** (each is its own reconciliation unit);
209
- 4. restores each descendant scope once;
210
- 5. validates that every config-referenced installed capability is supplied by a
211
- visible locked package (or capability lock); and
212
- 6. aggregates missing requirements and failures **by scope**.
213
-
214
- **Package payload is never a scope.** A directory holding an `oats-package.json`
215
- is a package root, and everything beneath it is content the package *exports* —
216
- including the `configTemplates` files under `config-templates/`. Those templates
217
- bind layers to capabilities the adopting deployment has not installed yet, so
218
- reconciling one as a live scope would report phantom "supplied by no visible
219
- locked package" failures for the whole team. Discovery therefore excludes any
220
- candidate whose containing **ancestor** directory carries an `oats-package.json`,
221
- whatever the payload root is named — templates are never reconciled, validated,
222
- or acquired. The rule is the manifest, not the path: a repository that ships a
223
- package *and* is itself a deployment scope (its own `oats-config.yaml` at the
224
- root, with the manifest in a subdirectory) stays a scope exactly as before.
225
-
226
- At a non-team scope, bare `oats install` keeps current-chain behavior. Pass
227
- `--recursive` to request descendant reconciliation outside a team boundary — the
228
- boundary is still printed first. OATS never scans downward from the laptop/home
229
- config by default.
230
-
231
- ## Host requirements — a separate consent gate
232
-
233
- A capability `requires` entry may declare structured, platform-aware install
234
- methods (the legacy `install: "https://…"` docs URL still works):
89
+ `sync` (run from the deployment — where `oats-local.yaml` is, or `--dir`):
90
+
91
+ 1. discovers the workspace over the remotes and confirms every member;
92
+ 2. resolves each `packages:` entry to a commit (`observeRemote`), reads its
93
+ manifests, computes the **integrity** (content digest of the package tree)
94
+ and records `url`, `path`, `version`, `commit`, `integrity`, `capabilities`;
95
+ 3. for an entry already locked at the same version/source/path: the commit must
96
+ be unchanged (else `E_PACKAGE_INTEGRITY` — "the tag moved; a version string
97
+ must change when its content does"), the integrity must match, and a
98
+ recorded approval must still describe the package's executables (else
99
+ `E_PACKAGE_UNAPPROVED` — approve again);
100
+ 4. for every unapproved entry, prints the exact executables (every `commands.*`
101
+ target and every `hooks.*.command` target of every capability manifest —
102
+ hooks run unattended at spawn/retire) and asks **once** on a terminal;
103
+ 5. writes `oats-lock.json` and reports the diff. Entries dropped from
104
+ `packages:` are dropped from the lock.
105
+
106
+ Exit status `2` means the lock is written but approvals are pending
107
+ (non-interactive, or declined). Spawns of souls using an unapproved package are
108
+ refused (`E_PACKAGE_UNAPPROVED`) until `oats sync` is run in a terminal and the
109
+ approval given. `--json` emits the `syncApi: 1` envelope documented in
110
+ [desktop-cli-api.md](desktop-cli-api.md#workspace-model-workspaceapi-2).
111
+
112
+ ## `oats package add | remove`
235
113
 
236
- ```json
237
- {
238
- "command": "example-cli",
239
- "why": "send and receive team messages",
240
- "install": {
241
- "docs": "https://example.invalid/install",
242
- "methods": [
243
- { "platform": "darwin", "manager": "npm-global", "package": "@example/cli@1.2.3" }
244
- ]
245
- }
246
- }
114
+ ```bash
115
+ oats package add oats.aweb v1.11.2 # a catalog version
116
+ oats package add acme.tools git:github.com/acme/tools@v0.4.0
117
+ oats package remove acme.tools
247
118
  ```
248
119
 
249
- Rules (all enforced):
250
-
251
- - **Allowlisted managers only**: `npm-global` and `brew`
252
- (download-with-checksum is declared but not implemented yet). Recipes are
253
- data — argv arrays, never shell snippets, no sudo, no shell metacharacters, no
254
- authentication.
255
- - **Informed, per-requirement consent.** Interactive `oats install` shows the
256
- exact command, source, version, and whether it changes user- or machine-level
257
- state, then asks per requirement. A plan may take more than one command — a
258
- runtime package can need its source registered first — so both the human and
259
- `--json` renderings carry `steps`, the ordered argv sequence that will run,
260
- alongside `argv` (its final command). What you consent to is the whole
261
- sequence. Nothing runs that the plan did not show.
262
- - **Aggregation is scoped**: only capabilities *activated somewhere in the
263
- reconciled scopes* are considered, deduplicated by required command, and the
264
- report names which capabilities requested each command.
265
- - **Noninteractive runs never install by default.** Automation names each
266
- accepted requirement: `oats install --accept-requirement example-cli`.
267
- `--no-requirements` restores packages only (CI). A **consented** install that
268
- fails (manager error, or the command still absent from PATH) makes
269
- `oats install` exit nonzero so automation can detect it. Unaccepted or skipped
270
- requirements stay non-fatal.
271
- - **PATH verification** runs after each install. A tool that does not land on
272
- PATH is reported honestly.
273
- - **Skipping is safe**: `oats doctor` keeps an actionable warning (the consent
274
- command to run) until the command is on PATH.
275
- - **Trust and requirement consent are distinct gates.** Installing a binary
276
- neither activates nor approves any capability, and capability trust never
277
- authorizes host installs.
278
-
279
- When no safe recipe matches the host, OATS prints the documented install URL.
280
-
281
- ## Lock, trust, and restore
282
-
283
- The scope's `oats-lock.json` uses `lockfileVersion: 2` and records both levels of
284
- the model in separate top-level maps:
120
+ Both edit `packages:` in `oats-workspace.yaml` **when the file is tracked by
121
+ the Git checkout the command runs in** (the workspace host repo); the edit is
122
+ validated against the full workspace schema before it is written, and the
123
+ receipt tells you to commit and `oats sync`. Anywhere else — a deployment folder,
124
+ a member clone — the command prints the line to add (`--json`: `edited: false`,
125
+ `line`) because the workspace file is shared through Git, not through this
126
+ machine. Nothing network-bound happens in `package add`; `sync` resolves.
127
+
128
+ ## Lock v3
129
+
130
+ `oats-lock.json` lives beside `oats-local.yaml`. Two operators who synced the
131
+ same workspace commit and approved the same versions hold identical locks.
285
132
 
286
133
  ```json
287
134
  {
288
- "lockfileVersion": 2,
135
+ "lockfileVersion": 3,
289
136
  "packages": {
290
- "example.engineering": {
291
- "source": "git:https://example.invalid/engineering.git@v3.0.0",
292
- "version": "3.0.0",
293
- "commit": "0123456789abcdef0123456789abcdef01234567",
137
+ "oats.okf": {
138
+ "source": "catalog:oats.okf",
139
+ "url": "https://github.com/awebai/oats-okf.git",
294
140
  "path": "oats-package",
295
- "integrity": "sha256-…",
296
- "dependencies": []
297
- }
298
- },
299
- "capabilities": {
300
- "example.review": {
301
- "version": "2.1.0",
302
- "package": "example.engineering",
303
- "path": "capabilities/example-review",
304
- "integrity": "sha256-…",
305
- "trusted": false
141
+ "version": "2.1.3",
142
+ "commit": "b2e16f2ea1555be519db76fda30cd0bea06f8609",
143
+ "integrity": "sha256-1c34dbe9c1cc3826dbe6ecbafbd9a1e189ed36a74bfb2ba8fb6f46a382e95c2d",
144
+ "capabilities": ["oats.okf"],
145
+ "approved": { "executables": "sha256-0d7615fa…", "at": "2026-09-24T09:02:11.000Z" }
146
+ },
147
+ "acme.tools": {
148
+ "source": "git:github.com/acme/tools@v0.4.0",
149
+ "url": "https://github.com/acme/tools.git",
150
+ "path": "oats-package",
151
+ "version": "0.4.0",
152
+ "commit": "47f4b81660e4cc9701d373088de52462762585a3",
153
+ "integrity": "sha256-4cd126a7…",
154
+ "capabilities": ["acme-deploy", "acme-lint"],
155
+ "approved": null
306
156
  }
307
157
  }
308
158
  }
309
159
  ```
310
160
 
311
- - The `packages` map proves **where the bytes came from** — exact source,
312
- commit, selected root path, payload integrity, and package-identity
313
- dependencies. It does not describe an installed directory, because there is no
314
- persistent package store.
315
- - The `capabilities` map proves **each materialized artifact** — its version,
316
- its provider package (a key of the `packages` map), its dedicated root path
317
- inside that package, its artifact integrity, and its executable trust.
318
- - **Trust binds to the capability artifact integrity, never to package
319
- identity.** `oats trust <capability>` approves that capability's commands and
320
- hooks at exactly its current artifact integrity. Any integrity change,
321
- including `oats update`, resets `trusted` to false and forces re-review.
322
- Official catalog identity grants no executable trust, and there is no
323
- package-level approval.
324
- - Bare `oats install` fetches the exact locked source, verifies package
325
- integrity, re-materializes any missing capability artifact, verifies its
326
- individual integrity, and never advances source, version, or commit.
327
-
328
- ## Upgrading a 0.18 deployment to the official packages
329
-
330
- Deployments created before official packages existed hold ordinary
331
- `oats-config.yaml` files, **v1** `oats-lock.json` files, and acquired capability
332
- artifacts under `.agents/capabilities/installed/`. Those keep working. A valid
333
- v1 lock still restores, activates, trusts, and spawns, and installing this
334
- release migrates nothing on its own.
335
-
336
- The upgrade is one explicit, guided command, and it lands directly in the
337
- revised `lockfileVersion: 2`:
338
-
339
- ```bash
340
- oats migrate --official --recursive --dry-run --dir <team-root> # plan first
341
- oats migrate --official --recursive --dir <team-root> # apply
161
+ | field | meaning |
162
+ |---|---|
163
+ | `source` | `catalog:<id>` or `git:<repo key>@<ref>` — how the workspace asked for it |
164
+ | `url` | the repo url the package was read from; travels in the lock so spawn needs no catalog |
165
+ | `path` | the package root inside the repo |
166
+ | `version` | the version string without a leading `v` (a `git:…@<OID>` pin records the OID) |
167
+ | `commit` | full 40-hex OID the version resolved to |
168
+ | `integrity` | `sha256-<hex>` content digest of the package tree at `path` |
169
+ | `capabilities` | the capability names the package provides (sorted) — what `from: package` looks up |
170
+ | `approved` | `{ executables: "sha256-<hex>", at }` — the digest of the approved executables — or `null` |
171
+
172
+ A capability provided by **two** locked packages is ambiguous and fails
173
+ closed (`E_PACKAGE_MISSING { ambiguous: [ids] }`): keep one of them in
174
+ `packages:`. A lock that is not v3 (a 0.24 lock, an unreadable file) is
175
+ `E_LOCK_SCHEMA`; it is never auto-repaired — delete it and `oats sync`. Agents
176
+ never hand-edit the lock.
177
+
178
+ ## Approval
179
+
180
+ Member capabilities are trusted by membership; **package executables are
181
+ approved once per version**, and every instance that materializes that version
182
+ inherits the approval. What is approved is a digest over the bytes of every
183
+ executable a manifest can make the kernel run — `commands.*` targets and
184
+ `hooks.*.command` targets — in canonical order; a hook object without
185
+ `command` is `E_PACKAGE_MANIFEST`, never an invisible no-op. Skills, injects
186
+ and other files are covered by `integrity`, not by the approval.
187
+
188
+ The approval lives next to the commit it approved. A new version starts
189
+ unapproved; a moved tag fails integrity and asks again; an approval whose digest
190
+ no longer matches the tree is refused. `oats spawn` re-checks `approved` on the
191
+ way to `from: package`: reaching materialization means approved.
192
+
193
+ ## Materialization from a package
194
+
195
+ At spawn a `from: package` module is fetched at the lock's commit from the
196
+ lock's `url`, at the manifest-listed directory (`oats-package.json#capabilities[]`
197
+ entry), into `<home>/.oats/modules/<cap>/`; the copy's digest is verified
198
+ against what the fetch reported; skills are copied to
199
+ `<home>/.agents/skills/<cap>/<skill>/`. `instance.json.modules.<cap>.from` is
200
+ `{ kind: "package", package, version, commit, integrity, repoKey }`. Bumping
201
+ `packages:` and syncing affects **only new spawns**; `oats status` shows a
202
+ running instance's package module as `moved` once the lock points elsewhere.
203
+
204
+ ## Compatibility floors
205
+
206
+ A soul may state floors on package versions — constraints, not sources:
207
+
208
+ ```yaml
209
+ compatibility:
210
+ oats.okf: ">=2.1"
342
211
  ```
343
212
 
344
- - **Scope discovery** is deterministic and covers every *visible* lock-owning
345
- scope: the explicit scope's ancestor chain (so an outer repo/laptop lock the
346
- deployment actually reads is migrated too), the team boundary, and descendant
347
- config/lock scopes found with reconciliation's pruning (nested team boundaries
348
- stay self-owned). Scopes are planned and applied in path order, ancestors
349
- first. Without `--recursive` only the named scope is migrated.
350
- - **Plan first, always.** The complete per-scope plan is printed (and available
351
- as stable JSON) before anything is applied. `--dry-run` stops after it.
352
- - **Which package supplies which capability is catalog data**, never code. The
353
- catalog maps identity by default (capability `oats.okf` → package `oats.okf`)
354
- and carries explicit aliases for capabilities a package exports under another
355
- identity (`oats.review` → package `oats.dev`). See the catalog shape below.
356
- - **Config files are not rewritten.** Packages export the same capability IDs,
357
- so activation, layer bindings, targets, settings, exclusions, and injection
358
- overrides remain valid byte-for-byte.
359
- - **Held, never half-converted.** If any official capability cannot map, the
360
- whole scope stays byte-identical v1 and the run is nonzero. A `--dry-run`
361
- reports the same blocked status, so readiness cannot be mistaken for success.
362
- - **Custom entries block a mixed guided scope.** `git:`/`path:`/unknown v1
363
- sources are never acquired by `--official`. A scope containing only those
364
- entries is skipped and reports their IDs under `retained`; a scope mixing them
365
- with official capabilities is refused before any write. Plain `oats migrate`
366
- can convert custom sources only when every entry in the scope maps to a
367
- package. There is no residue container.
368
- - **One package, several capabilities.** When catalog aliases map more than one
369
- legacy capability onto the same package, all of them convert together and the
370
- package is acquired once.
371
- - **Per scope transactional.** Each scope acquires its package closure, writes
372
- a fresh revised v2 lock, and only then removes the superseded v1 artifacts. A
373
- failing scope is rolled back byte-identically. Other scopes keep their
374
- (truthfully reported) result, and the aggregate exit is nonzero.
375
- - **Trust is re-earned, never transferred.** A capability's materialized
376
- integrity is not its v1 artifact's integrity, so approvals do not carry over.
377
- The run prints the exact `oats trust <capability> --dir <scope>` commands, then
378
- the bare `oats install --dir <scope>` pass (already-installed host requirements
379
- verify and are not reinstalled; anything missing gets its
380
- `oats install --accept-requirement <cmd>` consent command).
381
-
382
- Rerunning the command after a successful migration changes nothing.
383
-
384
- ### The transitional v2 lock is not migrated
385
-
386
- An earlier, unreleased shape of `lockfileVersion: 2` stored capability lists and
387
- trust on the package rows and used a persistent `.agents/packages/installed/`
388
- store. That transitional shape receives no product migration path. The reader
389
- rejects it centrally as `invalid-lock` with actionable guidance. It is recreated
390
- by a fresh acquisition, never converted or partially interpreted. There is no
391
- `lockfileVersion: 3`. Because the transitional contract had no external
392
- adoption, the founder chose to replace it in place rather than carry a migration
393
- for it.
394
-
395
- ### Catalog shape
396
-
397
- The official catalog is data (`package-catalog.json`, or the file named by
398
- `OATS_PACKAGE_CATALOG`). The v0.23.1 integration selects these already-published
399
- sources; installing a kernel does not advance existing package locks:
213
+ Checked at resolution against the locked version (`E_COMPATIBILITY`,
214
+ naming capability, package, version and range). A package pinned by OID has no
215
+ version to check (`why: "unversioned"`): pin a tagged version.
216
+
217
+ ## Publishing a package from a member repo
218
+
219
+ A repo can be a **member** of the workspace **and** publish a package; the two
220
+ roles never collapse (see [workspaces.md](workspaces.md#member-tier-vs-package-tier-the-non-collapse-rule)):
221
+
222
+ 1. Put the package under `oats-package/` with its `oats-package.json` and
223
+ capability directories. Everything under `capabilities/` at the repo root
224
+ stays member-tier (latest state, for people working *on* the package —
225
+ typically a `<name>-dev` capability); everything under `oats-package/` is
226
+ package-tier.
227
+ 2. Add a member soul that is the expert in the package (`souls/<name>-expert/`),
228
+ ordinary and discoverable, the natural owner of the package's PRs. It eats
229
+ its own published food: `acme-lint: { from: package }` at the pinned version,
230
+ plus `acme-tools-dev: { from: here }`.
231
+ 3. Tag a release (`v0.4.0`). Tags are immutable: a new content needs a new tag.
232
+ 4. Consumers pin it: `oats package add acme.tools git:github.com/acme/tools@v0.4.0`
233
+ → commit → `oats sync` → approve once. Discovery shows the member row with
234
+ `publishes: { package: "acme.tools", version: "0.4.0" }`.
235
+ 5. To become pinnable by id, open a PR adding the package to
236
+ `package-catalog.json` in the `oats` repo ([official-marketplace.md](official-marketplace.md)).
237
+
238
+ A soul that names one of the package's capabilities with
239
+ `from: github.com/acme/tools` fails: `E_CAPABILITY_MISSING` with the hint
240
+ `provided by package acme.tools; use from: package`.
241
+
242
+ ## Catalog shape
400
243
 
401
244
  ```json
402
245
  {
246
+ "policy": "docs/official-marketplace.md",
403
247
  "packages": {
404
- "oats.okf": { "url": "https://github.com/awebai/oats-okf.git", "ref": "v2.0.0", "path": "oats-package" },
405
- "oats.framework": { "url": "https://github.com/awebai/oats.git", "ref": "oats-framework/v1.1.3", "path": "oats-package" },
406
- "oats.dev": { "url": "https://github.com/awebai/oats-dev.git", "ref": "v1.0.0", "path": "oats-package" }
407
- },
408
- "capabilities": { "oats.review": "oats.dev" }
248
+ "oats.okf": { "url": "https://github.com/awebai/oats-okf.git", "ref": "v2.1.3", "path": "oats-package" },
249
+ "oats.framework": { "url": "https://github.com/awebai/oats.git", "ref": "oats-framework/v1.1.3", "path": "oats-package" }
250
+ }
409
251
  }
410
252
  ```
411
253
 
412
- `packages` is identity and discovery only — resolving through it never advances
413
- a lock and never grants executable trust. The released kernel bundles the
414
- official awebai entries. Once a short id appears there, `oats install <id>`
415
- prefers the distribution package over the legacy bundled capability marketplace.
416
- Existing v1 locks and artifacts remain supported until you run guided migration.
417
- `capabilities` is the legacy-capability → package alias map the guided migration
418
- reads; identity mappings need no entry. An alias value may also be spelled
419
- `{ "package": "<id>" }`.
420
-
421
- ### OKF v2 and optional theory distribution
422
-
423
- The standalone OKF package exports only `oats-package/capabilities/oats-okf/`.
424
- Use its catalog Git payload after [release gates](release-notes/v0.23.1.md) pass.
425
- The framework's bundled npm mirror is not a self-contained distribution:
426
- npm drops the source worker soul's `CLAUDE.md -> AGENTS.md`. It must not be
427
- advertised as a complete local package or repaired after acquisition to evade
428
- integrity checks. Git transport preserves the canonical source alias.
429
-
430
- The `oats.framework` distribution package is a separate Git payload in this
431
- repository's `oats-package/`, excluded from the kernel npm tarball. The catalog
432
- entry selects the published `oats-framework/v1.1.3` tag, which exports three
433
- capabilities: `oats.core` (day-to-day operation: `oats-operate`, `oats-souls`
434
- and the "you run on OATS" briefing — declared explicitly on every soul by
435
- default at creation and removable), `oats.setup` (OATS Soul Setup: `oats-config`,
436
- `oats-packages`, `oats-workspace-setup`) and the optional `oats.knowledge-theory`
437
- (authoring skill and `knowledge-theory-expert`). Acquire it with
438
- `oats install oats.framework`; the capability ids also resolve through the
439
- catalog aliases. Acquiring it does not activate anything, bind a knowledge
440
- layer or add a runtime judge.
441
-
442
- Updating OKF v1 to v2 is a breaking capability change. Preserve existing
443
- knowledge and source state/cursors, explicitly bind/provision external owners,
444
- accept provider delivery and perform deliberate cutover. Kernel package/lock
445
- migration does none of this. See [knowledge migration](knowledge-migration.md).
446
-
447
- ## Doctor
448
-
449
- `oats doctor` reports, in addition to its capability diagnostics:
450
-
451
- - **Distribution packages** visible in the lock (`packages:` in
452
- `oats-lock.json`), with source and the capabilities each supplies;
453
- - **adopted config templates** in the chain — the package and template each
454
- scope adopted, its recorded base, and whether local changes have drifted from
455
- it;
456
- - **available-but-unadopted templates** — a locked, installed package exporting
457
- config templates that no scope has adopted;
458
- - **missing host commands** for active capabilities, with the exact consent
459
- command when a safe installer exists;
460
- - **official capability migration** (`officialMigration` in `--json`) when the
461
- chain still holds legacy `marketplace:` locks: each capability with the
462
- package that supplies it, and either `ready` with the exact
463
- `oats migrate --official --recursive --dir <boundary>` command, or `unavailable`
464
- with the reason — the catalog has no mapping yet and the legacy capabilities
465
- remain supported.
466
-
467
- ## Engine integration
468
-
469
- The package engine (acquisition, capability materialization, revised v2 lock,
470
- exact restore, capability indexing, per-capability trust — see
471
- [`design/package-engine-contract.md`](design/package-engine-contract.md) and
472
- [`design/package-runtime-api.md`](design/package-runtime-api.md)) is merged.
473
- `oats init --package` acquires and exact-locks the full closure through the
474
- engine's `acquirePackage` for every source kind (git, catalog, local path), then
475
- adopts exactly one template. The team-boundary reconciliation above wraps the
476
- engine's exact-restore primitive (integrity, capability, and runtime-closure
477
- verification) per scope. Legacy v1 capability locks keep restoring via the
478
- capability path and are reported as LEGACY with the `oats migrate` pointer.
254
+ `ref` carries the tag convention: a workspace's `oats.framework: v1.2.0`
255
+ resolves to tag `oats-framework/v1.2.0`. Resolving through the catalog never
256
+ grants approval and never advances a lock by itself — `oats sync` does, and
257
+ says so.
258
+
259
+ ## Removed verbs
260
+
261
+ `oats install`, `restore`, `init`, `use`, `trust`, `list`, `catalog`, `remove`,
262
+ `migrate`, `config` are gone; each answers `E_UNKNOWN_COMMAND` naming its
263
+ replacement (`details.removed` / `details.replacement` in `--json`). There is
264
+ no installed-capability directory, no config template adoption, no host
265
+ requirement installer. A manifest's `requires` still describes what must exist
266
+ on the host (runtime packages are verified at spawn; host commands are the
267
+ operator's to install). See [rebuild-to-v2.md](rebuild-to-v2.md).