@awebai/oats 0.25.8 → 0.26.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/README.md +8 -6
- package/bin/oats.mjs +576 -1714
- package/capabilities/oats-authoring/oats-package.json +2 -2
- package/capabilities/oats-authoring/oats.json +2 -2
- package/capabilities/oats-authoring/skills/integration-authoring/SKILL.md +46 -25
- package/capabilities/oats-authoring/skills/soul-craft/SKILL.md +13 -6
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +334 -72
- package/capabilities/oats-aweb/injects/aweb.md +7 -2
- package/capabilities/oats-aweb/lib/binding-wire.mjs +107 -5
- package/capabilities/oats-aweb/lib/captured-native.mjs +1 -1
- package/capabilities/oats-aweb/lib/grant-custody.mjs +38 -0
- package/capabilities/oats-aweb/oats.json +14 -4
- package/capabilities/oats-jira/bin/oats-jira.mjs +4 -4
- package/capabilities/oats-jira/oats.json +2 -2
- package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +6 -3
- package/capabilities/oats-linear/bin/oats-linear-hook.mjs +6 -4
- package/capabilities/oats-linear/oats.json +2 -2
- package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +6 -0
- package/capabilities/oats-okf/bin/oats-okf.mjs +1 -1
- package/capabilities/oats-okf/lib/binding-wire.mjs +1 -1
- package/capabilities/oats-okf/lib/migration.mjs +2 -2
- package/capabilities/oats-okf/lib/sources.mjs +5 -4
- package/capabilities/oats-okf/lib/stores.mjs +40 -9
- package/capabilities/oats-okf/lib/worker.mjs +3 -3
- package/capabilities/oats-okf/oats.json +1 -1
- package/capabilities/oats-review/oats.json +3 -2
- package/docs/capabilities.md +218 -47
- package/docs/capability-manifest.schema.json +13 -4
- package/docs/configuration.md +17 -5
- package/docs/conventions.md +16 -26
- package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +1 -1
- package/docs/design/2026-09-13-knowledge-and-memory-direction.md +3 -3
- package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +2 -2
- package/docs/design/2026-09-15-portable-souls-handoff.md +2 -2
- package/docs/design/2026-09-15-portable-souls-implementation.md +1 -1
- package/docs/design/2026-09-20-redesign-program-board.md +2 -2
- package/docs/design/2026-09-23-workspace-module-contracts.md +1 -1
- package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +1 -1
- package/docs/design/2026-09-24-desktop-phase-f-boundary.md +54 -10
- package/docs/design/2026-09-24-phase-d-plan.md +77 -0
- package/docs/design/2026-09-25-teams-contract.md +226 -0
- package/docs/design/README.md +3 -3
- package/docs/design/launch-configurations.md +20 -16
- package/docs/design/operations-contract.md +27 -10
- package/docs/desktop-cli-api.md +546 -264
- package/docs/desktop-instance-start.md +1 -1
- package/docs/desktop.md +7 -13
- package/docs/execution-targets.md +16 -18
- package/docs/first-team.md +14 -17
- package/docs/implementation.md +28 -59
- package/docs/integrations.md +88 -32
- package/docs/knowledge-capability-authoring.md +1 -1
- package/docs/knowledge-reference/package-craft.md +10 -8
- package/docs/knowledge-theory.md +1 -1
- package/docs/knowledge.md +10 -11
- package/docs/layers.md +16 -17
- package/docs/oats-local.schema.json +29 -1
- package/docs/oats-membership.schema.json +5 -3
- package/docs/oats-package.schema.json +2 -2
- package/docs/oats-workspace.schema.json +1 -1
- package/docs/{official-marketplace.md → official-catalog.md} +15 -16
- package/docs/packages.md +75 -52
- package/docs/release-notes/v0.22.0.md +1 -1
- package/docs/release-notes/v0.23.1.md +1 -1
- package/docs/release-notes/v0.25.9.md +23 -0
- package/docs/release-notes/v0.26.0.md +670 -0
- package/docs/schedules.md +48 -126
- package/docs/soul.schema.json +11 -4
- package/docs/souls-and-instances.md +56 -43
- package/docs/workspaces.md +80 -58
- package/injects/instance-boundary.md +1 -1
- package/injects/work-attached.md +1 -1
- package/injects/work-workspace.md +2 -2
- package/lib/{portable-files.mjs → bounded-read.mjs} +6 -6
- package/lib/{portable-values.mjs → canonical-json.mjs} +3 -12
- package/lib/capability-contract.mjs +110 -0
- package/lib/config-data.mjs +2 -2
- package/lib/core.mjs +700 -4824
- package/lib/digest.mjs +12 -0
- package/lib/instance-inspect.mjs +396 -0
- package/lib/instance-lifecycle.mjs +3 -4
- package/lib/instance-resolution.mjs +212 -26
- package/lib/instruction-composition.mjs +0 -20
- package/lib/materialize.mjs +6 -4
- package/lib/operator-dispatch.mjs +33 -13
- package/lib/packages.mjs +25 -190
- package/lib/provider-binding.mjs +4 -2
- package/lib/provider-reasons.mjs +3 -68
- package/lib/resolve.mjs +204 -68
- package/lib/schedule.mjs +97 -272
- package/lib/servers.mjs +13 -13
- package/lib/{portable-shape.mjs → shape.mjs} +4 -3
- package/lib/tree-copy.mjs +44 -0
- package/lib/workspace.mjs +125 -20
- package/package-catalog.json +7 -7
- package/package.json +1 -1
- package/skills/integration-authoring/SKILL.md +48 -40
- package/skills/oats-getting-started/SKILL.md +105 -110
- package/skills/oats-support/SKILL.md +2 -2
- package/skills/soul-craft/SKILL.md +13 -6
- package/bin/oats-pi-sdk-host.mjs +0 -17
- package/docs/2026-09-03-architecture-proposal.md +0 -642
- package/docs/artifact-approvals.schema.json +0 -7
- package/docs/captured-invocation-context.schema.json +0 -7
- package/docs/captured-resolution.schema.json +0 -7
- package/docs/design/package-engine-contract.md +0 -813
- package/docs/design/package-runtime-api.md +0 -588
- package/docs/desktop-succession.md +0 -57
- package/docs/execution-capsule.schema.json +0 -108
- package/docs/first-team-demo.md +0 -92
- package/docs/knowledge-migration.md +0 -147
- package/docs/migration-from-oas.md +0 -103
- package/docs/oats-config.schema.json +0 -172
- package/docs/oats-lock-v3.schema.json +0 -7
- package/docs/oats-lock.schema.json +0 -175
- package/docs/operating-team-migration.md +0 -470
- package/docs/portable.schema.json +0 -2512
- package/docs/provider-check-input.schema.json +0 -7
- package/docs/rebuild-to-v2.md +0 -511
- package/docs/workspace-adoption.md +0 -74
- package/injects/framework-workspace.md +0 -7
- package/injects/local-soul.md +0 -19
- package/injects/oats-portable.md +0 -20
- package/injects/oats.md +0 -11
- package/injects/portable-instance-boundary.md +0 -39
- package/injects/portable-work-directory.md +0 -29
- package/lib/artifact-approvals.mjs +0 -120
- package/lib/artifact-tree.mjs +0 -141
- package/lib/capability-artifacts.mjs +0 -179
- package/lib/capability-execution.mjs +0 -15
- package/lib/capability-inputs.mjs +0 -39
- package/lib/capability-provenance.mjs +0 -231
- package/lib/captured-action-shape.mjs +0 -21
- package/lib/captured-admission-shape.mjs +0 -20
- package/lib/captured-binding-file.mjs +0 -36
- package/lib/captured-dispatch.mjs +0 -66
- package/lib/captured-instance-index.mjs +0 -277
- package/lib/captured-invocation-context.mjs +0 -130
- package/lib/captured-launch-request.mjs +0 -66
- package/lib/captured-operation-process.mjs +0 -15
- package/lib/captured-pi-custody.mjs +0 -29
- package/lib/captured-pi-host.mjs +0 -167
- package/lib/captured-pi-outcome.mjs +0 -172
- package/lib/captured-resolutions.mjs +0 -275
- package/lib/captured-scaffold.mjs +0 -87
- package/lib/captured-selector.mjs +0 -28
- package/lib/captured-session-backend.mjs +0 -52
- package/lib/captured-source-receipt-file.mjs +0 -72
- package/lib/helper-injection-policy.mjs +0 -104
- package/lib/legacy-lock-codec.mjs +0 -106
- package/lib/manifest-settings.mjs +0 -84
- package/lib/package-closure.mjs +0 -48
- package/lib/package-materialization.mjs +0 -83
- package/lib/pi-sdk-host.mjs +0 -229
- package/lib/portable-artifacts.mjs +0 -115
- package/lib/portable-choices.mjs +0 -82
- package/lib/portable-composition.mjs +0 -136
- package/lib/portable-digest.mjs +0 -105
- package/lib/portable-identity.mjs +0 -40
- package/lib/portable-lock.mjs +0 -117
- package/lib/portable-onboarding-request.mjs +0 -49
- package/lib/portable-onboarding.mjs +0 -256
- package/lib/portable-package-preparation.mjs +0 -188
- package/lib/portable-policy.mjs +0 -44
- package/lib/portable-soul.mjs +0 -42
- package/lib/portable-state.mjs +0 -80
- package/lib/prepare-composition.mjs +0 -170
- package/lib/prepared-bindings.mjs +0 -92
- package/lib/prepared-resources.mjs +0 -127
- package/lib/provider-binding-broker.mjs +0 -65
- package/lib/provider-binding-wire.mjs +0 -116
- package/lib/readiness.mjs +0 -225
- package/lib/repository-observation.mjs +0 -226
- package/lib/resolution-shape.mjs +0 -393
- package/lib/schedule-capsule.mjs +0 -206
- package/lib/soul-constraints.mjs +0 -40
- package/lib/source-projection.mjs +0 -84
- package/lib/source-spec.mjs +0 -189
- package/lib/workspace-definition.mjs +0 -126
- package/lib/workspace-discovery.mjs +0 -146
- package/skills/oats/SKILL.md +0 -162
- package/skills/oats-config/SKILL.md +0 -164
- package/skills/oats-packages/SKILL.md +0 -184
- package/skills/oats-portable/SKILL.md +0 -115
- package/skills/oats-portable-artifacts/SKILL.md +0 -63
package/docs/workspaces.md
CHANGED
|
@@ -8,7 +8,6 @@ contracts the kernel is built against are in
|
|
|
8
8
|
[design/2026-09-23-workspace-module-contracts.md](design/2026-09-23-workspace-module-contracts.md);
|
|
9
9
|
a full worked example (an imaginary company with three teams) is in
|
|
10
10
|
[design/2026-09-23-simplified-workspace-model.md](design/2026-09-23-simplified-workspace-model.md).
|
|
11
|
-
Moving an existing 0.24.x deployment: [rebuild-to-v2.md](rebuild-to-v2.md).
|
|
12
11
|
|
|
13
12
|
## The rule
|
|
14
13
|
|
|
@@ -19,7 +18,7 @@ versioned.**
|
|
|
19
18
|
| Source kind | `from:` | Versioned | Trust |
|
|
20
19
|
|---|---|---|---|
|
|
21
20
|
| Member repo | `<repo key>` or `here` | no — always the member's **latest** default-branch state | membership (the reciprocal handshake) |
|
|
22
|
-
| Package | `package` | yes — the version pinned in the workspace's `packages:`; `oats-lock.json` records the exact commit + integrity |
|
|
21
|
+
| Package | `package` | yes — the version pinned in the workspace's `packages:`; `oats-lock.json` records the exact commit + integrity | the declaration in `packages:` (no separate approval) |
|
|
23
22
|
|
|
24
23
|
A soul names each capability **with where it comes from — a location, never a
|
|
25
24
|
version**. The workspace's `packages:` says which version; materialization
|
|
@@ -39,8 +38,8 @@ machine (`oats-local.yaml`); the lock (`oats-lock.json`) sits beside
|
|
|
39
38
|
`oats-local.yaml` and is identical on every machine that synced the same
|
|
40
39
|
workspace commit. Schemas: [`oats-workspace.schema.json`](oats-workspace.schema.json),
|
|
41
40
|
[`oats-membership.schema.json`](oats-membership.schema.json),
|
|
42
|
-
[`soul.schema.json`](soul.schema.json), [`oats-local.schema.json`](oats-local.schema.json)
|
|
43
|
-
[
|
|
41
|
+
[`soul.schema.json`](soul.schema.json), [`oats-local.schema.json`](oats-local.schema.json);
|
|
42
|
+
the lock's format is in [packages](packages.md#lock-v3). The JSON schemas encode
|
|
44
43
|
shapes; domain rules (declared teams, duplicate members, canonical `from:` keys,
|
|
45
44
|
the two `packages:` value forms) live in the kernel's `validateWorkspace` /
|
|
46
45
|
`validateSoul`, which are the authority.
|
|
@@ -61,7 +60,7 @@ members: # repo refs, NO @revision (E_WORKSPAC
|
|
|
61
60
|
|
|
62
61
|
packages: # the ONLY versioned things
|
|
63
62
|
oats.framework: v1.1.3 # bare version → resolves through the official catalog
|
|
64
|
-
oats.okf: v2.1.
|
|
63
|
+
oats.okf: v2.1.5
|
|
65
64
|
acme.tools: git:github.com/acme/tools@v0.4.0 # outside the catalog → git:<repo>@<tag|OID>; still a package
|
|
66
65
|
|
|
67
66
|
teams: # labels, declared once so they cannot drift
|
|
@@ -141,8 +140,9 @@ declaration; it travels with the soul into the per-commit soul cache. The
|
|
|
141
140
|
carry only the binding's settings keys (`bindings-file`, `state-dir`,
|
|
142
141
|
`harvest-runtime`, `harvest-model`); `owns`/`reads`/`root` there are refused by
|
|
143
142
|
the provider, not read (a soul payload grammar is an OKF follow-up). Every
|
|
144
|
-
`souls/*/soul.yaml` in a member is
|
|
145
|
-
|
|
143
|
+
`souls/*/soul.yaml` in a member is listed and spawnable — souls have no
|
|
144
|
+
private mode (`private:` in a soul.yaml is ignored since 0.26.0, with a
|
|
145
|
+
`soul-private-ignored` warning). A soul's
|
|
146
146
|
`name` must equal its directory name; the first of two souls declaring one
|
|
147
147
|
name (by path) is listed, the second is a problem.
|
|
148
148
|
|
|
@@ -151,7 +151,8 @@ name (by path) is listed, the second is a problem.
|
|
|
151
151
|
The capability manifest is the one file that did not change (see
|
|
152
152
|
[capabilities.md](capabilities.md)). Discovery relies on `capability` (the same
|
|
153
153
|
`^[a-z0-9][a-z0-9._-]*$` grammar every `capabilities:` key uses), `version`,
|
|
154
|
-
`layer`, and may read `private: true`
|
|
154
|
+
`layer`, and may read `private: true` (a **repo-owned** capability) and
|
|
155
|
+
`team: <label>`. `version` is
|
|
155
156
|
informational for member capabilities — a materialized copy is identified by
|
|
156
157
|
its content digest.
|
|
157
158
|
|
|
@@ -188,7 +189,14 @@ with the right name, is not admission.
|
|
|
188
189
|
model as a repo's committed `.agents/skills/`: whoever can push to the repo
|
|
189
190
|
decides what runs, and the branch's latest state is what runs. No per-operator
|
|
190
191
|
trust lists, no per-capability approval for members. Packages come from
|
|
191
|
-
*outside* that boundary and
|
|
192
|
+
*outside* that boundary, and **declaring one in the workspace's `packages:` is
|
|
193
|
+
the trust decision** (human decision, 2026-09-24): people install a package only
|
|
194
|
+
when they trust it, so there is no second, per-version approval step. The lock
|
|
195
|
+
is reproducibility, not approval — it pins the exact commit and content
|
|
196
|
+
integrity, and `oats sync` refuses drift (a moved tag, changed content, an
|
|
197
|
+
edited capability list). A spawn admits only a locked package the workspace
|
|
198
|
+
**still declares**: one removed from `packages:` but left in a stale lock is
|
|
199
|
+
`E_PACKAGE_MISSING { reason: "undeclared" }` until `oats sync` drops it.
|
|
192
200
|
|
|
193
201
|
**The handshake is observed with the operator's own Git read access, in one
|
|
194
202
|
access context.** The kernel reads both halves over the remotes
|
|
@@ -210,10 +218,11 @@ its capabilities unresolvable (`E_NOT_A_MEMBER` / `E_MEMBERSHIP_UNCONFIRMED`).
|
|
|
210
218
|
Reading the workspace repo *is* being in the workspace — a workspace's access
|
|
211
219
|
control is Git's.
|
|
212
220
|
|
|
213
|
-
**
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
221
|
+
**Repo-owned capabilities.** `private: true` in a capability's `oats.json`
|
|
222
|
+
makes it **repo-owned**: it is listed like any other capability (with
|
|
223
|
+
`private: true`; the human table marks it "(repo-owned)"), and it is usable
|
|
224
|
+
only by souls of the same repo (`E_CAPABILITY_PRIVATE` otherwise). Souls have
|
|
225
|
+
no private mode: every soul of a confirmed member is listed and spawnable.
|
|
217
226
|
|
|
218
227
|
**External souls.** `external:` adopts a soul by reference from a repo that is
|
|
219
228
|
**not** a member, pinned to a full commit. No handshake is asked for and none is
|
|
@@ -226,7 +235,7 @@ its slots. An `external[].team` overrides the soul's own `team`.
|
|
|
226
235
|
A repository may be a **member** (it completed the handshake; its `souls/*` and
|
|
227
236
|
`capabilities/*` are member-tier: latest state, trusted by membership) **and** a
|
|
228
237
|
**package publisher** (its `oats-package/` is consumed only through
|
|
229
|
-
`packages:`: versioned
|
|
238
|
+
`packages:`: versioned and locked). The two never collapse:
|
|
230
239
|
|
|
231
240
|
- `from: <repo key>` looks **only** under `<repo>/capabilities/<name>/oats.json`
|
|
232
241
|
at the member's latest state. It never looks inside `oats-package/`. A name
|
|
@@ -244,31 +253,32 @@ So the framework's own souls say `oats.okf: { from: package }` even though
|
|
|
244
253
|
member soul that is the expert in that capability (`okf-expert`, `aweb-expert`,
|
|
245
254
|
…), discoverable at latest state like any member soul.
|
|
246
255
|
|
|
247
|
-
## Packages, lock,
|
|
256
|
+
## Packages, lock, catalog
|
|
248
257
|
|
|
249
258
|
`packages:` values have exactly two forms:
|
|
250
259
|
|
|
251
260
|
- a **bare version** (`v2.1.3`, `2.1.3`, `v1.0.0-rc.1`) — resolved through the
|
|
252
261
|
official catalog (`package-catalog.json` in the `oats` repo; the reviewed
|
|
253
|
-
|
|
262
|
+
list, see [official-catalog.md](official-catalog.md)). This is
|
|
254
263
|
the only way a package becomes *pinnable by id*.
|
|
255
264
|
- **`git:<repo>@<ref>`** — a direct package ref: `<repo>` is any ref the kernel
|
|
256
265
|
understands (`github.com/org/repo`, `https://…`, `git@host:…`, `/abs/bare.git`,
|
|
257
266
|
`file:///…`), `<ref>` a tag name or a full commit OID. The package is read at
|
|
258
267
|
`oats-package/` inside that repo.
|
|
259
268
|
|
|
260
|
-
Both are packages: versioned
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
269
|
+
Both are packages: versioned and locked. A ref that resolves to a **branch** is
|
|
270
|
+
refused (`E_PACKAGE_INTEGRITY { why: "branch" }`) — versions are immutable. A
|
|
271
|
+
tag that moved (same version string, different commit), or content that no
|
|
272
|
+
longer matches the locked integrity, fails with `E_PACKAGE_INTEGRITY` on the
|
|
273
|
+
next `oats sync`.
|
|
274
|
+
|
|
275
|
+
**There is no package approval** (human decision, 2026-09-24). Declaring a
|
|
276
|
+
package in `packages:` is the trust decision; `oats sync` asks nothing and
|
|
277
|
+
`--approve` is `E_BAD_ARGS`. `oats sync` confirms membership, resolves every
|
|
278
|
+
`packages:` entry to a commit + content digest, writes `oats-lock.json`
|
|
279
|
+
(lockfileVersion 3), creates `agents/` if absent, reports what changed and
|
|
280
|
+
exits `0`. A lock written by an earlier kernel may still carry an `approved`
|
|
281
|
+
record per entry: it is ignored, and the next write drops it. `oats package add <id> <version|git:…@…>` / `oats package remove <id>` edit
|
|
272
282
|
`packages:` in the workspace file when it is tracked by the current checkout,
|
|
273
283
|
else print the line to add — the workspace file is shared through Git. Details:
|
|
274
284
|
[packages.md](packages.md).
|
|
@@ -282,8 +292,8 @@ the workspace's slot default):
|
|
|
282
292
|
|
|
283
293
|
```
|
|
284
294
|
from: package → some locked package provides `name` else E_PACKAGE_MISSING (run `oats sync`)
|
|
285
|
-
→
|
|
286
|
-
→
|
|
295
|
+
→ read its manifests at the locked commit; the lock's capability list must match else E_PACKAGE_INTEGRITY
|
|
296
|
+
→ copy; record package/version/commit/digest
|
|
287
297
|
from: <repo> → <repo> is a CONFIRMED member else E_NOT_A_MEMBER / E_MEMBERSHIP_UNCONFIRMED
|
|
288
298
|
(or `here`) → it has capabilities/<name>/oats.json else E_CAPABILITY_MISSING
|
|
289
299
|
→ not private, unless <repo> is the soul's own else E_CAPABILITY_PRIVATE
|
|
@@ -308,13 +318,12 @@ Nothing is symlinked, nothing is shared between instances.
|
|
|
308
318
|
```
|
|
309
319
|
<agents-root>/<soul>/instances/<instance>/
|
|
310
320
|
├── AGENTS.md # composed: soul AGENTS.md + kernel/work-mode blocks + each module's inject
|
|
311
|
-
│ # (
|
|
321
|
+
│ # (the "You run on OATS" block is oats.core's inject; the kernel ships no copy)
|
|
312
322
|
├── CLAUDE.md → AGENTS.md
|
|
313
323
|
├── .agents/skills/<capability>/<skill>/SKILL.md # full copies; where pi/codex look
|
|
314
324
|
├── .claude/skills → ../.agents/skills
|
|
315
325
|
├── .oats/modules/<capability>/ # the full capability copy: oats.json, bin/, injects/, skills/
|
|
316
326
|
├── instance.json # modules{}, providers{}, workspace{} recorded here
|
|
317
|
-
├── soul → <agents-root>/<soul>/soul # read-only reference
|
|
318
327
|
├── TASK.md
|
|
319
328
|
└── work/
|
|
320
329
|
```
|
|
@@ -347,10 +356,16 @@ not exclude anything.
|
|
|
347
356
|
## Teams
|
|
348
357
|
|
|
349
358
|
`teams:` declares labels once (`global`, `engineering`, …) so they cannot drift
|
|
350
|
-
into typos. A soul
|
|
351
|
-
|
|
352
|
-
`
|
|
353
|
-
|
|
359
|
+
into typos. A soul carries `team:` — one label or a list (`team: [engineering,
|
|
360
|
+
reviewers]`, the first the primary) — else its repo's default from
|
|
361
|
+
`oats-membership.yaml` (same shape), else `unassigned`; a capability carries one
|
|
362
|
+
label. A label not declared in `teams:` is `E_TEAM_UNKNOWN` (the item is still
|
|
363
|
+
listed); a declared label without a `messaging.byTeam` entry is the
|
|
364
|
+
`unmapped-team-label` warning. `defaults.byTeam.<team>.capabilities` adds
|
|
365
|
+
capabilities additively for souls with that label, for each label in order
|
|
366
|
+
(`off` removes; two labels that disagree are `E_TEAM_CONFLICT`). Each label is
|
|
367
|
+
an *eligible* messaging team the provider may join on request — see
|
|
368
|
+
[capabilities.md](capabilities.md#several-team-labels). **A
|
|
354
369
|
label never gates, restricts, changes trust or partitions the knowledge
|
|
355
370
|
store** — it organises and can supply defaults. The messaging provider's payload
|
|
356
371
|
(private teams, channels) lives under `messaging:`, so "team" means one thing.
|
|
@@ -364,9 +379,13 @@ store** — it organises and can supply defaults. The messaging provider's paylo
|
|
|
364
379
|
| A fact about **this spawn** | `oats spawn … --provider <cap> key=value` (repeatable; dotted keys nest) → `instance.json.providers.<cap>` | `--provider oats.aweb identity.source=/abs/path/to/retained/.aw` |
|
|
365
380
|
|
|
366
381
|
The merged payload is `workspace.messaging` (messaging slot only; its base
|
|
367
|
-
keys
|
|
368
|
-
|
|
369
|
-
|
|
382
|
+
keys, with `byTeam` stripped) ⊕ soul slot payload ⊕ `local.settings[cap]` ⊕
|
|
383
|
+
`spawn.providers[cap]` — objects deep-merge, later wins on scalars and arrays.
|
|
384
|
+
**No `byTeam[<label>]` is merged into it, the primary's included** (teams
|
|
385
|
+
amendment K): each label's `base ⊕ byTeam[label]` reaches the provider only as
|
|
386
|
+
that label's entry in `OATS_TEAMS` (the preview's `teams`). So `settings.team`
|
|
387
|
+
(and `OATS_TEAM_ID`) is the personal team if the host, the soul or the spawn
|
|
388
|
+
set one; empty means the provider's own default. The provider's own `binding` contract
|
|
370
389
|
(`normalize → bind → check`) runs over the merged payload exactly as before.
|
|
371
390
|
Two teams, two messaging identities, one workspace:
|
|
372
391
|
|
|
@@ -378,18 +397,21 @@ messaging:
|
|
|
378
397
|
cloud: { team: aweb:example.cloud }
|
|
379
398
|
```
|
|
380
399
|
|
|
381
|
-
A soul with `team: cloud` hands its messaging provider
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
the
|
|
391
|
-
|
|
392
|
-
|
|
400
|
+
A soul with `team: cloud` hands its messaging provider the eligible team
|
|
401
|
+
`{ label: cloud, team: aweb:example.cloud, mapped: true, payload: { team: aweb:example.cloud, … } }`
|
|
402
|
+
in `OATS_TEAMS`; its settings carry no `team` unless the host, soul or spawn set
|
|
403
|
+
one. A label under `byTeam` that is not declared in `teams:` is
|
|
404
|
+
`E_WORKSPACE_SCHEMA`. **Joining an eligible team is the provider's explicit
|
|
405
|
+
act.** `spawn --preview` shows the merged `settings.<cap>` and the `teams`, so
|
|
406
|
+
the delivery is verifiable, and `instance.json` records both. With oats.aweb
|
|
407
|
+
1.13.1 (which reads `team` from its settings and ignores `OATS_TEAMS`) the
|
|
408
|
+
primary identity therefore mints into the personal team: the `.aw` root's
|
|
409
|
+
active team, or the one the host set. What the payload does not change is
|
|
410
|
+
**where the `.aw` root is found**: the hook still searches
|
|
411
|
+
bounded candidates, first hit wins — the instance home, the Git repository
|
|
412
|
+
containing it, the soul's work repository and the Git repository containing
|
|
413
|
+
it, then the deployment directory (`OATS_WORKSPACE`); never the user home or
|
|
414
|
+
above the deployment — and that root must hold a membership of the named team (the deployment's `.aw`
|
|
393
415
|
joined to every team its labels name is the simple layout). On oats.aweb
|
|
394
416
|
1.11.2 `team` was ignored (the root's active team won), so `byTeam` there is
|
|
395
417
|
a recorded intent only.
|
|
@@ -428,7 +450,7 @@ The deployment directory is **yours to choose** (decision 9) — an existing fol
|
|
|
428
450
|
```
|
|
429
451
|
~/acme/ ← the directory you chose
|
|
430
452
|
├── oats-local.yaml ← which workspace this machine realizes + host paths + disabled souls
|
|
431
|
-
├── oats-lock.json ← exact commit + integrity
|
|
453
|
+
├── oats-lock.json ← exact commit + integrity per package
|
|
432
454
|
├── agents/ ← instance homes (each self-contained) + fetched soul sources
|
|
433
455
|
├── platform/ ← clone of github.com/acme/platform (only if someone works IN it; may live elsewhere — see clones:)
|
|
434
456
|
└── tools/
|
|
@@ -454,8 +476,8 @@ the host repo readable (it holds declarations, no secrets) or grant access.
|
|
|
454
476
|
|
|
455
477
|
Two things keep the standalone spawn useful rather than hollow: `oats.core`
|
|
456
478
|
(the framework's own operational package) is the kernel's default here as
|
|
457
|
-
well, resolved from the official catalog through the operator's own lock
|
|
458
|
-
|
|
479
|
+
well, resolved from the official catalog through the operator's own lock like any
|
|
480
|
+
package (a soul may say `oats.core: off`); and the
|
|
459
481
|
operator's `oats-local.yaml` may name the repo directly (`workspace: <member
|
|
460
482
|
ref>` — the kernel notices it is a member whose workspace it cannot read and
|
|
461
483
|
falls back to the standalone view — or `standalone: <repo ref>` to ask for
|
|
@@ -467,8 +489,8 @@ an explicit `standalone:` header its next steps say so and name that one repo.
|
|
|
467
489
|
member capability's hooks and command scripts run on every operator's machine at
|
|
468
490
|
spawn, gated by nothing but the handshake. In a mixed public/private
|
|
469
491
|
organisation keep **souls only** in public members and let executable
|
|
470
|
-
capabilities come from packages (
|
|
471
|
-
private members.
|
|
492
|
+
capabilities come from packages (declared in `packages:`, pinned by the lock)
|
|
493
|
+
or from private members.
|
|
472
494
|
|
|
473
495
|
**Hosting the workspace file when some members are private.** Everyone who
|
|
474
496
|
can read the workspace file sees the member list. So: a public member never
|
|
@@ -502,12 +524,12 @@ installed-capability tier (`.agents/capabilities/installed/`) and
|
|
|
502
524
|
`E_UNKNOWN_COMMAND` naming its replacement); per-soul `stores.<x>.inherit`;
|
|
503
525
|
ambient-skill exclusion at launch. Lock v1/v2 files are `E_LOCK_SCHEMA`.
|
|
504
526
|
There is no converter and no dual-schema reader: a 0.24.x kernel keeps
|
|
505
|
-
spawning 0.24.x deployments
|
|
527
|
+
spawning 0.24.x deployments.
|
|
506
528
|
|
|
507
529
|
## Related
|
|
508
530
|
|
|
509
531
|
- [Souls and instances](souls-and-instances.md) · [Packages](packages.md) ·
|
|
510
|
-
[Configuration (`oats-local.yaml`)](configuration.md)
|
|
532
|
+
[Configuration (`oats-local.yaml`)](configuration.md)
|
|
511
533
|
- [Capability manifests](capabilities.md) · [Contracts](layers.md) ·
|
|
512
534
|
[Desktop CLI API — workspace model](desktop-cli-api.md#workspace-model-workspaceapi-2)
|
|
513
535
|
- [Design navigation](design/README.md)
|
|
@@ -20,7 +20,7 @@ root, and not the work tree. Anything that says "your home" means this directory
|
|
|
20
20
|
- **Soul work is repository work.** If your TASK is to change soul content that
|
|
21
21
|
lives in this repository, that is ordinary code work — do it on tracked paths
|
|
22
22
|
under `work/`, reviewed like the rest. How your own learnings reach your soul
|
|
23
|
-
is your knowledge
|
|
23
|
+
is your knowledge capability's business, and its instructions below say so if you
|
|
24
24
|
have one.
|
|
25
25
|
|
|
26
26
|
**`<instance-home>/work` is your repository or workspace view** — whatever your
|
package/injects/work-attached.md
CHANGED
|
@@ -8,7 +8,7 @@ their branch and their uncommitted state. You are a guest in their workspace.
|
|
|
8
8
|
- Keep your changes and commits **small and clearly attributable** (your
|
|
9
9
|
instance name in commit messages where ambiguity is possible).
|
|
10
10
|
- Do not touch files the owner is mid-editing unless your task says so; when
|
|
11
|
-
in doubt, coordinate through your messaging
|
|
11
|
+
in doubt, coordinate through your messaging capability or your spawner.
|
|
12
12
|
- Retiring you never removes the shared tree — cleanup of the tree is the
|
|
13
13
|
owner's concern, not yours.
|
|
14
14
|
|
|
@@ -6,8 +6,8 @@ cross-repo coordinator: your product is routing, analysis, and coordination —
|
|
|
6
6
|
not code changes.
|
|
7
7
|
|
|
8
8
|
- **Read freely across all member repos; never edit or commit inside them.**
|
|
9
|
-
Repo changes are routed to that repo's own agents (see `oats status
|
|
10
|
-
your
|
|
9
|
+
Repo changes are routed to that repo's own agents (see `oats status` in the
|
|
10
|
+
deployment, your tasks capability, or messaging) or to the human.
|
|
11
11
|
- No git state operations in any member repo: no branch switching, no
|
|
12
12
|
commits, no worktrees, no resets.
|
|
13
13
|
- Your own working state lives in your instance home, not in any member repo,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Bounded descriptor-backed reads
|
|
1
|
+
/** Bounded descriptor-backed reads of kernel-owned files (manifests, instance records). */
|
|
2
2
|
import { constants, closeSync, fstatSync, lstatSync, openSync, readSync } from "node:fs";
|
|
3
3
|
import { oatsError } from "./errors.mjs";
|
|
4
4
|
|
|
@@ -8,19 +8,19 @@ export function readPortableBytes(path, { missingCode = "resource-not-found", in
|
|
|
8
8
|
catch (error) {
|
|
9
9
|
if (error.code !== "ENOENT") throw error;
|
|
10
10
|
if (allowMissing) return null;
|
|
11
|
-
throw oatsError(missingCode, "
|
|
11
|
+
throw oatsError(missingCode, "metadata file is absent");
|
|
12
12
|
}
|
|
13
|
-
if (!stat.isFile()) throw oatsError(invalidCode, "
|
|
14
|
-
if (stat.size > 8 * 1024 * 1024) throw oatsError("resource-limit", "
|
|
13
|
+
if (!stat.isFile()) throw oatsError(invalidCode, "metadata file must be a regular file");
|
|
14
|
+
if (stat.size > 8 * 1024 * 1024) throw oatsError("resource-limit", "metadata file byte limit exceeded");
|
|
15
15
|
const fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
16
16
|
try {
|
|
17
17
|
const before = fstatSync(fd);
|
|
18
|
-
if (!before.isFile() || before.dev !== stat.dev || before.ino !== stat.ino || before.size !== stat.size) throw oatsError("integrity-drift", "
|
|
18
|
+
if (!before.isFile() || before.dev !== stat.dev || before.ino !== stat.ino || before.size !== stat.size) throw oatsError("integrity-drift", "metadata file changed during opening");
|
|
19
19
|
const buffer = Buffer.alloc(stat.size + 1);
|
|
20
20
|
let count = 0, size;
|
|
21
21
|
while (count < buffer.length && (size = readSync(fd, buffer, count, buffer.length - count, null)) > 0) count += size;
|
|
22
22
|
const after = fstatSync(fd);
|
|
23
|
-
if (count !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs) throw oatsError("integrity-drift", "
|
|
23
|
+
if (count !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs) throw oatsError("integrity-drift", "metadata file changed during reading");
|
|
24
24
|
return buffer.subarray(0, count);
|
|
25
25
|
} finally { closeSync(fd); }
|
|
26
26
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
/** Bounded data codecs
|
|
2
|
-
* No source resolution, filesystem access,
|
|
1
|
+
/** Bounded data codecs: strict JSON decoding and canonical JSON (config documents,
|
|
2
|
+
* instance records, provider answers). No source resolution, filesystem access,
|
|
3
|
+
* getters, or caller serialization hooks. */
|
|
3
4
|
import { oatsError } from "./errors.mjs";
|
|
4
5
|
|
|
5
6
|
const DEFAULTS = Object.freeze({ maxBytes: 8 * 1024 * 1024, maxDepth: 64, maxEntries: 100_000 });
|
|
@@ -39,16 +40,6 @@ export function decodeUtf8(input, where = "input") {
|
|
|
39
40
|
}
|
|
40
41
|
export const compareUtf8 = (a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
|
|
41
42
|
|
|
42
|
-
/** Freeze a bounded JSON observation, never caller accessors or cyclic objects. */
|
|
43
|
-
export function freezeJson(value) {
|
|
44
|
-
canonicalJson(value);
|
|
45
|
-
const seen = new WeakSet();
|
|
46
|
-
const freeze = (item) => {
|
|
47
|
-
if (!item || typeof item !== "object" || seen.has(item)) return;
|
|
48
|
-
seen.add(item); for (const child of Object.values(item)) freeze(child); Object.freeze(item);
|
|
49
|
-
};
|
|
50
|
-
freeze(value); return value;
|
|
51
|
-
}
|
|
52
43
|
|
|
53
44
|
/** Canonical JSON includes exactly one final LF. Emit sorted keys directly:
|
|
54
45
|
* JSON.stringify on a rebuilt object would reorder integer-looking keys. */
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/** The capability manifest's kernel contract, shared by every reader: workspace
|
|
2
|
+
* discovery (member capabilities, E_WORKSPACE_SCHEMA), package manifests
|
|
3
|
+
* (E_PACKAGE_MANIFEST), the resolver (payload values) and the kernel's own
|
|
4
|
+
* manifest loader. One rule set, so a manifest a workspace accepts is one the
|
|
5
|
+
* kernel can run.
|
|
6
|
+
*
|
|
7
|
+
* Covers what the kernel enforces when it RUNS a capability, checked where the
|
|
8
|
+
* manifest is READ instead: the launch environment a capability may declare
|
|
9
|
+
* (names, namespaces), its hooks (approved events, declaration shape, `required`
|
|
10
|
+
* only on spawn, the script inside the capability), and a provider payload's
|
|
11
|
+
* value against the manifest's `settings.<key>.values`. Dependency-free. */
|
|
12
|
+
|
|
13
|
+
export const APPROVED_HOOKS = new Set(["soul-scaffold", "spawn", "retire", "launch"]);
|
|
14
|
+
export const PORTABLE_ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
|
|
15
|
+
export const CAPABILITY_ENV_ID_RE = /^[a-z][a-z0-9]*\.[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
|
|
16
|
+
export const CORE_LAUNCH_ENV = new Set(["OATS_INSTANCE", "OATS_INSTANCE_HOME", "PI_AGENT_INSTANCE", "PI_AGENT_HOME"]);
|
|
17
|
+
export const PROCESS_BOOTSTRAP_ENV = new Set([
|
|
18
|
+
"PATH", "HOME", "SHELL", "TMPDIR", "TMP", "TEMP", "PWD", "OLDPWD", "SHLVL", "_",
|
|
19
|
+
"ENV", "BASH_ENV", "BASHOPTS", "SHELLOPTS", "CDPATH", "IFS", "PROMPT_COMMAND", "PS4", "ZDOTDIR",
|
|
20
|
+
"NODE_OPTIONS", "NODE_PATH", "_JAVA_OPTIONS", "GCONV_PATH", "GLIBC_TUNABLES", "ELECTRON_RUN_AS_NODE",
|
|
21
|
+
]);
|
|
22
|
+
export const PROCESS_BOOTSTRAP_PREFIXES = [
|
|
23
|
+
"NODE_", "LD_", "DYLD_", "PYTHON", "PERL", "RUBY", "JAVA_", "JDK_JAVA_",
|
|
24
|
+
"DOTNET_", "COMPlus_", "COREHOST_", "LUA_", "PHP_", "ELECTRON_", "GLIBC_",
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const isObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
28
|
+
const pointerKey = (k) => String(k).replace(/~/g, "~0").replace(/\//g, "~1");
|
|
29
|
+
|
|
30
|
+
/** A hook's script, the first word of its command: relative to the capability root and inside it. */
|
|
31
|
+
export function hookScriptEscapes(command) {
|
|
32
|
+
const script = String(command).trim().split(/\s+/)[0] || "";
|
|
33
|
+
return script.startsWith("/") || script.startsWith("~") || /^[A-Za-z]:[\\/]/.test(script) || script.split(/[\\/]/).includes("..");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Every contract problem of one manifest → [{ pointer, message }] (empty when it is sound).
|
|
37
|
+
* Messages name the capability; `pointer` is the JSON pointer inside oats.json. */
|
|
38
|
+
export function manifestContractProblems(m) {
|
|
39
|
+
const problems = [];
|
|
40
|
+
const id = m?.capability;
|
|
41
|
+
const bad = (pointer, message) => problems.push({ pointer, message: `capability ${id} ${message}` });
|
|
42
|
+
|
|
43
|
+
if (m.environmentNamespaces !== undefined && (!Array.isArray(m.environmentNamespaces) || m.environmentNamespaces.some((ns) => typeof ns !== "string"))) {
|
|
44
|
+
bad("/environmentNamespaces", "manifest environmentNamespaces must be an array of prefixes");
|
|
45
|
+
}
|
|
46
|
+
if (m.environment !== undefined) {
|
|
47
|
+
if (!Array.isArray(m.environment) || m.environment.some((name) => typeof name !== "string")) {
|
|
48
|
+
bad("/environment", "manifest environment must be an array of exact variable names");
|
|
49
|
+
} else if (new Set(m.environment).size !== m.environment.length) {
|
|
50
|
+
bad("/environment", "manifest environment contains duplicate names");
|
|
51
|
+
} else if (m.environment.length) {
|
|
52
|
+
const vendor = CAPABILITY_ENV_ID_RE.test(id) ? id.match(/^([a-z][a-z0-9]*)\./)?.[1] : undefined;
|
|
53
|
+
if (!vendor) bad("/capability", "must use a lowercase dotted ID (vendor.name) without package or path syntax to declare launch environment");
|
|
54
|
+
else {
|
|
55
|
+
const extra = Array.isArray(m.environmentNamespaces) ? m.environmentNamespaces.filter((ns) => typeof ns === "string") : [];
|
|
56
|
+
extra.forEach((ns, i) => {
|
|
57
|
+
if (!/^[A-Z][A-Z0-9]*_$/.test(ns)) bad(`/environmentNamespaces/${i}`, `manifest environmentNamespaces entry ${JSON.stringify(ns)} must be an uppercase prefix ending in an underscore`);
|
|
58
|
+
else if (ns === "OATS_" || ns === "PI_AGENT_" || PROCESS_BOOTSTRAP_PREFIXES.some((reserved) => ns.startsWith(reserved) || reserved.startsWith(ns))) bad(`/environmentNamespaces/${i}`, `manifest environmentNamespaces entry ${ns} is a reserved namespace`);
|
|
59
|
+
});
|
|
60
|
+
const allowed = [`${vendor.toUpperCase()}_`, ...extra];
|
|
61
|
+
m.environment.forEach((name, i) => {
|
|
62
|
+
const at = `/environment/${i}`;
|
|
63
|
+
if (!PORTABLE_ENV_NAME_RE.test(name)) bad(at, `manifest environment name ${JSON.stringify(name)} is invalid`);
|
|
64
|
+
else if (CORE_LAUNCH_ENV.has(name) || name.startsWith("OATS_") || name.startsWith("PI_AGENT_")) bad(at, `manifest environment name ${name} collides with a reserved core variable`);
|
|
65
|
+
else if (PROCESS_BOOTSTRAP_ENV.has(name) || PROCESS_BOOTSTRAP_PREFIXES.some((reserved) => name.startsWith(reserved))) bad(at, `manifest environment name ${name} collides with a reserved process bootstrap variable`);
|
|
66
|
+
else if (!allowed.some((ns) => name.startsWith(ns))) bad(at, `manifest environment name ${name} is outside its ${allowed.join(", ")} namespace${allowed.length > 1 ? "s" : ""} (declare another in environmentNamespaces)`);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (m.hooks !== undefined && !isObject(m.hooks)) bad("/hooks", "manifest hooks must be an object of event → command");
|
|
73
|
+
const hooks = isObject(m.hooks) ? Object.entries(m.hooks) : [];
|
|
74
|
+
// A hook's launch environment is claimed under the capability's dotted vendor
|
|
75
|
+
// component; an undotted id has none, so its hooks cannot run.
|
|
76
|
+
if (hooks.length && !/^[a-z][a-z0-9]*\./.test(String(id))) bad("/hooks", "declares hooks but its id has no dotted lowercase vendor component (e.g. acme.tool); only a dotted id can carry hooks");
|
|
77
|
+
for (const [event, value] of hooks) {
|
|
78
|
+
const at = `/hooks/${pointerKey(event)}`;
|
|
79
|
+
if (!APPROVED_HOOKS.has(event)) { bad(at, `declares unsupported hook "${event}" (${[...APPROVED_HOOKS].join(", ")})`); continue; }
|
|
80
|
+
const extraKeys = isObject(value) ? Object.keys(value).filter((k) => !["command", "required", "inputs"].includes(k)) : [];
|
|
81
|
+
const command = typeof value === "string" ? value : isObject(value) && typeof value.command === "string" ? value.command : undefined;
|
|
82
|
+
if (command === undefined || !command.trim() || extraKeys.length) { bad(at, `hook "${event}" must be a command string or { command, required, inputs }${extraKeys.length ? ` (unknown: ${extraKeys.join(", ")})` : ""}`); continue; }
|
|
83
|
+
if (isObject(value) && value.required !== undefined && typeof value.required !== "boolean") bad(`${at}/required`, `hook "${event}": "required" must be a boolean`);
|
|
84
|
+
// Only a spawn hook can fail a spawn; marking others required would promise
|
|
85
|
+
// an enforcement that has no defined moment to act.
|
|
86
|
+
else if (isObject(value) && value.required === true && event !== "spawn") bad(`${at}/required`, `hook "${event}" cannot be required — only the spawn hook is enforced (retire, launch and soul-scaffold run outside a spawn transaction)`);
|
|
87
|
+
if (hookScriptEscapes(command)) bad(isObject(value) ? `${at}/command` : at, `hook "${event}" script ${JSON.stringify(command.trim().split(/\s+/)[0])} escapes the capability directory; a hook script is a path inside it`);
|
|
88
|
+
}
|
|
89
|
+
return problems;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The setting keys a manifest declares (`settings.<key>`), sorted: names only, never their
|
|
93
|
+
* descriptions or defaults. The spawn preview and inspect expose them so a client can gate
|
|
94
|
+
* a choice on a declared key (e.g. a messaging provider's `join`). */
|
|
95
|
+
export function declaredSettings(manifest) {
|
|
96
|
+
return isObject(manifest?.settings) ? Object.keys(manifest.settings).sort() : [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Top-level payload keys whose value is outside the manifest's `settings.<key>.values`
|
|
100
|
+
* → [{ key, value, values }]. A conditional `requires` row reads these values, so a
|
|
101
|
+
* misspelled one would silently skip every row instead of failing. */
|
|
102
|
+
export function settingValueProblems(manifest, payload) {
|
|
103
|
+
const out = [];
|
|
104
|
+
if (!isObject(manifest?.settings) || !isObject(payload)) return out;
|
|
105
|
+
for (const [key, decl] of Object.entries(manifest.settings)) {
|
|
106
|
+
if (!isObject(decl) || !Array.isArray(decl.values) || !Object.hasOwn(payload, key) || payload[key] === undefined) continue;
|
|
107
|
+
if (!decl.values.some((v) => String(v) === String(payload[key]))) out.push({ key, value: payload[key], values: decl.values });
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
package/lib/config-data.mjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* object-construction language: no tags, anchors, aliases, merges or coercive keys.
|
|
3
3
|
* Legacy core readers are unchanged until the explicit consumer migration. */
|
|
4
4
|
import { Composer, CST, Lexer, Parser, isAlias, isMap, isScalar, isSeq } from "yaml";
|
|
5
|
-
import { bytesIntegrity } from "./
|
|
6
|
-
import { byteView, canonicalJson, dataLimits, decodeUtf8, parseStrictJson } from "./
|
|
5
|
+
import { bytesIntegrity } from "./digest.mjs";
|
|
6
|
+
import { byteView, canonicalJson, dataLimits, decodeUtf8, parseStrictJson } from "./canonical-json.mjs";
|
|
7
7
|
import { oatsError } from "./errors.mjs";
|
|
8
8
|
|
|
9
9
|
const pointerKey = (key) => key.replace(/~/g, "~0").replace(/\//g, "~1");
|