@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.
- package/LICENSE +21 -0
- package/README.md +380 -0
- package/bin/oats.mjs +3294 -0
- package/capabilities/oats-authoring/oats.json +12 -0
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +297 -0
- package/capabilities/oats-aweb/injects/aweb.md +55 -0
- package/capabilities/oats-aweb/oats.json +47 -0
- package/capabilities/oats-aweb/package.json +20 -0
- package/capabilities/oats-jira/bin/oats-jira.mjs +40 -0
- package/capabilities/oats-jira/injects/jira.md +10 -0
- package/capabilities/oats-jira/oats.json +22 -0
- package/capabilities/oats-jira/package.json +25 -0
- package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +176 -0
- package/capabilities/oats-linear/README.md +234 -0
- package/capabilities/oats-linear/bin/oats-linear-hook.mjs +32 -0
- package/capabilities/oats-linear/bin/oats-linear.mjs +344 -0
- package/capabilities/oats-linear/injects/linear.md +8 -0
- package/capabilities/oats-linear/oats.json +24 -0
- package/capabilities/oats-linear/package.json +29 -0
- package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +217 -0
- package/capabilities/oats-linear/test/oats-linear.test.mjs +168 -0
- package/capabilities/oats-okf/agents/memory-harvest.md +25 -0
- package/capabilities/oats-okf/bin/oats-okf.mjs +283 -0
- package/capabilities/oats-okf/injects/okf.md +62 -0
- package/capabilities/oats-okf/oats.json +20 -0
- package/capabilities/oats-okf/package.json +22 -0
- package/capabilities/oats-okf/skills/memory-harvest/SKILL.md +100 -0
- package/capabilities/oats-okf/skills/okf/SKILL.md +107 -0
- package/capabilities/oats-okf/skills/okf/scripts/okf-validate.mjs +123 -0
- package/capabilities/oats-review/agents/reviewer/AGENTS.md +53 -0
- package/capabilities/oats-review/agents/reviewer/soul.yaml +6 -0
- package/capabilities/oats-review/injects/review.md +68 -0
- package/capabilities/oats-review/oats.json +10 -0
- package/capabilities/oats-review/skills/code-review/SKILL.md +44 -0
- package/capabilities/oats-review/skills/security-review/SKILL.md +59 -0
- package/docs/capabilities.md +505 -0
- package/docs/capability-manifest.schema.json +223 -0
- package/docs/configuration.md +482 -0
- package/docs/conventions.md +73 -0
- package/docs/design/desktop-ux-plan.md +362 -0
- package/docs/design/package-engine-contract.md +813 -0
- package/docs/design/package-runtime-api.md +414 -0
- package/docs/desktop-cli-api.md +89 -0
- package/docs/desktop-succession.md +51 -0
- package/docs/desktop.md +187 -0
- package/docs/implementation.md +282 -0
- package/docs/integrations.md +123 -0
- package/docs/knowledge-theory.md +97 -0
- package/docs/knowledge.md +139 -0
- package/docs/layers.md +110 -0
- package/docs/migration-from-oas.md +122 -0
- package/docs/oats-config.schema.json +140 -0
- package/docs/oats-lock.schema.json +175 -0
- package/docs/oats-package.schema.json +129 -0
- package/docs/packages.md +442 -0
- package/docs/release-lane.md +132 -0
- package/docs/release-notes/v0.18.2.md +79 -0
- package/docs/release-notes/v0.18.3.md +63 -0
- package/docs/release-notes/v0.18.4.md +65 -0
- package/docs/release-notes/v0.18.5.md +66 -0
- package/docs/release-notes/v0.18.6.md +87 -0
- package/docs/release-notes/v0.19.0.md +186 -0
- package/docs/release-notes/v0.19.1.md +24 -0
- package/docs/release-notes/v0.19.2.md +32 -0
- package/docs/release-notes/v0.19.3.md +16 -0
- package/docs/release-notes/v0.19.4.md +12 -0
- package/docs/release-notes/v0.20.0.md +89 -0
- package/docs/release-notes/v0.22.0.md +180 -0
- package/docs/souls-and-instances.md +344 -0
- package/injects/framework-workspace.md +7 -0
- package/injects/instance-boundary.md +43 -0
- package/injects/local-soul.md +19 -0
- package/injects/oats.md +11 -0
- package/injects/work-attached.md +16 -0
- package/injects/work-checkout.md +12 -0
- package/injects/work-workspace.md +22 -0
- package/injects/work-worktree.md +13 -0
- package/lib/core.mjs +6792 -0
- package/lib/packages.mjs +1375 -0
- package/lib/tmux-config.mjs +35 -0
- package/package-catalog.json +44 -0
- package/package.json +55 -0
- package/packages/record/README.md +151 -0
- package/packages/record/bin/capture.mjs +256 -0
- package/packages/record/bin/recall.mjs +102 -0
- package/packages/record/bin/setup.mjs +281 -0
- package/packages/record/bin/turn-record.mjs +47 -0
- package/packages/record/docs/turn-record-sot.md +350 -0
- package/packages/record/lib/canonical.mjs +151 -0
- package/packages/record/lib/capture-aw.mjs +179 -0
- package/packages/record/lib/capture-cc.mjs +322 -0
- package/packages/record/lib/formats.mjs +296 -0
- package/packages/record/lib/ignore.mjs +155 -0
- package/packages/record/lib/index-db.mjs +524 -0
- package/packages/record/lib/project-aweb.mjs +251 -0
- package/packages/record/lib/segments.mjs +186 -0
- package/packages/record/lib/store.mjs +447 -0
- package/packages/record/lib/tags.mjs +114 -0
- package/packages/record/package.json +45 -0
- package/packages/record/test/vectors/README.md +34 -0
- package/packages/record/test/vectors/aweb-projection-v1.json +231 -0
- package/packages/record/test/vectors/journal-merge-v1.json +261 -0
- package/packages/record/test/vectors/turn-id-v1.json +152 -0
- package/packages/record/test/vectors/validate.mjs +391 -0
- package/skills/integration-authoring/SKILL.md +76 -0
- package/skills/oats/SKILL.md +151 -0
- package/skills/oats-config/SKILL.md +162 -0
- package/skills/oats-getting-started/SKILL.md +159 -0
- package/skills/oats-packages/SKILL.md +180 -0
- package/skills/oats-support/SKILL.md +79 -0
- package/skills/skill-craft/SKILL.md +109 -0
- 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).
|