@awebai/oats 0.24.13 → 0.25.1

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 (58) hide show
  1. package/bin/oats.mjs +994 -2837
  2. package/docs/capabilities.md +136 -323
  3. package/docs/configuration.md +68 -533
  4. package/docs/conventions.md +51 -24
  5. package/docs/design/2026-09-16-fresh-operator-walkthrough.md +19 -14
  6. package/docs/design/2026-09-16-portable-migration-evidence.md +2 -0
  7. package/docs/design/2026-09-16-portable-onboarding.md +4 -2
  8. package/docs/design/2026-09-20-redesign-program-board.md +1 -1
  9. package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +2 -0
  10. package/docs/design/2026-09-23-simplified-workspace-model.md +711 -0
  11. package/docs/design/2026-09-23-workspace-module-contracts.md +460 -0
  12. package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +65 -0
  13. package/docs/design/README.md +20 -8
  14. package/docs/design/operations-contract.md +1 -0
  15. package/docs/design/package-engine-contract.md +1 -1
  16. package/docs/design/package-runtime-api.md +1 -1
  17. package/docs/desktop-cli-api.md +356 -5
  18. package/docs/desktop-succession.md +12 -6
  19. package/docs/desktop.md +9 -4
  20. package/docs/execution-targets.md +16 -4
  21. package/docs/first-team.md +107 -224
  22. package/docs/implementation.md +41 -11
  23. package/docs/integrations.md +50 -47
  24. package/docs/knowledge-capability-authoring.md +10 -4
  25. package/docs/knowledge-migration.md +21 -12
  26. package/docs/knowledge-reference/package-craft.md +11 -3
  27. package/docs/knowledge.md +60 -18
  28. package/docs/layers.md +3 -3
  29. package/docs/migration-from-oas.md +20 -9
  30. package/docs/oats-local.schema.json +50 -0
  31. package/docs/oats-membership.schema.json +23 -0
  32. package/docs/oats-workspace.schema.json +133 -48
  33. package/docs/official-marketplace.md +9 -6
  34. package/docs/packages.md +229 -440
  35. package/docs/rebuild-to-v2.md +347 -0
  36. package/docs/release-notes/v0.25.0.md +99 -0
  37. package/docs/release-notes/v0.25.1.md +94 -0
  38. package/docs/schedules.md +12 -6
  39. package/docs/soul.schema.json +41 -68
  40. package/docs/souls-and-instances.md +175 -108
  41. package/docs/workspace-adoption.md +70 -345
  42. package/docs/workspaces.md +436 -119
  43. package/lib/core.mjs +462 -61
  44. package/lib/instance-resolution.mjs +387 -0
  45. package/lib/materialize.mjs +580 -0
  46. package/lib/operator-dispatch.mjs +117 -0
  47. package/lib/packages.mjs +558 -1269
  48. package/lib/remote.mjs +718 -0
  49. package/lib/resolve.mjs +638 -0
  50. package/lib/schedule.mjs +90 -16
  51. package/lib/workspace.mjs +654 -0
  52. package/package.json +1 -1
  53. package/lib/portable-migration-artifacts.mjs +0 -135
  54. package/lib/portable-migration-evidence.mjs +0 -305
  55. package/lib/portable-migration-store.mjs +0 -199
  56. package/lib/portable-migration.mjs +0 -104
  57. package/lib/portable-onboarding-acceptance.mjs +0 -66
  58. package/lib/setup-expert-source.mjs +0 -100
@@ -88,6 +88,7 @@ name the provider answered is kept for `oats schedule reconcile`.
88
88
 
89
89
  ## Mutations
90
90
 
91
+ *(0.24 only — `oats use` was removed by the workspace model v2; activation is the soul's `capabilities:` + workspace defaults. Kept for history.)*
91
92
  `oats use ... --json` answers `{ capability, action: enable|disable|
92
93
  layer-none|inherit, target, layer, level, file, settings, before, after
93
94
  {..., effective}, remaining?, note?, missingRequires }`. `--inherit`
@@ -1,6 +1,6 @@
1
1
  # Package engine contract (capability materialization, lock v2)
2
2
 
3
- Status: **FROZEN** for the capability-materialization delivery. This document is
3
+ Status: **SUPERSEDED** by the workspace model v2 ([2026-09-23-workspace-module-contracts.md](2026-09-23-workspace-module-contracts.md) §4–5, [../packages.md](../packages.md)): the installed-capability tier, `oats install`/`restore`/`use`/`trust`, config templates and lock v2 described here were removed; the package tier is now `lib/packages.mjs` (lock v3) + `lib/materialize.mjs`. Kept as history. Original status: **FROZEN** for the capability-materialization delivery. This document is
4
4
  the resolver / projection / lock API that the config-and-CLI lane builds
5
5
  against. It implements the accepted Decision "Packages materialize capabilities
6
6
  while config templates remain explicitly adopted local policy" (2026-07-29) as
@@ -1,6 +1,6 @@
1
1
  # Package-runtime API contract (addendum to the package-engine contract)
2
2
 
3
- Status: **FROZEN** for the capability-materialization delivery, as an addendum to
3
+ Status: **SUPERSEDED** with its parent (workspace model v2 — see [2026-09-23-workspace-module-contracts.md](2026-09-23-workspace-module-contracts.md)); kept as history. Original status: **FROZEN** for the capability-materialization delivery, as an addendum to
4
4
  [`package-engine-contract.md`](./package-engine-contract.md). It answers the
5
5
  maintainer's four clarifications on the M1 freeze (maintainer review of
6
6
  1db919b): the public package-runtime boundary, the npm runtime closure,
