@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
package/lib/workspace.mjs CHANGED
@@ -17,6 +17,7 @@ import { oatsError } from "./errors.mjs";
17
17
  import * as defaultRemote from "./remote.mjs";
18
18
  import { bindRemote, classifyPackageValue, lockedPackageRef } from "./packages.mjs";
19
19
  import { manifestContractProblems } from "./capability-contract.mjs";
20
+ import { BY_TEAM_REMOVED, TEAM_MEMBERSHIP_MOVED } from "./teams.mjs";
20
21
 
21
22
  /* ───────────────────────────── errors ─────────────────────────────────── */
22
23
 
@@ -163,10 +164,10 @@ const ABSOLUTE_PATH = /^(?:\/|\\\\|[A-Za-z]:[\\/])/;
163
164
  /** The workspace file's REF/PATH fields — the only values the absolute-path refusal applies to (contract §2,
164
165
  * Phase B: "absolute paths" means bare filesystem paths as VALUES that stand for a repo or a path inside one;
165
166
  * host state belongs in oats-local.yaml). Free text (`teams.*.description`) and the opaque provider payload
166
- * (`messaging`, incl. `byTeam.*`) are never scanned: a description may mention `/srv`, a provider may
167
+ * (`messaging`) are never scanned: a description may mention `/srv`, a provider may
167
168
  * legitimately carry a socket path or a URL, and neither is a place the kernel resolves.
168
- * → [[path, value]] for members[], packages.*, stores.*, external[].source|soul, defaults.<slot|capabilities>.*.from,
169
- * defaults.byTeam.*.capabilities.*.from */
169
+ * → [[path, value]] for members[], packages.*, stores.*, external[].source|soul, defaults.<slot|capabilities>.*.from
170
+ */
170
171
  function* refFields(value) {
171
172
  if (Array.isArray(value.members)) for (let i = 0; i < value.members.length; i++) if (typeof value.members[i] === "string") yield [`/members/${i}`, value.members[i]];
172
173
  if (isObject(value.packages)) for (const [id, v] of Object.entries(value.packages)) if (typeof v === "string") yield [`/packages/${pointerKey(id)}`, v];
@@ -180,7 +181,6 @@ function* refFields(value) {
180
181
  const d = value.defaults;
181
182
  const froms = function* (caps, path) { if (isObject(caps)) for (const [name, choice] of Object.entries(caps)) if (isObject(choice) && typeof choice.from === "string") yield [`${path}/${pointerKey(name)}/from`, choice.from]; };
182
183
  for (const slot of ["knowledge", "messaging", "tasks", "capabilities"]) yield* froms(d[slot], `/defaults/${slot}`);
183
- if (isObject(d.byTeam)) for (const [label, team] of Object.entries(d.byTeam)) yield* froms(team?.capabilities, `/defaults/byTeam/${pointerKey(label)}/capabilities`);
184
184
  }
185
185
  }
