@awebai/oats 0.29.4 → 0.30.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 (263) hide show
  1. package/README.md +12 -6
  2. package/bin/oats.mjs +194 -50
  3. package/docs/capabilities.md +160 -171
  4. package/docs/capability-manifest.schema.json +6 -11
  5. package/docs/configuration.md +213 -64
  6. package/docs/design/2026-09-16-knowledge-capability-contract.md +36 -50
  7. package/docs/design/2026-09-23-workspace-module-contracts.md +377 -544
  8. package/docs/design/2026-09-26-okf-knowledge-operations.md +132 -357
  9. package/docs/design/2026-09-27-team-model-v2.md +97 -117
  10. package/docs/design/2026-09-28-automations-trust.md +38 -0
  11. package/docs/design/2026-09-28-soul-launch-preference.md +63 -0
  12. package/docs/design/HISTORY.md +65 -0
  13. package/docs/design/README.md +23 -54
  14. package/docs/desktop-cli-api.md +1787 -1777
  15. package/docs/desktop.md +30 -91
  16. package/docs/execution-targets.md +146 -292
  17. package/docs/first-team.md +31 -17
  18. package/docs/implementation.md +77 -288
  19. package/docs/integrations.md +118 -320
  20. package/docs/knowledge-capability-authoring.md +25 -52
  21. package/docs/knowledge-reference/acceptance.md +3 -3
  22. package/docs/knowledge-reference/adoption.md +1 -1
  23. package/docs/knowledge-reference/harvester.md +2 -2
  24. package/docs/knowledge-reference/package-craft.md +3 -3
  25. package/docs/knowledge-reference/provider-mapping.md +3 -6
  26. package/docs/knowledge-reference/reader-capture.md +3 -3
  27. package/docs/knowledge-theory.md +62 -166
  28. package/docs/knowledge.md +225 -404
  29. package/docs/layers.md +42 -97
  30. package/docs/oats-local.schema.json +58 -5
  31. package/docs/oats-membership.schema.json +1 -8
  32. package/docs/oats-package.schema.json +5 -5
  33. package/docs/oats-workspace.schema.json +8 -22
  34. package/docs/official-catalog.md +25 -28
  35. package/docs/packages.md +45 -63
  36. package/docs/plans/0.30-close-out.md +83 -0
  37. package/docs/release-lane.md +82 -0
  38. package/docs/release-notes/oats-framework-v1.1.3.md +10 -8
  39. package/docs/release-notes/v0.19.0.md +48 -147
  40. package/docs/release-notes/v0.19.1.md +2 -3
  41. package/docs/release-notes/v0.19.3.md +2 -15
  42. package/docs/release-notes/v0.20.0.md +0 -15
  43. package/docs/release-notes/v0.22.0.md +71 -138
  44. package/docs/release-notes/v0.22.1.md +42 -90
  45. package/docs/release-notes/v0.22.10.md +1 -1
  46. package/docs/release-notes/v0.22.11.md +1 -47
  47. package/docs/release-notes/v0.22.12.md +4 -13
  48. package/docs/release-notes/v0.22.13.md +1 -42
  49. package/docs/release-notes/v0.22.14.md +3 -11
  50. package/docs/release-notes/v0.22.15.md +1 -46
  51. package/docs/release-notes/v0.22.16.md +6 -8
  52. package/docs/release-notes/v0.22.18.md +1 -99
  53. package/docs/release-notes/v0.22.19.md +3 -14
  54. package/docs/release-notes/v0.22.2.md +6 -15
  55. package/docs/release-notes/v0.22.3.md +0 -1
  56. package/docs/release-notes/v0.22.4.md +1 -14
  57. package/docs/release-notes/v0.22.5.md +2 -12
  58. package/docs/release-notes/v0.22.6.md +0 -3
  59. package/docs/release-notes/v0.23.0.md +9 -25
  60. package/docs/release-notes/v0.23.1.md +9 -25
  61. package/docs/release-notes/v0.23.2.md +2 -4
  62. package/docs/release-notes/v0.24.0.md +56 -97
  63. package/docs/release-notes/v0.24.1.md +7 -11
  64. package/docs/release-notes/v0.24.10.md +34 -45
  65. package/docs/release-notes/v0.24.11.md +12 -20
  66. package/docs/release-notes/v0.24.12.md +35 -48
  67. package/docs/release-notes/v0.24.13.md +34 -41
  68. package/docs/release-notes/v0.24.2.md +9 -13
  69. package/docs/release-notes/v0.24.3.md +7 -11
  70. package/docs/release-notes/v0.24.4.md +6 -6
  71. package/docs/release-notes/v0.24.5.md +6 -10
  72. package/docs/release-notes/v0.24.6.md +2 -5
  73. package/docs/release-notes/v0.24.7.md +46 -75
  74. package/docs/release-notes/v0.24.8.md +58 -96
  75. package/docs/release-notes/v0.24.9.md +38 -54
  76. package/docs/release-notes/v0.25.0.md +59 -76
  77. package/docs/release-notes/v0.25.1.md +57 -81
  78. package/docs/release-notes/v0.25.2.md +51 -70
  79. package/docs/release-notes/v0.25.3.md +11 -13
  80. package/docs/release-notes/v0.25.4.md +9 -13
  81. package/docs/release-notes/v0.25.5.md +3 -5
  82. package/docs/release-notes/v0.25.6.md +20 -29
  83. package/docs/release-notes/v0.25.7.md +5 -7
  84. package/docs/release-notes/v0.25.8.md +26 -39
  85. package/docs/release-notes/v0.26.0.md +175 -646
  86. package/docs/release-notes/v0.27.0.md +4 -5
  87. package/docs/release-notes/v0.27.1.md +4 -6
  88. package/docs/release-notes/v0.27.2.md +1 -1
  89. package/docs/release-notes/v0.28.0.md +57 -124
  90. package/docs/release-notes/v0.29.0.md +89 -208
  91. package/docs/release-notes/v0.29.1.md +1 -1
  92. package/docs/release-notes/v0.29.2.md +3 -4
  93. package/docs/release-notes/v0.30.0.md +205 -0
  94. package/docs/release-notes/v0.30.1.md +123 -0
  95. package/docs/schedules.md +280 -363
  96. package/docs/servers.md +99 -117
  97. package/docs/soul.schema.json +2 -9
  98. package/docs/souls-and-instances.md +145 -158
  99. package/docs/workspaces.md +137 -215
  100. package/lib/automations.mjs +21 -6
  101. package/lib/core.mjs +226 -74
  102. package/lib/instance-events.mjs +1 -1
  103. package/lib/instance-inspect.mjs +109 -34
  104. package/lib/instance-lifecycle.mjs +14 -1
  105. package/lib/instance-resolution.mjs +26 -27
  106. package/lib/launch-preference.mjs +87 -0
  107. package/lib/materialize.mjs +3 -3
  108. package/lib/packages.mjs +1 -1
  109. package/lib/resolve.mjs +30 -88
  110. package/lib/schedule.mjs +1 -1
  111. package/lib/teams-verbs.mjs +195 -0
  112. package/lib/teams.mjs +190 -0
  113. package/lib/triggers.mjs +2 -2
  114. package/lib/workspace.mjs +54 -147
  115. package/package-catalog.json +10 -16
  116. package/package.json +1 -3
  117. package/skills/oats-getting-started/SKILL.md +25 -13
  118. package/capabilities/oats-authoring/LICENSE +0 -21
  119. package/capabilities/oats-authoring/oats-package.json +0 -11
  120. package/capabilities/oats-authoring/oats.json +0 -12
  121. package/capabilities/oats-authoring/skills/integration-authoring/SKILL.md +0 -84
  122. package/capabilities/oats-authoring/skills/skill-craft/SKILL.md +0 -109
  123. package/capabilities/oats-authoring/skills/soul-craft/SKILL.md +0 -116
  124. package/capabilities/oats-aweb/bin/oats-aweb-binding.mjs +0 -11
  125. package/capabilities/oats-aweb/bin/oats-aweb.mjs +0 -1338
  126. package/capabilities/oats-aweb/injects/aweb.md +0 -47
  127. package/capabilities/oats-aweb/lib/binding-wire.mjs +0 -356
  128. package/capabilities/oats-aweb/lib/captured-execution.mjs +0 -91
  129. package/capabilities/oats-aweb/lib/captured-native.mjs +0 -91
  130. package/capabilities/oats-aweb/lib/grant-custody.mjs +0 -38
  131. package/capabilities/oats-aweb/lib/invocation-shape.mjs +0 -135
  132. package/capabilities/oats-aweb/lib/portable-binding.mjs +0 -146
  133. package/capabilities/oats-aweb/lib/session-readiness.mjs +0 -56
  134. package/capabilities/oats-aweb/lib/wake-receive.mjs +0 -56
  135. package/capabilities/oats-aweb/oats.json +0 -208
  136. package/capabilities/oats-aweb/skills/LICENSE +0 -21
  137. package/capabilities/oats-aweb/skills/VENDORED.md +0 -31
  138. package/capabilities/oats-aweb/skills/aweb-identity/SKILL.md +0 -201
  139. package/capabilities/oats-aweb/skills/aweb-messaging/SKILL.md +0 -161
  140. package/capabilities/oats-aweb/skills/aweb-messaging/references/messaging-scenarios.md +0 -61
  141. package/capabilities/oats-aweb/skills/aweb-team-membership/SKILL.md +0 -116
  142. package/capabilities/oats-aweb/skills/aweb-team-membership/references/team-membership-reference.md +0 -74
  143. package/capabilities/oats-aweb/skills/oats-aweb/SKILL.md +0 -216
  144. package/capabilities/oats-jira/bin/oats-jira.mjs +0 -40
  145. package/capabilities/oats-jira/injects/jira.md +0 -10
  146. package/capabilities/oats-jira/oats.json +0 -22
  147. package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +0 -179
  148. package/capabilities/oats-linear/bin/oats-linear-hook.mjs +0 -34
  149. package/capabilities/oats-linear/bin/oats-linear.mjs +0 -344
  150. package/capabilities/oats-linear/injects/linear.md +0 -8
  151. package/capabilities/oats-linear/oats.json +0 -24
  152. package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +0 -223
  153. package/capabilities/oats-okf/bin/oats-okf-binding.mjs +0 -14
  154. package/capabilities/oats-okf/bin/oats-okf.mjs +0 -209
  155. package/capabilities/oats-okf/injects/okf.md +0 -42
  156. package/capabilities/oats-okf/lib/binding-wire.mjs +0 -348
  157. package/capabilities/oats-okf/lib/captured-worker.mjs +0 -109
  158. package/capabilities/oats-okf/lib/config.mjs +0 -124
  159. package/capabilities/oats-okf/lib/consult.mjs +0 -518
  160. package/capabilities/oats-okf/lib/harvest-status.mjs +0 -88
  161. package/capabilities/oats-okf/lib/harvest-switch.mjs +0 -94
  162. package/capabilities/oats-okf/lib/inspection.mjs +0 -119
  163. package/capabilities/oats-okf/lib/invocation-context.mjs +0 -111
  164. package/capabilities/oats-okf/lib/invocation-shape.mjs +0 -135
  165. package/capabilities/oats-okf/lib/io.mjs +0 -118
  166. package/capabilities/oats-okf/lib/migration.mjs +0 -137
  167. package/capabilities/oats-okf/lib/okf-validate.mjs +0 -123
  168. package/capabilities/oats-okf/lib/portable-binding.mjs +0 -199
  169. package/capabilities/oats-okf/lib/source-contract.mjs +0 -46
  170. package/capabilities/oats-okf/lib/sources.mjs +0 -424
  171. package/capabilities/oats-okf/lib/stores.mjs +0 -473
  172. package/capabilities/oats-okf/lib/worker.mjs +0 -497
  173. package/capabilities/oats-okf/oats.json +0 -148
  174. package/capabilities/oats-okf/schemas/okf-base.schema.json +0 -46
  175. package/capabilities/oats-okf/schemas/okf-bindings.schema.json +0 -112
  176. package/capabilities/oats-okf/schemas/okf-portable-declaration.schema.json +0 -87
  177. package/capabilities/oats-okf/schemas/okf-portable-payload.schema.json +0 -113
  178. package/capabilities/oats-okf/schemas/okf-soul.schema.json +0 -37
  179. package/capabilities/oats-okf/skills/okf-consultation/SKILL.md +0 -144
  180. package/capabilities/oats-okf/skills/okf-consultation/references/consult.md +0 -86
  181. package/capabilities/oats-okf/skills/okf-instance-knowledge/SKILL.md +0 -104
  182. package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +0 -140
  183. package/capabilities/oats-okf-harvest/injects/harvester.md +0 -12
  184. package/capabilities/oats-okf-harvest/oats.json +0 -26
  185. package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +0 -168
  186. package/capabilities/oats-okf-harvest/skills/knowledge-theory/SKILL.md +0 -192
  187. package/capabilities/oats-okf-harvest/skills/okf-authoring/SKILL.md +0 -151
  188. package/capabilities/oats-okf-harvest/skills/okf-authoring/scripts/okf-validate.mjs +0 -123
  189. package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +0 -170
  190. package/capabilities/oats-okf-maintenance/injects/maintainer.md +0 -12
  191. package/capabilities/oats-okf-maintenance/lib/provenance.mjs +0 -50
  192. package/capabilities/oats-okf-maintenance/oats.json +0 -21
  193. package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +0 -159
  194. package/capabilities/oats-okf-maintenance/skills/knowledge-theory/SKILL.md +0 -192
  195. package/capabilities/oats-okf-maintenance/skills/okf-authoring/SKILL.md +0 -151
  196. package/capabilities/oats-okf-maintenance/skills/okf-authoring/scripts/okf-validate.mjs +0 -123
  197. package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +0 -146
  198. package/capabilities/oats-review/injects/review.md +0 -69
  199. package/capabilities/oats-review/oats.json +0 -10
  200. package/capabilities/oats-review/skills/code-review/SKILL.md +0 -44
  201. package/capabilities/oats-review/skills/security-review/SKILL.md +0 -59
  202. package/docs/conventions.md +0 -90
  203. package/docs/design/2026-09-07-architecture-reassessment.md +0 -131
  204. package/docs/design/2026-09-07-desktop-souls-capabilities.md +0 -50
  205. package/docs/design/2026-09-07-mobile-agent-management-proposal.md +0 -228
  206. package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +0 -558
  207. package/docs/design/2026-09-13-knowledge-and-memory-direction.md +0 -744
  208. package/docs/design/2026-09-13-knowledge-implementation.md +0 -127
  209. package/docs/design/2026-09-13-knowledge-location-contract.md +0 -340
  210. package/docs/design/2026-09-14-artifact-retention-contract.md +0 -190
  211. package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +0 -708
  212. package/docs/design/2026-09-14-portable-souls-contract-amendments.md +0 -85
  213. package/docs/design/2026-09-14-portable-souls-explainer.md +0 -750
  214. package/docs/design/2026-09-15-captured-dispatch.md +0 -127
  215. package/docs/design/2026-09-15-captured-resolution-records.md +0 -143
  216. package/docs/design/2026-09-15-package-preparation.md +0 -100
  217. package/docs/design/2026-09-15-portable-data-contract.md +0 -121
  218. package/docs/design/2026-09-15-portable-declarations.md +0 -189
  219. package/docs/design/2026-09-15-portable-souls-handoff.md +0 -150
  220. package/docs/design/2026-09-15-portable-souls-implementation.md +0 -417
  221. package/docs/design/2026-09-15-selection-lock-and-approval.md +0 -122
  222. package/docs/design/2026-09-15-source-observation.md +0 -119
  223. package/docs/design/2026-09-16-captured-admission.md +0 -77
  224. package/docs/design/2026-09-16-captured-helper-dispatch.md +0 -105
  225. package/docs/design/2026-09-16-captured-launch-inputs.md +0 -42
  226. package/docs/design/2026-09-16-command-profile-preparation.md +0 -86
  227. package/docs/design/2026-09-16-fresh-install-first-rollout.md +0 -47
  228. package/docs/design/2026-09-16-fresh-operator-walkthrough.md +0 -282
  229. package/docs/design/2026-09-16-messaging-capability-contract.md +0 -59
  230. package/docs/design/2026-09-16-portable-migration-evidence.md +0 -158
  231. package/docs/design/2026-09-16-portable-onboarding.md +0 -179
  232. package/docs/design/2026-09-16-prepare-request-transport.md +0 -26
  233. package/docs/design/2026-09-16-provider-binding-codecs.md +0 -98
  234. package/docs/design/2026-09-16-provider-binding-wire.md +0 -274
  235. package/docs/design/2026-09-17-capability-helper-input-contract.md +0 -95
  236. package/docs/design/2026-09-17-captured-backend-parity.md +0 -53
  237. package/docs/design/2026-09-17-captured-native-start.md +0 -58
  238. package/docs/design/2026-09-17-portable-boundary-hookup.md +0 -19
  239. package/docs/design/2026-09-17-portable-boundary-resources.md +0 -52
  240. package/docs/design/2026-09-17-public-captured-start.md +0 -108
  241. package/docs/design/2026-09-17-public-prepare-request.md +0 -90
  242. package/docs/design/2026-09-18-captured-pi-host.md +0 -205
  243. package/docs/design/2026-09-18-first-cut-release-checklist.md +0 -131
  244. package/docs/design/2026-09-18-herdr-protocol-compatibility.md +0 -60
  245. package/docs/design/2026-09-20-redesign-program-board.md +0 -142
  246. package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +0 -289
  247. package/docs/design/2026-09-20-workspace-onboarding-public.md +0 -207
  248. package/docs/design/2026-09-22-desktop-parity-seams.md +0 -58
  249. package/docs/design/2026-09-23-simplified-workspace-model.md +0 -711
  250. package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +0 -65
  251. package/docs/design/2026-09-24-desktop-phase-f-boundary.md +0 -242
  252. package/docs/design/2026-09-24-phase-d-plan.md +0 -305
  253. package/docs/design/2026-09-25-teams-contract.md +0 -258
  254. package/docs/design/2026-09-26-desktop-design-brief-architecture.md +0 -241
  255. package/docs/design/desktop-ux-plan.md +0 -362
  256. package/docs/design/launch-configurations.md +0 -168
  257. package/docs/design/okf-mirror-provenance.md +0 -105
  258. package/docs/design/operations-contract.md +0 -141
  259. package/docs/oats-member.schema.json +0 -38
  260. package/skills/integration-authoring/SKILL.md +0 -84
  261. package/skills/oats-support/SKILL.md +0 -79
  262. package/skills/skill-craft/SKILL.md +0 -109
  263. package/skills/soul-craft/SKILL.md +0 -116
