@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.
- package/bin/oats.mjs +936 -2822
- package/docs/capabilities.md +136 -323
- package/docs/configuration.md +68 -533
- package/docs/design/2026-09-16-fresh-operator-walkthrough.md +19 -14
- package/docs/design/2026-09-16-portable-migration-evidence.md +2 -0
- package/docs/design/2026-09-16-portable-onboarding.md +4 -2
- package/docs/design/2026-09-20-redesign-program-board.md +1 -1
- package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +2 -0
- package/docs/design/2026-09-23-simplified-workspace-model.md +711 -0
- package/docs/design/2026-09-23-workspace-module-contracts.md +309 -0
- package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +65 -0
- package/docs/design/README.md +20 -8
- package/docs/design/operations-contract.md +1 -0
- package/docs/design/package-engine-contract.md +1 -1
- package/docs/design/package-runtime-api.md +1 -1
- package/docs/desktop-cli-api.md +386 -6
- package/docs/desktop-succession.md +3 -4
- package/docs/first-team.md +107 -224
- package/docs/implementation.md +6 -4
- package/docs/integrations.md +45 -44
- package/docs/knowledge-capability-authoring.md +10 -4
- package/docs/knowledge-migration.md +5 -4
- package/docs/knowledge-reference/package-craft.md +11 -3
- package/docs/knowledge.md +24 -8
- package/docs/layers.md +3 -3
- package/docs/oats-local.schema.json +50 -0
- package/docs/oats-membership.schema.json +23 -0
- package/docs/oats-workspace.schema.json +133 -48
- package/docs/official-marketplace.md +9 -6
- package/docs/packages.md +229 -440
- package/docs/rebuild-to-v2.md +233 -0
- package/docs/release-notes/v0.24.13.md +51 -0
- package/docs/release-notes/v0.25.0.md +99 -0
- package/docs/soul.schema.json +41 -68
- package/docs/souls-and-instances.md +175 -108
- package/docs/workspace-adoption.md +70 -345
- package/docs/workspaces.md +429 -119
- package/lib/core.mjs +419 -55
- package/lib/instance-resolution.mjs +312 -0
- package/lib/materialize.mjs +580 -0
- package/lib/packages.mjs +501 -1273
- package/lib/remote.mjs +639 -0
- package/lib/resolve.mjs +576 -0
- package/lib/schedule.mjs +194 -34
- package/lib/workspace.mjs +635 -0
- package/package.json +1 -1
- package/lib/portable-migration-artifacts.mjs +0 -135
- package/lib/portable-migration-evidence.mjs +0 -305
- package/lib/portable-migration-store.mjs +0 -199
- package/lib/portable-migration.mjs +0 -104
- package/lib/portable-onboarding-acceptance.mjs +0 -66
- package/lib/setup-expert-source.mjs +0 -100
package/docs/desktop-cli-api.md
CHANGED
|
@@ -18,7 +18,7 @@ prints exactly one JSON object on stdout:
|
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
`version` is the installed package's exact semver (e.g. `0.20.0`).
|
|
21
|
-
Desktop 0.
|
|
21
|
+
Desktop 0.25 accepts `desktopApi === 1` and semver `>=0.22.0 <0.26.0`
|
|
22
22
|
(the earlier Desktop 0.23 band was `>=0.22.0 <0.24.0`). This admits the paired
|
|
23
23
|
0.24 CLI without changing Desktop API v1. It does not establish complete captured
|
|
24
24
|
UI, backend, plugin, retirement or recovery parity; capability checks and explicit
|
|
@@ -58,8 +58,10 @@ infers a field that is not there:
|
|
|
58
58
|
since 0.24.8: `{spawn: boolean}`, see readiness policy).
|
|
59
59
|
- `provenance: { kind, source, revision, path, workspaceRevision } | null` —
|
|
60
60
|
where this soul copy came from, as recorded by the kernel when it created
|
|
61
|
-
it (
|
|
62
|
-
`exported-edition-copy
|
|
61
|
+
it (the 0.24 bootstrap recorded `packaged-definition` or
|
|
62
|
+
`exported-edition-copy`; the 0.25 `oats onboard` creates no soul and records
|
|
63
|
+
nothing here — see [`oats onboard`](#oats-onboard-onboardapi-2)).
|
|
64
|
+
Souls created before 0.24.7 or authored by hand
|
|
63
65
|
read `null`; render that as *unrecorded*, not as local or as anything else.
|
|
64
66
|
- `readiness` — the soul's **declared sources**, joined against
|
|
65
67
|
`result.capabilities[]` from the same payload. Distinct from launchability
|
|
@@ -245,7 +247,7 @@ torn lines and cleared claims silently. API 2:
|
|
|
245
247
|
|
|
246
248
|
Desktop passes `--limit` (50|100|200) only; `--since` remains a human flag.
|
|
247
249
|
|
|
248
|
-
## Schedule run history (`scheduleApi: 2`, OATS 0.24.8+) — K8
|
|
250
|
+
## Schedule run history (`scheduleApi: 2`, `scheduleHistoryApi: 2` → **3**, OATS 0.24.8+) — K8
|
|
249
251
|
|
|
250
252
|
`oats schedule show|list --json` entries gain **`recentRuns`**: the last 50
|
|
251
253
|
settled runs (newest first) — every `lastRun` the scheduler recorded once its
|
|
@@ -259,6 +261,67 @@ Schedules view (frame 08) renders `recentRuns` as the recent-runs list and the
|
|
|
259
261
|
transcript pointer as the handoff; captured-policy definitions are preserved
|
|
260
262
|
as they are (definition fields are untouched by this addition).
|
|
261
263
|
|
|
264
|
+
### History API 3 (`scheduleHistoryApi: 3`, feature `schedule-read-2`, OATS 0.24.13+) — K8b
|
|
265
|
+
|
|
266
|
+
Gate a Desktop history read on **both** `scheduleHistoryApi === 3` and
|
|
267
|
+
`"schedule-read-2"` in `features[]` (`scheduleApi` stays 2 — mutation verbs are
|
|
268
|
+
unchanged). API 2's reader keyed runs by outcome, read state files whole and
|
|
269
|
+
unchecked, echoed a stored `definition.id` without checking it, and named a
|
|
270
|
+
`transcript` that no reader backs. API 3:
|
|
271
|
+
|
|
272
|
+
- **Run identity is time, not outcome.** `runId = sha256(scheduledFor|startedAt|attemptId)[0:24]`.
|
|
273
|
+
A run's later facts update its one row; `transitions[]` keeps the outcome
|
|
274
|
+
sequence (`["started","unknown","ended"]`); `settled: boolean`
|
|
275
|
+
(`pending: true` is never settled); `recordedAt`. Pre-API-3 rows are returned
|
|
276
|
+
with `runId: null, legacy: true, settled: null, transitions: null` and are
|
|
277
|
+
never merged. `lastRun` carries the same `runId` as its history row.
|
|
278
|
+
- **Bounded, descriptor-safe state.** `oats-schedules.json` and
|
|
279
|
+
`.agents/schedules/state.json` are `lstat`ed (regular file only), opened
|
|
280
|
+
`O_NOFOLLOW|O_NONBLOCK`, `fstat`-verified (dev+ino), and read whole **only
|
|
281
|
+
within a 1 MiB budget** — over budget is a typed `E_SCHEDULE_STATE_OVERSIZE`
|
|
282
|
+
refusal with `details.source`, never truncated JSON. `list`/`show` carry
|
|
283
|
+
`integrity: {sources: [{path: "definitions"|"state", status: "ok"|"absent"|"refused"|"oversize"|"corrupt", bytes}]}`.
|
|
284
|
+
History is capped at 50 rows **at read** (`history: {status, stored, truncated}`);
|
|
285
|
+
one job's corrupt history (`history.status: "corrupt"`, `recentRuns: []`) or
|
|
286
|
+
bad identity (`unreadable: {code, message}`) never fails the other jobs in `list`.
|
|
287
|
+
- **Subject truth.** `list` and `show` echo `scope` (the resolved schedule-owning
|
|
288
|
+
workspace) and canonical `id`. A definition whose own `id` differs from its
|
|
289
|
+
key → `E_SCHEDULE_IDENTITY` (`details.key`, `details.declared`). IDs must match
|
|
290
|
+
`^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$` (`E_BAD_ARGS` otherwise, before any read).
|
|
291
|
+
- **Session provenance, never a transcript.** The `transcript` key is gone.
|
|
292
|
+
Each run (and `lastRun`) carries
|
|
293
|
+
`session: {instance: string|null, home: string|null, incarnation: string|null, server: string|null, delivery: "launched"|"delivered-active"|"none"}`
|
|
294
|
+
— facts the recorder had at write time (an active-session wake names the
|
|
295
|
+
instance only when the input result did; `incarnation` = the home's
|
|
296
|
+
`instance.json.createdAt` at record time; `server` = the answering peer for a
|
|
297
|
+
remote command). **There is no reader behind this block**: a consumer renders
|
|
298
|
+
provenance and a precise unavailable reason. A read-only transcript verb is a
|
|
299
|
+
separate seam (K12), not implied by this API.
|
|
300
|
+
|
|
301
|
+
```
|
|
302
|
+
{"scope":"/abs/ws","scheduleApi":2,"scheduleHistoryApi":3,
|
|
303
|
+
"integrity":{"sources":[{"path":"definitions","status":"ok","bytes":812},{"path":"state","status":"ok","bytes":4410}]},
|
|
304
|
+
"schedules":[{"id":"nightly","scope":"/abs/ws","scheduleApi":2,"scheduleHistoryApi":3,…,
|
|
305
|
+
"history":{"status":"ok","stored":7,"truncated":false},
|
|
306
|
+
"recentRuns":[{"runId":"3f…","scheduledFor":"<iso>","startedAt":"<iso>","outcome":"ended","settled":true,"transitions":["started","ended"],"recordedAt":"<iso>",
|
|
307
|
+
"session":{"instance":"dev-1","home":"/abs/home","incarnation":"<iso>","server":null,"delivery":"launched"}}]}],
|
|
308
|
+
"scheduler":{…}}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Exact shapes (API 3):**
|
|
312
|
+
- `schedule list --json` → `result = {scope, scheduleApi: 2, scheduleHistoryApi: 3, integrity, schedules: Entry[], scheduler}`.
|
|
313
|
+
- `schedule show <id> --json` → `result = {schedule: Entry}` (one level of nesting; `integrity` is NOT on `show` — it is a scope fact reported by `list`).
|
|
314
|
+
- `Entry` (readable) = definition fields (`id, kind, home, message|operation, cron, enabled, …`) + `{scope, scheduleApi: 2, scheduleHistoryApi: 3, executionStatus, nextRun: ISO|null, lastRun: Run|null, history, recentRuns: Run[], running: boolean, attempt?, pendingWake?}`.
|
|
315
|
+
- `Entry` (unreadable, `list` only) = `{id, scope, scheduleApi: 2, scheduleHistoryApi: 3, unreadable: {code, message}, history: {status: "corrupt", stored: null, truncated: false}, recentRuns: []}` — no definition fields.
|
|
316
|
+
- `history` = `{status: "ok", stored: integer, truncated: boolean}` | `{status: "corrupt", stored: null, truncated: false}`.
|
|
317
|
+
- `Run` (API 3 row) = producer-written fields (`scheduledFor, startedAt, kind, outcome, …`) + `{runId: string, legacy: false, settled: boolean, recordedAt: ISO, transitions: string[], session}`; `transitions[]` elements are outcome strings in write order, first element = the first recorded outcome.
|
|
318
|
+
- `Run` (legacy row) = producer-written fields + `{runId: null, legacy: true, settled: null, transitions: null, session}` — no `recordedAt`, no `key`.
|
|
319
|
+
- `Run` (corrupt element) = `{runId: null, legacy: true, corrupt: true}` only.
|
|
320
|
+
- `session` = `{instance: string|null, home: string|null, incarnation: ISO|null, server: string|null, delivery: "launched"|"delivered-active"|"none"}`; always present on rows and on `lastRun`.
|
|
321
|
+
- `runId` is opaque to consumers: never recompute, never dedup client-side.
|
|
322
|
+
- **Refusals** (`ok:false`): `E_SCHEDULE_STATE_OVERSIZE` / `E_SCHEDULE_INVALID` carry `error.details.source = {path, status, bytes}` (+ `field`); `E_SCHEDULE_IDENTITY` carries `error.details.key` and `error.details.declared`; `E_BAD_ARGS` (id shape) carries no details. A refusal has no `integrity` block — `list` refuses as a whole only when a scope file itself is unreadable.
|
|
323
|
+
- **Open path** (both files): `lstat` → regular file → `open(O_RDONLY|O_NOFOLLOW|O_NONBLOCK)` → `fstat` regular + same dev/ino + `size ≤ 1 MiB` → read exactly `fstat.size` bytes by descriptor (a file that grows past the budget between lstat and fstat is refused, never partially read).
|
|
324
|
+
|
|
262
325
|
## Spawn preview (`oats spawn … --preview`, `spawnPreviewApi: 1`, OATS 0.24.8+)
|
|
263
326
|
|
|
264
327
|
The Spawn modal's fields are backed by the kernel's own decision, taken **before
|
|
@@ -422,6 +485,9 @@ is the first-run readiness view (frame 09) and the Capabilities readiness rows
|
|
|
422
485
|
deployment (no `workspace:` in `oats.yaml`), `unknown` until admission is
|
|
423
486
|
verified against the workspace observation, `pass`/`fail` when it is. Never
|
|
424
487
|
login, never team registration; "Skip" leaves it not-applicable, never pass.
|
|
488
|
+
*(The readiness producer still reads the 0.24 `oats.yaml` backlink; under the
|
|
489
|
+
workspace model membership is `oats-membership.yaml` observed by
|
|
490
|
+
`oats workspace status` — re-basing this item is an open thread.)*
|
|
425
491
|
- Subject: `--soul <name>` scopes required items to the soul's declared
|
|
426
492
|
requirements; without it, to the scope's active capabilities.
|
|
427
493
|
|
|
@@ -619,14 +685,328 @@ location. The receipt says so:
|
|
|
619
685
|
|
|
620
686
|
### Feature advertisement — gate every new command on the probe
|
|
621
687
|
|
|
622
|
-
`oats version --json` `features` now lists: `
|
|
688
|
+
`oats version --json` `features` now lists: `instance-git`,
|
|
623
689
|
`instance-git-remote`, `souls-declarations`, `lifecycle-plans`,
|
|
624
690
|
`retire-retention`, `readiness`, `spawn-preview`, `instance-events`,
|
|
625
691
|
`schedule-history`, and carries the API integers (`instanceGitApi`, `soulsApi`,
|
|
626
692
|
`lifecycleApi`, `readinessApi`, `spawnPreviewApi`, `eventsApi`,
|
|
627
693
|
`scheduleHistoryApi`). **Gate on these, never on a version string and never by
|
|
628
694
|
optimistic invocation**: an older CLI ignores an unknown `--plan` on `retire`
|
|
629
|
-
and *retires*. Absent feature → the view is unavailable.
|
|
695
|
+
and *retires*. Absent feature → the view is unavailable. (`catalog` was the
|
|
696
|
+
0.24 `oats catalog` verb's flag; the verb is removed under the workspace model
|
|
697
|
+
and the flag is no longer advertised — the official catalog is reached through
|
|
698
|
+
`packages:` + `oats sync`, not a command.)
|
|
699
|
+
|
|
700
|
+
## Workspace model (`workspaceApi: 2`)
|
|
701
|
+
|
|
702
|
+
Features: **`workspace-v2`** (the declaration files, `sync`, `package`,
|
|
703
|
+
`workspace status`, `capabilities`, `souls`; `init`/`use`/`install`/`restore`
|
|
704
|
+
removed), **`instance-modules`** (`instance.json.modules` / `providers` /
|
|
705
|
+
`workspace`; `status --json` module drift; preview `modules[]`),
|
|
706
|
+
**`spawn-provider-payload`** (`oats spawn … --provider <cap> k=v`). The probe
|
|
707
|
+
carries `workspaceApi: 2`. Model: [workspaces.md](workspaces.md).
|
|
708
|
+
|
|
709
|
+
Every command below needs a deployment with `oats-local.yaml` (walked up from
|
|
710
|
+
`--dir`/cwd) — else `E_LOCAL_MISSING { dir, searched[] }` — and reads the
|
|
711
|
+
workspace over Git remotes with the operator's credentials, never prompting
|
|
712
|
+
(`E_REMOTE_UNREADABLE { url, reason: "auth"|"not-found"|"network"|"timeout" }`).
|
|
713
|
+
Every `commit` is a full 40-hex OID; every digest is `sha256-<hex>`; every
|
|
714
|
+
`at`/`observedAt` is ISO-8601 UTC. Repo keys are canonical
|
|
715
|
+
(`github.com/org/repo`; `local/<abs-path>` for file remotes).
|
|
716
|
+
|
|
717
|
+
### Removed verbs answer `E_UNKNOWN_COMMAND` with a replacement
|
|
718
|
+
|
|
719
|
+
`install`, `restore`, `init`, `use`, `trust`, `list`, `catalog`, `remove`,
|
|
720
|
+
`migrate`, `config` — checked before capability dispatch, both modes:
|
|
721
|
+
|
|
722
|
+
```json
|
|
723
|
+
{"schemaVersion":1,"ok":false,"error":{"code":"E_UNKNOWN_COMMAND","message":"unknown command \"install\" — removed by the workspace model v2; use oats sync","details":{"removed":"install","replacement":"oats sync"}}}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### oats onboard (onboardApi 2)
|
|
727
|
+
|
|
728
|
+
`oats onboard [<dir>] --workspace <repo ref> [--json]`
|
|
729
|
+
|
|
730
|
+
The **bootstrap** of a deployment (decision 9): realizes a workspace on this
|
|
731
|
+
machine in the taught `<name>-workspace/` layout. It writes
|
|
732
|
+
`<dir>/oats-local.yaml` (`{ schemaVersion: 2, workspace: <ref> }`), creates
|
|
733
|
+
`<dir>/agents/` (the instance homes), then runs exactly the `oats sync` body
|
|
734
|
+
over the directory just written — discover over the remotes, confirm
|
|
735
|
+
membership, resolve `packages:`, approve (TTY) or list what needs approval,
|
|
736
|
+
write `oats-lock.json`. It installs nothing, creates no soul, spawns nothing
|
|
737
|
+
and writes no `oats-config.yaml`; the member clones and the setup-expert spawn
|
|
738
|
+
are printed as next steps. `<dir>` defaults to cwd; `--dir <d>` is the same
|
|
739
|
+
argument (give it once). `--workspace` is required and must be a ref
|
|
740
|
+
`lib/remote.mjs` parses (`E_REPO_REF`) — checked **before** anything is
|
|
741
|
+
written. Captured selectors are refused (`E_BAD_ARGS`).
|
|
742
|
+
|
|
743
|
+
```json
|
|
744
|
+
{"onboardApi":2,
|
|
745
|
+
"local":"/abs/acme-workspace/oats-local.yaml","dir":"/abs/acme-workspace","agents":"/abs/acme-workspace/agents",
|
|
746
|
+
"lock":"/abs/acme-workspace/oats-lock.json",
|
|
747
|
+
"sync":{"syncApi":1,"…":"the full sync report (next section)"},
|
|
748
|
+
"hosting":{"host":"github.com/acme/agents","hostIsMember":true,
|
|
749
|
+
"rule":"If any member is private, host oats-workspace.yaml in a private repo that is not a public member (a dedicated <org>/workspace repo); public contributors then use the standalone case (from: here capabilities + oats.core)."},
|
|
750
|
+
"next":{"clone":[{"key":"github.com/acme/agents","name":"agents","url":"https://github.com/acme/agents.git","dir":"/abs/acme-workspace/agents-repo"},
|
|
751
|
+
{"key":"github.com/acme/platform","name":"platform","url":"https://github.com/acme/platform.git","dir":"/abs/acme-workspace/platform"}],
|
|
752
|
+
"spawn":"oats spawn oats-setup-expert --dir /abs/acme-workspace"}}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
- `sync` is the `syncApi: 1` report of the first sync (members, packages,
|
|
756
|
+
changes, `approvalNeeded`, `problems`); `lock` is the lock it wrote.
|
|
757
|
+
- **Exit `2` with `ok: true`** when `sync.approvalNeeded` is non-empty (approval
|
|
758
|
+
is interactive-only; tell the operator to run `oats sync --dir <dir>` in a
|
|
759
|
+
terminal). Exit `0` otherwise.
|
|
760
|
+
- `hosting` states decision 26 (the kernel cannot see forge visibility, so it
|
|
761
|
+
reports `hostIsMember` and the rule rather than judging).
|
|
762
|
+
- `next.clone[]` is one row per **confirmed** member (`url` = what the
|
|
763
|
+
workspace's `members:` ref resolves to; `dir` = `<dir>/<name>`, or
|
|
764
|
+
`<dir>/agents-repo` for a member called `agents`, since `agents/` is the
|
|
765
|
+
instance homes). `next.spawn` is the setup-expert spawn command string.
|
|
766
|
+
- Errors (all `E_*`): `E_BAD_ARGS` (usage; missing `--workspace`; dir given
|
|
767
|
+
twice), `E_REPO_REF`, `E_ALREADY_ONBOARDED { local, dir }` — **this**
|
|
768
|
+
directory already has `oats-local.yaml` (an enclosing deployment's file does
|
|
769
|
+
not count; run `oats sync` there instead), `E_ONBOARD_FAILED { dir }`
|
|
770
|
+
(cannot inspect/write the directory; `<dir>` exists and is not a directory).
|
|
771
|
+
Failures while **discovering** — `E_REMOTE_UNREADABLE`, `E_WORKSPACE_SCHEMA`
|
|
772
|
+
(not a workspace host), `E_LOCAL_MISSING`, plus `E_PACKAGE_MISSING` /
|
|
773
|
+
`E_PACKAGE_INTEGRITY` / `E_LOCK_SCHEMA` and the other `sync` errors raised
|
|
774
|
+
before the workspace was read — roll back the files onboarding created and
|
|
775
|
+
carry `details.rolledBack: true` and `details.dir`; a typo'd ref never
|
|
776
|
+
leaves a half-onboarded directory that looks finished. Once the workspace
|
|
777
|
+
has been read, a later failure keeps the files (a lock may exist) and
|
|
778
|
+
carries `details.dir` + `details.local` instead.
|
|
779
|
+
|
|
780
|
+
### `oats sync [--dir <d>] --json` → `syncApi: 1`
|
|
781
|
+
|
|
782
|
+
Discovers, confirms membership, resolves `packages:` to commits, writes
|
|
783
|
+
`oats-lock.json` (lockfileVersion 3), reports. **Exit `2` with `ok: true`** when
|
|
784
|
+
the lock was written but approvals are pending (`approvalNeeded` non-empty);
|
|
785
|
+
approval is interactive-only, so a Desktop must tell the operator to run
|
|
786
|
+
`oats sync` in a terminal. Exit `0` otherwise.
|
|
787
|
+
|
|
788
|
+
```json
|
|
789
|
+
{"syncApi":1,
|
|
790
|
+
"workspace":{"name":"acme","key":"github.com/acme/agents","url":"https://github.com/acme/agents.git","commit":"<oid>","observedAt":"<iso>",
|
|
791
|
+
"local":"/abs/acme-workspace/oats-local.yaml","lock":"/abs/acme-workspace/oats-lock.json"},
|
|
792
|
+
"members":[{"key":"github.com/acme/agents","name":"agents","commit":"<oid>","confirmed":true,"status":"confirmed","detail":null,"team":"global",
|
|
793
|
+
"souls":["release-manager"],"capabilities":["acme-house-style"],"publishes":null},
|
|
794
|
+
{"key":"github.com/acme/tools","name":"tools","commit":"<oid>","confirmed":true,"status":"confirmed","detail":null,"team":"engineering",
|
|
795
|
+
"souls":["tools-expert"],"capabilities":["acme-tools-dev"],"publishes":{"package":"acme.tools","version":"0.4.0"}},
|
|
796
|
+
{"key":"github.com/acme/billing","name":"billing","commit":"<oid>","confirmed":false,"status":"no-backlink","detail":"github.com/acme/billing@… has no oats-membership.yaml","team":null,
|
|
797
|
+
"souls":[],"capabilities":[],"publishes":null}],
|
|
798
|
+
"packages":[{"id":"oats.okf","version":"2.1.3","source":"catalog:oats.okf","commit":"<oid>","integrity":"sha256-…","capabilities":["oats.okf"],
|
|
799
|
+
"approved":{"executables":"sha256-…","at":"<iso>"}},
|
|
800
|
+
{"id":"acme.tools","version":"0.4.0","source":"git:github.com/acme/tools@v0.4.0","commit":"<oid>","integrity":"sha256-…","capabilities":["acme-deploy","acme-lint"],"approved":null}],
|
|
801
|
+
"changes":[{"id":"acme.tools","from":null,"to":"0.4.0","commit":"<oid>","approvalNeeded":true}],
|
|
802
|
+
"approvalNeeded":[{"id":"acme.tools","version":"0.4.0","commit":"<oid>","executables":"sha256-…",
|
|
803
|
+
"targets":["acme-deploy: command apply → bin/acme-deploy.mjs","acme-deploy: hook spawn → bin/acme-deploy.mjs"]}],
|
|
804
|
+
"problems":[]}
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
- `members[].status`: `confirmed` | `not-listed` | `no-backlink` |
|
|
808
|
+
`backlink-elsewhere` | `cannot-read`; `detail` explains an unconfirmed row.
|
|
809
|
+
`publishes` reports a member's `oats-package/` (informational — its
|
|
810
|
+
capabilities are **not** in `capabilities[]`; the non-collapse rule).
|
|
811
|
+
- `changes[]`: `from` = previously locked version or `null`; `to` = `null` when
|
|
812
|
+
the package was dropped from `packages:` and from the lock.
|
|
813
|
+
- `approvalNeeded[].targets` are human-readable lines
|
|
814
|
+
(`<cap>: command|hook <name> → <relpath>`); `executables` is the digest an
|
|
815
|
+
approval would record.
|
|
816
|
+
- `problems[]`: `{ code, path, message, repoKey? }` — per-item discovery
|
|
817
|
+
problems (`E_WORKSPACE_SCHEMA`, `E_TEAM_UNKNOWN`, `E_REMOTE_*`, …). Never an
|
|
818
|
+
abort: an unreadable member directory is a problem of that member.
|
|
819
|
+
- Errors: `E_LOCAL_MISSING`, `E_WORKSPACE_SCHEMA { path, problems[] }`,
|
|
820
|
+
`E_REMOTE_UNREADABLE`, `E_LOCK_SCHEMA`, `E_PACKAGE_MISSING` (catalog has no
|
|
821
|
+
such id), `E_PACKAGE_INTEGRITY { why: "branch" | locked/observed }`,
|
|
822
|
+
`E_PACKAGE_UNAPPROVED` (approval digest no longer matches),
|
|
823
|
+
`E_PACKAGE_MANIFEST`, `E_REPO_REF`.
|
|
824
|
+
|
|
825
|
+
### `oats package add <id> <version|git:<repo>@<ref>> | remove <id> [--dir] --json`
|
|
826
|
+
|
|
827
|
+
Edits `packages:` **only** when `oats-workspace.yaml` is tracked by the Git
|
|
828
|
+
checkout walked up from `--dir`; otherwise reports the line to add.
|
|
829
|
+
|
|
830
|
+
```json
|
|
831
|
+
{"action":"add","id":"oats.aweb","value":"v1.11.2","previous":null,"edited":true,"file":"/abs/agents/oats-workspace.yaml"}
|
|
832
|
+
{"action":"add","id":"oats.aweb","value":"v1.11.2","edited":false,"file":null,"line":"packages:\n oats.aweb: v1.11.2","hint":"oats-workspace.yaml is not in this checkout; commit the change in the workspace repo, then `oats sync`"}
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
`remove` → `{ action: "remove", id, value: null, previous: "<old value>", edited: true, file }`
|
|
836
|
+
on the tracked branch. On the untracked branch the shape is
|
|
837
|
+
`{ action: "remove", id, value: null, edited: false, file: null, line: null, hint }`
|
|
838
|
+
— **no `previous`** (nothing was read), `line: null` (there is no line to add
|
|
839
|
+
for a removal). **Both branches** answer `E_PACKAGE_MISSING { id, path? }` when
|
|
840
|
+
`<id>` is not declared in `packages:` — the untracked branch reads the file it
|
|
841
|
+
found to check the declaration even though it does not edit it. `E_USAGE`,
|
|
842
|
+
`E_WORKSPACE_SCHEMA` (bad id/value, or the edit would make the file invalid),
|
|
843
|
+
`E_REPO_REF`. No network.
|
|
844
|
+
|
|
845
|
+
### `oats workspace status [--dir] --json` → `workspaceStatusApi: 1`
|
|
846
|
+
|
|
847
|
+
```json
|
|
848
|
+
{"workspaceStatusApi":1,
|
|
849
|
+
"workspace":{"name":"acme","key":"github.com/acme/agents","url":"…","commit":"<oid>","observedAt":"<iso>","local":"/abs/…/oats-local.yaml","teams":["global","engineering"]},
|
|
850
|
+
"members":[ … same rows as sync … ],
|
|
851
|
+
"packages":[ … same rows as sync (from the lock) … ],
|
|
852
|
+
"declaredPackages":["acme.tools","oats.okf"],
|
|
853
|
+
"unsynced":[],
|
|
854
|
+
"stale":[],
|
|
855
|
+
"approval":{"approved":["oats.okf"],"needed":["acme.tools"]},
|
|
856
|
+
"external":[{"source":"git:github.com/oss-collective/experts@<oid>","soul":"security-reviewer","team":"unassigned"}],
|
|
857
|
+
"problems":[]}
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
`unsynced` = declared in `packages:` but not in the lock (run `sync`);
|
|
861
|
+
`stale` = locked but no longer declared. Read-only: does not write the lock.
|
|
862
|
+
|
|
863
|
+
### `oats capabilities [--dir] --json` → `capabilitiesApi: 1` · `oats souls [--dir] --json` → `soulsApi: 1`
|
|
864
|
+
|
|
865
|
+
Every **non-private** item of every confirmed member, external souls, and
|
|
866
|
+
locked package capabilities, sorted by name then origin. `origin` is the
|
|
867
|
+
human string (`member <key> @ <8-char commit>` | `package <id> v<version>` |
|
|
868
|
+
`external <key> @ <commit>`); `kind` is the machine field. `team` is the label
|
|
869
|
+
or `"unassigned"`.
|
|
870
|
+
|
|
871
|
+
```json
|
|
872
|
+
{"capabilitiesApi":1,"workspace":{"name":"acme","key":"github.com/acme/agents","commit":"<oid>"},
|
|
873
|
+
"capabilities":[
|
|
874
|
+
{"name":"acme-house-style","origin":"member github.com/acme/agents @ 3f2a9c1e","kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>",
|
|
875
|
+
"team":"global","private":false,"path":"capabilities/acme-house-style","layer":null,"version":"0.0.0-workspace"},
|
|
876
|
+
{"name":"oats.okf","origin":"package oats.okf v2.1.3","kind":"package","package":"oats.okf","version":"2.1.3","commit":"<oid>",
|
|
877
|
+
"team":"unassigned","private":false,"approved":true}],
|
|
878
|
+
"problems":[]}
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
```json
|
|
882
|
+
{"soulsApi":1,"workspace":{…},
|
|
883
|
+
"souls":[
|
|
884
|
+
{"name":"release-manager","origin":"member github.com/acme/agents @ 3f2a9c1e","kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>",
|
|
885
|
+
"team":"engineering","private":false,"path":"souls/release-manager","work":"worktree","description":"Cuts, verifies and announces releases."},
|
|
886
|
+
{"name":"security-reviewer","origin":"external github.com/oss-collective/experts @ 9c4e1f2a","kind":"external",…}],
|
|
887
|
+
"problems":[]}
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
Package capabilities of declared-but-unsynced packages are absent until `sync`.
|
|
891
|
+
(`soulsApi: 1` is also the integer of the existing `oats inspect --json`
|
|
892
|
+
declarations block; the two payloads are distinguished by their command.)
|
|
893
|
+
|
|
894
|
+
### `oats spawn <soul> … --preview --json` — additions (Preview API 2 unchanged)
|
|
895
|
+
|
|
896
|
+
On a workspace deployment the preview carries four extra top-level fields, and
|
|
897
|
+
`decision.resolution` binds the resolution revision (so a member that moved
|
|
898
|
+
between preview and apply is `E_DECISION_STALE`):
|
|
899
|
+
|
|
900
|
+
```json
|
|
901
|
+
{"modules":[
|
|
902
|
+
{"name":"acme-release-tooling","from":{"kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>"},"layer":null,"private":false,
|
|
903
|
+
"changedSince":{"instance":"release-manager-v2","was":"<old oid>"}},
|
|
904
|
+
{"name":"oats.okf","from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"<oid>","integrity":"sha256-…","repoKey":"github.com/awebai/oats-okf"},
|
|
905
|
+
"layer":"knowledge","private":false,"changedSince":false}],
|
|
906
|
+
"team":"engineering",
|
|
907
|
+
"resolution":"6e3050c0d005879441ab017d",
|
|
908
|
+
"workspace":"github.com/acme/agents",
|
|
909
|
+
"spawnPreviewApi":2,"preview":true,"agent":"release-manager","instance":"release-manager-cut","home":"/abs/…",
|
|
910
|
+
"decision":{"instance":"…","home":"…","branch":"…","base":{…},"effective":{…},"resolution":"6e3050c0d005879441ab017d","revision":"<24 hex>"},
|
|
911
|
+
"…":"every Preview API 2 field as before"}
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
- `modules[].from` is exactly the `from` recorded in `instance.json` on apply.
|
|
915
|
+
- `changedSince`: `null` (no previous instance of this soul), `false`
|
|
916
|
+
(unchanged since the newest previous instance), or
|
|
917
|
+
`{ instance, was }` (`was` = the previous commit, or `null` when the previous
|
|
918
|
+
instance had no such module).
|
|
919
|
+
- `capabilities[]` / `skills[]` keep their Preview-1 meaning; on a workspace
|
|
920
|
+
spawn the authoritative module set is `modules[]` (`capabilities[]` may be
|
|
921
|
+
empty there, since capability rows are filled after materialization).
|
|
922
|
+
- `workspace` is the workspace host's canonical key, `team` the soul's label
|
|
923
|
+
(or `null`), `resolution` the 24-hex revision `decision.resolution` binds.
|
|
924
|
+
- `--provider <cap> <key>=<value>` (repeatable; `a.b=c` nests) is accepted by
|
|
925
|
+
preview and apply. `E_BAD_ARGS` for a malformed pair or when the deployment
|
|
926
|
+
has no `oats-local.yaml`; `E_CAPABILITY_MISSING { capability, soul, modules[] }`
|
|
927
|
+
when the soul does not resolve that capability. `byTeam` is a **reserved
|
|
928
|
+
key**: legal only at the top level of the workspace file's `messaging:`;
|
|
929
|
+
anywhere else in any payload layer (soul, `oats-local.yaml` `settings`,
|
|
930
|
+
`--provider`, at any depth) it is `E_WORKSPACE_SCHEMA { reason: "reserved-key", path, key }`.
|
|
931
|
+
- Soul lookup: `E_SOUL_UNKNOWN { name, members[] }` (not among confirmed
|
|
932
|
+
members or externals), `E_SOUL_AMBIGUOUS { name, repos[] }` (name it
|
|
933
|
+
`<repo>/<soul>`). Resolution errors keep their codes (`E_NOT_A_MEMBER`,
|
|
934
|
+
`E_MEMBERSHIP_UNCONFIRMED { repoKey, reason }`, `E_CAPABILITY_MISSING { hint? }`,
|
|
935
|
+
`E_CAPABILITY_PRIVATE`, `E_PACKAGE_MISSING`, `E_PACKAGE_UNAPPROVED { id, version, commit }`,
|
|
936
|
+
`E_SLOT_CONFLICT { slot, modules[], reason? }`, `E_SKILL_DUPLICATE { name, modules[] }`,
|
|
937
|
+
`E_COMPATIBILITY { capability, package, version, range, why? }`).
|
|
938
|
+
|
|
939
|
+
The apply result (`oats spawn … --json`) is unchanged in shape; the new facts
|
|
940
|
+
live in the home's `instance.json`.
|
|
941
|
+
|
|
942
|
+
### `instance.json` — `modules`, `providers`, `workspace` (feature `instance-modules`)
|
|
943
|
+
|
|
944
|
+
Written by materialization inside the spawn transaction; read back by
|
|
945
|
+
`oats status --json` and the roster.
|
|
946
|
+
|
|
947
|
+
```json
|
|
948
|
+
{"modules":{
|
|
949
|
+
"acme-release-tooling":{"from":{"kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>"},
|
|
950
|
+
"commit":"<oid>","digest":"sha256-…","materializedAt":"<iso>"},
|
|
951
|
+
"oats.okf":{"from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"<oid>","integrity":"sha256-…","repoKey":"github.com/awebai/oats-okf"},
|
|
952
|
+
"commit":"<oid>","digest":"sha256-…","materializedAt":"<iso>"}},
|
|
953
|
+
"providers":{"acme-release-tooling":{},"oats.okf":{"owns":"release-manager","reads":["platform-engineer"],"state-dir":"/Users/ana/.oats/okf"}},
|
|
954
|
+
"workspace":{"key":"github.com/acme/agents","commit":"<oid>","resolution":"<24 hex>","standalone":false,
|
|
955
|
+
"soul":{"repoKey":"github.com/acme/agents","commit":"<oid>","team":"engineering"}},
|
|
956
|
+
"capabilities":[{"id":"oats.okf","capability":"oats.okf","origin":"package:oats.okf@2.1.3","…":"one row per module"}]}
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
`workspace.standalone` is `true` when the instance was spawned from the
|
|
960
|
+
**standalone view** (decisions 10/25: a *member* whose workspace could not be
|
|
961
|
+
read — `workspace.key` is then the member repo's key, and `modules` holds the
|
|
962
|
+
soul's `from: here` capabilities plus `oats.core`); `false` for a workspace
|
|
963
|
+
spawn. The same view is marked `standalone: true` in `oats sync --json` (with
|
|
964
|
+
`workspace.name` = `standalone:<repo>`) and in the roster. `capabilities[]` is
|
|
965
|
+
the per-module row set (`toCapabilityRows`) that `oats inspect`/`status` read.
|
|
966
|
+
|
|
967
|
+
`digest` is the sha256 of the copied module tree (`<home>/.oats/modules/<cap>/`);
|
|
968
|
+
`providers.<cap>` is the merged payload (soul ⊕ `oats-local.yaml`
|
|
969
|
+
`settings.<cap>` ⊕ `--provider`), `{}` for a capability with none. Copies live at
|
|
970
|
+
`<home>/.oats/modules/<cap>/` and `<home>/.agents/skills/<cap>/<skill>/`.
|
|
971
|
+
|
|
972
|
+
### `oats status [--dir] --json` — module drift
|
|
973
|
+
|
|
974
|
+
On a workspace deployment the roster does one discovery and rewrites each
|
|
975
|
+
instance's `modules` from the recorded map into **drift rows**, and adds a
|
|
976
|
+
top-level `workspace` reachability field:
|
|
977
|
+
|
|
978
|
+
```json
|
|
979
|
+
{"root":"/abs/acme-workspace/agents",
|
|
980
|
+
"agents":[{"name":"release-manager",…,"instances":[{"instance":"release-manager-cut",…,
|
|
981
|
+
"modules":[
|
|
982
|
+
{"name":"acme-release-tooling","from":{"kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>"},"commit":"<oid>",
|
|
983
|
+
"current":{"commit":"<new oid>"},"status":"moved"},
|
|
984
|
+
{"name":"acme-house-style","from":{…},"commit":"<oid>","current":{"commit":"<oid>"},"status":"missing","reason":"capability-absent"},
|
|
985
|
+
{"name":"oats.okf","from":{"kind":"package",…},"commit":"<oid>","current":{"commit":"<oid>","version":"2.1.3"},"status":"current"}]}]}],
|
|
986
|
+
"workspace":{"reachable":true}}
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
- `status`: `current` | `moved` (member or locked package now at another
|
|
990
|
+
commit) | `missing` with `reason`: `capability-absent` (gone from the member /
|
|
991
|
+
package), `package-absent` (no longer locked), or the member's unconfirmed
|
|
992
|
+
reason (`no-backlink`, `cannot-read`, `unconfirmed`, …; `current: null`).
|
|
993
|
+
- Package modules without a lock are reported `current`.
|
|
994
|
+
- Offline: `"workspace":{"reachable":false,"code":"E_REMOTE_UNREADABLE","reason":"E_REMOTE_UNREADABLE: network","message":"…"}`
|
|
995
|
+
and `modules` stays the recorded map (no drift rows). A deployment without
|
|
996
|
+
`oats-local.yaml` has no `workspace` field and `modules` as recorded.
|
|
997
|
+
- Text mode prints `modules: <cap> from <member|package …> @ <7-char>` lines
|
|
998
|
+
for non-current modules (`--verbose` for all).
|
|
999
|
+
|
|
1000
|
+
### Probe
|
|
1001
|
+
|
|
1002
|
+
```json
|
|
1003
|
+
{"…":"…","features":["…","workspace-v2","instance-modules","spawn-provider-payload"],"workspaceApi":2}
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
A feature is listed only once the binary implements it. Gate `sync`/`package`/
|
|
1007
|
+
`workspace status`/`capabilities`/`souls` on `workspace-v2`; gate reading
|
|
1008
|
+
`instance.json.modules` and preview `modules[]` on `instance-modules`; gate
|
|
1009
|
+
`--provider` on `spawn-provider-payload`.
|
|
630
1010
|
|
|
631
1011
|
## Instruction refresh (`oats session recompose`, feature `session-recompose`, OATS 0.24.8+)
|
|
632
1012
|
|
|
@@ -23,10 +23,9 @@ The CLI diagnoses stale references instead of failing opaquely:
|
|
|
23
23
|
|
|
24
24
|
- `oats doctor` warns when an `oats-lock.json` still pins `oats.web`, with the
|
|
25
25
|
fix spelled out.
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
message naming the successor and the exact cleanup steps.
|
|
26
|
+
- A soul or workspace default naming `oats.web` fails at resolution with a
|
|
27
|
+
message naming the successor and the exact cleanup steps (remove it from
|
|
28
|
+
`packages:` / the soul's `capabilities:`).
|
|
30
29
|
|
|
31
30
|
## Migrating `oats pane` usage
|
|
32
31
|
|