@awebai/oats 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +380 -0
  3. package/bin/oats.mjs +3294 -0
  4. package/capabilities/oats-authoring/oats.json +12 -0
  5. package/capabilities/oats-aweb/bin/oats-aweb.mjs +297 -0
  6. package/capabilities/oats-aweb/injects/aweb.md +55 -0
  7. package/capabilities/oats-aweb/oats.json +47 -0
  8. package/capabilities/oats-aweb/package.json +20 -0
  9. package/capabilities/oats-jira/bin/oats-jira.mjs +40 -0
  10. package/capabilities/oats-jira/injects/jira.md +10 -0
  11. package/capabilities/oats-jira/oats.json +22 -0
  12. package/capabilities/oats-jira/package.json +25 -0
  13. package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +176 -0
  14. package/capabilities/oats-linear/README.md +234 -0
  15. package/capabilities/oats-linear/bin/oats-linear-hook.mjs +32 -0
  16. package/capabilities/oats-linear/bin/oats-linear.mjs +344 -0
  17. package/capabilities/oats-linear/injects/linear.md +8 -0
  18. package/capabilities/oats-linear/oats.json +24 -0
  19. package/capabilities/oats-linear/package.json +29 -0
  20. package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +217 -0
  21. package/capabilities/oats-linear/test/oats-linear.test.mjs +168 -0
  22. package/capabilities/oats-okf/agents/memory-harvest.md +25 -0
  23. package/capabilities/oats-okf/bin/oats-okf.mjs +283 -0
  24. package/capabilities/oats-okf/injects/okf.md +62 -0
  25. package/capabilities/oats-okf/oats.json +20 -0
  26. package/capabilities/oats-okf/package.json +22 -0
  27. package/capabilities/oats-okf/skills/memory-harvest/SKILL.md +100 -0
  28. package/capabilities/oats-okf/skills/okf/SKILL.md +107 -0
  29. package/capabilities/oats-okf/skills/okf/scripts/okf-validate.mjs +123 -0
  30. package/capabilities/oats-review/agents/reviewer/AGENTS.md +53 -0
  31. package/capabilities/oats-review/agents/reviewer/soul.yaml +6 -0
  32. package/capabilities/oats-review/injects/review.md +68 -0
  33. package/capabilities/oats-review/oats.json +10 -0
  34. package/capabilities/oats-review/skills/code-review/SKILL.md +44 -0
  35. package/capabilities/oats-review/skills/security-review/SKILL.md +59 -0
  36. package/docs/capabilities.md +505 -0
  37. package/docs/capability-manifest.schema.json +223 -0
  38. package/docs/configuration.md +482 -0
  39. package/docs/conventions.md +73 -0
  40. package/docs/design/desktop-ux-plan.md +362 -0
  41. package/docs/design/package-engine-contract.md +813 -0
  42. package/docs/design/package-runtime-api.md +414 -0
  43. package/docs/desktop-cli-api.md +89 -0
  44. package/docs/desktop-succession.md +51 -0
  45. package/docs/desktop.md +187 -0
  46. package/docs/implementation.md +282 -0
  47. package/docs/integrations.md +123 -0
  48. package/docs/knowledge-theory.md +97 -0
  49. package/docs/knowledge.md +139 -0
  50. package/docs/layers.md +110 -0
  51. package/docs/migration-from-oas.md +122 -0
  52. package/docs/oats-config.schema.json +140 -0
  53. package/docs/oats-lock.schema.json +175 -0
  54. package/docs/oats-package.schema.json +129 -0
  55. package/docs/packages.md +442 -0
  56. package/docs/release-lane.md +132 -0
  57. package/docs/release-notes/v0.18.2.md +79 -0
  58. package/docs/release-notes/v0.18.3.md +63 -0
  59. package/docs/release-notes/v0.18.4.md +65 -0
  60. package/docs/release-notes/v0.18.5.md +66 -0
  61. package/docs/release-notes/v0.18.6.md +87 -0
  62. package/docs/release-notes/v0.19.0.md +186 -0
  63. package/docs/release-notes/v0.19.1.md +24 -0
  64. package/docs/release-notes/v0.19.2.md +32 -0
  65. package/docs/release-notes/v0.19.3.md +16 -0
  66. package/docs/release-notes/v0.19.4.md +12 -0
  67. package/docs/release-notes/v0.20.0.md +89 -0
  68. package/docs/release-notes/v0.22.0.md +180 -0
  69. package/docs/souls-and-instances.md +344 -0
  70. package/injects/framework-workspace.md +7 -0
  71. package/injects/instance-boundary.md +43 -0
  72. package/injects/local-soul.md +19 -0
  73. package/injects/oats.md +11 -0
  74. package/injects/work-attached.md +16 -0
  75. package/injects/work-checkout.md +12 -0
  76. package/injects/work-workspace.md +22 -0
  77. package/injects/work-worktree.md +13 -0
  78. package/lib/core.mjs +6792 -0
  79. package/lib/packages.mjs +1375 -0
  80. package/lib/tmux-config.mjs +35 -0
  81. package/package-catalog.json +44 -0
  82. package/package.json +55 -0
  83. package/packages/record/README.md +151 -0
  84. package/packages/record/bin/capture.mjs +256 -0
  85. package/packages/record/bin/recall.mjs +102 -0
  86. package/packages/record/bin/setup.mjs +281 -0
  87. package/packages/record/bin/turn-record.mjs +47 -0
  88. package/packages/record/docs/turn-record-sot.md +350 -0
  89. package/packages/record/lib/canonical.mjs +151 -0
  90. package/packages/record/lib/capture-aw.mjs +179 -0
  91. package/packages/record/lib/capture-cc.mjs +322 -0
  92. package/packages/record/lib/formats.mjs +296 -0
  93. package/packages/record/lib/ignore.mjs +155 -0
  94. package/packages/record/lib/index-db.mjs +524 -0
  95. package/packages/record/lib/project-aweb.mjs +251 -0
  96. package/packages/record/lib/segments.mjs +186 -0
  97. package/packages/record/lib/store.mjs +447 -0
  98. package/packages/record/lib/tags.mjs +114 -0
  99. package/packages/record/package.json +45 -0
  100. package/packages/record/test/vectors/README.md +34 -0
  101. package/packages/record/test/vectors/aweb-projection-v1.json +231 -0
  102. package/packages/record/test/vectors/journal-merge-v1.json +261 -0
  103. package/packages/record/test/vectors/turn-id-v1.json +152 -0
  104. package/packages/record/test/vectors/validate.mjs +391 -0
  105. package/skills/integration-authoring/SKILL.md +76 -0
  106. package/skills/oats/SKILL.md +151 -0
  107. package/skills/oats-config/SKILL.md +162 -0
  108. package/skills/oats-getting-started/SKILL.md +159 -0
  109. package/skills/oats-packages/SKILL.md +180 -0
  110. package/skills/oats-support/SKILL.md +79 -0
  111. package/skills/skill-craft/SKILL.md +109 -0
  112. package/skills/soul-craft/SKILL.md +109 -0