@@ -1,13 +1,8 @@
1
- # Workspaces — one workspace per organisation, members are trust, nothing is installed
1
+ # Workspaces: one workspace per organisation, members are trust, nothing is installed
2
2
 
3
- This is the OATS workspace model (v2, the 0.25 line). It replaces the per-soul
4
- `source:` grammar, the installed-capability tier and `oats-config.yaml`. The
5
- normative record is the Decision concept
6
- `agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md`; the module
7
- contracts the kernel is built against are in
8
- [design/2026-09-23-workspace-module-contracts.md](design/2026-09-23-workspace-module-contracts.md);
9
- a full worked example (an imaginary company with three teams) is in
10
- [design/2026-09-23-simplified-workspace-model.md](design/2026-09-23-simplified-workspace-model.md).
3
+ This is the OATS workspace model. The module contracts the kernel is built
4
+ against are in
5
+ [design/2026-09-23-workspace-module-contracts.md](design/2026-09-23-workspace-module-contracts.md).
11
6
 
12
7
  ## The rule
13
8
 
@@ -44,7 +39,7 @@ shapes; domain rules (declared teams, duplicate members, canonical `from:` keys,
44
39
  the two `packages:` value forms) live in the kernel's `validateWorkspace` /
45
40
  `validateSoul`, which are the authority.