@@ -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.24 accepts `desktopApi === 1` and semver `>=0.22.0 <0.25.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 (`oats onboard` records `packaged-definition` or
62
- `exported-edition-copy`). Souls created before 0.24.7 or authored by hand
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
@@ -448,6 +450,26 @@ the pre-fix marker and is never accepted for dispatch.
448
450
 
449
451
  ## Readiness quartet, signatures, enforced policy (`oats readiness`, `readinessApi: 1`, OATS 0.24.8+)
450
452
 
453
+ > **0.25 status — the producers below are the 0.24 tier.** The readiness DTO
454
+ > (`readinessApi: 1`) still ships unchanged, but its four checks are *produced*
455
+ > by the classic observers: `installed` by `oats list` over the
456
+ > `.agents/capabilities/installed/` tier, `trusted` by the per-artifact approval
457
+ > that `oats trust` wrote, `configured` by `oats-config.yaml` activation, and
458
+ > `enrolled` by the `oats.yaml` backlink. On a **workspace deployment**
459
+ > (`oats-local.yaml` present) none of those sources exists: nothing is installed,
460
+ > approval is per package version in `oats-lock.json` v3 (`oats sync`), activation
461
+ > is derived (workspace defaults ⊕ soul `capabilities:`), and membership is
462
+ > `oats-membership.yaml` observed over the remotes. `oats list` and `oats trust`
463
+ > are removed verbs (`E_UNKNOWN_COMMAND`), so a `remedy` naming them cannot be
464
+ > run. Treat the field names and producer strings as the stable wire shape they
465
+ > are; for the workspace-model facts read `oats spawn <soul> --preview --json`
466
+ > (`modules[]` with from/commit/digest — the "installed" and "configured"
467
+ > truth), `oats sync --json` `approvalNeeded[]` (the "trusted" truth) and
468
+ > `oats workspace status --json` (the "enrolled" truth). Re-basing the quartet on
469
+ > those producers is an open thread of [the workspace model](#workspace-model-workspaceapi-2);
470
+ > when it lands it will be announced as a new feature name, not a silent change of
471
+ > `readinessApi: 1`.
472
+
451
473
  `oats readiness [--soul <name>] [--home <abs>] [--verify-signatures] [--policy] [--dir <d>] --json`
452
474
  is the first-run readiness view (frame 09) and the Capabilities readiness rows
453
475
  (frame 04). Every fact is derived from the **same** data `oats inspect` reports
@@ -483,6 +505,9 @@ is the first-run readiness view (frame 09) and the Capabilities readiness rows
483
505
  deployment (no `workspace:` in `oats.yaml`), `unknown` until admission is
484
506
  verified against the workspace observation, `pass`/`fail` when it is. Never
485
507
  login, never team registration; "Skip" leaves it not-applicable, never pass.
508
+ *(The readiness producer still reads the 0.24 `oats.yaml` backlink; under the
509
+ workspace model membership is `oats-membership.yaml` observed by
510
+ `oats workspace status` — re-basing this item is an open thread.)*
486
511
  - Subject: `--soul <name>` scopes required items to the soul's declared
487
512
  requirements; without it, to the scope's active capabilities.
488
513
 
@@ -680,14 +705,328 @@ location. The receipt says so:
680
705
 
681
706
  ### Feature advertisement — gate every new command on the probe
682
707
 
683
- `oats version --json` `features` now lists: `catalog`, `instance-git`,
708
+ `oats version --json` `features` now lists: `instance-git`,
684
709
  `instance-git-remote`, `souls-declarations`, `lifecycle-plans`,
685
710
  `retire-retention`, `readiness`, `spawn-preview`, `instance-events`,
686
711
  `schedule-history`, and carries the API integers (`instanceGitApi`, `soulsApi`,
687
712
  `lifecycleApi`, `readinessApi`, `spawnPreviewApi`, `eventsApi`,
688
713
  `scheduleHistoryApi`). **Gate on these, never on a version string and never by
689
714
  optimistic invocation**: an older CLI ignores an unknown `--plan` on `retire`
690
- and *retires*. Absent feature → the view is unavailable.
715
+ and *retires*. Absent feature → the view is unavailable. (`catalog` was the
716
+ 0.24 `oats catalog` verb's flag; the verb is removed under the workspace model
717
+ and the flag is no longer advertised — the official catalog is reached through
718
+ `packages:` + `oats sync`, not a command.)
719
+
720
+ ## Workspace model (`workspaceApi: 2`)
721
+
722
+ Features: **`workspace-v2`** (the declaration files, `sync`, `package`,
723
+ `workspace status`, `capabilities`, `souls`; `init`/`use`/`install`/`restore`
724
+ removed), **`instance-modules`** (`instance.json.modules` / `providers` /
725
+ `workspace`; `status --json` module drift; preview `modules[]`),
726
+ **`spawn-provider-payload`** (`oats spawn … --provider <cap> k=v`). The probe
727
+ carries `workspaceApi: 2`. Model: [workspaces.md](workspaces.md).
728
+
729
+ Every command below needs a deployment with `oats-local.yaml` (walked up from
730
+ `--dir`/cwd) — else `E_LOCAL_MISSING { dir, searched[] }` — and reads the
731
+ workspace over Git remotes with the operator's credentials, never prompting
732
+ (`E_REMOTE_UNREADABLE { url, reason: "auth"|"not-found"|"network"|"timeout" }`).
733
+ Every `commit` is a full 40-hex OID; every digest is `sha256-<hex>`; every
734
+ `at`/`observedAt` is ISO-8601 UTC. Repo keys are canonical
735
+ (`github.com/org/repo`; `local/<abs-path>` for file remotes).
736
+
737
+ ### Removed verbs answer `E_UNKNOWN_COMMAND` with a replacement
738
+
739
+ `install`, `restore`, `init`, `use`, `trust`, `list`, `catalog`, `remove`,
740
+ `migrate`, `config` — checked before capability dispatch, both modes:
741
+
742
+ ```json
743
+ {"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"}}}
744
+ ```
745
+
746
+ ### oats onboard (onboardApi 2)
747
+
748
+ `oats onboard [<dir>] --workspace <repo ref> [--json]`
749
+
750
+ The **bootstrap** of a deployment (decision 9): realizes a workspace on this
751
+ machine in the taught `<name>-workspace/` layout. It writes
752
+ `<dir>/oats-local.yaml` (`{ schemaVersion: 2, workspace: <ref> }`), creates
753
+ `<dir>/agents/` (the instance homes), then runs exactly the `oats sync` body
754
+ over the directory just written — discover over the remotes, confirm
755
+ membership, resolve `packages:`, approve (TTY) or list what needs approval,
756
+ write `oats-lock.json`. It installs nothing, creates no soul, spawns nothing
757
+ and writes no `oats-config.yaml`; the member clones and the setup-expert spawn
758
+ are printed as next steps. `<dir>` defaults to cwd; `--dir <d>` is the same
759
+ argument (give it once). `--workspace` is required and must be a ref
760
+ `lib/remote.mjs` parses (`E_REPO_REF`) — checked **before** anything is
761
+ written. Captured selectors are refused (`E_BAD_ARGS`).
762
+
763
+ ```json
764
+ {"onboardApi":2,
765
+ "local":"/abs/acme-workspace/oats-local.yaml","dir":"/abs/acme-workspace","agents":"/abs/acme-workspace/agents",
766
+ "lock":"/abs/acme-workspace/oats-lock.json",
767
+ "sync":{"syncApi":1,"…":"the full sync report (next section)"},
768
+ "hosting":{"host":"github.com/acme/agents","hostIsMember":true,
769
+ "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)."},
770
+ "next":{"clone":[{"key":"github.com/acme/agents","name":"agents","url":"https://github.com/acme/agents.git","dir":"/abs/acme-workspace/agents-repo"},
771
+ {"key":"github.com/acme/platform","name":"platform","url":"https://github.com/acme/platform.git","dir":"/abs/acme-workspace/platform"}],
772
+ "spawn":"oats spawn oats-setup-expert --dir /abs/acme-workspace"}}
773
+ ```
774
+
775
+ - `sync` is the `syncApi: 1` report of the first sync (members, packages,
776
+ changes, `approvalNeeded`, `problems`); `lock` is the lock it wrote.
777
+ - **Exit `2` with `ok: true`** when `sync.approvalNeeded` is non-empty (approval
778
+ is interactive-only; tell the operator to run `oats sync --dir <dir>` in a
779
+ terminal). Exit `0` otherwise.
780
+ - `hosting` states decision 26 (the kernel cannot see forge visibility, so it
781
+ reports `hostIsMember` and the rule rather than judging).
782
+ - `next.clone[]` is one row per **confirmed** member (`url` = what the
783
+ workspace's `members:` ref resolves to; `dir` = `<dir>/<name>`, or
784
+ `<dir>/agents-repo` for a member called `agents`, since `agents/` is the
785
+ instance homes). `next.spawn` is the setup-expert spawn command string.
786
+ - Errors (all `E_*`): `E_BAD_ARGS` (usage; missing `--workspace`; dir given
787
+ twice), `E_REPO_REF`, `E_ALREADY_ONBOARDED { local, dir }` — **this**
788
+ directory already has `oats-local.yaml` (an enclosing deployment's file does
789
+ not count; run `oats sync` there instead), `E_ONBOARD_FAILED { dir }`
790
+ (cannot inspect/write the directory; `<dir>` exists and is not a directory).
791
+ Failures while **discovering** — `E_REMOTE_UNREADABLE`, `E_WORKSPACE_SCHEMA`
792
+ (not a workspace host), `E_LOCAL_MISSING`, plus `E_PACKAGE_MISSING` /
793
+ `E_PACKAGE_INTEGRITY` / `E_LOCK_SCHEMA` and the other `sync` errors raised
794
+ before the workspace was read — roll back the files onboarding created and
795
+ carry `details.rolledBack: true` and `details.dir`; a typo'd ref never
796
+ leaves a half-onboarded directory that looks finished. Once the workspace
797
+ has been read, a later failure keeps the files (a lock may exist) and
798
+ carries `details.dir` + `details.local` instead.
799
+
800
+ ### `oats sync [--dir <d>] --json` → `syncApi: 1`
801
+
802
+ Discovers, confirms membership, resolves `packages:` to commits, writes
803
+ `oats-lock.json` (lockfileVersion 3), reports. **Exit `2` with `ok: true`** when
804
+ the lock was written but approvals are pending (`approvalNeeded` non-empty);
805
+ approval is interactive-only, so a Desktop must tell the operator to run
806
+ `oats sync` in a terminal. Exit `0` otherwise.
807
+
808
+ ```json
809
+ {"syncApi":1,
810
+ "workspace":{"name":"acme","key":"github.com/acme/agents","url":"https://github.com/acme/agents.git","commit":"<oid>","observedAt":"<iso>",
811
+ "local":"/abs/acme-workspace/oats-local.yaml","lock":"/abs/acme-workspace/oats-lock.json"},
812
+ "members":[{"key":"github.com/acme/agents","name":"agents","commit":"<oid>","confirmed":true,"status":"confirmed","detail":null,"team":"global",
813
+ "souls":["release-manager"],"capabilities":["acme-house-style"],"publishes":null},
814
+ {"key":"github.com/acme/tools","name":"tools","commit":"<oid>","confirmed":true,"status":"confirmed","detail":null,"team":"engineering",
815
+ "souls":["tools-expert"],"capabilities":["acme-tools-dev"],"publishes":{"package":"acme.tools","version":"0.4.0"}},
816
+ {"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,
817
+ "souls":[],"capabilities":[],"publishes":null}],
818
+ "packages":[{"id":"oats.okf","version":"2.1.3","source":"catalog:oats.okf","commit":"<oid>","integrity":"sha256-…","capabilities":["oats.okf"],
819
+ "approved":{"executables":"sha256-…","at":"<iso>"}},
820
+ {"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}],
821
+ "changes":[{"id":"acme.tools","from":null,"to":"0.4.0","commit":"<oid>","approvalNeeded":true}],
822
+ "approvalNeeded":[{"id":"acme.tools","version":"0.4.0","commit":"<oid>","executables":"sha256-…",
823
+ "targets":["acme-deploy: command apply → bin/acme-deploy.mjs","acme-deploy: hook spawn → bin/acme-deploy.mjs"]}],
824
+ "problems":[]}
825
+ ```
826
+
827
+ - `members[].status`: `confirmed` | `not-listed` | `no-backlink` |
828
+ `backlink-elsewhere` | `cannot-read`; `detail` explains an unconfirmed row.
829
+ `publishes` reports a member's `oats-package/` (informational — its
830
+ capabilities are **not** in `capabilities[]`; the non-collapse rule).
831
+ - `changes[]`: `from` = previously locked version or `null`; `to` = `null` when
832
+ the package was dropped from `packages:` and from the lock.
833
+ - `approvalNeeded[].targets` are human-readable lines
834
+ (`<cap>: command|hook <name> → <relpath>`); `executables` is the digest an
835
+ approval would record.
836
+ - `problems[]`: `{ code, path, message, repoKey? }` — per-item discovery
837
+ problems (`E_WORKSPACE_SCHEMA`, `E_TEAM_UNKNOWN`, `E_REMOTE_*`, …). Never an
838
+ abort: an unreadable member directory is a problem of that member.
839
+ - Errors: `E_LOCAL_MISSING`, `E_WORKSPACE_SCHEMA { path, problems[] }`,
840
+ `E_REMOTE_UNREADABLE`, `E_LOCK_SCHEMA`, `E_PACKAGE_MISSING` (catalog has no
841
+ such id), `E_PACKAGE_INTEGRITY { why: "branch" | locked/observed }`,
842
+ `E_PACKAGE_UNAPPROVED` (approval digest no longer matches),
843
+ `E_PACKAGE_MANIFEST`, `E_REPO_REF`.
844
+
845
+ ### `oats package add <id> <version|git:<repo>@<ref>> | remove <id> [--dir] --json`
846
+
847
+ Edits `packages:` **only** when `oats-workspace.yaml` is tracked by the Git
848
+ checkout walked up from `--dir`; otherwise reports the line to add.
849
+
850
+ ```json
851
+ {"action":"add","id":"oats.aweb","value":"v1.11.2","previous":null,"edited":true,"file":"/abs/agents/oats-workspace.yaml"}
852
+ {"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`"}
853
+ ```
854
+
855
+ `remove` → `{ action: "remove", id, value: null, previous: "<old value>", edited: true, file }`
856
+ on the tracked branch. On the untracked branch the shape is
857
+ `{ action: "remove", id, value: null, edited: false, file: null, line: null, hint }`
858
+ — **no `previous`** (nothing was read), `line: null` (there is no line to add
859
+ for a removal). **Both branches** answer `E_PACKAGE_MISSING { id, path? }` when
860
+ `<id>` is not declared in `packages:` — the untracked branch reads the file it
861
+ found to check the declaration even though it does not edit it. `E_USAGE`,
862
+ `E_WORKSPACE_SCHEMA` (bad id/value, or the edit would make the file invalid),
863
+ `E_REPO_REF`. No network.
864
+
865
+ ### `oats workspace status [--dir] --json` → `workspaceStatusApi: 1`
866
+
867
+ ```json
868
+ {"workspaceStatusApi":1,
869
+ "workspace":{"name":"acme","key":"github.com/acme/agents","url":"…","commit":"<oid>","observedAt":"<iso>","local":"/abs/…/oats-local.yaml","teams":["global","engineering"]},
870
+ "members":[ … same rows as sync … ],
871
+ "packages":[ … same rows as sync (from the lock) … ],
872
+ "declaredPackages":["acme.tools","oats.okf"],
873
+ "unsynced":[],
874
+ "stale":[],
875
+ "approval":{"approved":["oats.okf"],"needed":["acme.tools"]},
876
+ "external":[{"source":"git:github.com/oss-collective/experts@<oid>","soul":"security-reviewer","team":"unassigned"}],
877
+ "problems":[]}
878
+ ```
879
+
880
+ `unsynced` = declared in `packages:` but not in the lock (run `sync`);
881
+ `stale` = locked but no longer declared. Read-only: does not write the lock.
882
+
883
+ ### `oats capabilities [--dir] --json` → `capabilitiesApi: 1` · `oats souls [--dir] --json` → `soulsApi: 1`
884
+
885
+ Every **non-private** item of every confirmed member, external souls, and
886
+ locked package capabilities, sorted by name then origin. `origin` is the
887
+ human string (`member <key> @ <8-char commit>` | `package <id> v<version>` |
888
+ `external <key> @ <commit>`); `kind` is the machine field. `team` is the label
889
+ or `"unassigned"`.
890
+
891
+ ```json
892
+ {"capabilitiesApi":1,"workspace":{"name":"acme","key":"github.com/acme/agents","commit":"<oid>"},
893
+ "capabilities":[
894
+ {"name":"acme-house-style","origin":"member github.com/acme/agents @ 3f2a9c1e","kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>",
895
+ "team":"global","private":false,"path":"capabilities/acme-house-style","layer":null,"version":"0.0.0-workspace"},
896
+ {"name":"oats.okf","origin":"package oats.okf v2.1.3","kind":"package","package":"oats.okf","version":"2.1.3","commit":"<oid>",
897
+ "team":"unassigned","private":false,"approved":true}],
898
+ "problems":[]}
899
+ ```
900
+
901
+ ```json
902
+ {"soulsApi":1,"workspace":{…},
903
+ "souls":[
904
+ {"name":"release-manager","origin":"member github.com/acme/agents @ 3f2a9c1e","kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>",
905
+ "team":"engineering","private":false,"path":"souls/release-manager","work":"worktree","description":"Cuts, verifies and announces releases."},
906
+ {"name":"security-reviewer","origin":"external github.com/oss-collective/experts @ 9c4e1f2a","kind":"external",…}],
907
+ "problems":[]}
908
+ ```
909
+
910
+ Package capabilities of declared-but-unsynced packages are absent until `sync`.
911
+ (`soulsApi: 1` is also the integer of the existing `oats inspect --json`
912
+ declarations block; the two payloads are distinguished by their command.)
913
+
914
+ ### `oats spawn <soul> … --preview --json` — additions (Preview API 2 unchanged)
915
+
916
+ On a workspace deployment the preview carries four extra top-level fields, and
917
+ `decision.resolution` binds the resolution revision (so a member that moved
918
+ between preview and apply is `E_DECISION_STALE`):
919
+
920
+ ```json
921
+ {"modules":[
922
+ {"name":"acme-release-tooling","from":{"kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>"},"layer":null,"private":false,
923
+ "changedSince":{"instance":"release-manager-v2","was":"<old oid>"}},
924
+ {"name":"oats.okf","from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"<oid>","integrity":"sha256-…","repoKey":"github.com/awebai/oats-okf"},
925
+ "layer":"knowledge","private":false,"changedSince":false}],
926
+ "team":"engineering",
927
+ "resolution":"6e3050c0d005879441ab017d",
928
+ "workspace":"github.com/acme/agents",
929
+ "spawnPreviewApi":2,"preview":true,"agent":"release-manager","instance":"release-manager-cut","home":"/abs/…",
930
+ "decision":{"instance":"…","home":"…","branch":"…","base":{…},"effective":{…},"resolution":"6e3050c0d005879441ab017d","revision":"<24 hex>"},
931
+ "…":"every Preview API 2 field as before"}
932
+ ```
933
+
934
+ - `modules[].from` is exactly the `from` recorded in `instance.json` on apply.
935
+ - `changedSince`: `null` (no previous instance of this soul), `false`
936
+ (unchanged since the newest previous instance), or
937
+ `{ instance, was }` (`was` = the previous commit, or `null` when the previous
938
+ instance had no such module).
939
+ - `capabilities[]` / `skills[]` keep their Preview-1 meaning; on a workspace
940
+ spawn the authoritative module set is `modules[]` (`capabilities[]` may be
941
+ empty there, since capability rows are filled after materialization).
942
+ - `workspace` is the workspace host's canonical key, `team` the soul's label
943
+ (or `null`), `resolution` the 24-hex revision `decision.resolution` binds.
944
+ - `--provider <cap> <key>=<value>` (repeatable; `a.b=c` nests) is accepted by
945
+ preview and apply. `E_BAD_ARGS` for a malformed pair or when the deployment
946
+ has no `oats-local.yaml`; `E_CAPABILITY_MISSING { capability, soul, modules[] }`
947
+ when the soul does not resolve that capability. `byTeam` is a **reserved
948
+ key**: legal only at the top level of the workspace file's `messaging:`;
949
+ anywhere else in any payload layer (soul, `oats-local.yaml` `settings`,
950
+ `--provider`, at any depth) it is `E_WORKSPACE_SCHEMA { reason: "reserved-key", path, key }`.
951
+ - Soul lookup: `E_SOUL_UNKNOWN { name, members[] }` (not among confirmed
952
+ members or externals), `E_SOUL_AMBIGUOUS { name, repos[] }` (name it
953
+ `<repo>/<soul>`). Resolution errors keep their codes (`E_NOT_A_MEMBER`,
954
+ `E_MEMBERSHIP_UNCONFIRMED { repoKey, reason }`, `E_CAPABILITY_MISSING { hint? }`,
955
+ `E_CAPABILITY_PRIVATE`, `E_PACKAGE_MISSING`, `E_PACKAGE_UNAPPROVED { id, version, commit }`,
956
+ `E_SLOT_CONFLICT { slot, modules[], reason? }`, `E_SKILL_DUPLICATE { name, modules[] }`,
957
+ `E_COMPATIBILITY { capability, package, version, range, why? }`).
958
+
959
+ The apply result (`oats spawn … --json`) is unchanged in shape; the new facts
960
+ live in the home's `instance.json`.
961
+
962
+ ### `instance.json` — `modules`, `providers`, `workspace` (feature `instance-modules`)
963
+
964
+ Written by materialization inside the spawn transaction; read back by
965
+ `oats status --json` and the roster.
966
+
967
+ ```json
968
+ {"modules":{
969
+ "acme-release-tooling":{"from":{"kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>"},
970
+ "commit":"<oid>","digest":"sha256-…","materializedAt":"<iso>"},
971
+ "oats.okf":{"from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"<oid>","integrity":"sha256-…","repoKey":"github.com/awebai/oats-okf"},
972
+ "commit":"<oid>","digest":"sha256-…","materializedAt":"<iso>"}},
973
+ "providers":{"acme-release-tooling":{},"oats.okf":{"owns":"release-manager","reads":["platform-engineer"],"state-dir":"/Users/ana/.oats/okf"}},
974
+ "workspace":{"key":"github.com/acme/agents","commit":"<oid>","resolution":"<24 hex>","standalone":false,
975
+ "soul":{"repoKey":"github.com/acme/agents","commit":"<oid>","team":"engineering"}},
976
+ "capabilities":[{"id":"oats.okf","capability":"oats.okf","origin":"package:oats.okf@2.1.3","…":"one row per module"}]}
977
+ ```
978
+
979
+ `workspace.standalone` is `true` when the instance was spawned from the
980
+ **standalone view** (decisions 10/25: a *member* whose workspace could not be
981
+ read — `workspace.key` is then the member repo's key, and `modules` holds the
982
+ soul's `from: here` capabilities plus `oats.core`); `false` for a workspace
983
+ spawn. The same view is marked `standalone: true` in `oats sync --json` (with
984
+ `workspace.name` = `standalone:<repo>`) and in the roster. `capabilities[]` is
985
+ the per-module row set (`toCapabilityRows`) that `oats inspect`/`status` read.
986
+
987
+ `digest` is the sha256 of the copied module tree (`<home>/.oats/modules/<cap>/`);
988
+ `providers.<cap>` is the merged payload (soul ⊕ `oats-local.yaml`
989
+ `settings.<cap>` ⊕ `--provider`), `{}` for a capability with none. Copies live at
990
+ `<home>/.oats/modules/<cap>/` and `<home>/.agents/skills/<cap>/<skill>/`.
991
+
992
+ ### `oats status [--dir] --json` — module drift
993
+
994
+ On a workspace deployment the roster does one discovery and rewrites each
995
+ instance's `modules` from the recorded map into **drift rows**, and adds a
996
+ top-level `workspace` reachability field:
997
+
998
+ ```json
999
+ {"root":"/abs/acme-workspace/agents",
1000
+ "agents":[{"name":"release-manager",…,"instances":[{"instance":"release-manager-cut",…,
1001
+ "modules":[
1002
+ {"name":"acme-release-tooling","from":{"kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>"},"commit":"<oid>",
1003
+ "current":{"commit":"<new oid>"},"status":"moved"},
1004
+ {"name":"acme-house-style","from":{…},"commit":"<oid>","current":{"commit":"<oid>"},"status":"missing","reason":"capability-absent"},
1005
+ {"name":"oats.okf","from":{"kind":"package",…},"commit":"<oid>","current":{"commit":"<oid>","version":"2.1.3"},"status":"current"}]}]}],
1006
+ "workspace":{"reachable":true}}
1007
+ ```
1008
+
1009
+ - `status`: `current` | `moved` (member or locked package now at another
1010
+ commit) | `missing` with `reason`: `capability-absent` (gone from the member /
1011
+ package), `package-absent` (no longer locked), or the member's unconfirmed
1012
+ reason (`no-backlink`, `cannot-read`, `unconfirmed`, …; `current: null`).
1013
+ - Package modules without a lock are reported `current`.
1014
+ - Offline: `"workspace":{"reachable":false,"code":"E_REMOTE_UNREADABLE","reason":"E_REMOTE_UNREADABLE: network","message":"…"}`
1015
+ and `modules` stays the recorded map (no drift rows). A deployment without
1016
+ `oats-local.yaml` has no `workspace` field and `modules` as recorded.
1017
+ - Text mode prints `modules: <cap> from <member|package …> @ <7-char>` lines
1018
+ for non-current modules (`--verbose` for all).
1019
+
1020
+ ### Probe
1021
+
1022
+ ```json
1023
+ {"…":"…","features":["…","workspace-v2","instance-modules","spawn-provider-payload"],"workspaceApi":2}
1024
+ ```
1025
+
1026
+ A feature is listed only once the binary implements it. Gate `sync`/`package`/
1027
+ `workspace status`/`capabilities`/`souls` on `workspace-v2`; gate reading
1028
+ `instance.json.modules` and preview `modules[]` on `instance-modules`; gate
1029
+ `--provider` on `spawn-provider-payload`.
691
1030
 
692
1031
  ## Instruction refresh (`oats session recompose`, feature `session-recompose`, OATS 0.24.8+)
693
1032
 
@@ -707,6 +1046,18 @@ refreshes the home in place:
707
1046
  schedule; the receipt's `note` says so. Refuses a retiring home
708
1047
  (`E_INSTANCE_RETIRING`), captured incarnations and capability-defined souls
709
1048
  (`E_UNSUPPORTED_MODE`: those are refreshed by a new resolution / package).
1049
+ - **Module homes (0.25+) are `E_UNSUPPORTED_MODE` too.** A home whose
1050
+ `instance.json` carries `modules{}` (spawned on a workspace deployment,
1051
+ `instance-modules`) answers `E_UNSUPPORTED_MODE` ("recompose from
1052
+ materialized modules is not supported yet; re-spawn"): its `AGENTS.md` was
1053
+ composed from the soul at a recorded member commit plus the materialized
1054
+ modules' injects, and the instance never changes under itself (decision 7).
1055
+ The refresh path for such a home is a new spawn (the soul is re-fetched at
1056
+ the member's current commit). `session-recompose` **stays advertised** in
1057
+ `features[]` because the verb still works for classic homes; gate the UI
1058
+ action on the feature AND on the absence of `instance.json.modules`
1059
+ (`oats status --json` `instances[].modules` is non-empty for a module home),
1060
+ and render the typed refusal otherwise.
710
1061
  - Gate on `features.includes("session-recompose")`. It is an **operator
711
1062
  action** (the human or the instance's parent), never something a Desktop
712
1063
  poll or an agent runs on itself.
@@ -12,10 +12,17 @@ the OATS Desktop app (`packages/desktop/` in the framework repo):
12
12
 
13
13
  ## Migrating a deployment that used `oats.web`
14
14
 
15
+ Under the **0.25 workspace model** there is nothing to uninstall: remove
16
+ `oats.web` from `oats-workspace.yaml` `packages:` / `defaults.capabilities`
17
+ and from any `soul.yaml` `capabilities:`, run `oats sync` (the lock v3 entry
18
+ disappears with the declaration), and use the Desktop app (step 3 below).
19
+
20
+ For a **0.24 classic** deployment:
21
+
15
22
  1. Remove the `oats.web` entry from `capabilities.additive` in every
16
23
  `oats-config.yaml` in your config chain.
17
- 2. Remove the `oats.web` entry from `oats-lock.json` at the same scope(s), and
18
- delete any stale installed copy under `.agents/capabilities/installed/`.
24
+ 2. Remove the `oats.web` entry from `oats-lock.json` (v2) at the same scope(s),
25
+ and delete any stale installed copy under `.agents/capabilities/installed/`.
19
26
  3. Use the OATS Desktop app instead: `cd packages/desktop && npm install &&
20
27
  npm run rebuild && npm start` (see `packages/desktop/README.md`).
21
28
 
@@ -23,10 +30,9 @@ The CLI diagnoses stale references instead of failing opaquely:
23
30
 
24
31
  - `oats doctor` warns when an `oats-lock.json` still pins `oats.web`, with the
25
32
  fix spelled out.
26
- - Bare `oats install` reports the lock entry as `RETIRED` (with guidance)
27
- rather than a restore failure.
28
- - `oats install oats.web` and a config activation of `oats.web` fail with a
29
- message naming the successor and the exact cleanup steps.
33
+ - A soul or workspace default naming `oats.web` fails at resolution with a
34
+ message naming the successor and the exact cleanup steps (remove it from
35
+ `packages:` / the soul's `capabilities:`).
30
36
 
31
37
  ## Migrating `oats pane` usage
32
38
 
package/docs/desktop.md CHANGED
@@ -84,10 +84,15 @@ The probe/mutation contract is specified in
84
84
 
85
85
  The app starts on the directory it was launched with (its own folder by
86
86
  default). To view a deployment, open the workspace switcher in the sidebar
87
- and choose **Add workspace → Browse**, then point it at an OATS workspace —
88
- a directory containing `agents/`, or `local-agents/` for machine-local
89
- souls, or a team scope whose `oats-config.yaml` declares `team:`. Team scopes
90
- show every member repo's agents under one roster with a workspace switcher.
87
+ and choose **Add workspace → Browse**, then point it at an OATS deployment —
88
+ a directory containing `agents/` (under the 0.25 workspace model that is the
89
+ `<name>-workspace/` directory holding `oats-local.yaml` and `agents/`;
90
+ under 0.24, an `agents/` root, a `local-agents/` root for machine-local souls,
91
+ or a team scope whose `oats-config.yaml` declares `team:`). *The Desktop's own
92
+ multi-repo roster ("team scopes show every member repo's agents under one
93
+ roster") still keys on the 0.24 `oats-config.yaml` `team:` declaration; reading
94
+ the member set from `oats-local.yaml` / `oats workspace status` is the Phase F
95
+ follow-up named in the [0.25.0 notes](release-notes/v0.25.0.md#desktop).*
91
96
  Added workspaces are remembered and offered as suggestions next time.
92
97
 
93
98
  Local souls (uncommitted, machine-local agents under `local-agents/`) are
@@ -231,10 +231,22 @@ and needs equivalent registration glue when switched to session delivery.
231
231
 
232
232
  ## Shared permission setting
233
233
 
234
- Set `yolo: true` in an `oats-config.yaml` to apply it to that scope. The closest
235
- scope wins; an optional `yolo` in soul.yaml overrides it; `oats spawn --yolo` or
236
- `--no-yolo` overrides both. `oats create` accepts those flags too. Desktop offers
237
- the same per-launch choice. With no setting, native policy is retained.
234
+ The opt-in is per launch or per soul: `oats spawn --yolo` / `--no-yolo`
235
+ (`oats create` accepts the same flags), an optional `yolo` in `soul.yaml`
236
+ (0.24 schema; the v2 `soul.yaml` schema does not carry it — use the spawn flag
237
+ or a launch configuration), and the Desktop's per-launch choice. With no
238
+ setting, native policy is retained.
239
+
240
+ *0.24 classic deployments* may also set `yolo: true` in an `oats-config.yaml`
241
+ to apply it to that scope; the closest scope wins, soul overrides scope, the
242
+ spawn flag overrides both. *Under the workspace model* `oats-config.yaml` is
243
+ not configuration ([configuration.md](configuration.md)); the kernel's
244
+ `composeInstance` still consults the classic chain for the machine-level knobs
245
+ `yolo` and `launch-configs` when such a file happens to sit above the
246
+ deployment, but nothing writes one and the rebuild guide tells you to delete
247
+ it — treat a scope-level `yolo` as a 0.24 feature and prefer the explicit
248
+ spawn flag.
249
+
238
250
  Autonomous or unattended execution is not permission to synthesize `yolo: true`.
239
251
  Explicit user CLI/UI input or a user-selected configuration is the opt-in; explicit
240
252
  `false` remains false.