@@ -0,0 +1,414 @@
1
+ # Package-runtime API contract (addendum to the package-engine contract)
2
+
3
+ Status: **FROZEN** for the capability-materialization delivery, as an addendum to
4
+ [`package-engine-contract.md`](./package-engine-contract.md). It answers the
5
+ maintainer's four clarifications on the M1 freeze (maintainer review of
6
+ 1db919b): the public package-runtime boundary, the npm runtime closure,
7
+ incremental transaction semantics, and runtime-validated schema invariants.
8
+ Changes go through the coordinator to the maintainer.
9
+
10
+ **What capability materialization changed here.** §1 (the public CLI boundary) is
11
+ unchanged. §2 keeps every npm rule and moves the materialization root from the
12
+ package root to each declared *capability* root, because the closure now lives
13
+ inside the materialized artifact. §3 becomes incremental with respect to the
14
+ *capability store*. §4 states the current lock invariants and the prototype-safety
15
+ requirement. §5 records that a `"."` capability root is read compatibility only,
16
+ discriminated by `configTemplates` rather than `configs`. §6 records that v1 is
17
+ the only legacy format supported, and that the earlier transitional package-root
18
+ `lockfileVersion: 2` is unsupported input rather than something to migrate.
19
+
20
+ ## 1. Public package-runtime boundary (structured CLI API)
21
+
22
+ **Transport choice: the structured CLI API.** Rationale (tradeoff surfaced to
23
+ the coordinator/maintainer before freezing, mail 09447984): a process contract
24
+ is a true version boundary — it survives kernel-internal refactors and node/ESM
25
+ changes, nothing private is importable by construction, and it extends the
26
+ already-proven Desktop CLI API v1 envelope discipline instead of creating a
27
+ second public JS surface that must be kept in semver lockstep with the CLI
28
+ forever. The rejected alternative (a blessed `lib/runtime.mjs` import resolved
29
+ via `oats root`) preserves exactly the dynamic-import coupling the maintainer
30
+ ruled out.
31
+
32
+ **Rule: independently released packages MUST NOT import kernel-private
33
+ `lib/core.mjs` (including via `oats root` + dynamic import).** Package
34
+ commands/hooks execute the CLI at the exact absolute path the dispatcher
35
+ provides in `OATS_CLI_BIN` (§1 item 4) — never by resolving `oats` from PATH,
36
+ which is untrusted inside worktrees.
37
+
38
+ ### Envelope and versioning
39
+
40
+ Every boundary command supports `--json` with the Desktop CLI API v1 envelope:
41
+ exactly one JSON object on stdout — `{ schemaVersion: 1, ok: true, result }`
42
+ or `{ schemaVersion: 1, ok: false, error: { code, message } }` — nonzero exit
43
+ on failure; progress prose only on stderr.
44
+
45
+ - **Versioning** (maintainer ruling): the boundary is versioned by the
46
+ **compatibility floor plus a pinned consumer fixture** — the boundary shipped
47
+ in kernel **0.19.0** and is unchanged by capability materialization; the
48
+ materialized store and the capability-materialization lock (which REPLACES the
49
+ earlier package-root spelling in place and remains `lockfileVersion: 2`) raise
50
+ the floor for packages that rely on the new manifest surface
51
+ (`configTemplates`, dedicated capability roots), which
52
+ declare the materialization release's floor instead. Official packages declare
53
+ their floor as `compatibility.oats: ">=<floor>"` in `oats-package.json` (and
54
+ capability `compatibility.oats` likewise), and each consumer repo pins the kernel
55
+ consumer-fixture version its CI probes against. The exact Desktop
56
+ `oats version --json` probe payload is NOT extended (no `packageRuntimeApi`
57
+ field) — Desktop API compatibility is a separate contract.
58
+ - Kernels below the floor are rejected by the consumer's normal
59
+ compatibility check (`incompatible-oats` at acquire; the consumer fixture
60
+ asserts the rejection).
61
+
62
+ ### Commands (exact surface, boundary v1 — maintainer-ruled minimal)
63
+
64
+ The public boundary is HIGHER-LEVEL than the private core calls it replaces:
65
+ private `findAgent`/`upsertLocalAgent`/`spawnInstance`/`resolveOatsConfig`
66
+ usage maps onto capability-defined agents, `oats spawn`, and dispatch-provided
67
+ settings — not onto one-for-one public equivalents. File-of-record for the
68
+ consumer inventory: `packaging/oats-okf/KERNEL-API-NEEDS.md` on kernel branch
69
+ `integrations-expert/official-packages-staging` @ `60d5eb6` (design input;
70
+ this contract remains authoritative).
71
+
72
+ 1. **Capability-defined agents own lookup/registration/ephemerality.** A
73
+ package capability declares its service agents in its manifest `agents:`
74
+ (package-relative soul dirs, e.g. oats.okf ships
75
+ `agents/memory-harvest/{soul.yaml,AGENTS.md}`). `oats spawn <agent>`
76
+ resolves capability-defined agents for the active context, scaffolds a
77
+ fresh soul homed locally, and applies ephemeral (`kind: "capability"`)
78
+ semantics automatically. There is NO public `oats agent show`,
79
+ `oats agent upsert`, or generic `--ephemeral` flag — add such a surface
80
+ only when a reusable use case proves it.
81
+ 2. **Spawn** — `oats spawn <agent> ... --json` with the EXISTING flags:
82
+ `--purpose <slug>` (deterministic derived naming
83
+ `<agent>-<purpose>`; no raw instance-name authority), `--parent`,
84
+ `--repo`, `--work attached|worktree|checkout|workspace`, `--work-dir`,
85
+ `--branch`, `--model`, `--task`/`--task-file` (owner-only tempfiles:
86
+ mode 0600, removed on every outcome). Existing validation and error codes
87
+ (`E_BAD_ARGS`, `E_PARENT_NOT_FOUND`, `E_SPAWN_FAILED`, ...) are part of
88
+ the contract; result is the fixed Desktop CLI API v1 spawn shape
89
+ (`{ instance, agent, home, work, tmux, ... }`). If an accepted consumer
90
+ mode cannot be expressed by an existing flag, ONE narrow flag is added
91
+ with JSON tests — never a general override.
92
+ 3. **Settings via dispatch** — `oats <namespace> <command>` passes the active
93
+ capability's EFFECTIVE settings to the dispatched process as
94
+ `OATS_SETTINGS` (JSON; from the instance metadata snapshot or the resolved
95
+ context), the same contract lifecycle hooks already have. Capabilities
96
+ read their settings (e.g. oats.okf's `harvest-model`) from `OATS_SETTINGS`;
97
+ there is NO public resolved-config read command.
98
+ 4. **Consumer rules**: a package command executes the CLI at the exact
99
+ absolute path the dispatcher provides in the **`OATS_CLI_BIN`** environment
100
+ variable (part of the dispatch env contract, beside `OATS_SETTINGS`), via
101
+ `execFile` on that path — **never** by resolving `oats` from `PATH` and
102
+ never through a shell: PATH is not a trusted runtime boundary, and package
103
+ commands run in worktrees where it can be shadowed. The consumer parses
104
+ the one schema-v1 envelope, emits its own envelope, and never imports
105
+ `lib/core.mjs` or calls `oats root` for kernel-file resolution.
106
+
107
+ Error codes are part of the contract: `E_USAGE`, `E_BAD_ARGS`,
108
+ `E_UNKNOWN_COMMAND`, `E_SPAWN_FAILED`, `E_PARENT_NOT_FOUND`,
109
+ `E_RELATIVE_NOT_FOUND`, `E_RELATIVE_AMBIGUOUS`, `E_CAPABILITY_BLOCKED`,
110
+ `E_CAPABILITY_INACTIVE`.
111
+
112
+ ### Consumer fixture
113
+
114
+ The engine ships a consumer fixture driving the full oats.okf pattern
115
+ exclusively through this surface: a capability-defined `memory-harvest`
116
+ agent resolved and spawned via `oats spawn --json` in all three source modes
117
+ (local-soul / workspace-mode / repo-resident), parent relation,
118
+ purpose-derived naming + debounce, model via `OATS_SETTINGS` dispatch,
119
+ task-file privacy/cleanup, clean JSON success/failure, no private
120
+ import/`oats root` lookup, Pi + Claude scaffold parity, and sub-floor kernel
121
+ rejection. WS3 reuses the fixture shape as each official repo's per-repo CI
122
+ probe, combined with the acquire → lock → trust → activate → spawn probe
123
+ from `test/packages.test.mjs`. (The oats.okf tree changes themselves —
124
+ `agents/memory-harvest`, dropping the core import — are WS3 deliverables.)
125
+
126
+ ## 2. Capability-local npm runtime closure
127
+
128
+ - **Detection and placement**: materialization roots are the manifest-declared
129
+ CAPABILITY roots — each one carrying BOTH `package.json` AND
130
+ `package-lock.json` is materialized independently, and the resulting
131
+ `node_modules` becomes part of that capability's materialized artifact. This
132
+ is what lets an inner `oats.json` resolve resources via `node_modules/...`
133
+ relative to its own manifest (e.g. oats-aweb's
134
+ `node_modules/@awebai/pi/skills/...`) inside a self-contained artifact.
135
+ A **package-root-only** closure has no durable home and is NOT
136
+ materialized: it is package tooling. If a capability actually depends on it,
137
+ self-containment fails and the package is rejected
138
+ (`capability-not-self-contained`) rather than silently installing a broken
139
+ artifact. For a legacy `"."` capability root the capability root *is* the
140
+ package root, so that package-root lock is the capability's own closure (it is
141
+ detected once, not twice). Directories not enumerated by the manifest are
142
+ never scanned.
143
+ - **When and how**: materialization runs IN STAGING during acquire, update and
144
+ restore, after payload integrity verification and BEFORE the capability
145
+ artifact's integrity is computed or swapped in. The command is exactly
146
+ `npm ci --omit=dev --omit=peer --ignore-scripts` (plus `--no-audit
147
+ --no-fund` noise suppression) per materialization root — **dev AND host
148
+ peer dependencies are omitted**; **no npm lifecycle scripts ever run**, at
149
+ any phase. A package may consume host-provided peer APIs only through an
150
+ explicit supported host boundary (§1) — never by auto-materializing an
151
+ unrelated harness peer into its closure.
152
+ - **Closure/integrity/audit scope**: the runtime-closure contract covers the
153
+ ACTUALLY MATERIALIZED production dependency tree, not the full lock
154
+ metadata (a lockfile may describe dev/peer subtrees that are never
155
+ materialized and are out of contract). Vulnerability audit uses the
156
+ identical scope: `npm audit --omit=dev --omit=peer --ignore-scripts`.
157
+ Consumer/package CI must include a fixture asserting omitted peer
158
+ dependencies are ABSENT from the materialized tree.
159
+ - **Integrity coverage**: the lock has TWO digests at two levels, and the
160
+ closure sits inside one of them.
161
+ - The package row's `integrity` covers the staged package PAYLOAD only —
162
+ every `node_modules` (at any depth) and a root `oats-lock.json` are excluded,
163
+ so it is stable whether or not materialization has run. Root source-control
164
+ metadata (`.git`) is stripped before staging; if it later appears in a
165
+ managed artifact it is ordinary drift, not an integrity exclusion.
166
+ - The capability row's `integrity` covers the MATERIALIZED ARTIFACT with **no
167
+ exclusions at all**: capability source bytes, the materialized
168
+ `node_modules`, and the generated `.oats-installation.json` provenance file.
169
+ - There is consequently NO separate dependency digest anywhere in the model —
170
+ tampering with a materialized dependency changes the capability artifact
171
+ integrity directly, which invalidates `trusted` exactly like source drift and
172
+ makes bare restore reproject. A lock row carrying `depsIntegrity` is
173
+ evidence of the unsupported transitional shape (contract §4.1), not a field
174
+ to honour.
175
+ - `npm ci` fails closed on any lockfile mismatch. Doctor reports the package
176
+ payload integrity and each capability artifact's integrity/trust state.
177
+ - **Reproducibility (v1 MUST: platform-invariant closures)**: `node_modules`
178
+ is a derived artifact — never part of the package payload hash, never
179
+ committed, always reproduced from the locked `package-lock.json` and verified
180
+ through the capability artifact integrity that contains it. Because that
181
+ single artifact digest lives in a shared lock, **v1 packages MUST have
182
+ platform-invariant materialized closures**: no native builds, no
183
+ platform-specific optional dependencies, no install-time variance of any kind.
184
+ A closure that cannot materialize byte-identically across supported platforms
185
+ is UNSUPPORTED in v1 — the package must vendor a pure-JS closure or drop the
186
+ dependency; official dependency-bearing packages gate this across their
187
+ published platforms in CI. The engine ENFORCES this at materialization as a
188
+ transaction-wide preflight PLUS a post-materialization scan: every
189
+ materialization root's lockfile (every declared capability root, kept and
190
+ fresh) is scanned BEFORE any `npm ci` — only entries in the materialized
191
+ non-dev/non-peer closure are evaluated (omitted metadata cannot fail an
192
+ otherwise valid closure); an INCLUDED entry with os/cpu/libc constraints,
193
+ optional-dependency variance, or an install script is rejected (an included
194
+ install script is disallowed even though `--ignore-scripts` inerts it — the
195
+ runtime almost certainly expects the artifacts it would have built). After
196
+ `npm ci`, before digest/swap, the materialized tree is scanned for `.node`
197
+ native binaries alongside symlink containment. npm lockfileVersion 1 (no
198
+ `packages` map) fails closed — regenerate with modern npm. (A future keyed
199
+ per-platform closure map may relax this.)
200
+ - **Containment**: capability code/hook/skill/agent paths must resolve inside
201
+ the CAPABILITY root after symlink resolution — that is what makes the
202
+ materialized artifact self-contained and independently hashable. Materialized
203
+ `node_modules` trees under that root are inside the boundary by construction —
204
+ and ENFORCED: after `npm ci`, before any digest or swap, every symlink under
205
+ every materialized `node_modules` is realpath-checked to resolve inside the
206
+ capability root; a broken or escaping link fails the transaction
207
+ (`path-escape`) with full rollback. Node import resolution follows symlinks,
208
+ so this check is global, not best-effort.
209
+ - **Rollback**: materialization happens IN STAGING before any destination
210
+ mutation; a materialization failure fails the whole acquire/update
211
+ transaction with the capability store and lock unchanged, and a restore whose
212
+ reprojected artifact does not reproduce the locked capability `integrity`
213
+ fails as `integrity-drift` with the prior artifact left in place. Staging
214
+ directories are removed wholesale on any failure.
215
+
216
+ ## 3. Incremental transaction semantics
217
+
218
+ Acquire/update of one package closure is **incremental with respect to the
219
+ scope's capability store**, never a wholesale store replacement:
220
+
221
+ - Capability artifacts, package rows and capability rows belonging to packages
222
+ NOT in the resolved closure are untouched — bytes on disk and lock JSON both.
223
+ - Within the closure, a capability whose newly projected artifact integrity
224
+ EQUALS its currently locked integrity is kept in place ("kept" in reports) and
225
+ its `trusted` flag is preserved verbatim.
226
+ - Only capabilities whose artifact integrity CHANGES have their artifact
227
+ replaced and their `trusted` reset to `false`. Trust is per capability, so an
228
+ unchanged capability inside a changed package keeps its approval.
229
+ - All validation (manifests, self-containment, cycles, identity and
230
+ capability-ID collisions, compatibility, platform invariance) completes against
231
+ a staging area BEFORE any destination mutation; the artifact swaps and the lock
232
+ write happen only after full-closure validation. On any failure before that
233
+ point the staging area is removed and the destination store + lock are
234
+ byte-identical to the pre-operation state.
235
+ - An update replaces ALL of one package's exports together or none of them; a
236
+ removed export is retired only when no config references it (otherwise
237
+ `remove-blocked`, with nothing changed).
238
+ - Restore is per-capability transactional: a failure (`integrity-drift`,
239
+ `capability-list-mismatch`) leaves that capability absent or untouched — never
240
+ partially installed — and does not affect other capabilities' restores.
241
+
242
+ ## 4. Runtime-validated schema invariants
243
+
244
+ JSON Schema cannot express these in the current shapes, so they are normative
245
+ SEMANTIC validation rules with tests; validators of the schemas alone are not
246
+ complete:
247
+
248
+ - `oats-package.json`:
249
+ - `capabilities` is REQUIRED and non-empty — config-only and empty packages
250
+ are `invalid-package-manifest`;
251
+ - `configTemplates` is OPTIONAL and is the canonical spelling; `configs` is a
252
+ deprecated read-only alias; both spellings normalize to one descriptor shape
253
+ carrying a diagnostic `legacySpelling`, and carrying BOTH is
254
+ `invalid-package-manifest`;
255
+ - a `"."` capability root is accepted only when the manifest does NOT carry
256
+ `configTemplates` (§5), and remains exclusive with any other capability path;
257
+ authoring never emits it;
258
+ - at most one `configTemplates.*.default === true` (equivalently
259
+ `configs.*.default`) per manifest → `invalid-package-manifest`;
260
+ - `compatibility.oats` is REQUIRED with exactly the grammar `>=x.y.z`,
261
+ `^x.y.z`, or `x.y.z` — schema and runtime agree; malformed/missing →
262
+ `invalid-package-manifest`, valid-but-unsatisfied → `incompatible-oats`;
263
+ - every declared capability must be projectable self-contained — each declared
264
+ resource exists and realpath-resolves inside its own capability root →
265
+ `capability-not-self-contained` / `path-escape`. JSON Schema cannot see this
266
+ at all: it is a filesystem property of the staged payload.
267
+ - `oats-lock.json`, validated BEFORE restore, trust/approval, update/remove
268
+ planning, migration planning, the locked-template reader, and doctor/list
269
+ consumption → `invalid-lock` (fail closed before executable approval or
270
+ artifact replacement; no normalization, no auto-repair, NO side effects;
271
+ message/provenance carry lock file, package or capability identity, and the
272
+ violated field/edge):
273
+ - both top-level `packages` and `capabilities` maps are required;
274
+ - `dependencies` is required on every package row (empty array when none), so
275
+ a reader never distinguishes absent from empty;
276
+ - normalized source prefix (`git:`/`path:`/`catalog:`) and source/commit
277
+ pairing: `path:` requires `commit: "local"` AND `path: "."`; `git:`/`catalog:`
278
+ require an exact 40-hex `commit`;
279
+ - canonical `path` spelling on both row kinds — a non-canonical spelling is
280
+ invalid, never repaired;
281
+ - every `capabilities.*.package` is a key of the same lock's `packages` map
282
+ (the provider back-reference is the single truth for which capabilities a
283
+ package supplies — package rows never list them);
284
+ - every `packages.*.dependencies[]` id is a key of the same lock's `packages`
285
+ map; no self-dependency and no cycle in the locked dependency graph;
286
+ - `trusted` is a boolean, and it is the ONLY trust field: there is no
287
+ package-level approval anywhere in the model;
288
+ - `integrity` digests are well-formed sha256 on both row kinds;
289
+ - arrays retain schema uniqueness (no duplicates);
290
+ - `.oats-installation.json` inside a materialized artifact must AGREE with the
291
+ capability and package rows it was projected from (§3.1 of the contract);
292
+ disagreement is `invalid-lock`, modification is `integrity-drift`;
293
+ - the unsupported transitional v2 shape is rejected centrally by the exact
294
+ predicate of contract §4.1, using direct raw lock-scope reads rather than
295
+ `configChain` so lock-only scopes are visible, with own-property presence —
296
+ never truthiness or array length — as the row test;
297
+ - v1 documents are validated against their own historical shape when read, so
298
+ migration planning and doctor operate on verified data.
299
+
300
+ **Prototype safety is required at EVERY lookup or membership check keyed by a
301
+ package or capability ID** — central read, dependency graph, provider
302
+ resolution, trust, approval, update and remove alike, not merely at the
303
+ transitional tell fields. Raw parsed JSON objects return inherited
304
+ `constructor`, `toString` or `valueOf` for `map[id]` even when no own entry
305
+ exists, so identity keys are charset-validated and every map is null-prototype
306
+ or accessed through `Object.hasOwn`. A prototype-named or hostile raw-JSON ID
307
+ must never impersonate a provider, a dependency or a trust entry, nor bypass a
308
+ membership check. Fixtures cover empty transitional arrays and falsey values
309
+ plus prototype-named package AND capability IDs across central read, graph,
310
+ provider and trust lookups.
311
+
312
+ Fail-closed enforcement points: `parseLockFileStrict`, `readPackageLocks` and
313
+ `listInstalledPackages` RAISE `invalid-lock` — consumers never see invalid
314
+ locks as absent or usable data; `writePackageLock` and
315
+ `writeCapabilityLockEntry` validate the complete prospective document (both
316
+ maps, together) before writing; restore, trust queries, approval, update/remove
317
+ planning, migration planning and the locked-template reader validate before
318
+ acting. Doctor (human and `--json`) catches the typed error and renders the
319
+ actionable diagnosis — it is the only consumer that continues past an invalid
320
+ lock, and it never uses the invalid data.
321
+
322
+ `invalid-lock` joins the error taxonomy of the main contract (§8).
323
+
324
+ ## 5. Flat single-capability packages (`capabilities: ["."]`)
325
+
326
+ **Read compatibility only.** A capability directory may BE the package root —
327
+ `oats-package.json` and `oats.json` side by side with `capabilities: ["."]` — in
328
+ an already-published manifest. The discriminator is `configTemplates`, NOT
329
+ `configs`: `oats.authoring@1.0.0` is `capabilities: ["."]` and ships no template
330
+ map at all, so keying acceptance on the deprecated spelling would strand a
331
+ package the kernel is required to keep reading. A manifest carrying
332
+ `configTemplates` is unambiguously new and its `"."` is
333
+ `invalid-package-manifest`; authoring tooling never emits `"."` either way.
334
+ Semantics for the layouts that still exist:
335
+
336
+ - **The projection is still a capability artifact.** The capability root equals
337
+ the package root, so the materialized artifact under
338
+ `.agents/capabilities/installed/<id>/` contains the whole package root
339
+ including `oats-package.json` and any config templates. That is a superset, not
340
+ a leak: everything in it is validated payload from one exact locked source,
341
+ and the artifact remains self-contained, independently hashable and
342
+ independently trustable. Its `integrity` is the artifact hash like any other.
343
+ - **Two digests, no double counting.** The package row's payload `integrity` and
344
+ the capability row's artifact `integrity` cover overlapping bytes on purpose:
345
+ one proves the distribution, the other proves the installation. Trust binds to
346
+ the capability digest only.
347
+ - **Resource indexing**: only the manifest-declared `.` is indexed; `oats.json`
348
+ loads from the root with normal capability validation. `oats-package.json`
349
+ living inside the capability's file set is harmless — each file has exactly
350
+ one loader (`oats-package.json` → package manifest, `oats.json` → capability
351
+ manifest), so no manifest-kind ambiguity can arise.
352
+ - **Constraint**: `.` implies a SINGLE-capability package. Listing `.` together
353
+ with any other capability path would nest one capability inside another and is
354
+ rejected as `invalid-package-manifest`.
355
+ - Per-capability npm closures (§2) degenerate to the package root: a root
356
+ `package.json` + `package-lock.json` pair is the capability's closure (it is
357
+ detected once, not twice).
358
+ - **Fail rather than degrade**: if such a package's capability cannot be
359
+ projected self-contained, acquisition and migration fail
360
+ (`capability-not-self-contained`) instead of silently retaining package-only
361
+ paths.
362
+
363
+ ## 6. Legacy locks: v1 compatibility, and no transitional-v2 compatibility
364
+
365
+ There is exactly one legacy format to support, and it is v1.
366
+
367
+ 1. The kernel **writes only** the capability-materialization lock.
368
+ `writePackageLock` and `writeCapabilityLockEntry` refuse an existing v1
369
+ file — **including an empty one** — with `legacy-lock`. Only an ABSENT lock
370
+ is a fresh document; an empty v1 file still carries a format decision the
371
+ user has not made, and converting it implicitly would contradict explicit
372
+ migration.
373
+ 2. **v1 stays usable.** Runtime discovery, exact restore, trust checks,
374
+ approval updates and doctor/list diagnosis keep working against v1 locks and
375
+ the existing v1 artifacts in `.agents/capabilities/installed/`. Ordinary use
376
+ of an unconverted deployment never requires migration; only lifecycle
377
+ mutation through the package surface does. `readPackageLocks` surfaces v1
378
+ files in `legacy` and in `migration` (with kind `v1` or `v1-empty`), and
379
+ nothing is normalized, repaired or rewritten on read.
380
+ 3. **Conversion is explicit, transactional and all-or-nothing per scope.** The
381
+ lock has no residue container, so a v1 scope with even one unmappable entry
382
+ stays v1 in full — reported as `hold`/`manual` — and keeps working.
383
+ Re-running `oats migrate` retries it once the catalog can map it. Guided
384
+ official migration converts directly into flat capability materialization.
385
+ 4. **Trust is never carried over from v1.** A v1 capability artifact and a
386
+ materialized artifact are different bytes, so every executable surface is
387
+ re-earned and listed in the returned `trust[]`.
388
+ 5. **Rollback is byte-exact.** Any conversion failure restores the original v1
389
+ lock byte-identically, removes every artifact the conversion created, leaves
390
+ superseded v1 artifacts in place, and rolls back the ignore bytes. Owned/path
391
+ capabilities are never touched.
392
+ 6. **The earlier transitional package-root v2 is not supported at all.** It is
393
+ detected centrally by contract §4.1 and rejected as `invalid-lock` with an
394
+ actionable message naming the unsupported shape and scope recreation. It is
395
+ never converted, never partially interpreted, and there is no
396
+ `.agents/packages/installed/` handling, offline projection, or trust
397
+ carry-over anywhere in the engine. Existing local pre-adoption state is
398
+ recreated by reinstalling. The one exception is the state-free empty
399
+ document `{ "lockfileVersion": 2, "packages": {} }`, which carries no state
400
+ and normalizes to the empty current lock.
401
+ 7. **Cutover gate**: zero lockfileVersion 1 files (including empty
402
+ `{capabilities:{}}` ones) and zero `.agents/packages/` directories across
403
+ every reconciled scope. Doctor reports each remaining one with its exact
404
+ command.
405
+
406
+ Required engine tests (`test/package-engine.test.mjs`): v1 empty file stays
407
+ pending (never implicitly converted), v1 partial-mappability hold with the scope
408
+ untouched, v1 full conversion with trust not carried, byte-exact rollback on
409
+ failure, unsupported transitional v2 rejected with no side effects (both
410
+ predicate arms, including empty transitional arrays and a dependency-free old
411
+ row), state-free empty transitional v2 normalization, prototype-named package
412
+ and capability IDs across central read / graph / provider / trust lookups, and
413
+ `.oats-installation.json` determinism, field agreement, tamper failure and
414
+ future-kernel restore.
@@ -0,0 +1,89 @@
1
+ # Desktop CLI API v1
2
+
3
+ The contract between the OATS Desktop app and the `oats` CLI. Desktop never
4
+ imports kernel code; it shells out (via `execFile`, argv, absolute binary — no
5
+ shell) to a discovered `oats` and speaks this JSON protocol. **API version, not
6
+ source adjacency, is authoritative.**
7
+
8
+ ## Probe
9
+
10
+ ```
11
+ oats version --json
12
+ ```
13
+
14
+ prints exactly one JSON object on stdout:
15
+
16
+ ```json
17
+ {"schemaVersion":1,"name":"@awebai/oats","version":"<installed version>","desktopApi":1}
18
+ ```
19
+
20
+ `version` is the installed package's exact semver (e.g. `0.20.0`).
21
+ Desktop 0.22 accepts `desktopApi === 1` and semver `>=0.22.0 <0.23.0`.
22
+
23
+ The band is widened one kernel minor at a time, after confirming this v1
24
+ surface is unchanged, and always admits the kernel published by the same
25
+ release — Desktop and the CLI are built from one tag, so a band excluding its
26
+ own kernel would degrade the shipped app to observation-only. Prereleases are
27
+ never accepted.
28
+
29
+ ## Envelope
30
+
31
+ Every other `--json` command emits **exactly one JSON object on stdout** and
32
+ no progress prose (progress goes to stderr):
33
+
34
+ - success (exit 0): `{"schemaVersion":1,"ok":true,"result":{...}}`
35
+ - failure (nonzero exit): `{"schemaVersion":1,"ok":false,"error":{"code":"...","message":"..."}}`
36
+
37
+ ## Mutations exposed to Desktop v1
38
+
39
+ Only two:
40
+
41
+ ### `oats spawn <agent> … --json`
42
+
43
+ `result` fields (always present):
44
+
45
+ | field | type | meaning |
46
+ | ---------- | --------------- | ------------------------------------------ |
47
+ | `instance` | string | new instance name |
48
+ | `agent` | string | soul/agent name |
49
+ | `home` | string | absolute instance home path |
50
+ | `work` | string | work mode (worktree/checkout/attached/workspace) |
51
+ | `branch` | string \| null | work branch when applicable |
52
+ | `launched` | boolean | whether a tmux window was started |
53
+ | `warnings` | string[] | non-fatal warnings (always an array) |
54
+ | `tmux` | {session,window} \| null | tmux target |
55
+
56
+ Additional informative fields: `repo`, `runtime`, `model`, `parent`,
57
+ `sibling` (explicit sibling cluster link when a root-level sibling relation
58
+ was declared, else null), `relation` (`child`/`sibling`/`parent` when a
59
+ relation was declared at spawn, else null), `spawnOrigin`, `attach`.
60
+
61
+ Stable error codes: `E_USAGE`, `E_NO_DEPLOYMENT`, `E_UNKNOWN_AGENT`,
62
+ `E_AMBIGUOUS_SOUL`, `E_PARENT_NOT_FOUND`, `E_RELATIVE_NOT_FOUND`,
63
+ `E_RELATIVE_AMBIGUOUS` (a `--relative-to`/`--parent` anchor name matches
64
+ multiple team instances — disambiguate with `--relative-root <agents-root>`
65
+ — or the chosen anchor is shadowed by a same-named instance so the lineage
66
+ edge would resolve wrongly), `E_BAD_ARGS`,
67
+ `E_SPAWN_FAILED`.
68
+
69
+ Dispatch-level failures (any `--json` command): `E_UNKNOWN_COMMAND` (no
70
+ kernel subcommand or capability namespace matches, or unknown capability
71
+ subcommand), `E_CAPABILITY_INACTIVE`, `E_CAPABILITY_BLOCKED` (untrusted),
72
+ `E_CAPABILITY_BROKEN`, `E_DUPLICATE_NAMESPACE`, `E_CONFIG_BROKEN` — all still
73
+ exactly one stdout envelope with a nonzero exit.
74
+
75
+ ### `oats okf harvest --json`
76
+
77
+ Run with cwd fixed to the resolved instance home. `result` is one of:
78
+
79
+ ```json
80
+ {"harvest":"spawned","instance":"memory-harvest-<slug>","window":"memory-harvest-<slug>"}
81
+ {"harvest":"skipped","reason":"no pending notes"}
82
+ ```
83
+
84
+ Failure: `{"schemaVersion":1,"ok":false,"error":{"code":"E_HARVEST_FAILED","message":"..."}}`
85
+ with exit 1. Skip reasons are human-readable strings (loop guard, no notes,
86
+ no root, no identity, harvester already running, workspace-mode soul not in a
87
+ git repo).
88
+
89
+ Contract tests / canonical fixtures: `test/cli-json-contract.test.mjs`.
@@ -0,0 +1,51 @@
1
+ # BREAKING: desktop succession — `oats.web`, `oats pane`, and the control-pane library are retired
2
+
3
+ **The next release of `@awebai/oats` containing this change is a
4
+ BREAKING release.** Three previously shipped surfaces were removed in favor of
5
+ the OATS Desktop app (`packages/desktop/` in the framework repo):
6
+
7
+ | Removed surface | Replacement |
8
+ |---|---|
9
+ | `oats.web` marketplace capability (`oats web start`, browser panel) | OATS Desktop app — the same zero-dependency loopback server is bundled at `packages/desktop/server/` and spawned by the app |
10
+ | `oats pane` CLI command and the Control Pane TUI | OATS Desktop app (Active overview / instance roster) |
11
+ | `@awebai/oats/control-pane` package export (`lib/control-pane/model.mjs`) | The roster model moved into `packages/desktop/server/model.mjs`; it is no longer a public kernel export |
12
+
13
+ ## Migrating a deployment that used `oats.web`
14
+
15
+ 1. Remove the `oats.web` entry from `capabilities.additive` in every
16
+ `oats-config.yaml` in your config chain.
17
+ 2. Remove the `oats.web` entry from `oats-lock.json` at the same scope(s), and
18
+ delete any stale installed copy under `.agents/capabilities/installed/`.
19
+ 3. Use the OATS Desktop app instead: `cd packages/desktop && npm install &&
20
+ npm run rebuild && npm start` (see `packages/desktop/README.md`).
21
+
22
+ The CLI diagnoses stale references instead of failing opaquely:
23
+
24
+ - `oats doctor` warns when an `oats-lock.json` still pins `oats.web`, with the
25
+ fix spelled out.
26
+ - Bare `oats install` reports the lock entry as `RETIRED` (with guidance)
27
+ rather than a restore failure.
28
+ - `oats install oats.web` and a config activation of `oats.web` fail with a
29
+ message naming the successor and the exact cleanup steps.
30
+
31
+ ## Migrating `oats pane` usage
32
+
33
+ `oats pane` now exits with a pointer to the desktop app. Scripts or docs
34
+ invoking it should launch OATS Desktop instead. The `--theme` themes (dark,
35
+ solarized) exist in the app's theme system.
36
+
37
+ ## Consumers of the `./control-pane` export
38
+
39
+ `import ... from "@awebai/oats/control-pane"` no longer resolves. The
40
+ model's pure helpers (`readMarkdownSection`, `parseTmuxWindows`,
41
+ `parseGitStatus`, `parseGitDiffStat`, `buildConstellation`, `relativeAge`)
42
+ live in `packages/desktop/server/model.mjs`, which is private to the desktop
43
+ app. If you depended on this export, vendor the helpers or open an issue —
44
+ no known external consumer existed at removal time.
45
+
46
+ ## Release gating (maintainers)
47
+
48
+ Downstream installers/packaging for the desktop app must exist **before** the
49
+ next release ships; this migration note travels with the release notes and
50
+ the release must be flagged **BREAKING** (major or clearly-marked minor per
51
+ the project's pre-1.0 conventions).