46
41
 
47
- ### `oats-workspace.yaml` — the one shared declaration
42
+ ### `oats-workspace.yaml`: the one shared declaration
48
43
 
49
44
  Lives in the repository that **hosts** the workspace (often a dedicated
50
45
  `agents` repo, but any member can host it). One per organisation.
@@ -59,13 +54,13 @@ members: # repo refs, NO @revision (E_WORKSPAC
59
54
  - git:github.com/acme/tools # a member that ALSO publishes a package (see below)
60
55
 
61
56
  packages: # the ONLY versioned things
62
- oats.framework: v1.3.2 # bare version → resolves through the official catalog
63
- oats.okf: v4.0.1
57
+ oats.framework: v1.4.0 # bare version → resolves through the official catalog
58
+ oats.okf: v4.0.5
64
59
  acme.tools: git:github.com/acme/tools@v0.4.0 # outside the catalog → git:<repo>@<tag|OID>; still a package
65
60
 
66
- teams: # labels, declared once so they cannot drift
67
- global: { description: Org-wide souls and house capabilities }
68
- engineering: { description: Platform and release automation }
61
+ teams: # SHARED teams: the same provider team for everyone
62
+ engineering: { description: Platform and release automation, team: "engineering:acme.aweb.ai" }
63
+ reviewers: { description: Code review } # declared, not created yet (no `team` id): readiness team-unmapped
69
64
 