186
186
  function refKey(remote, ref) {
@@ -211,7 +211,6 @@ export function validateWorkspace(value, { remote = defaultRemote } = {}) {
211
211
  if (c.problem) { problems.push({ path: `/packages/${pointerKey(id)}`, message: `${c.problem} (a package is a bare version like v2.1.3 or git:<repo>@<ref>)` }); continue; }
212
212
  if (c.kind === "git") keyOrProblem(remote, c.repo, `/packages/${pointerKey(id)}`, problems);
213
213
  }
214
- const teams = isObject(value.teams) ? Object.keys(value.teams) : [];
215
214
  if (Array.isArray(value.members)) {
216
215
  const seen = new Map();
217
216
  value.members.forEach((ref, i) => {
@@ -223,15 +222,8 @@ export function validateWorkspace(value, { remote = defaultRemote } = {}) {
223
222
  });
224
223
  }
225
224
  if (isObject(value.stores)) for (const [name, ref] of Object.entries(value.stores)) if (typeof ref === "string") keyOrProblem(remote, ref, `/stores/${pointerKey(name)}`, problems);
226
- // Decision 23: messaging.byTeam addresses provider payload by team label — every label must be declared.
227
- // `byTeam` is legal HERE only; nested inside a team's payload it would be a second, unmerged byTeam (M9).
228
- if (isObject(value.messaging) && value.messaging.byTeam !== undefined) {
229
- if (!isObject(value.messaging.byTeam)) problems.push({ path: "/messaging/byTeam", message: "must be an object of { <team label>: <payload> }" });
230
- else for (const label of Object.keys(value.messaging.byTeam)) {
231
- if (!teams.includes(label)) problems.push({ path: `/messaging/byTeam/${pointerKey(label)}`, message: `team ${show(label)} is not declared in teams` });
232
- if (!isObject(value.messaging.byTeam[label])) problems.push({ path: `/messaging/byTeam/${pointerKey(label)}`, message: "must be an object (the payload for that team)" });
233
- else reservedKeyProblems(value.messaging.byTeam[label], `/messaging/byTeam/${pointerKey(label)}`, problems);
234
- }
225
+ if (isObject(value.messaging) && Object.hasOwn(value.messaging, "byTeam")) {
226
+ problems.push({ path: "/messaging/byTeam", reason: "removed-key", message: "messaging.byTeam was removed in 0.30: a team's provider id is teams.<label>.team (committed, shared) or oats-local.yaml teams.<label>.team (local)" });
235
227
  }
236
228
  if (Array.isArray(value.external)) value.external.forEach((entry, i) => {
237
229
  if (!isObject(entry)) return;
@@ -239,23 +231,35 @@ export function validateWorkspace(value, { remote = defaultRemote } = {}) {
239
231
  const at = entry.source.lastIndexOf("@");
240
232
  if (at > 0) keyOrProblem(remote, entry.source.slice(0, at), `/external/${i}/source`, problems);
241
233
  }
242
- if (typeof entry.team === "string" && !teams.includes(entry.team)) problems.push({ path: `/external/${i}/team`, message: `team ${show(entry.team)} is not declared in teams` });
243
234
  });
244
235
  if (isObject(value.defaults)) {
245
236
  const d = value.defaults;
246
237
  for (const slot of ["knowledge", "messaging", "tasks", "capabilities"]) fromProblems(remote, d[slot], `/defaults/${slot}`, problems, { here: false });
247
- if (isObject(d.byTeam)) for (const label of Object.keys(d.byTeam)) {
248
- if (!teams.includes(label)) problems.push({ path: `/defaults/byTeam/${pointerKey(label)}`, message: `team ${show(label)} is not declared in teams` });
249
- fromProblems(remote, d.byTeam[label]?.capabilities, `/defaults/byTeam/${pointerKey(label)}/capabilities`, problems, { here: false });
250
- }
251
238
  }
252
- return problems;
239
+ return withRemovedKeys(problems, value, REMOVED_KEYS.workspace);
240
+ }
241
+ export function validateMembership(value) { return withRemovedKeys(validateAgainst(schemaFor("membership"), value), value, REMOVED_KEYS.membership); }
242
+
243
+ /** Keys 0.30 removed (team model v2): each is a schema problem naming its replacement, in place of
244
+ * the schema's generic "unexpected property". `at(value)` → the JSON pointers where it appears. */
245
+ const REMOVED_KEYS = {
246
+ workspace: [
247
+ { at: (v) => (isObject(v?.defaults) && Object.hasOwn(v.defaults, "byTeam") ? ["/defaults/byTeam"] : []), message: "defaults.byTeam was removed in 0.30: capabilities compose from defaults.capabilities and the soul only" },
248
+ { at: (v) => (Array.isArray(v?.external) ? v.external.flatMap((e, i) => (isObject(e) && Object.hasOwn(e, "team") ? [`/external/${i}/team`] : [])) : []), message: TEAM_MEMBERSHIP_MOVED },
249
+ ],
250
+ membership: [{ at: (v) => (isObject(v) && Object.hasOwn(v, "team") ? ["/team"] : []), message: TEAM_MEMBERSHIP_MOVED }],
251
+ soul: [{ at: (v) => (isObject(v) && Object.hasOwn(v, "team") ? ["/team"] : []), message: TEAM_MEMBERSHIP_MOVED }],
252
+ };
253
+ function withRemovedKeys(problems, value, removed) {
254
+ const found = removed.flatMap((r) => r.at(value).map((path) => ({ path, reason: "removed-key", message: r.message })));
255
+ if (!found.length) return problems;
256
+ const paths = new Set(found.map((p) => p.path));
257
+ return [...problems.filter((p) => !paths.has(p.path)), ...found];
253
258
  }
254
- export function validateMembership(value) { return validateAgainst(schemaFor("membership"), value); }
255
259
  /** Schema + domain problems for a soul.yaml value: `from:` values must be canonical (see fromProblems);
256
- * slot payloads may not carry the reserved `byTeam` key (see reservedKeyProblems). */
260
+ * slot payloads may not carry the removed `byTeam` key (see reservedKeyProblems). */
257
261
  export function validateSoul(value, { remote = defaultRemote } = {}) {
258
- const problems = validateAgainst(schemaFor("soul"), value);
262
+ const problems = withRemovedKeys(validateAgainst(schemaFor("soul"), value), value, REMOVED_KEYS.soul);
259
263
  if (isObject(value) && value.schemaVersion === 2) {
260
264
  fromProblems(remote, value.capabilities, "/capabilities", problems, { here: true });
261
265
  for (const slot of ["knowledge", "messaging", "tasks"]) if (isObject(value[slot])) reservedKeyProblems(value[slot], `/${slot}`, problems);
@@ -263,15 +267,14 @@ export function validateSoul(value, { remote = defaultRemote } = {}) {
263
267
  return problems;
264
268
  }
265
269
 
266
- /** `byTeam` is RESERVED (decision 23): it addresses a per-team payload and is legal only at the top level
267
- * of workspace.messaging, where resolve merges base ⊕ byTeam[soul.team] and strips it. Anywhere else — a
268
- * soul's slot payload, oats-local.yaml settings[cap], a spawn provider, or nested inside a team's own
269
- * payload — it would reach the provider verbatim (or shadow the real one); refused as a schema problem
270
- * with `reason: "reserved-key"`. Only the payload's TOP level is checked: deeper keys are the provider's. */
271
- const RESERVED_PAYLOAD_KEY = "byTeam";
270
+ /** `byTeam` was removed in 0.30 (team model v2): a per-team payload no longer exists — a team's
271
+ * provider id is teams.<label>.team. At the top level of any payload layer (a soul's slot payload,
272
+ * oats-local.yaml settings[cap], a spawn provider) it would reach the provider verbatim as a dead key;
273
+ * refused as a schema problem with `reason: "removed-key"`. Deeper keys are the provider's. */
274
+ const REMOVED_PAYLOAD_KEY = "byTeam";
272
275
  function reservedKeyProblems(payload, path, problems) {
273
276
  if (!isObject(payload)) return;
274
- if (Object.hasOwn(payload, RESERVED_PAYLOAD_KEY)) problems.push({ path: `${path}/${RESERVED_PAYLOAD_KEY}`, reason: "reserved-key", message: `${show(RESERVED_PAYLOAD_KEY)} is reserved: it is legal only at the top level of the workspace file's messaging: payload (decision 23)` });
277
+ if (Object.hasOwn(payload, REMOVED_PAYLOAD_KEY)) problems.push({ path: `${path}/${REMOVED_PAYLOAD_KEY}`, reason: "removed-key", message: BY_TEAM_REMOVED });
275
278
  }
276
279
 
277
280
  /** `from:` must be exactly what the resolver compares against — "package", "here" (souls only) or a CANONICAL
@@ -290,7 +293,7 @@ function fromProblems(remote, capabilities, path, problems, { here }) {
290
293
  if (key !== from) problems.push({ path: at, message: `${show(from)} is not a canonical repo key (expected <host>/<path> as parseRepoRef(...).key${key ? `, e.g. ${show(key)}` : ""}); \`here\`, \`package\` or a key are the only forms` });
291
294
  }
292
295
  }
293
- /** Schema + domain problems for an oats-local.yaml value: settings[<cap>] may not carry the reserved `byTeam` key. */
296
+ /** Schema + domain problems for an oats-local.yaml value: settings[<cap>] may not carry the removed `byTeam` key. */
294
297
  export function validateLocal(value) {
295
298
  const problems = validateAgainst(schemaFor("local"), value);
296
299
  if (isObject(value) && isObject(value.settings)) for (const [cap, payload] of Object.entries(value.settings)) reservedKeyProblems(payload, `/settings/${pointerKey(cap)}`, problems);
@@ -345,7 +348,7 @@ function schemaError(kind, origin, problems, value) {
345
348
 
346
349
  /** What a 0.25 `oats-config.yaml` held, and where each piece lives now (0.26.0 removed the file). */
347
350
  export const LEGACY_CONFIG_MIGRATION = "oats-config.yaml is no longer read (removed in 0.26.0): launch-configs moved to the deployment's oats-local.yaml; "
348
- + "capability bindings, layers and team moved to oats-workspace.yaml (defaults, teams) and each soul's soul.yaml in its member repository; "
351
+ + "capability bindings and layers moved to oats-workspace.yaml (defaults) and each soul's soul.yaml in its member repository, and team membership to the deployment's oats-local.yaml (`oats teams`, `oats soul teams`); "
349
352
  + "agents-md-injection, skill-overrides, work-modes and yolo are removed (yolo is --yolo or a launch configuration's yolo). Move what you still need, then delete the file";
350
353
 
351
354
  /** Walk up from dir to find oats-local.yaml → { path, local } | E_LOCAL_MISSING.
@@ -392,52 +395,11 @@ export async function observeWorkspace(ref, { at, remote: injected, remoteOption
392
395
  return { key: obs.key, url: obs.url, ref, commit: obs.commit, workspace: read.value, observedAt: obs.observedAt };
393
396
  }
394
397
 
395
- /**
396
- * A soul's team labels as its repository declares them NOW, without a workspace discovery (teams
397
- * contract decision 6, the live teams of an existing home): the workspace host has already been read
398
- * (`wsObs`, observeWorkspace); this reads the soul's own repo once — a member at its current commit
399
- * (`souls/<name>/soul.yaml`, and `oats-membership.yaml` only when the soul declares no `team`), or an
400
- * `external[]` soul at its pinned commit (its entry's `team` wins, as in discovery).
401
- * → labels (primary first), or null when the workspace no longer lists the soul's repo.
402
- * Membership is not re-confirmed here: the home's soul was confirmed at spawn; this reads labels only.
403
- */
404
- export async function observeSoulLabels(wsObs, { name, repoKey }, { remote: injected, remoteOptions } = {}) {
405
- const remote = remoteOf({ remote: injected, remoteOptions });
406
- const workspace = wsObs.workspace;
407
- const readSoul = async (ref, commit, path) => {
408
- const file = `${path.replace(/\/+$/, "")}/soul.yaml`;
409
- const { bytes } = await remote.readRemoteFile(ref, commit, file);
410
- const read = readDeclaration("soul", bytes, { kind: "soul", repoKey, commit, path: file });
411
- if (read.problems) throw schemaError("soul", { kind: "soul", repoKey, commit, path: file }, read.problems, read.value);
412
- return read.value;
413
- };
414
- for (const entry of workspace.external || []) {
415
- const pin = entry.source.lastIndexOf("@");
416
- let key; try { key = refKey(remote, entry.source.slice(0, pin)); } catch { continue; }
417
- if (key !== repoKey) continue;
418
- if (typeof entry.team === "string") return [entry.team];
419
- const soul = await readSoul(entry.source.slice(0, pin), entry.source.slice(pin + 1), entry.soul);
420
- if (soul.name !== name) continue;
421
- return labelList(soul.team) ?? [];
422
- }
423
- const memberRef = (workspace.members || []).find((ref) => { try { return refKey(remote, ref) === repoKey; } catch { return false; } });
424
- if (!memberRef) return null;
425
- const obs = await remote.observeRemote(memberRef);
426
- const soul = await readSoul(memberRef, obs.commit, `souls/${name}`);
427
- const own = labelList(soul.team);
428
- if (own) return own;
429
- try {
430
- const { bytes } = await remote.readRemoteFile(memberRef, obs.commit, "oats-membership.yaml");
431
- const read = readDeclaration("membership", bytes, { kind: "membership", repoKey, commit: obs.commit, path: "oats-membership.yaml" });
432
- return read.problems ? [] : labelList(read.value.team) ?? [];
433
- } catch (e) { if (e?.code === "E_REMOTE_PATH_MISSING") return []; throw e; }
434
- }
435
-
436
398
  const unconfirmed = (key, reason, detail, extra = {}) => ({ key, confirmed: false, reason, detail, ...extra });
437
399
 
438
400
  /**
439
401
  * Read the member's oats-membership.yaml at its default branch in the same access context.
440
- * → { key, commit, confirmed: true, team } | { key, confirmed: false, reason, detail }
402
+ * → { key, commit, confirmed: true } | { key, confirmed: false, reason, detail }
441
403
  */
442
404
  export async function confirmMembership(workspaceObs, memberRef, { remote: injected, remoteOptions } = {}) {
443
405
  const remote = remoteOf({ remote: injected, remoteOptions });
@@ -481,35 +443,24 @@ export async function confirmMembership(workspaceObs, memberRef, { remote: injec
481
443
  const caseOnly = backlinkKey.toLowerCase() === workspaceObs.key.toLowerCase();
482
444
  return unconfirmed(key, "backlink-elsewhere", `${key}@${obs.commit.slice(0, 12)} names workspace ${backlinkKey}, not ${workspaceObs.key}${caseOnly ? " (the keys differ only by letter case: repo paths are case-sensitive identities; spell the workspace ref exactly as the workspace lists itself)" : ""}`, { commit: obs.commit, backlink: backlinkKey, ...(caseOnly ? { caseOnly: true } : {}) });
483
445
  }
484
- const labels = labelList(read.value.team) ?? [];
485
- return { key, commit: obs.commit, confirmed: true, team: labels[0] ?? null, labels };
446
+ return { key, commit: obs.commit, confirmed: true };
486
447
  }
487
448
 
488
449
  /* ───────────────────────────── enumeration ────────────────────────────── */
489
450
 
490
451
  const SOUL_FILE = /^([^/]+)\/soul\.yaml$/;
491
452
  const CAP_FILE = /^([^/]+)\/oats\.json$/;
492
- /** A declared `team` as a label list (teams contract 2026-09-25: a label or a non-empty list of distinct
493
- * labels, the first the primary); null when the item declares none. */
494
- const labelList = (team) => (typeof team === "string" ? [team] : Array.isArray(team) ? team.filter((l) => typeof l === "string") : null);
495
- /** The item's labels, else the repo default's (oats-membership.yaml). */
496
- const labelsOf = (item, fallback) => labelList(item?.team) ?? [...(fallback || [])];
497
- /** Each label with the path it is declared at: `/team` for one label, `/team/<i>` in a list. */
498
- const labelPaths = (item, labels, file) => labels.map((label, i) => [label, Array.isArray(item?.team) ? `${file}#/team/${i}` : `${file}#/team`]);
499
453
 
500
454
  /**
501
455
  * Enumerate souls/*\/soul.yaml and capabilities/*\/oats.json of one repo at one commit.
502
456
  * Validates each item; collects problems instead of aborting.
503
457
  * → { souls: [SoulEntry], capabilities: [CapEntry], problems: [{ code, path, message, repoKey }] }
504
458
  */
505
- async function enumerateRepo(remote, ref, key, commit, { defaultLabels = [], teams = null } = {}) {
459
+ async function enumerateRepo(remote, ref, key, commit) {
506
460
  const souls = [];
507
461
  const capabilities = [];
508
462
  const problems = [];
509
463
  const problem = (code, path, message) => problems.push({ code, repoKey: key, path, message });
510
- const checkTeam = (team, path) => {
511
- if (team !== null && teams && !teams.includes(team)) problem("E_TEAM_UNKNOWN", path, `team ${show(team)} is not declared in the workspace's teams (${teams.join(", ") || "none"})`);
512
- };
513
464
  // A listing failure on one directory is a problem of THIS repo, never an abort of the whole picture.
514
465
  const list = async (dir) => {
515
466
  try { return await remote.listRemoteTree(ref, commit, dir, { depth: 2 }); }
@@ -533,10 +484,8 @@ async function enumerateRepo(remote, ref, key, commit, { defaultLabels = [], tea
533
484
  if (definition.name !== m[1]) problem("E_WORKSPACE_SCHEMA", `${file}#/name`, `soul name ${show(definition.name)} does not match its directory ${show(m[1])}`);
534
485
  if (soulNames.has(definition.name)) { problem("E_WORKSPACE_SCHEMA", `${file}#/name`, `soul name ${show(definition.name)} is already declared by ${soulNames.get(definition.name)}; the second declaration is not listed`); continue; }
535
486
  soulNames.set(definition.name, file);
536
- const labels = labelsOf(definition, defaultLabels);
537
- for (const [label, at] of labelPaths(definition, labels, file)) checkTeam(label, at);
538
487
  // Souls have no private mode since 0.26.0: `private` is accepted, warned (soul-private-ignored) and never hides a soul.
539
- souls.push({ name: definition.name, path, repoKey: key, commit, team: labels[0] ?? null, labels, private: false, definition });
488
+ souls.push({ name: definition.name, path, repoKey: key, commit, private: false, definition });
540
489
  }
541
490
  const capNames = new Map();
542
491
  for (const entry of await list("capabilities")) {
@@ -559,7 +508,6 @@ async function enumerateRepo(remote, ref, key, commit, { defaultLabels = [], tea
559
508
  capability: { type: "string", pattern: "^[a-z0-9][a-z0-9._-]*$" },
560
509
  version: { type: "string", minLength: 1 },
561
510
  private: { type: "boolean" },
562
- team: { type: "string", pattern: "^[a-z0-9][a-z0-9._-]*$" },
563
511
  layer: { enum: ["knowledge", "messaging", "tasks"] },
564
512
  },
565
513
  }, manifest);
@@ -569,10 +517,7 @@ async function enumerateRepo(remote, ref, key, commit, { defaultLabels = [], tea
569
517
  if (contract.length) { for (const p of contract) problem("E_WORKSPACE_SCHEMA", `${file}#${p.pointer}`, p.message); continue; }
570
518
  if (capNames.has(manifest.capability)) { problem("E_WORKSPACE_SCHEMA", `${file}#/capability`, `capability ${show(manifest.capability)} is already declared by ${capNames.get(manifest.capability)}; the second declaration is not listed`); continue; }
571
519
  capNames.set(manifest.capability, file);
572
- // A capability is LISTED under one team (its own `team:`, else the repo default's primary); it joins none.
573
- const team = typeof manifest.team === "string" ? manifest.team : defaultLabels[0] ?? null;
574
- checkTeam(team, `${file}#/team`);
575
- capabilities.push({ name: manifest.capability, path, repoKey: key, commit, team, private: manifest.private === true, manifest });
520
+ capabilities.push({ name: manifest.capability, path, repoKey: key, commit, private: manifest.private === true, manifest });
576
521
  }
577
522
  // Non-collapse rule: a member's oats-package/ is REPORTED (publishes), never enumerated as member capabilities.
578
523
  let publishes = null;
@@ -608,7 +553,7 @@ export async function discoverRepo(ref, { at, remote: injected, remoteOptions }
608
553
  } catch (e) {
609
554
  if (e?.code !== "E_REMOTE_PATH_MISSING") throw e;
610
555
  }
611
- const items = await enumerateRepo(remote, ref, obs.key, obs.commit, { defaultLabels: labelList(membership?.team) ?? [], teams: null });
556
+ const items = await enumerateRepo(remote, ref, obs.key, obs.commit);
612
557
  return { key: obs.key, commit: obs.commit, membership, ...items, problems: [...problems, ...items.problems] };
613
558
  }
614
559
 
@@ -622,8 +567,8 @@ export function packageSoulAgentName(id, soul) { return `${String(id).replace(/[
622
567
  /**
623
568
  * Package souls (0.28.0): the souls the lock records for each package the workspace STILL declares,
624
569
  * read at the locked commit. A package soul is listed like a member soul, with its package origin
625
- * (`package`, `version`) and its qualified name `<package>/<soul>`; its labels are its soul.yaml's
626
- * own `team` (a package has no membership default) and each must be declared (E_TEAM_UNKNOWN).
570
+ * (`package`, `version`) and its qualified name `<package>/<soul>`. A package soul.yaml still carrying
571
+ * `team:` (removed in 0.30: team membership is local) is invalid and not listed.
627
572
  * Nothing here trusts the lock's digests — a spawn verifies the fetched soul against them.
628
573
  * `lock` null (or no workspace) → nothing. → { souls: [SoulEntry], problems }
629
574
  */
@@ -632,7 +577,6 @@ export async function discoverPackageSouls(workspace, lock, { remote: injected,
632
577
  if (!isObject(lock?.packages) || !isObject(workspace)) return { souls, problems };
633
578
  const remote = remoteOf({ remote: injected, remoteOptions });
634
579
  const declared = isObject(workspace.packages) ? workspace.packages : {};
635
- const teams = isObject(workspace.teams) ? Object.keys(workspace.teams) : [];
636
580
  for (const id of Object.keys(lock.packages).sort()) {
637
581
  const entry = lock.packages[id];
638
582
  if (!Array.isArray(entry?.souls) || entry.souls.length === 0 || !Object.hasOwn(declared, id)) continue;
@@ -656,12 +600,8 @@ export async function discoverPackageSouls(workspace, lock, { remote: injected,
656
600
  if (read.problems) { for (const p of read.problems) problem("E_WORKSPACE_SCHEMA", `${file}#${p.path}`, `${p.message}${schemaHint("soul", read.value)}`); continue; }
657
601
  const definition = read.value;
658
602
  if (definition.name !== locked.name) { problem("E_WORKSPACE_SCHEMA", `${file}#/name`, `soul name ${show(definition.name)} does not match its directory ${show(locked.name)}`); continue; }
659
- const labels = labelList(definition.team) ?? [];
660
- for (const [label, where] of labelPaths(definition, labels, file)) {
661
- if (!teams.includes(label)) problems.push({ code: "E_TEAM_UNKNOWN", repoKey: null, package: id, path: at(where), message: `team ${show(label)} is not declared in the workspace's teams (${teams.join(", ") || "none"}) — declare it in oats-workspace.yaml teams: to spawn ${id}/${locked.name}` });
662
- }
663
603
  souls.push({
664
- name: locked.name, path, repoKey: key, ref, commit: entry.commit, team: labels[0] ?? null, labels, private: false, definition,
604
+ name: locked.name, path, repoKey: key, ref, commit: entry.commit, private: false, definition,
665
605
  package: id, version: entry.version, qualifiedName: `${id}/${locked.name}`, agentName: packageSoulAgentName(id, locked.name), digest: locked.digest,
666
606
  });
667
607
  }
@@ -683,7 +623,7 @@ export async function discoverPackageSouls(workspace, lock, { remote: injected,
683
623
 
684
624
  /**
685
625
  * The whole picture in one access context.
686
- * → { workspace, members: [{ key, commit, confirmed, reason?, team, souls, capabilities, publishes }], external: [...],
626
+ * → { workspace, members: [{ key, commit, confirmed, reason?, souls, capabilities, publishes }], external: [...],
687
627
  * problems: [{ code, path, message, repoKey? }] }
688
628
  * `publishes` = { package, version } when the member carries oats-package/oats-package.json (informational:
689
629
  * the package's capabilities are NOT member capabilities — non-collapse rule), else null.
@@ -692,12 +632,11 @@ export async function discoverWorkspace(ref, { at, local, lock = null, remote: i
692
632
  const remote = remoteOf({ remote: injected, remoteOptions });
693
633
  const wsObs = await observeWorkspace(ref, { at, remote });
694
634
  const workspace = wsObs.workspace;
695
- const teams = isObject(workspace.teams) ? Object.keys(workspace.teams) : [];
696
635
  const problems = [];
697
636
  const members = [];
698
637
  for (const memberRef of workspace.members || []) {
699
638
  const confirmation = await confirmMembership(wsObs, memberRef, { remote });
700
- const row = { key: confirmation.key, ref: memberRef, commit: confirmation.commit ?? null, confirmed: confirmation.confirmed, team: confirmation.team ?? null, labels: confirmation.labels ?? [], souls: [], capabilities: [], publishes: null };
639
+ const row = { key: confirmation.key, ref: memberRef, commit: confirmation.commit ?? null, confirmed: confirmation.confirmed, souls: [], capabilities: [], publishes: null };
701
640
  if (!confirmation.confirmed) {
702
641
  // Contract: an unconfirmed member contributes nothing but its row.
703
642
  row.reason = confirmation.reason;
@@ -705,10 +644,7 @@ export async function discoverWorkspace(ref, { at, local, lock = null, remote: i
705
644
  members.push(row);
706
645
  continue;
707
646
  }
708
- for (const [i, label] of row.labels.entries()) {
709
- if (!teams.includes(label)) problems.push({ code: "E_TEAM_UNKNOWN", repoKey: row.key, path: row.labels.length > 1 ? `oats-membership.yaml#/team/${i}` : "oats-membership.yaml#/team", message: `team ${show(label)} is not declared in the workspace's teams (${teams.join(", ") || "none"})` });
710
- }
711
- const items = await enumerateRepo(remote, memberRef, row.key, row.commit, { defaultLabels: row.labels, teams });
647
+ const items = await enumerateRepo(remote, memberRef, row.key, row.commit);
712
648
  row.souls = items.souls;
713
649
  row.capabilities = items.capabilities;
714
650
  row.publishes = items.publishes;
@@ -735,47 +671,18 @@ export async function discoverWorkspace(ref, { at, local, lock = null, remote: i
735
671
  }
736
672
  const read = readDeclaration("soul", bytes, { kind: "soul", repoKey: key, commit, path: file });
737
673
  if (read.problems) { for (const p of read.problems) pushProblem("E_WORKSPACE_SCHEMA", `${file}#${p.path}`, `${p.message}${schemaHint("soul", read.value)}`); continue; }
738
- // The workspace's `external[].team` (one label) overrides the soul's own labels.
739
- const labels = labelList(entry.team) ?? labelList(read.value.team) ?? [];
740
- for (const label of labels) if (!teams.includes(label)) pushProblem("E_TEAM_UNKNOWN", `${file}#/team`, `team ${show(label)} is not declared in the workspace's teams`);
741
- externalRows.push({ source: entry.source, key, commit, soul: { name: read.value.name, path: entry.soul, repoKey: key, commit, team: labels[0] ?? null, labels, private: false, definition: read.value } });
674
+ externalRows.push({ source: entry.source, key, commit, soul: { name: read.value.name, path: entry.soul, repoKey: key, commit, private: false, definition: read.value } });
742
675
  }
743
676
  const pkg = await discoverPackageSouls(workspace, lock, { remote });
744
677
  problems.push(...pkg.problems);
745
- return { workspace, key: wsObs.key, url: wsObs.url, commit: wsObs.commit, observedAt: wsObs.observedAt, local: local ?? null, members, external: externalRows, packageSouls: pkg.souls, problems, warnings: workspaceWarnings(workspace, members, externalRows, pkg.souls, teams) };
746
- }
747
-
748
- /** The discovery's warnings: unmapped team labels and ignored `private:`, over every listed soul. */
749
- export function workspaceWarnings(workspace, members, externalRows, packageSouls, teams = isObject(workspace?.teams) ? Object.keys(workspace.teams) : []) {
750
- const extra = (packageSouls || []).map((soul) => ({ soul }));
751
- return [...unmappedLabelWarnings(workspace, members, [...externalRows, ...extra], teams), ...privateSoulWarnings([...members.filter((m) => m.confirmed).flatMap((m) => m.souls), ...externalRows.map((e) => e.soul), ...(packageSouls || [])])];
678
+ return { workspace, key: wsObs.key, url: wsObs.url, commit: wsObs.commit, observedAt: wsObs.observedAt, local: local ?? null, members, external: externalRows, packageSouls: pkg.souls, problems, warnings: workspaceWarnings(workspace, members, externalRows, pkg.souls) };
752
679
  }
753
680
 
754
- /** Teams contract decision 5: a soul label the workspace declares in `teams:` but does not map in
755
- * `messaging.byTeam` is a WARNING, not a problem — the label stays eligible-but-unmapped and the
756
- * messaging provider falls back to the workspace's default team for it. (An undeclared label is E_TEAM_UNKNOWN.)
757
- * ONE warning per unmapped label, naming its souls (a workspace on its default team alone would otherwise print
758
- * a line per soul): { code, label, souls, paths, message }, sorted by label. */
681
+ /** The discovery's warnings: ignored `private:`, over every listed soul. (0.30 removed the
682
+ * `unmapped-team-label` warning: an unmapped shared team is the readiness item `team-unmapped`.) */
759
683
  const byCodepointOrder = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
760
- function unmappedLabelWarnings(workspace, members, externalRows, teams) {
761
- const byTeam = isObject(workspace?.messaging) && isObject(workspace.messaging.byTeam) ? workspace.messaging.byTeam : {};
762
- const byLabel = new Map();
763
- const souls = [...members.filter((m) => m.confirmed).flatMap((m) => m.souls), ...externalRows.map((e) => e.soul)];
764
- for (const soul of souls) {
765
- for (const label of soul.labels || []) {
766
- if (!teams.includes(label) || Object.hasOwn(byTeam, label)) continue;
767
- const row = byLabel.get(label) || { souls: [], paths: [] };
768
- row.souls.push(soul.name); row.paths.push(`${soul.repoKey}:${soul.path}/soul.yaml#/team`);
769
- byLabel.set(label, row);
770
- }
771
- }
772
- return [...byLabel.keys()].sort(byCodepointOrder).map((label) => {
773
- const { souls: names, paths } = byLabel.get(label);
774
- const order = names.map((n, i) => i).sort((x, y) => byCodepointOrder(names[x], names[y]) || byCodepointOrder(paths[x], paths[y]));
775
- const sorted = order.map((i) => names[i]);
776
- return { code: "unmapped-team-label", label, souls: sorted, paths: order.map((i) => paths[i]),
777
- message: `team ${show(label)} has no messaging.byTeam entry; its souls (${sorted.join(", ")}) fall back to the workspace's default team for it` };
778
- });
684
+ export function workspaceWarnings(workspace, members, externalRows, packageSouls) {
685
+ return privateSoulWarnings([...members.filter((m) => m.confirmed).flatMap((m) => m.souls), ...externalRows.map((e) => e.soul), ...(packageSouls || [])]);
779
686
  }
780
687
 
781
688
  /** `private:` in a soul.yaml has no effect since 0.26.0 (human decision 2026-09-25): every soul of a
@@ -837,7 +744,7 @@ export function standaloneRepo(ref, commit, discovery, { remote: injected } = {}
837
744
  });
838
745
  return {
839
746
  standalone: true, key, commit: source.commit ?? commit ?? null, workspace: null,
840
- members: [{ key, commit: source.commit ?? commit ?? null, confirmed: false, reason: "cannot-read", detail: `workspace of ${key} cannot be read; standalone view`, team: source.team ?? labelList(source.membership?.team)?.[0] ?? null, souls, capabilities, publishes: source.publishes ?? null }],
747
+ members: [{ key, commit: source.commit ?? commit ?? null, confirmed: false, reason: "cannot-read", detail: `workspace of ${key} cannot be read; standalone view`, souls, capabilities, publishes: source.publishes ?? null }],
841
748
  external: [], problems, warnings: privateSoulWarnings(souls),
842
749
  };
843
750
  }
@@ -3,12 +3,12 @@
3
3
  "packages": {
4
4
  "oats.okf": {
5
5
  "url": "https://github.com/awebai/oats-okf.git",
6
- "ref": "v4.0.1",
6
+ "ref": "v4.0.5",
7
7
  "path": "oats-package"
8
8
  },
9
9
  "oats.aweb": {
10
10
  "url": "https://github.com/awebai/oats-aweb.git",
11
- "ref": "v1.16.0",
11
+ "ref": "v1.17.1",
12
12
  "path": "oats-package"
13
13
  },
14
14
  "oats.jira": {
@@ -26,19 +26,18 @@
26
26
  "ref": "v1.0.3",
27
27
  "path": "oats-package"
28
28
  },
29
- "oats.dev": {
30
- "url": "https://github.com/awebai/oats-dev.git",
31
- "ref": "v1.1.0",
29
+ "oats.engineering": {
30
+ "url": "https://github.com/awebai/oats-engineering.git",
31
+ "ref": "v1.3.0",
32
32
  "path": "oats-package"
33
33
  },
34
34
  "oats.framework": {
35
35
  "url": "https://github.com/awebai/oats.git",
36
- "ref": "oats-framework/v1.3.2",
36
+ "ref": "oats-framework/v1.4.0",
37
37
  "path": "oats-package"
38
38
  }
39
39
  },
40
40
  "capabilities": {
41
- "oats.review": "oats.dev",
42
41
  "oas.okf": {
43
42
  "package": "oats.okf",
44
43
  "capability": "oats.okf"
@@ -59,18 +58,13 @@
59
58
  "package": "oats.authoring",
60
59
  "capability": "oats.authoring"
61
60
  },
62
- "oas.dev": {
63
- "package": "oats.dev",
64
- "capability": "oats.dev"
65
- },
66
- "oas.review": {
67
- "package": "oats.dev",
68
- "capability": "oats.review"
69
- },
70
61
  "oats.knowledge-theory": "oats.framework",
71
62
  "oats.core": "oats.framework",
72
63
  "oats.setup": "oats.framework",
73
64
  "oats.okf-harvest": "oats.okf",
74
- "oats.okf-maintenance": "oats.okf"
65
+ "oats.okf-maintenance": "oats.okf",
66
+ "oats.engineering-expert": "oats.engineering",
67
+ "oats.developer": "oats.engineering",
68
+ "oats.code-review": "oats.engineering"
75
69
  }
76
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awebai/oats",
3
- "version": "0.29.4",
3
+ "version": "0.30.1",
4
4
  "description": "OATS (Open Agent Team Specification) — durable souls, disposable instances, targetable capability packages, and the runtime-neutral oats CLI/kernel.",
5
5
  "keywords": [
6
6
  "agents",
@@ -18,7 +18,6 @@
18
18
  "check": "node scripts/check-package-dry-runs.mjs --syntax-only",
19
19
  "check:pi": "node --experimental-strip-types --check packages/pi/extension/index.ts",
20
20
  "validate": "node scripts/validate-project.mjs",
21
- "validate:okf": "node scripts/validate-okf.mjs",
22
21
  "pack:check": "node scripts/check-package-dry-runs.mjs",
23
22
  "smoke:tarball": "node scripts/clean-room-smoke.mjs"
24
23
  },
@@ -32,7 +31,6 @@
32
31
  "lib/",
33
32
  "skills/",
34
33
  "injects/",
35
- "capabilities/",
36
34
  "docs/",
37
35
  "README.md",
38
36
  "package-catalog.json",
@@ -60,10 +60,8 @@ members:
60
60
  - git:github.com/acme/agents # the host is a member too
61
61
  - git:github.com/acme/platform
62
62
  packages:
63
- oats.framework: v1.1.3 # bare versions resolve through the official catalog
64
- oats.okf: v2.1.5
65
- teams:
66
- global: { description: Org-wide souls }
63
+ oats.framework: v1.4.0 # bare versions resolve through the official catalog
64
+ oats.okf: v4.0.5
67
65
  defaults:
68
66
  capabilities: { oats.core: { from: package } }
69
67
  knowledge: { oats.okf: { from: package } }
@@ -72,7 +70,7 @@ defaults:
72
70
  Take the current package versions from the official catalog
73
71
  (`package-catalog.json` in the OATS repository); ask which slots the user wants
74
72
  filled (knowledge, messaging, tasks) instead of copying the example. No absolute
75
- paths, accounts or team ids go in this file.
73
+ paths or accounts go in this file. Teams come with messaging (step 6).
76
74
 
77
75
  Declaring a package in `packages:` is the decision to trust it: its commands
78
76
  and hooks run on every machine that spawns a soul using it. Show the user what
@@ -84,7 +82,6 @@ In **every** member repository, including the host, `oats-membership.yaml`:
84
82
  ```yaml
85
83
  schemaVersion: 2
86
84
  workspace: git:github.com/acme/agents
87
- team: global
88
85
  ```
89
86
 
90
87
  Membership is reciprocal: the workspace lists the repository and the
@@ -135,20 +132,35 @@ each package to an exact commit and integrity, and content that no longer
135
132
  matches is refused (`E_PACKAGE_INTEGRITY`). Member capabilities come from
136
133
  membership.
137
134
 
138
- ## 6. Spawn the first soul
135
+ ## 6. Give the deployment a team (with messaging)
136
+
137
+ If a messaging capability fills the messaging slot, every instance lives in a
138
+ team, and readiness fails with `E_TEAM_UNCONFIGURED` until this deployment has
139
+ a default team. Create the team with the messaging provider (its own skills say
140
+ how), then record it:
141
+
142
+ ```bash
143
+ oats teams add research --team <provider team id> # a local team; the first one becomes the default
144
+ oats teams
145
+ ```
146
+
147
+ A team the whole organisation uses is committed in `oats-workspace.yaml` as
148
+ `teams.<label>` with its provider id; which souls join which team on this
149
+ machine is `oats soul teams` (`/oats-teams`, in `oats.setup`).
150
+
151
+ ## 7. Spawn the first soul
139
152
 
140
153
  A soul with `work: worktree | checkout` needs a clone of its repository at
141
154
  `<dir>/<repo name>` (or named in `oats-local.yaml` `clones:`).
142
155
 
143
156
  ```bash
144
- oats souls --dir <dir> # what the workspace offers, with origin and team
157
+ oats souls --dir <dir> # what the workspace offers, with origin and teams
145
158
  oats spawn backend-expert --preview # modules, commits, merged provider settings — nothing created
146
159
  oats spawn backend-expert --task "First concrete task"
147
160
  oats status
148
161
  ```
149
162
 
150
- Create and spawn only when asked. After the first spawn, load the `oats.setup`
151
- skills for the rest of the deployment (messaging, more souls, rebuilds). For
152
- custom capabilities and integrations, use `integration-authoring`; for deep
153
- architecture questions or bugs, use `oats-support`. The model in full is
154
- `docs/workspaces.md` in the OATS repository.
163
+ Create and spawn only when asked. After the first spawn, the `oats.setup`
164
+ skills carry the rest of the deployment (messaging, more souls, rebuilds); the
165
+ `oats.authoring` package teaches writing capabilities, skills and souls. The
166
+ model in full is `docs/workspaces.md` in the OATS repository.
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 OATS Framework
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,11 +0,0 @@
1
- {
2
- "package": "oats.authoring",
3
- "version": "1.0.3",
4
- "description": "Official additive OATS guidance for capability, skill, and soul authoring.",
5
- "compatibility": {
6
- "oats": ">=0.25.0"
7
- },
8
- "capabilities": [
9
- "."
10
- ]
11
- }
@@ -1,12 +0,0 @@
1
- {
2
- "capability": "oats.authoring",
3
- "version": "1.0.3",
4
- "compatibility": { "oats": ">=0.25.0" },
5
- "description": "Additive framework-authoring guidance for capability packages, agent skills, and souls.",
6
- "requires": [],
7
- "skills": [
8
- "skills/integration-authoring",
9
- "skills/skill-craft",
10
- "skills/soul-craft"
11
- ]
12
- }