70
65
  defaults:
71
66
  capabilities:
@@ -74,9 +69,6 @@ defaults:
74
69
  knowledge: { oats.okf: { from: package } } # one slot default at most; a soul may say `none`
75
70
  messaging: none
76
71
  tasks: none
77
- byTeam:
78
- engineering:
79
- capabilities: { acme-release-tooling: { from: github.com/acme/agents } }
80
72
 
81
73
  stores: # knowledge stores, declared once
82
74
  org: git:github.com/acme/knowledge
@@ -97,92 +89,68 @@ exactly as the kernel spells it (`parseRepoRef(ref).key`: lowercase host,
97
89
  file/bare-directory remote). Any other spelling is a schema error at
98
90
  validation, not a late membership error.
99
91
 
100
- ### `oats-membership.yaml` — the backlink, in every member
92
+ ### `oats-membership.yaml`: the backlink, in every member
101
93
 
102
94
  ```yaml
103
95
  schemaVersion: 2
104
96
  workspace: git:github.com/acme/agents # "I am a member of acme"
105
- team: engineering # optional: default team label for this repo's items
106
97
  ```
107
98
 
108
- Nothing else. It replaces `oats.yaml`; there are no export lists.
99
+ Nothing else: there are no export lists, and team membership is local to each
100
+ deployment ([Teams](#teams)).
109
101
 
110
- ### `souls/<name>/soul.yaml` — where each capability comes from
102
+ ### `souls/<name>/soul.yaml`: where each capability comes from
111
103
 
112
104
  ```yaml
113
105
  schemaVersion: 2
114
106
  name: release-manager
115
107
  description: Cuts, verifies and announces releases.
116
108
  work: worktree # worktree | checkout | directory | workspace
117
- team: engineering # optional; else the repo's default; else "unassigned"
118
109
 
119
110
  capabilities:
120
111
  acme-release-tooling: { from: here } # `here` = the repo this soul.yaml lives in
121
112
  acme-deploy: { from: package } # provided by acme.tools, pinned in packages:
122
113
  acme-house-style: off # removes a workspace default
123
114
 
124
- knowledge: # provider payload, opaque to the kernel — the slot capability's BINDING keys
125
- harvest-runtime: claude # (oats.okf 2.1.3: what this soul owns/reads is in souls/<name>/okf.json, not here — see below)
115
+ knowledge: # provider payload, opaque to the kernel: the slot capability's settings
116
+ harvest: off # (what this soul owns and reads is in souls/<name>/okf.json, not here)
126
117
  messaging:
127
118
  channels: [acme-eng]
128
119
  tasks: none # empties the slot
129
120
 
130
- compatibility: # optional FLOORS on package versions — constraints, not sources
131
- oats.okf: ">=2.1"
121
+ compatibility: # optional FLOORS on package versions: constraints, not sources
122
+ oats.okf: ">=4.0"
132
123
  ```
133
124
 
134
125
  Beside it: `AGENTS.md` (canonical), `CLAUDE.md → AGENTS.md`, `skills/`, and
135
- whatever the slot providers read from the soul directory — for `oats.okf`
136
- 2.1.3 that is **`okf.json`** (`{ version: 1, owner, owns: ["<base>/<node>"],
137
- reads: […] }`, written by `oats okf init|migrate`), the soul's knowledge
138
- declaration; it travels with the soul into the per-commit soul cache. The
139
- `knowledge:` payload on `soul.yaml` reaches OKF as `OATS_SETTINGS` and may
140
- carry only the binding's settings keys (`bindings-file`, `state-dir`,
141
- `harvest-runtime`, `harvest-model`); `owns`/`reads`/`root` there are refused by
142
- the provider, not read (a soul payload grammar is an OKF follow-up). Every
143
- `souls/*/soul.yaml` in a member is listed and spawnable — souls have no
144
- private mode (`private:` in a soul.yaml is ignored since 0.26.0, with a
145
- `soul-private-ignored` warning). A soul's
146
- `name` must equal its directory name; the first of two souls declaring one
147
- name (by path) is listed, the second is a problem.
148
-
149
- ### `capabilities/<name>/oats.json` — the manifest, unchanged shape
150
-
151
- The capability manifest is the one file that did not change (see
152
- [capabilities.md](capabilities.md)). Discovery relies on `capability` (the same
126
+ whatever the slot providers read from the soul directory. For `oats.okf` that
127
+ is **`okf.json`** (`{ version: 1, owner, owns: ["<base>/<node>"], reads: […] }`),
128
+ the soul's knowledge declaration; it travels with the soul into the per-commit
129
+ soul cache ([knowledge.md](knowledge.md)). A slot payload on `soul.yaml` reaches
130
+ the provider as `OATS_SETTINGS` and may carry only the settings the provider's
131
+ manifest declares; the provider refuses any other key.
132
+
133
+ Every `souls/*/soul.yaml` in a member is listed and spawnable; souls have no
134
+ private mode. A soul's `name` must equal its directory name; the first of two
135
+ souls declaring one name (by path) is listed, the second is a problem.
136
+
137
+ ### `capabilities/<name>/oats.json`: the manifest
138
+
139
+ The capability manifest is described in [capabilities.md](capabilities.md).
140
+ Discovery relies on `capability` (the same
153
141
  `^[a-z0-9][a-z0-9._-]*$` grammar every `capabilities:` key uses), `version`,
154
- `layer`, and may read `private: true` (a **repo-owned** capability) and
155
- `team: <label>`. `version` is
142
+ `layer`, and may read `private: true` (a **repo-owned** capability). `version` is
156
143
  informational for member capabilities — a materialized copy is identified by
157
144
  its content digest.
158
145
 
159
- ### `oats-local.yaml` — the only per-machine file
146
+ ### `oats-local.yaml`: the only per-machine file
160
147
 
161
- ```yaml
162
- schemaVersion: 2
163
- workspace: git:github.com/acme/agents # observed over the remote; need not be cloned
164
- clones: # optional: where member clones live, if not the convention
165
- github.com/acme/platform: /Users/ana/src/acme-platform
166
- settings: # host-owned values the manifests ask for
167
- oats.okf:
168
- bindings-file: /Users/ana/.oats/okf-bindings.json
169
- state-dir: /Users/ana/.oats/okf
170
- souls:
171
- disabled: [data-analyst] # not run on this machine (E_SOUL_DISABLED); oats.okf/knowledge-harvester names a package soul
172
- host:
173
- name: ana-laptop # this machine's name: runs the workspace triggers/schedules whose runsOn names it
174
- triggers:
175
- disabled: [knowledge/okf-harvest-review] # workspace triggers this host does not run (oats trigger disable)
176
- schedules:
177
- disabled: [platform/nightly-digest] # workspace schedules this host does not run (oats schedule disable)
178
- ```
148
+ Which workspace this machine realizes, where member clones live, host-owned
149
+ provider settings, this deployment's local teams and team membership, host
150
+ facts for automations, and launch configurations. The full reference is
151
+ [configuration.md](configuration.md).
179
152
 
180
- `host`, `triggers.disabled` and `schedules.disabled` (0.29.0) are machine facts:
181
- see [schedules.md#workspace-triggers-and-schedules](schedules.md#workspace-triggers-and-schedules).
182
-
183
- See [configuration.md](configuration.md). `oats-config.yaml` no longer exists.
184
-
185
- ### `oats-lock.json` — lock v3
153
+ ### `oats-lock.json`: lock v3
186
154
 
187
155
  Written by `oats sync`; the only persisted state at the deployment besides
188
156
  `oats-local.yaml`. See [packages.md](packages.md).
@@ -194,16 +162,11 @@ Written by `oats sync`; the only persisted state at the deployment besides
194
162
  the workspace back. One file per side; a copied backlink in a fork, or a folder
195
163
  with the right name, is not admission.
196
164
 
197
- **Membership is the whole trust decision for member capabilities** — the same
165
+ **Membership is the whole trust decision for member capabilities**, the same
198
166
  model as a repo's committed `.agents/skills/`: whoever can push to the repo
199
- decides what runs, and the branch's latest state is what runs. No per-operator
200
- trust lists, no per-capability approval for members. Packages come from
201
- *outside* that boundary, and **declaring one in the workspace's `packages:` is
202
- the trust decision** (human decision, 2026-09-24): people install a package only
203
- when they trust it, so there is no second, per-version approval step. The lock
204
- is reproducibility, not approval — it pins the exact commit and content
205
- integrity, and `oats sync` refuses drift (a moved tag, changed content, an
206
- edited capability list). A spawn admits only a locked package the workspace
167
+ decides what runs, and the branch's latest state is what runs. Packages come
168
+ from *outside* that boundary, and **declaring one in the workspace's
169
+ `packages:` is the trust decision** ([packages.md](packages.md#trust)). A spawn admits only a locked package the workspace
207
170
  **still declares**: one removed from `packages:` but left in a stale lock is
208
171
  `E_PACKAGE_MISSING { reason: "undeclared" }` until `oats sync` drops it.
209
172
 
@@ -237,15 +200,15 @@ no private mode: every soul of a confirmed member is listed and spawnable.
237
200
  **not** a member, pinned to a full commit. No handshake is asked for and none is
238
201
  read; the soul gets no member-tier capabilities of its own repo; it is
239
202
  "source-complete" (its skills travel with it) and the workspace's defaults fill
240
- its slots. An `external[].team` overrides the soul's own `team`.
203
+ its slots.
241
204
 
242
- **Package souls.** A package may ship souls (`souls:` in `oats-package.json`,
243
- 0.28.0): they are listed from the lock for each package the workspace declares,
205
+ **Package souls.** A package may ship souls (`souls:` in `oats-package.json`):
206
+ they are listed from the lock for each package the workspace declares,
244
207
  named `<package>/<soul>` (a bare name when unique), resolved like any soul
245
208
  (`from: here` = their own package at the locked commit) and trusted as the
246
209
  package is. See [packages](packages.md#package-souls).
247
210
 
248
- ## Member tier vs package tier — the non-collapse rule
211
+ ## Member tier vs package tier: the non-collapse rule
249
212
 
250
213
  A repository may be a **member** (it completed the handshake; its `souls/*` and
251
214
  `capabilities/*` are member-tier: latest state, trusted by membership) **and** a
@@ -263,45 +226,30 @@ A repository may be a **member** (it completed the handshake; its `souls/*` and
263
226
  version }` on the member row (informational) and does **not** list the
264
227
  package's capabilities as member capabilities.
265
228
 
229
+ A publisher that also keeps copies of its package's capabilities (a mirror, a
230
+ fixture) keeps them outside `capabilities/`, or discovery lists them as member
231
+ capabilities too: the same capability offered twice, once at latest state. The
232
+ `oats` repository keeps its mirrors of the official packages in `mirrors/`.
233
+
266
234
  So the framework's own souls say `oats.okf: { from: package }` even though
267
- `oats-okf` is a member of the OATS workspace — and every package repo carries a
268
- member soul that is the expert in that capability (`okf-expert`, `aweb-expert`,
269
- …), discoverable at latest state like any member soul.
235
+ `oats-okf` is a member of the OATS workspace, and every package repo carries a
236
+ member soul that is the expert in that capability (`oats-okf-expert`,
237
+ `oats-aweb-expert`, …), discoverable at latest state like any member soul.
270
238
 
271
239
  ## Packages, lock, catalog
272
240
 
273
- `packages:` values have exactly two forms:
274
-
275
- - a **bare version** (`v2.1.3`, `2.1.3`, `v1.0.0-rc.1`) — resolved through the
276
- official catalog (`package-catalog.json` in the `oats` repo; the reviewed
277
- list, see [official-catalog.md](official-catalog.md)). This is
278
- the only way a package becomes *pinnable by id*.
279
- - **`git:<repo>@<ref>`** — a direct package ref: `<repo>` is any ref the kernel
280
- understands (`github.com/org/repo`, `https://…`, `git@host:…`, `/abs/bare.git`,
281
- `file:///…`), `<ref>` a tag name or a full commit OID. The package is read at
282
- `oats-package/` inside that repo.
283
-
284
- Both are packages: versioned and locked. A ref that resolves to a **branch** is
285
- refused (`E_PACKAGE_INTEGRITY { why: "branch" }`) — versions are immutable. A
286
- tag that moved (same version string, different commit), or content that no
287
- longer matches the locked integrity, fails with `E_PACKAGE_INTEGRITY` on the
288
- next `oats sync`.
289
-
290
- **There is no package approval** (human decision, 2026-09-24). Declaring a
291
- package in `packages:` is the trust decision; `oats sync` asks nothing and
292
- `--approve` is `E_BAD_ARGS`. `oats sync` confirms membership, resolves every
293
- `packages:` entry to a commit + content digest, writes `oats-lock.json`
294
- (lockfileVersion 3), creates `agents/` if absent, reports what changed and
295
- exits `0`. A lock written by an earlier kernel may still carry an `approved`
296
- record per entry: it is ignored, and the next write drops it. `oats package add <id> <version|git:…@…>` / `oats package remove <id>` edit
297
- `packages:` in the workspace file when it is tracked by the current checkout,
298
- else print the line to add — the workspace file is shared through Git. Details:
299
- [packages.md](packages.md).
241
+ `packages:` values have two forms, a **bare version** resolved through the
242
+ official catalog and a **`git:<repo>@<ref>`** direct ref; both are versioned
243
+ and locked, and a ref that resolves to a branch is refused. `oats sync`
244
+ confirms membership, resolves every entry to a commit and content digest,
245
+ writes `oats-lock.json`, creates `agents/` if absent and reports what changed.
246
+ `oats package add | remove` edit `packages:`. The grammar, the lock, trust and
247
+ the catalog are in [packages.md](packages.md).
300
248
 
301
249
  ## Resolution, spelled out
302
250
 
303
251
  For each `(name, from)` in
304
- `defaults.<slot>` ⊕ `defaults.capabilities` ⊕ `defaults.byTeam[<soul team>]` ⊕
252
+ `defaults.<slot>` ⊕ `defaults.capabilities` ⊕
305
253
  `soul.capabilities` (later wins; `off` removes; a soul `<slot>: none` drops
306
254
  the workspace's slot default):
307
255
 
@@ -326,30 +274,13 @@ preview and apply is `E_DECISION_STALE`, not a silent drift.
326
274
 
327
275
  ## Materialization and the instance home
328
276
 
329
- At spawn every resolved capability is **copied whole** into the instance home —
330
- skills, injects, scripts, hooks — from the remote at the recorded commit.
331
- Nothing is symlinked, nothing is shared between instances.
332
-
333
- ```
334
- <agents-root>/<soul>/instances/<instance>/
335
- ├── AGENTS.md # composed: soul AGENTS.md + kernel/work-mode blocks + each module's inject
336
- │ # (the "You run on OATS" block is oats.core's inject; the kernel ships no copy)
337
- ├── CLAUDE.md → AGENTS.md
338
- ├── .agents/skills/<capability>/<skill>/SKILL.md # full copies; where pi/codex look
339
- ├── .claude/skills → ../.agents/skills
340
- ├── .oats/modules/<capability>/ # the full capability copy: oats.json, bin/, injects/, skills/
341
- ├── instance.json # modules{}, providers{}, workspace{} recorded here
342
- ├── TASK.md
343
- └── work/
344
- ```
345
-
346
- `instance.json.modules.<cap>` records `from` (`{ kind: "member", repoKey,
347
- commit }` or `{ kind: "package", package, version, commit, integrity, repoKey }`),
348
- `commit`, `digest` (sha256 of the copied tree) and `materializedAt`;
349
- `instance.json.providers.<cap>` records the merged provider payload. A running
350
- instance never changes under itself: a member moving or `packages:` being
351
- bumped affects only new spawns. Details and DTOs:
352
- [souls-and-instances.md](souls-and-instances.md), [desktop-cli-api.md](desktop-cli-api.md).
277
+ At spawn every resolved capability is **copied whole** into the instance home
278
+ (skills, injects, scripts, hooks) from the remote at the recorded commit, and
279
+ `instance.json` records each module's source, commit and digest. Nothing is
280
+ symlinked or shared between instances, and a running instance never changes
281
+ under itself: a member moving or `packages:` being bumped affects only new
282
+ spawns. The home's layout and records are in
283
+ [souls-and-instances.md](souls-and-instances.md).
353
284
 
354
285
  **Drift is shown, not prevented.** `oats status` compares each instance's
355
286
  recorded modules — and its recorded **soul source** (`instance.json.workspace.soul`)
@@ -361,29 +292,61 @@ carries `instances[].soul`. `oats spawn --preview` lists `changedSince` the
361
292
  newest previous instance of the same soul, plus `providers` (the `--provider`
362
293
  map as given) and `settings.<cap>` (the merged payload each provider receives).
363
294
 
364
- **Harnesses start normally.** OATS is a skill contributor, not a skill sandbox:
365
- cwd = the instance home, the harness's own skill discovery intact
366
- (`~/.pi/agent/skills`, `.agents/skills` up the tree, `.claude/`, …); machine-
367
- and repo-level skills resolve exactly as they would without OATS. OATS
368
- composes instructions (`AGENTS.md`) and pins model/provider settings; it does
369
- not exclude anything.
295
+ Harnesses start normally, with their own skill discovery intact
296
+ ([souls-and-instances.md](souls-and-instances.md)).
370
297
 
371
298
  ## Teams
372
299
 
373
- `teams:` declares labels once (`global`, `engineering`, …) so they cannot drift
374
- into typos. A soul carries `team:` — one label or a list (`team: [engineering,
375
- reviewers]`, the first the primary) — else its repo's default from
376
- `oats-membership.yaml` (same shape), else `unassigned`; a capability carries one
377
- label. A label not declared in `teams:` is `E_TEAM_UNKNOWN` (the item is still
378
- listed); a declared label without a `messaging.byTeam` entry is the
379
- `unmapped-team-label` warning. `defaults.byTeam.<team>.capabilities` adds
380
- capabilities additively for souls with that label, for each label in order
381
- (`off` removes; two labels that disagree are `E_TEAM_CONFLICT`). Each label is
382
- an *eligible* messaging team the provider may join on request — see
383
- [capabilities.md](capabilities.md#several-team-labels). **A
384
- label never gates, restricts, changes trust or partitions the knowledge
385
- store** — it organises and can supply defaults. The messaging provider's payload
386
- (private teams, channels) lives under `messaging:`, so "team" means one thing.
300
+ A team is a messaging-provider team (for oats.aweb, an
301
+ aweb team id `<team>:<namespace>`) under a **label**. Two files declare them:
302
+
303
+ - **Shared teams**: the committed `oats-workspace.yaml` `teams.<label> =
304
+ { description?, team? }`: the same provider team for everyone, edited by a PR.
305
+ A shared team without `team` is declared but not created yet (readiness
306
+ `team-unmapped`): its owner creates it with the messaging provider, then
307
+ commits the id.
308
+ - **Local teams**: the deployment's `oats-local.yaml` `teams.<label> = { team,
309
+ description? }`: a team only this deployment uses (a personal team). A label in
310
+ both files is `team-label-collision` (a warning); the **shared** definition
311
+ wins, and the fix is renaming the local label.
312
+
313
+ `oats-local.yaml` also says which teams each soul belongs to **here**:
314
+
315
+ - `defaultTeam: <label>`: the team every instance of this deployment lives in
316
+ (its default-team identity);
317
+ - `souls.teams`: `"*"` for every soul, and a soul's own entry (its bare name, or
318
+ `<package>/<soul>` for a package soul) adds to it;
319
+ - `souls.default`: a per-soul override of `defaultTeam`; it must be one of that
320
+ soul's teams (`E_TEAM_NOT_ELIGIBLE`).
321
+
322
+ A soul's default is `souls.default[soul] ?? defaultTeam`; its teams are that
323
+ default ∪ `souls.teams["*"]` ∪ `souls.teams[soul]`. A label no file declares is
324
+ `E_TEAM_UNKNOWN` (a spawn, preview or `inspect --soul` of that soul is
325
+ refused). At spawn an instance joins its **default** only; the others are
326
+ eligible: offered, and joined on request through the provider (`join=` at
327
+ spawn, or its own verbs later). Nothing committed besides the shared `teams:`
328
+ says anything about teams, and capabilities compose from the workspace defaults
329
+ and the soul only, the same for everyone. **A label never gates, restricts,
330
+ changes trust or partitions the knowledge store.**
331
+
332
+ The verbs edit `oats-local.yaml` in place; they never call a provider:
333
+
334
+ ```
335
+ oats teams [--json] # this deployment's teams, ids, the default, problems
336
+ oats teams add <label> --team <id> [--description d] # declare a local team (the first one becomes the default)
337
+ oats teams remove <label> # refused while referenced (E_TEAM_IN_USE) or shared (E_TEAM_SHARED)
338
+ oats teams default <label>
339
+ oats soul teams <soul>|'*' [--add a,b] [--remove a,b] [--default <label> | --clear-default] [--json]
340
+ ```
341
+
342
+ The messaging provider's own setup creates provider teams and records them with
343
+ `oats teams add` (see the provider's documentation). The spawn preview, `inspect` and `oats souls` report a soul's
344
+ `teams` and `defaultTeam`; readiness reports the team problems in
345
+ `checks.configured` (`E_TEAM_UNCONFIGURED` when a messaging layer is active and
346
+ there is no default; `team-unmapped`, blocking when it is the default;
347
+ `default-team-changed` for a running instance). The provider receives them in
348
+ its environment — see [capabilities.md](capabilities.md#teams-in-the-provider-environment).
349
+ Exact shapes: [desktop-cli-api.md](desktop-cli-api.md#team-model-v2-feature-team-model-2-oats-0300-replaces-feature-teams).
387
350
 
388
351
  ## Provider payloads have three homes
389
352
 
@@ -393,46 +356,17 @@ store** — it organises and can supply defaults. The messaging provider's paylo
393
356
  | A fact about this machine | `oats-local.yaml` → `settings.<cap>.<key>` (absolute paths are refused in the workspace file) | `settings.oats.okf.state-dir: /Users/ana/.oats/okf` |
394
357
  | A fact about **this spawn** | `oats spawn … --provider <cap> key=value` (repeatable; dotted keys nest) → `instance.json.providers.<cap>` | `--provider oats.aweb identity.source=/abs/path/to/retained/.aw` |
395
358
 
396
- The merged payload is `workspace.messaging` (messaging slot only; its base
397
- keys, with `byTeam` stripped) ⊕ soul slot payload ⊕ `local.settings[cap]` ⊕
398
- `spawn.providers[cap]` — objects deep-merge, later wins on scalars and arrays.
399
- **No `byTeam[<label>]` is merged into it, the primary's included** (teams
400
- amendment K): each label's `base ⊕ byTeam[label]` reaches the provider only as
401
- that label's entry in `OATS_TEAMS` (the preview's `teams`). So `settings.team`
402
- (and `OATS_TEAM_ID`) is the workspace's default team if the host, the soul or the spawn
403
- set one; empty means the provider's own default. The provider's own `binding` contract
404
- (`normalize → bind → check`) runs over the merged payload exactly as before.
405
- Two teams, two messaging identities, one workspace:
359
+ The merged payload is `workspace.messaging` (messaging slot only) ⊕ soul slot
360
+ payload ⊕ `local.settings[cap]` ⊕ `spawn.providers[cap]` — objects deep-merge,
361
+ later wins on scalars and arrays. The provider's own `binding` contract
362
+ (its readiness check) runs over the merged payload. Teams are **not** settings:
363
+ they reach the provider beside them, in its environment ([Teams](#teams)).
364
+ Where a provider keeps its own state (for oats.aweb, its identity roots) is the
365
+ provider's concern; see its documentation.
406
366
 
407
- ```yaml
408
- teams: { oss: { description: Open protocol }, cloud: { description: Hosted application } }
409
- messaging:
410
- byTeam:
411
- oss: { team: aweb:example.oss }
412
- cloud: { team: aweb:example.cloud }
413
- ```
414
-
415
- A soul with `team: cloud` hands its messaging provider the eligible team
416
- `{ label: cloud, team: aweb:example.cloud, mapped: true, payload: { team: aweb:example.cloud, … } }`
417
- in `OATS_TEAMS`; its settings carry no `team` unless the host, soul or spawn set
418
- one. A label under `byTeam` that is not declared in `teams:` is
419
- `E_WORKSPACE_SCHEMA`. **Joining an eligible team is the provider's explicit
420
- act.** `spawn --preview` shows the merged `settings.<cap>` and the `teams`, so
421
- the delivery is verifiable, and `instance.json` records both. With oats.aweb
422
- 1.13.1 (which reads `team` from its settings and ignores `OATS_TEAMS`) the
423
- primary identity therefore mints into the workspace's default team: the `.aw` root's
424
- active team, or the one the host set. What the payload does not change is
425
- **where the `.aw` root is found**: the hook still searches
426
- bounded candidates, first hit wins — the instance home, the Git repository
427
- containing it, the soul's work repository and the Git repository containing
428
- it, then the deployment directory (`OATS_WORKSPACE`); never the user home or
429
- above the deployment — and that root must hold a membership of the named team (the deployment's `.aw`
430
- joined to every team its labels name is the simple layout). On oats.aweb
431
- 1.11.2 `team` was ignored (the root's active team won), so `byTeam` there is
432
- a recorded intent only.
433
367
  A store (`stores: { <name>: <repo ref> }`) names a repository; where a
434
- knowledge base lives inside it is the knowledge provider's own concern — for
435
- OKF 2.1.3 that is the **bindings file** (`bases.<alias>.repository` + `root`,
368
+ knowledge base lives inside it is the knowledge provider's own concern. For
369
+ oats.okf that is the **bindings file** (`bases.<alias>.repository` + `root`,
436
370
  `oats-local.yaml settings.oats.okf.bindings-file`), not a soul payload key; a
437
371
  repo ref never carries a `#path`.
438
372
 
@@ -460,11 +394,11 @@ different repo → `E_CLONE_MISMATCH`. Spawning a soul whose repo is not yet
460
394
  cloned is a guided clone-then-spawn, a job for the onboarding skill, not the
461
395
  kernel.
462
396
 
463
- The deployment directory is **yours to choose** (decision 9) — an existing folder that already holds your member clones is the usual case; `oats onboard <dir>` adds what the kernel needs and nothing else:
397
+ The deployment directory is **yours to choose**: an existing folder that already holds your member clones is the usual case, and `oats onboard <dir>` adds what the kernel needs and nothing else ([configuration.md](configuration.md#the-deployment-directory)):
464
398
 
465
399
  ```
466
400
  ~/acme/ ← the directory you chose
467
- ├── oats-local.yaml ← which workspace this machine realizes + host paths + disabled souls
401
+ ├── oats-local.yaml ← which workspace this machine realizes, and host facts
468
402
  ├── oats-lock.json ← exact commit + integrity per package
469
403
  ├── agents/ ← instance homes (each self-contained) + fetched soul sources
470
404
  ├── platform/ ← clone of github.com/acme/platform (only if someone works IN it; may live elsewhere — see clones:)
@@ -500,7 +434,7 @@ that view explicitly). `oats onboard` lists the host among the clones to make
500
434
  like any member (the host is a member; its souls may need a work clone); under
501
435
  an explicit `standalone:` header its next steps say so and name that one repo.
502
436
 
503
- **Executables from public members.** Membership is the trust (decision 2): a
437
+ **Executables from public members.** Membership is the trust: a
504
438
  member capability's hooks and command scripts run on every operator's machine at
505
439
  spawn, gated by nothing but the handshake. In a mixed public/private
506
440
  organisation keep **souls only** in public members and let executable
@@ -520,27 +454,15 @@ onboarding skill asks this question first.
520
454
  - **Members.** A member is always its latest state; there is no `@revision`
521
455
  on `members:`. A team that wants frozen capabilities publishes them as a
522
456
  package and pins that.
523
- - **Member capabilities' `version` field** — informational; the content digest
457
+ - **Member capabilities' `version` field**: informational; the content digest
524
458
  recorded at spawn identifies a copy.
525
459
  - **Souls in members.** A soul is spawned from its repo's current state; the
526
460
  commit is recorded in `instance.json.workspace.soul`.
527
- - **The deployment layout** — the operator's; only the convention is taught.
461
+ - **The deployment layout**: the operator's; only the convention is taught.
528
462
 
529
463
  What **is** versioned: `packages:` (the workspace's one list), the lock's exact
530
464
  commits and digests, and `external:` pins (a stranger's repo is never "latest").
531
465
 
532
- ## Removed
533
-
534
- Per-soul `source: git:…@v#…` lines and the `git:`/`repo:`/`path:` grammar;
535
- `imports:` of member souls; `exports:` lists; `oats.yaml`; the
536
- installed-capability tier (`.agents/capabilities/installed/`) and
537
- `oats-config.yaml` entirely; `oats init` / `use` / `install` / `restore` /
538
- `trust` / `list` / `catalog` / `remove` / `migrate` / `config` (each answers
539
- `E_UNKNOWN_COMMAND` naming its replacement); per-soul `stores.<x>.inherit`;
540
- ambient-skill exclusion at launch. Lock v1/v2 files are `E_LOCK_SCHEMA`.
541
- There is no converter and no dual-schema reader: a 0.24.x kernel keeps
542
- spawning 0.24.x deployments.
543
-
544
466
  ## Related
545
467
 
546
468
  - [Souls and instances](souls-and-instances.md) · [Packages](packages.md) ·
@@ -21,9 +21,10 @@
21
21
  * E_AUTOMATION_SCHEMA), `id:` or the filename stem, `runsOn` (a host name), `owner` (a GitHub
22
22
  * account, `<host>/<login>`), `description?`, `enabled?`. A duplicate id within one member and
23
23
  * one kind is E_AUTOMATION_DUPLICATE.
24
- * - PLACEMENT: a host runs one ONLY when `runsOn` is its `host.name` (oats-local.yaml) AND its
25
- * authenticated `gh` account is `owner`; otherwise it is listed with the reason
26
- * (`host-unnamed`, `assigned-elsewhere`, `owner-mismatch`). Each kind's own list in
24
+ * - PLACEMENT: a host runs one ONLY when `runsOn` is its `host.name` (oats-local.yaml), its
25
+ * authenticated `gh` account is `owner` AND its `automations.trust` admits it (0.30: both
26
+ * names come from a commit; trust is the operator's own yes); otherwise it is listed with the
27
+ * reason (`host-unnamed`, `assigned-elsewhere`, `owner-mismatch`, `untrusted`). Each kind's own list in
27
28
  * oats-local.yaml (`triggers.disabled`, `schedules.disabled`) stops a named host from running
28
29
  * one.
29
30
  * - THE SNAPSHOT: discovery reads the remotes (async), the host tick is synchronous; so
@@ -38,7 +39,7 @@ import { parseConfigData } from "./config-data.mjs";
38
39
 
39
40
  export const AUTOMATIONS_API = 1;
40
41
  export const SNAPSHOT_MAX_AGE_MS = 10 * 60_000;
41
- export const PLACEMENT_REASONS = Object.freeze(["host-unnamed", "assigned-elsewhere", "owner-mismatch"]);
42
+ export const PLACEMENT_REASONS = Object.freeze(["host-unnamed", "assigned-elsewhere", "owner-mismatch", "untrusted"]);
42
43
  /** The kinds, in the order the snapshot keeps them (`triggers`, `schedules`). */
43
44
  export const KIND_NAMES = Object.freeze(["trigger", "schedule"]);
44
45
  const NEVER_SCANNED = new Set(["oats-package", ".git", "node_modules"]);
@@ -225,7 +226,20 @@ const snapshotAge = (snap, now) => (snap?.takenAt ? now.getTime() - Date.parse(s
225
226
  export function hostOf(local) {
226
227
  const name = isObject(local?.host) && typeof local.host.name === "string" ? local.host.name : null;
227
228
  const disabled = Object.fromEntries(KIND_NAMES.map((k) => [k, new Set(Array.isArray(local?.[`${k}s`]?.disabled) ? local[`${k}s`].disabled : [])]));
228
- return { name, disabled };
229
+ const t = isObject(local?.automations) ? local.automations.trust : undefined;
230
+ const trust = t === "*" ? "*" : new Set(Array.isArray(t) ? t : []);
231
+ return { name, disabled, trust };
232
+ }
233
+ /** Whether this host's `automations.trust` admits a workspace automation (`<member>/<id>`). */
234
+ export const trusted = (host, id) => host.trust === "*" || !!host.trust?.has(id);
235
+ /** What an operator adds to trust `id`: the exact list line under `automations:` `trust:`. */
236
+ export const trustRemedy = (id) => `add the line "- ${id}" under automations: trust: in oats-local.yaml`;
237
+ /** `automations.trust` entries that name no workspace trigger or schedule in `automations`
238
+ * (the snapshot's): stale, or a member not synced yet. A warning, never an error. */
239
+ export function staleTrust(host, automations) {
240
+ if (host.trust === "*") return [];
241
+ const known = new Set(automations.map((a) => a.id));
242
+ return [...host.trust].filter((id) => !known.has(id));
229
243
  }
230
244
  /** The account the host's `gh` is logged in as on one GitHub host (`gh api user`), memoized in
231
245
  * `cache` (one per tick). `io.gh(args)` is the seam. → { ok, login } | { ok: false, error } */
@@ -244,7 +258,7 @@ export function ghLogin(ghHost, { io, cache } = {}) {
244
258
  return out;
245
259
  }
246
260
  /** Where a workspace trigger or schedule runs, from this host's point of view.
247
- * → { runsHere, reason: null | host-unnamed | assigned-elsewhere | owner-mismatch, detail?, enabledHere } */
261
+ * → { runsHere, reason: null | host-unnamed | assigned-elsewhere | owner-mismatch | untrusted, detail?, enabledHere } */
248
262
  export function placementOf(a, { host, io, cache }) {
249
263
  const enabledHere = a.enabled !== false && !host.disabled[a.kind]?.has(a.id);
250
264
  let reason = null, detail;
@@ -255,6 +269,7 @@ export function placementOf(a, { host, io, cache }) {
255
269
  const who = owner ? ghLogin(owner.host, { io, cache }) : { ok: false, error: "owner is not <host>/<login>" };
256
270
  if (!who.ok) { reason = "owner-mismatch"; detail = `acts as ${a.owner}, but this host's gh is not logged in on ${owner?.host ?? "?"}: ${who.error}`; }
257
271
  else if (who.login.toLowerCase() !== owner.login.toLowerCase()) { reason = "owner-mismatch"; detail = `acts as ${a.owner}; this host's gh is logged in as ${owner.host}/${who.login}`; }
272
+ else if (!trusted(host, a.id)) { reason = "untrusted"; detail = `declared for this host, not trusted here; to run it, ${trustRemedy(a.id)}`; }
258
273
  }
259
274
  return { runsHere: reason === null && enabledHere && !a.invalid && !!a.definition, reason, ...(detail ? { detail } : {}), enabledHere };
260
275
  }