@awebai/oats 0.29.4 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -6
- package/bin/oats.mjs +194 -50
- package/docs/capabilities.md +160 -171
- package/docs/capability-manifest.schema.json +6 -11
- package/docs/configuration.md +213 -64
- package/docs/design/2026-09-16-knowledge-capability-contract.md +36 -50
- package/docs/design/2026-09-23-workspace-module-contracts.md +377 -544
- package/docs/design/2026-09-26-okf-knowledge-operations.md +132 -357
- package/docs/design/2026-09-27-team-model-v2.md +97 -117
- package/docs/design/2026-09-28-automations-trust.md +38 -0
- package/docs/design/2026-09-28-soul-launch-preference.md +63 -0
- package/docs/design/HISTORY.md +65 -0
- package/docs/design/README.md +23 -54
- package/docs/desktop-cli-api.md +1787 -1777
- package/docs/desktop.md +30 -91
- package/docs/execution-targets.md +146 -292
- package/docs/first-team.md +31 -17
- package/docs/implementation.md +77 -288
- package/docs/integrations.md +118 -320
- package/docs/knowledge-capability-authoring.md +25 -52
- package/docs/knowledge-reference/acceptance.md +3 -3
- package/docs/knowledge-reference/adoption.md +1 -1
- package/docs/knowledge-reference/harvester.md +2 -2
- package/docs/knowledge-reference/package-craft.md +3 -3
- package/docs/knowledge-reference/provider-mapping.md +3 -6
- package/docs/knowledge-reference/reader-capture.md +3 -3
- package/docs/knowledge-theory.md +62 -166
- package/docs/knowledge.md +225 -404
- package/docs/layers.md +42 -97
- package/docs/oats-local.schema.json +58 -5
- package/docs/oats-membership.schema.json +1 -8
- package/docs/oats-package.schema.json +5 -5
- package/docs/oats-workspace.schema.json +8 -22
- package/docs/official-catalog.md +25 -28
- package/docs/packages.md +45 -63
- package/docs/plans/0.30-close-out.md +83 -0
- package/docs/release-lane.md +82 -0
- package/docs/release-notes/oats-framework-v1.1.3.md +10 -8
- package/docs/release-notes/v0.19.0.md +48 -147
- package/docs/release-notes/v0.19.1.md +2 -3
- package/docs/release-notes/v0.19.3.md +2 -15
- package/docs/release-notes/v0.20.0.md +0 -15
- package/docs/release-notes/v0.22.0.md +71 -138
- package/docs/release-notes/v0.22.1.md +42 -90
- package/docs/release-notes/v0.22.10.md +1 -1
- package/docs/release-notes/v0.22.11.md +1 -47
- package/docs/release-notes/v0.22.12.md +4 -13
- package/docs/release-notes/v0.22.13.md +1 -42
- package/docs/release-notes/v0.22.14.md +3 -11
- package/docs/release-notes/v0.22.15.md +1 -46
- package/docs/release-notes/v0.22.16.md +6 -8
- package/docs/release-notes/v0.22.18.md +1 -99
- package/docs/release-notes/v0.22.19.md +3 -14
- package/docs/release-notes/v0.22.2.md +6 -15
- package/docs/release-notes/v0.22.3.md +0 -1
- package/docs/release-notes/v0.22.4.md +1 -14
- package/docs/release-notes/v0.22.5.md +2 -12
- package/docs/release-notes/v0.22.6.md +0 -3
- package/docs/release-notes/v0.23.0.md +9 -25
- package/docs/release-notes/v0.23.1.md +9 -25
- package/docs/release-notes/v0.23.2.md +2 -4
- package/docs/release-notes/v0.24.0.md +56 -97
- package/docs/release-notes/v0.24.1.md +7 -11
- package/docs/release-notes/v0.24.10.md +34 -45
- package/docs/release-notes/v0.24.11.md +12 -20
- package/docs/release-notes/v0.24.12.md +35 -48
- package/docs/release-notes/v0.24.13.md +34 -41
- package/docs/release-notes/v0.24.2.md +9 -13
- package/docs/release-notes/v0.24.3.md +7 -11
- package/docs/release-notes/v0.24.4.md +6 -6
- package/docs/release-notes/v0.24.5.md +6 -10
- package/docs/release-notes/v0.24.6.md +2 -5
- package/docs/release-notes/v0.24.7.md +46 -75
- package/docs/release-notes/v0.24.8.md +58 -96
- package/docs/release-notes/v0.24.9.md +38 -54
- package/docs/release-notes/v0.25.0.md +59 -76
- package/docs/release-notes/v0.25.1.md +57 -81
- package/docs/release-notes/v0.25.2.md +51 -70
- package/docs/release-notes/v0.25.3.md +11 -13
- package/docs/release-notes/v0.25.4.md +9 -13
- package/docs/release-notes/v0.25.5.md +3 -5
- package/docs/release-notes/v0.25.6.md +20 -29
- package/docs/release-notes/v0.25.7.md +5 -7
- package/docs/release-notes/v0.25.8.md +26 -39
- package/docs/release-notes/v0.26.0.md +175 -646
- package/docs/release-notes/v0.27.0.md +4 -5
- package/docs/release-notes/v0.27.1.md +4 -6
- package/docs/release-notes/v0.27.2.md +1 -1
- package/docs/release-notes/v0.28.0.md +57 -124
- package/docs/release-notes/v0.29.0.md +89 -208
- package/docs/release-notes/v0.29.1.md +1 -1
- package/docs/release-notes/v0.29.2.md +3 -4
- package/docs/release-notes/v0.30.0.md +205 -0
- package/docs/release-notes/v0.30.1.md +123 -0
- package/docs/schedules.md +280 -363
- package/docs/servers.md +99 -117
- package/docs/soul.schema.json +2 -9
- package/docs/souls-and-instances.md +145 -158
- package/docs/workspaces.md +137 -215
- package/lib/automations.mjs +21 -6
- package/lib/core.mjs +226 -74
- package/lib/instance-events.mjs +1 -1
- package/lib/instance-inspect.mjs +109 -34
- package/lib/instance-lifecycle.mjs +14 -1
- package/lib/instance-resolution.mjs +26 -27
- package/lib/launch-preference.mjs +87 -0
- package/lib/materialize.mjs +3 -3
- package/lib/packages.mjs +1 -1
- package/lib/resolve.mjs +30 -88
- package/lib/schedule.mjs +1 -1
- package/lib/teams-verbs.mjs +195 -0
- package/lib/teams.mjs +190 -0
- package/lib/triggers.mjs +2 -2
- package/lib/workspace.mjs +54 -147
- package/package-catalog.json +10 -16
- package/package.json +1 -3
- package/skills/oats-getting-started/SKILL.md +25 -13
- package/capabilities/oats-authoring/LICENSE +0 -21
- package/capabilities/oats-authoring/oats-package.json +0 -11
- package/capabilities/oats-authoring/oats.json +0 -12
- package/capabilities/oats-authoring/skills/integration-authoring/SKILL.md +0 -84
- package/capabilities/oats-authoring/skills/skill-craft/SKILL.md +0 -109
- package/capabilities/oats-authoring/skills/soul-craft/SKILL.md +0 -116
- package/capabilities/oats-aweb/bin/oats-aweb-binding.mjs +0 -11
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +0 -1338
- package/capabilities/oats-aweb/injects/aweb.md +0 -47
- package/capabilities/oats-aweb/lib/binding-wire.mjs +0 -356
- package/capabilities/oats-aweb/lib/captured-execution.mjs +0 -91
- package/capabilities/oats-aweb/lib/captured-native.mjs +0 -91
- package/capabilities/oats-aweb/lib/grant-custody.mjs +0 -38
- package/capabilities/oats-aweb/lib/invocation-shape.mjs +0 -135
- package/capabilities/oats-aweb/lib/portable-binding.mjs +0 -146
- package/capabilities/oats-aweb/lib/session-readiness.mjs +0 -56
- package/capabilities/oats-aweb/lib/wake-receive.mjs +0 -56
- package/capabilities/oats-aweb/oats.json +0 -208
- package/capabilities/oats-aweb/skills/LICENSE +0 -21
- package/capabilities/oats-aweb/skills/VENDORED.md +0 -31
- package/capabilities/oats-aweb/skills/aweb-identity/SKILL.md +0 -201
- package/capabilities/oats-aweb/skills/aweb-messaging/SKILL.md +0 -161
- package/capabilities/oats-aweb/skills/aweb-messaging/references/messaging-scenarios.md +0 -61
- package/capabilities/oats-aweb/skills/aweb-team-membership/SKILL.md +0 -116
- package/capabilities/oats-aweb/skills/aweb-team-membership/references/team-membership-reference.md +0 -74
- package/capabilities/oats-aweb/skills/oats-aweb/SKILL.md +0 -216
- package/capabilities/oats-jira/bin/oats-jira.mjs +0 -40
- package/capabilities/oats-jira/injects/jira.md +0 -10
- package/capabilities/oats-jira/oats.json +0 -22
- package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +0 -179
- package/capabilities/oats-linear/bin/oats-linear-hook.mjs +0 -34
- package/capabilities/oats-linear/bin/oats-linear.mjs +0 -344
- package/capabilities/oats-linear/injects/linear.md +0 -8
- package/capabilities/oats-linear/oats.json +0 -24
- package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +0 -223
- package/capabilities/oats-okf/bin/oats-okf-binding.mjs +0 -14
- package/capabilities/oats-okf/bin/oats-okf.mjs +0 -209
- package/capabilities/oats-okf/injects/okf.md +0 -42
- package/capabilities/oats-okf/lib/binding-wire.mjs +0 -348
- package/capabilities/oats-okf/lib/captured-worker.mjs +0 -109
- package/capabilities/oats-okf/lib/config.mjs +0 -124
- package/capabilities/oats-okf/lib/consult.mjs +0 -518
- package/capabilities/oats-okf/lib/harvest-status.mjs +0 -88
- package/capabilities/oats-okf/lib/harvest-switch.mjs +0 -94
- package/capabilities/oats-okf/lib/inspection.mjs +0 -119
- package/capabilities/oats-okf/lib/invocation-context.mjs +0 -111
- package/capabilities/oats-okf/lib/invocation-shape.mjs +0 -135
- package/capabilities/oats-okf/lib/io.mjs +0 -118
- package/capabilities/oats-okf/lib/migration.mjs +0 -137
- package/capabilities/oats-okf/lib/okf-validate.mjs +0 -123
- package/capabilities/oats-okf/lib/portable-binding.mjs +0 -199
- package/capabilities/oats-okf/lib/source-contract.mjs +0 -46
- package/capabilities/oats-okf/lib/sources.mjs +0 -424
- package/capabilities/oats-okf/lib/stores.mjs +0 -473
- package/capabilities/oats-okf/lib/worker.mjs +0 -497
- package/capabilities/oats-okf/oats.json +0 -148
- package/capabilities/oats-okf/schemas/okf-base.schema.json +0 -46
- package/capabilities/oats-okf/schemas/okf-bindings.schema.json +0 -112
- package/capabilities/oats-okf/schemas/okf-portable-declaration.schema.json +0 -87
- package/capabilities/oats-okf/schemas/okf-portable-payload.schema.json +0 -113
- package/capabilities/oats-okf/schemas/okf-soul.schema.json +0 -37
- package/capabilities/oats-okf/skills/okf-consultation/SKILL.md +0 -144
- package/capabilities/oats-okf/skills/okf-consultation/references/consult.md +0 -86
- package/capabilities/oats-okf/skills/okf-instance-knowledge/SKILL.md +0 -104
- package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +0 -140
- package/capabilities/oats-okf-harvest/injects/harvester.md +0 -12
- package/capabilities/oats-okf-harvest/oats.json +0 -26
- package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +0 -168
- package/capabilities/oats-okf-harvest/skills/knowledge-theory/SKILL.md +0 -192
- package/capabilities/oats-okf-harvest/skills/okf-authoring/SKILL.md +0 -151
- package/capabilities/oats-okf-harvest/skills/okf-authoring/scripts/okf-validate.mjs +0 -123
- package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +0 -170
- package/capabilities/oats-okf-maintenance/injects/maintainer.md +0 -12
- package/capabilities/oats-okf-maintenance/lib/provenance.mjs +0 -50
- package/capabilities/oats-okf-maintenance/oats.json +0 -21
- package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +0 -159
- package/capabilities/oats-okf-maintenance/skills/knowledge-theory/SKILL.md +0 -192
- package/capabilities/oats-okf-maintenance/skills/okf-authoring/SKILL.md +0 -151
- package/capabilities/oats-okf-maintenance/skills/okf-authoring/scripts/okf-validate.mjs +0 -123
- package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +0 -146
- package/capabilities/oats-review/injects/review.md +0 -69
- package/capabilities/oats-review/oats.json +0 -10
- package/capabilities/oats-review/skills/code-review/SKILL.md +0 -44
- package/capabilities/oats-review/skills/security-review/SKILL.md +0 -59
- package/docs/conventions.md +0 -90
- package/docs/design/2026-09-07-architecture-reassessment.md +0 -131
- package/docs/design/2026-09-07-desktop-souls-capabilities.md +0 -50
- package/docs/design/2026-09-07-mobile-agent-management-proposal.md +0 -228
- package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +0 -558
- package/docs/design/2026-09-13-knowledge-and-memory-direction.md +0 -744
- package/docs/design/2026-09-13-knowledge-implementation.md +0 -127
- package/docs/design/2026-09-13-knowledge-location-contract.md +0 -340
- package/docs/design/2026-09-14-artifact-retention-contract.md +0 -190
- package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +0 -708
- package/docs/design/2026-09-14-portable-souls-contract-amendments.md +0 -85
- package/docs/design/2026-09-14-portable-souls-explainer.md +0 -750
- package/docs/design/2026-09-15-captured-dispatch.md +0 -127
- package/docs/design/2026-09-15-captured-resolution-records.md +0 -143
- package/docs/design/2026-09-15-package-preparation.md +0 -100
- package/docs/design/2026-09-15-portable-data-contract.md +0 -121
- package/docs/design/2026-09-15-portable-declarations.md +0 -189
- package/docs/design/2026-09-15-portable-souls-handoff.md +0 -150
- package/docs/design/2026-09-15-portable-souls-implementation.md +0 -417
- package/docs/design/2026-09-15-selection-lock-and-approval.md +0 -122
- package/docs/design/2026-09-15-source-observation.md +0 -119
- package/docs/design/2026-09-16-captured-admission.md +0 -77
- package/docs/design/2026-09-16-captured-helper-dispatch.md +0 -105
- package/docs/design/2026-09-16-captured-launch-inputs.md +0 -42
- package/docs/design/2026-09-16-command-profile-preparation.md +0 -86
- package/docs/design/2026-09-16-fresh-install-first-rollout.md +0 -47
- package/docs/design/2026-09-16-fresh-operator-walkthrough.md +0 -282
- package/docs/design/2026-09-16-messaging-capability-contract.md +0 -59
- package/docs/design/2026-09-16-portable-migration-evidence.md +0 -158
- package/docs/design/2026-09-16-portable-onboarding.md +0 -179
- package/docs/design/2026-09-16-prepare-request-transport.md +0 -26
- package/docs/design/2026-09-16-provider-binding-codecs.md +0 -98
- package/docs/design/2026-09-16-provider-binding-wire.md +0 -274
- package/docs/design/2026-09-17-capability-helper-input-contract.md +0 -95
- package/docs/design/2026-09-17-captured-backend-parity.md +0 -53
- package/docs/design/2026-09-17-captured-native-start.md +0 -58
- package/docs/design/2026-09-17-portable-boundary-hookup.md +0 -19
- package/docs/design/2026-09-17-portable-boundary-resources.md +0 -52
- package/docs/design/2026-09-17-public-captured-start.md +0 -108
- package/docs/design/2026-09-17-public-prepare-request.md +0 -90
- package/docs/design/2026-09-18-captured-pi-host.md +0 -205
- package/docs/design/2026-09-18-first-cut-release-checklist.md +0 -131
- package/docs/design/2026-09-18-herdr-protocol-compatibility.md +0 -60
- package/docs/design/2026-09-20-redesign-program-board.md +0 -142
- package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +0 -289
- package/docs/design/2026-09-20-workspace-onboarding-public.md +0 -207
- package/docs/design/2026-09-22-desktop-parity-seams.md +0 -58
- package/docs/design/2026-09-23-simplified-workspace-model.md +0 -711
- package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +0 -65
- package/docs/design/2026-09-24-desktop-phase-f-boundary.md +0 -242
- package/docs/design/2026-09-24-phase-d-plan.md +0 -305
- package/docs/design/2026-09-25-teams-contract.md +0 -258
- package/docs/design/2026-09-26-desktop-design-brief-architecture.md +0 -241
- package/docs/design/desktop-ux-plan.md +0 -362
- package/docs/design/launch-configurations.md +0 -168
- package/docs/design/okf-mirror-provenance.md +0 -105
- package/docs/design/operations-contract.md +0 -141
- package/docs/oats-member.schema.json +0 -38
- package/skills/integration-authoring/SKILL.md +0 -84
- package/skills/oats-support/SKILL.md +0 -79
- package/skills/skill-craft/SKILL.md +0 -109
- package/skills/soul-craft/SKILL.md +0 -116
|
@@ -1,613 +1,446 @@
|
|
|
1
|
-
# Workspace model
|
|
1
|
+
# Workspace model: module contracts
|
|
2
2
|
|
|
3
|
-
**Status:** normative for
|
|
3
|
+
**Status:** normative for `lib/remote.mjs`, `lib/workspace.mjs`, `lib/resolve.mjs`, `lib/packages.mjs`,
|
|
4
|
+
`lib/materialize.mjs` and the CLI verbs built on them. When this record and a reference page
|
|
5
|
+
(for example [workspaces](../workspaces.md) or [packages](../packages.md)) disagree about
|
|
6
|
+
operator-visible behaviour, the reference page wins.
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
Conventions: ESM, Node 22+, no runtime dependency beyond `yaml` and `node:*`. Errors are
|
|
9
|
+
`oatsError(code, message, details)` with stable `E_*` codes. Commits are full 40-hex OIDs, digests
|
|
10
|
+
`sha256-<hex>`. Only `lib/remote.mjs` (to `git`) and the launch path shell out. Modules that read a
|
|
11
|
+
remote take `{ remote, remoteOptions }`: `remote` defaults to `lib/remote.mjs` (tests inject a fake);
|
|
12
|
+
`remoteOptions` (`cacheDir`, `exec`, `transport`) reaches every remote call.
|
|
8
13
|
|
|
9
14
|
---
|
|
10
15
|
|
|
11
16
|
## 1. `lib/remote.mjs` — observe Git remotes in the operator's access context
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
### 1.1 Repo refs and keys
|
|
19
|
+
|
|
20
|
+
`parseRepoRef(text, options?) → { host, path, url, key }` or `E_REPO_REF`. Accepted forms:
|
|
21
|
+
`git:host/org/repo`, `https://host/org/repo`, `git@host:org/repo.git`, `ssh://[user@]host[:port]/org/repo`,
|
|
22
|
+
`/abs/bare.git` and `file:///abs/path`. The fetch `url` keeps SSH forms as written, canonicalizes HTTPS,
|
|
23
|
+
and fetches `git:` over HTTPS unless `options.transport === "ssh"`.
|
|
24
|
+
|
|
25
|
+
`key` is the identity everywhere: `<host>/<path>` with a lowercase host, no scheme, no `.git`; a local
|
|
26
|
+
remote's key is `local/<abs-path>`. Modules compare keys, never URLs. The path part is case-sensitive.
|
|
27
|
+
|
|
28
|
+
### 1.2 Reads
|
|
14
29
|
|
|
15
30
|
```js
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// → {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export async function observeRemote(ref, { at } = {})
|
|
22
|
-
// at: undefined → the remote's default branch (git ls-remote --symref HEAD); or a full OID; or a tag/branch name.
|
|
23
|
-
// → { key, url, commit, ref: "<resolved symbolic ref or null>", observedAt }
|
|
24
|
-
// throws E_REMOTE_UNREADABLE { url, reason: "auth"|"not-found"|"network"|"timeout" } — NEVER half-succeeds; NEVER prompts.
|
|
25
|
-
|
|
26
|
-
export async function readRemoteFile(ref, commit, path)
|
|
27
|
-
// → { bytes: Buffer, size } | throws E_REMOTE_UNREADABLE | E_REMOTE_PATH_MISSING { path }
|
|
28
|
-
// Bounded: size > 4 MiB → E_REMOTE_FILE_OVERSIZE { path, size, budget }.
|
|
29
|
-
|
|
30
|
-
export async function listRemoteTree(ref, commit, dir, { depth = 2 } = {})
|
|
31
|
-
// → [{ path, type: "blob"|"tree"|"symlink", size? }] relative to dir, depth-bounded; missing dir → [];
|
|
32
|
-
// submodule gitlinks are omitted. Unsafe entry names → E_REMOTE_TREE_UNSAFE { why: "path" } (see below).
|
|
33
|
-
|
|
34
|
-
export async function fetchRemoteTree(ref, commit, dir, destDir)
|
|
35
|
-
// Copies the subtree at <dir> of <commit> into destDir (created; must not exist, not even as a dangling symlink).
|
|
36
|
-
// Regular files and dirs only; everything is inspected BEFORE any write:
|
|
37
|
-
// symlinks / submodules / odd modes → E_REMOTE_TREE_UNSAFE { path, why: "symlink"|"device" }; total > 64 MiB → "oversize";
|
|
38
|
-
// an entry-name component that is "", ".", "..", ".git" (any case) or contains "\"/NUL → why: "path";
|
|
39
|
-
// two entries equal under NFC+case folding (README.md/readme.md, NFC/NFD) → why: "collision".
|
|
40
|
-
// → { files, bytes, digest } digest = sha256 over (relpath, git-normalized mode "755"|"644", size, bytes) in
|
|
41
|
-
// byte-wise relpath order — the "content hash" (umask-independent: a checkout digests like the fetched tree).
|
|
42
|
-
|
|
43
|
-
export function contentDigest(dir)
|
|
44
|
-
// Same digest computation over a local directory (used by materialize to verify a copy); a top-level `.git/` is ignored.
|
|
31
|
+
observeRemote(ref, { at }) // → { key, url, commit, ref, observedAt }
|
|
32
|
+
readRemoteFile(ref, commit, path) // → { bytes, size }
|
|
33
|
+
listRemoteTree(ref, commit, dir, { depth = 2 }) // → [{ path, type: "blob"|"tree"|"symlink", size? }]
|
|
34
|
+
fetchRemoteTree(ref, commit, dir, destDir, { allowSymlinks }) // → { files, bytes, digest }
|
|
35
|
+
contentDigest(dir, { allowSymlinks }) // → "sha256-<hex>"
|
|
45
36
|
```
|
|
46
37
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
`
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
38
|
+
- `observeRemote`: `at` absent or `HEAD` is the default branch; else a full OID or a plain tag or branch
|
|
39
|
+
name (revision syntax or a leading `-` is `E_REPO_REF`). `commit` is always the peeled commit. No match,
|
|
40
|
+
or a non-commit object, is `E_REMOTE_UNREADABLE { reason: "not-found" }`.
|
|
41
|
+
- `readRemoteFile`: missing or a directory → `E_REMOTE_PATH_MISSING`; a symlink →
|
|
42
|
+
`E_REMOTE_TREE_UNSAFE { why: "symlink" }`; over 4 MiB → `E_REMOTE_FILE_OVERSIZE { path, size, budget }`.
|
|
43
|
+
- `listRemoteTree`: a missing `dir` is `[]`; submodules are omitted; entries beyond `depth` are dropped
|
|
44
|
+
before names are checked.
|
|
45
|
+
- `fetchRemoteTree`: `destDir` must not exist; a missing `dir` is `E_REMOTE_PATH_MISSING`. Every entry is checked before anything is written, then
|
|
46
|
+
the copy is staged and renamed in. `E_REMOTE_TREE_UNSAFE { why }`: `path` (a name component empty,
|
|
47
|
+
`.`, `..`, `.git` or containing `\` or NUL), `collision` (equal under NFC and case folding), `symlink`
|
|
48
|
+
(unless `allowSymlinks` admits it and it stays inside the tree), `device`, `oversize` (over 64 MiB),
|
|
49
|
+
`exists`. The kernel admits one symlink (`OATS_ALIAS_SYMLINK`): `CLAUDE.md` → `AGENTS.md`.
|
|
50
|
+
|
|
51
|
+
**Content digest.** SHA-256 over `"F" NUL relpath NUL mode NUL size NUL bytes NUL` per file in byte-wise
|
|
52
|
+
path order, `mode` normalized to `755` or `644` (a checkout digests like the fetched tree). An admitted
|
|
53
|
+
symlink enters as `symlink:<target>`; empty directories and a top-level `.git/` do not count.
|
|
54
|
+
|
|
55
|
+
### 1.3 Access, cache, failures
|
|
56
|
+
|
|
57
|
+
- Git uses the operator's own configuration and credentials; `GIT_TERMINAL_PROMPT=0`,
|
|
58
|
+
`GIT_ASKPASS=/usr/bin/false` and ssh `-o BatchMode=yes` mean nothing prompts. 30 s per git call.
|
|
59
|
+
- A commit is fetched depth 1 (no blob filter) into a bare cache `<cacheDir>/<sha256(key)>/` (default
|
|
60
|
+
`~/.cache/oats/remotes`; the CLI honours `OATS_REMOTE_CACHE`) and pinned as `refs/oats/commits/<oid>`.
|
|
61
|
+
The cache may be wiped at any time. Operations on one cache repo are serialized.
|
|
62
|
+
- Failures: `E_REMOTE_UNREADABLE { url, key, reason }`, `reason` ∈ `auth`, `not-found`, `network`,
|
|
63
|
+
`timeout`, `unknown`. Nothing half-succeeds.
|
|
57
64
|
|
|
58
65
|
---
|
|
59
66
|
|
|
60
67
|
## 2. `lib/workspace.mjs` — declarations, membership, discovery
|
|
61
68
|
|
|
62
|
-
|
|
69
|
+
### 2.1 Declaration files
|
|
63
70
|
|
|
64
|
-
|
|
71
|
+
Schemas are in `docs/*.schema.json`; the `validate*` functions add the domain rules and are the
|
|
72
|
+
authority. Only `schemaVersion: 2` is read.
|
|
65
73
|
|
|
66
|
-
`oats-workspace.yaml` (schemaVersion 2):
|
|
67
74
|
```yaml
|
|
75
|
+
# oats-workspace.yaml (committed in the host)
|
|
68
76
|
schemaVersion: 2
|
|
69
77
|
name: <slug>
|
|
70
|
-
members: [<repo ref
|
|
71
|
-
packages: { <
|
|
72
|
-
teams: { <label>: { description } }
|
|
78
|
+
members: [<repo ref>] # no @revision
|
|
79
|
+
packages: { <id>: v2.1.3 | git:<repo>@<ref> }
|
|
80
|
+
teams: { <label>: { team?: <provider team id>, description? } } # shared teams
|
|
73
81
|
defaults:
|
|
74
|
-
capabilities: { <cap>: { from: <repo key|package
|
|
75
|
-
knowledge: { <cap>: { from } } | none
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
stores: { <name>: <repo ref> } # a REPOSITORY; the root inside it is the provider's (OKF `root`) — no `#path`
|
|
80
|
-
messaging: <opaque provider payload> # may carry byTeam: { <label>: <payload> } — merged base ⊕ byTeam[soul.team], stripped
|
|
81
|
-
external: [ { source: <repo ref>@<full OID>, soul: <path> } ] # revision REQUIRED
|
|
82
|
+
capabilities: { <cap>: { from: <repo key> | package } | off }
|
|
83
|
+
knowledge | messaging | tasks: { <cap>: { from } } | none # at most one entry
|
|
84
|
+
stores: { <name>: <repo ref> }
|
|
85
|
+
messaging: <opaque provider payload>
|
|
86
|
+
external: [{ source: <repo ref>@<full OID>, soul: <repo-relative dir> }]
|
|
82
87
|
```
|
|
83
|
-
Schema refuses: absolute paths anywhere; `@revision` on members; unknown top-level keys.
|
|
84
|
-
*(clarified Phase B)* "Absolute paths" means bare filesystem paths as VALUES (`/Users/x/store`, `C:\…`) — host state that belongs in `oats-local.yaml`. A repo ref in `file:///…` or `git:/abs/bare.git@<ref>` form is a **repo ref** (§1 accepts it; it is how tests build remotes), not an absolute path, and is accepted wherever a repo ref is. The JSON schemas encode only what a JSON schema can (shapes, grammars); domain rules — declared teams, duplicate members, canonical `from:` keys, one form per `packages:` value — live in `validateWorkspace`/`validateSoul`, which are the authority; a consumer validating against the schema alone accepts a superset.
|
|
85
|
-
*(refined Phase C, decisions 23–25)* `messaging.byTeam.<label>` must name a label in `teams:` (`E_WORKSPACE_SCHEMA`). Standalone resolution (`standaloneRepo`) adds `oats.core: { from: package }` unless the soul says `off`; the package resolves through the operator's lock exactly as in a workspace (`E_PACKAGE_UNAPPROVED` until approved). `stores` values are repo refs only.
|
|
86
|
-
|
|
87
|
-
*(clarified Phase B)* A `from:` value is `package`, `here` (souls only — a workspace default has no referent for `here` → `E_WORKSPACE_SCHEMA`), or a **canonical** repo key exactly as `parseRepoRef(ref).key` spells it (lowercase host, no scheme, no `git:`, no `.git`; `local/<abs-path>` for file remotes). Any other spelling is a schema problem at validation, never a late `E_NOT_A_MEMBER`. `soul.yaml` requires `name`, `description` and `work`.
|
|
88
88
|
|
|
89
|
-
`oats-membership.yaml
|
|
89
|
+
- `oats-membership.yaml` (in each member): `{ schemaVersion: 2, workspace: <repo ref> }`, the backlink.
|
|
90
|
+
- `souls/<name>/soul.yaml`: `name` (equal to the directory), `description`, `work`
|
|
91
|
+
(`worktree | checkout | directory | workspace`), `capabilities` (`{ from: here | package | <repo key> }`
|
|
92
|
+
or `off`), slot payloads (`knowledge | messaging | tasks`: opaque or `none`), `compatibility`.
|
|
93
|
+
`private:` is ignored with the warning `soul-private-ignored`.
|
|
94
|
+
- `oats-local.yaml` (per machine): `workspace` (required), `standalone`, `clones`, `settings`,
|
|
95
|
+
`souls.disabled`, local team keys, host keys ([configuration](../configuration.md)). `loadLocal(dir)`
|
|
96
|
+
walks up to it: none is `E_LOCAL_MISSING`; an `oats-config.yaml` on the way is `E_CONFIG_BROKEN`.
|
|
97
|
+
|
|
98
|
+
**Teams.** Shared teams and their provider ids are in `oats-workspace.yaml` `teams:`. Local teams,
|
|
99
|
+
`defaultTeam`, `souls.teams` and `souls.default` are in `oats-local.yaml`. There is no `team:` in soul,
|
|
100
|
+
membership or `external[]` entries, no `byTeam`, and no kernel-defined team. See
|
|
101
|
+
[Team model v2](2026-09-27-team-model-v2.md).
|
|
102
|
+
|
|
103
|
+
### 2.2 Validation rules
|
|
104
|
+
|
|
105
|
+
- **Absolute paths** are refused in ref and path fields only (`members`, `packages`, `stores`,
|
|
106
|
+
`external[].source` and `.soul`, `defaults.*.from`), never in descriptions or `messaging`.
|
|
107
|
+
`file:///…` and `git:/abs/bare.git@<ref>` are repo refs, not paths.
|
|
108
|
+
- **`from:`** is `package`, `here` (souls only) or a key spelled exactly as `parseRepoRef(ref).key`.
|
|
109
|
+
- Members must parse and not repeat. `packages:` values have exactly two forms (§2.6).
|
|
110
|
+
- **Removed keys** (`defaults.byTeam`, `messaging.byTeam`, `external[].team`, `team` in membership and
|
|
111
|
+
soul files, top-level `byTeam` in a soul slot payload or `settings.<cap>`) are problems with
|
|
112
|
+
`reason: "removed-key"`.
|
|
113
|
+
- Invalid files raise `E_WORKSPACE_SCHEMA { path, repoKey, commit, problems[] }`.
|
|
114
|
+
|
|
115
|
+
### 2.3 Membership
|
|
116
|
+
|
|
117
|
+
`confirmMembership(workspaceObs, memberRef)` reads the member's `oats-membership.yaml` at its default
|
|
118
|
+
branch, in the same access context → `{ key, commit, confirmed: true }` or `{ key, confirmed: false,
|
|
119
|
+
reason, detail }`, `reason` ∈ `not-listed`, `cannot-read`, `no-backlink` (missing, invalid or unusable
|
|
120
|
+
file) and `backlink-elsewhere` (`caseOnly: true` when only letter case differs). It throws only for an
|
|
121
|
+
invalid workspace file.
|
|
122
|
+
|
|
123
|
+
### 2.4 Discovery
|
|
90
124
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
private
|
|
97
|
-
capabilities: { <cap>: { from: <repo key|here|package> } | off }
|
|
98
|
-
knowledge | messaging | tasks: <opaque provider payload> | none
|
|
99
|
-
compatibility?: { <cap>: <semver range> }
|
|
125
|
+
```js
|
|
126
|
+
discoverWorkspace(ref, { at, local, lock })
|
|
127
|
+
// → { workspace, key, url, commit, observedAt, members, external, packageSouls, problems, warnings }
|
|
128
|
+
// member row: { key, ref, commit, confirmed, reason?, detail?, souls, capabilities, publishes }
|
|
129
|
+
// SoulEntry: { name, path, repoKey, commit, private: false, definition }
|
|
130
|
+
// CapEntry: { name, path, repoKey, commit, private, manifest }
|
|
100
131
|
```
|
|
101
|
-
`capabilities/<name>/oats.json` — unchanged manifest; may carry `private: true` and `team: <label>`. Discovery
|
|
102
|
-
relies on `capability` (the `capabilityName` grammar `^[a-z0-9][a-z0-9._-]*$` — the same grammar every
|
|
103
|
-
`capabilities:` key uses, so a discoverable name is always referenceable; no `/`), `version`, `private`, `team`,
|
|
104
|
-
`layer`; a manifest failing that is a problem, not listed. Two souls or two capabilities declaring one name in
|
|
105
|
-
a repo: the first (by path) is listed, the second is a problem. `external[].team` overrides the soul's `team`.
|
|
106
|
-
`loadLocal` throws `E_WORKSPACE_SCHEMA` for an invalid `oats-local.yaml`.
|
|
107
132
|
|
|
108
|
-
`oats-
|
|
133
|
+
- A repo without `oats-workspace.yaml` is `E_WORKSPACE_SCHEMA { notAHost: true }`.
|
|
134
|
+
- An unconfirmed member contributes only its row. A failure or invalid item in one member is a problem,
|
|
135
|
+
never an abort; of two same-named items in one repo, the second is a problem.
|
|
136
|
+
- Members are read at their latest commit. A capability manifest needs `capability`
|
|
137
|
+
(`^[a-z0-9][a-z0-9._-]*$`) and `version` and must pass the kernel manifest contract, or it is not listed.
|
|
138
|
+
- `publishes: { package, version } | null` reports a member's `oats-package/` (§2.6).
|
|
139
|
+
- Package souls come from the lock, at the locked commit, for still-declared packages: `package`,
|
|
140
|
+
`version`, `qualifiedName` (`<package>/<soul>`), `agentName` (`<package>--<soul>`), `digest`.
|
|
141
|
+
- `external[]` souls are read at the pinned OID.
|
|
142
|
+
|
|
143
|
+
### 2.5 Standalone view
|
|
144
|
+
|
|
145
|
+
A standalone view is a member whose workspace cannot be read. `discoverOrStandalone(local)` builds it
|
|
146
|
+
when `oats-local.yaml` says `standalone:` (`standaloneReason: "explicit"`), or when `workspace:` names a
|
|
147
|
+
member whose host fails with `auth` or `not-found` (`"unreadable-host"`, with `hostFailure { code,
|
|
148
|
+
reason, url }`); `network` and `timeout` propagate. The repo must carry `oats-membership.yaml`. The view
|
|
149
|
+
has `standalone: true`, `workspace: null` and one unconfirmed row (`cannot-read`); souls keep only
|
|
150
|
+
`from: here` capabilities, plus `oats.core: { from: package }` unless the soul mentions `oats.core`.
|
|
151
|
+
|
|
152
|
+
### 2.6 Member vs package tier — the non-collapse rule
|
|
153
|
+
|
|
154
|
+
A repository may be a member (its `souls/*` and `capabilities/*`, latest commit) and publish a package
|
|
155
|
+
(its `oats-package/`, consumed only through `packages:`, versioned and locked). They never collapse:
|
|
156
|
+
|
|
157
|
+
- `from: <repo key>` looks only at `capabilities/<name>/oats.json`. A name found only in the repo's
|
|
158
|
+
package is `E_CAPABILITY_MISSING` with `details.hint: "provided by package <id>; use from: package"`.
|
|
159
|
+
- `from: package` looks only in the lock, even when the package's repo is a member.
|
|
160
|
+
- A `packages:` value is a **bare version** (`v2.1.3`, through the official catalog) or
|
|
161
|
+
**`git:<repo>@<ref>`** (`<repo>` any §1 form, `<ref>` a tag or full OID). There is no third form;
|
|
162
|
+
`classifyPackageValue` is the one grammar. A branch is refused (§4.3).
|
|
109
163
|
|
|
110
|
-
|
|
164
|
+
---
|
|
111
165
|
|
|
112
|
-
|
|
113
|
-
export function loadLocal(dir) // walks up from dir to find oats-local.yaml → { path, local } | E_LOCAL_MISSING
|
|
114
|
-
export async function observeWorkspace(ref, { at } = {})
|
|
115
|
-
// → { key, commit, workspace: <parsed+validated>, observedAt } E_REMOTE_UNREADABLE | E_WORKSPACE_SCHEMA { path, problems[] }
|
|
116
|
-
// A repo without oats-workspace.yaml is E_WORKSPACE_SCHEMA (it is not a workspace host), never a leaked E_REMOTE_PATH_MISSING.
|
|
117
|
-
|
|
118
|
-
export async function confirmMembership(workspaceObs, memberRef)
|
|
119
|
-
// Reads the member's oats-membership.yaml at its default branch in the SAME access context.
|
|
120
|
-
// → { key, commit, confirmed: true, team } |
|
|
121
|
-
// { key, confirmed: false, reason: "not-listed"|"no-backlink"|"backlink-elsewhere"|"cannot-read", detail }
|
|
122
|
-
// Never throws for an unconfirmed member (ANY E_REMOTE_* about the member's file — oversize, symlink, unsafe
|
|
123
|
-
// tree — is an unconfirmed row; an unparseable memberRef is "not-listed"); throws only on schema errors of the
|
|
124
|
-
// WORKSPACE file. Repo keys are case-sensitive in the path part; a backlink that differs only by case is
|
|
125
|
-
// "backlink-elsewhere" with `caseOnly: true` and a hint in `detail`.
|
|
126
|
-
|
|
127
|
-
export async function discoverWorkspace(ref, { at, local } = {})
|
|
128
|
-
// The whole picture, in one access context:
|
|
129
|
-
// → { workspace, members: [{ key, commit, confirmed, reason?, team, souls: [SoulEntry], capabilities: [CapEntry],
|
|
130
|
-
// publishes: { package, version } | null }],
|
|
131
|
-
// external: [{ source, commit, soul: SoulEntry }], problems: [{ code, path, message }] }
|
|
132
|
-
// A remote failure on ONE member's directory listing is a problem of that member, never an abort.
|
|
133
|
-
// SoulEntry = { name, path, repoKey, commit, team, private, definition } (definition = validated soul.yaml)
|
|
134
|
-
// CapEntry = { name, path, repoKey, commit, team, private, manifest }
|
|
135
|
-
// Unconfirmed members contribute nothing but their row. `private` items are included with private:true (callers filter).
|
|
136
|
-
// Unknown team labels on items → problems[] E_TEAM_UNKNOWN (the item is still listed).
|
|
137
|
-
|
|
138
|
-
export function standaloneRepo(ref, commit, discovery?)
|
|
139
|
-
// For a readable member whose workspace cannot be read: souls with from:here capabilities only.
|
|
140
|
-
```
|
|
166
|
+
## 3. `lib/resolve.mjs` — from a soul to an immutable resolution
|
|
141
167
|
|
|
142
|
-
|
|
168
|
+
`resolveSoul(discovery, soulEntry, { local, lock, spawn: { providers }, catalog })` → Resolution.
|
|
143
169
|
|
|
144
|
-
###
|
|
170
|
+
### 3.1 Membership gate
|
|
145
171
|
|
|
146
|
-
|
|
172
|
+
The soul must be one the discovery lists: a confirmed member's (same `repoKey`, `name`, `commit`; or the
|
|
173
|
+
repo's own, standalone), an `external[]` soul, or a listed package soul. Otherwise `E_NOT_A_MEMBER`,
|
|
174
|
+
`E_MEMBERSHIP_UNCONFIRMED { reason }` (`reason: "stale"` for an entry the row lacks), or
|
|
175
|
+
`E_PACKAGE_MISSING { reason: "stale" }`. An in-memory lock is validated (`E_LOCK_SCHEMA`).
|
|
147
176
|
|
|
148
|
-
|
|
149
|
-
- `from: package` looks ONLY in the lock (`packageProviding`). It never looks at member capabilities, even when the package's repo is a member.
|
|
150
|
-
- `discoverWorkspace` lists a member's `oats-package/` presence as `publishes: { package, version }` on the member row (informational) and does NOT enumerate the package's capabilities as member capabilities.
|
|
151
|
-
- `packages:` values: a **bare version** (`v2.1.3`) resolves through the official catalog (`package-catalog.json` in the workspace's `catalog:` source, default the `oats` repo's); a **`git:<repo>@<ref>`** value is a direct package ref, where `<repo>` is any ref §1 understands — `github.com/org/repo`, `https://…`, `git@host:…`, `/abs/bare.git`, `file:///…` — and `<ref>` a tag name or full OID. Both are packages. There is no third form: `lib/packages.mjs#classifyPackageValue` is the one grammar, used by `validateWorkspace` and `resolvePackages`. A `<ref>` (or catalog ref) that resolves to a **branch** is refused (`E_PACKAGE_INTEGRITY { why: "branch" }`) — versions are immutable.
|
|
177
|
+
### 3.2 Composition and lookups
|
|
152
178
|
|
|
153
|
-
|
|
179
|
+
Order, soul wins, `off` removes: `defaults.<slot>` ⊕ `defaults.capabilities` ⊕ `soul.capabilities`.
|
|
180
|
+
Teams compose nothing: composition is the same for every person and machine.
|
|
154
181
|
|
|
155
|
-
|
|
182
|
+
- `from: here`: the soul's repo; in a package soul, its own locked package.
|
|
183
|
+
- `from: <repo key>`: a confirmed member (`E_NOT_A_MEMBER`) listing it (`E_CAPABILITY_MISSING`). A
|
|
184
|
+
`private` capability serves only its own repo's souls (`E_CAPABILITY_PRIVATE`).
|
|
185
|
+
- `from: package`: a lock (`E_PACKAGE_MISSING { reason: "no-lock" }`), one locked provider
|
|
186
|
+
(`E_PACKAGE_MISSING`, `{ ambiguous }` for two), still declared (`reason: "undeclared"`). The package
|
|
187
|
+
must list exactly the lock's capabilities (`E_PACKAGE_INTEGRITY { why: "capabilities" }`).
|
|
188
|
+
|
|
189
|
+
Declaring a package is the trust decision; there is no approval gate. Member capabilities are trusted by
|
|
190
|
+
membership and their hooks run on every operator's machine, so a mixed public/private organisation keeps
|
|
191
|
+
executable capabilities in packages or private members.
|
|
192
|
+
|
|
193
|
+
### 3.3 Slots
|
|
194
|
+
|
|
195
|
+
- A module with `layer: <slot>` fills that slot; two are `E_SLOT_CONFLICT`.
|
|
196
|
+
- A slot default must be of that layer (`E_SLOT_CONFLICT { reason: "layer-mismatch" }`).
|
|
197
|
+
- A soul's `<slot>: none` drops `defaults.<slot>` and every capability of that layer the workspace
|
|
198
|
+
defaults contributed. One the soul itself declares beside `none` is `E_SLOT_CONFLICT { reason: "none" }`.
|
|
199
|
+
|
|
200
|
+
### 3.4 Payloads
|
|
201
|
+
|
|
202
|
+
Merged per module, later wins (objects deep-merge): manifest `settings.<key>.default` ⊕
|
|
203
|
+
`workspace.messaging` (messaging layer) ⊕ soul slot payload ⊕ `oats-local.yaml settings.<cap>` ⊕
|
|
204
|
+
`--provider <cap> k=v`. All refusals are `E_WORKSPACE_SCHEMA` with a `reason`:
|
|
205
|
+
|
|
206
|
+
- `poison-key`: `__proto__`, `constructor` or `prototype` at any depth;
|
|
207
|
+
- `removed-key`: top-level `byTeam` (any segment of a `--provider` key);
|
|
208
|
+
- `host-only-key`: a manifest `hostOnly` key outside `oats-local.yaml`, so host paths stay out of
|
|
209
|
+
committed files;
|
|
210
|
+
- `setting-value`: a value outside `settings.<key>.values`.
|
|
211
|
+
|
|
212
|
+
`--provider` for a capability the soul does not resolve is `E_CAPABILITY_MISSING`. The kernel merges and
|
|
213
|
+
delivers; what a provider consumes is its own contract. Teams reach providers as `OATS_DEFAULT_TEAM*` and
|
|
214
|
+
`OATS_TEAMS`, not in settings.
|
|
215
|
+
|
|
216
|
+
### 3.5 Checks
|
|
217
|
+
|
|
218
|
+
- `skills[]` entries must hold `SKILL.md` or `<skill>/SKILL.md` (`E_CAPABILITY_MISSING` or
|
|
219
|
+
`E_PACKAGE_MANIFEST`); a skill name twice is `E_SKILL_DUPLICATE`.
|
|
220
|
+
- `compatibility.oats` must admit the kernel (`E_CAPABILITY_INCOMPATIBLE`); `agents:` in a manifest is
|
|
221
|
+
`E_CAPABILITY_AGENTS_REMOVED`.
|
|
222
|
+
- `soul.compatibility` floors apply to package versions; an OID or non-version pin is
|
|
223
|
+
`E_COMPATIBILITY { why: "unversioned" }`.
|
|
224
|
+
|
|
225
|
+
### 3.6 The Resolution
|
|
156
226
|
|
|
157
227
|
```js
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// modules: [ { name, from: { kind: "member", repoKey, commit } | { kind: "package", package, version, commit, integrity },
|
|
165
|
-
// manifest, layer: "knowledge"|"messaging"|"tasks"|null, private } ],
|
|
166
|
-
// slots: { knowledge: <module name>|null, messaging, tasks },
|
|
167
|
-
// payloads: { <cap>: <merged provider payload: soul ⊕ local.settings[cap] ⊕ spawn.providers[cap]> },
|
|
168
|
-
// skills: [ { module, name, path } ], // composed skill set; duplicates → E_SKILL_DUPLICATE { name, modules }
|
|
169
|
-
// injects: [ { module, path } ],
|
|
170
|
-
// revision: "<sha256 of the canonical JSON of everything above>[0:24]"
|
|
171
|
-
// }
|
|
172
|
-
// Order: workspace.defaults.capabilities ⊕ defaults.byTeam[soul.team] ⊕ soul.capabilities (soul wins; `off` removes).
|
|
173
|
-
// from:here → soul.repoKey. from:<repo> → must be a confirmed member (E_NOT_A_MEMBER) that has the capability
|
|
174
|
-
// (E_CAPABILITY_MISSING), not private unless same repo (E_CAPABILITY_PRIVATE). from:package → lock.packages must
|
|
175
|
-
// provide it (E_PACKAGE_MISSING) and be approved (E_PACKAGE_UNAPPROVED).
|
|
176
|
-
// Slots: a module with manifest.layer fills that slot; two → E_SLOT_CONFLICT; soul `none` empties; else workspace default.
|
|
177
|
-
// compatibility floors checked against package versions → E_COMPATIBILITY.
|
|
228
|
+
{ resolutionApi: 1, soul: { name, repoKey, commit, path },
|
|
229
|
+
modules: [{ name, layer, private, dir, manifest,
|
|
230
|
+
from: { kind: "member", repoKey, commit }
|
|
231
|
+
| { kind: "package", package, version, commit, integrity, repoKey } }],
|
|
232
|
+
slots, skills, injects, payloads, payloadOrigins,
|
|
233
|
+
teams, defaultTeam, slotsFrom, capabilitiesFrom, turnedOff, declRevision, payloadRevision, revision }
|
|
178
234
|
```
|
|
179
235
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
- **Compatibility floors** need a version: a package pinned by OID (`git:<repo>@<OID>` records the OID as its version) or any non-version string → `E_COMPATIBILITY { why: "unversioned", capability, package, version, range }`; every `E_COMPATIBILITY` names `capability` and `package`.
|
|
185
|
-
- **Recorded for materialize** (extra fields, part of the revision): `module.dir` — the capability directory as the manifest lists it (repo-relative; a package's `oats-package.json#capabilities[]` entry, which need not equal the capability name), and `from.repoKey` on package modules. An in-memory `lock` is validated like one read from disk (`E_LOCK_SCHEMA`); `approved` must be a well-formed `{ executables: sha256-…, at }`, not merely truthy.
|
|
236
|
+
`module.dir` is the listed capability directory (for a package, the `oats-package.json#capabilities[]`
|
|
237
|
+
entry). `declRevision` covers `{ resolutionApi, soul, modules, slots, skills, injects }`,
|
|
238
|
+
`payloadRevision` the payloads, `revision` both (24 hex of SHA-256 over canonical JSON). A spawn decision
|
|
239
|
+
binds `revision`. Provenance fields enter neither revision.
|
|
186
240
|
|
|
187
241
|
---
|
|
188
242
|
|
|
189
|
-
## 4. `lib/packages.mjs` —
|
|
243
|
+
## 4. `lib/packages.mjs` — packages and lock v3
|
|
190
244
|
|
|
191
|
-
|
|
245
|
+
Nothing is installed; `oats-lock.json` and `oats-local.yaml` are the only persisted deployment state.
|
|
246
|
+
|
|
247
|
+
### 4.1 Lock v3
|
|
192
248
|
|
|
193
249
|
```js
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// (clarified Phase B) `url` is the repo url the package was read from (observeRemote's `url`): the package's repo
|
|
198
|
-
// identity travels in the lock, so resolveSoul/materialize of a catalog-locked package need no catalog at spawn time.
|
|
199
|
-
|
|
200
|
-
export async function resolvePackages(workspace, { catalog, lock })
|
|
201
|
-
// For each workspace.packages entry: catalog lookup or git ref → observeRemote → commit; read oats-package.json at
|
|
202
|
-
// path; enumerate its capability manifests; integrity = contentDigest of the package tree.
|
|
203
|
-
// → { lock: <updated>, changes: [{ id, from: <old version|null>, to: <version>, commit, approvalNeeded: bool }] }
|
|
204
|
-
// A locked entry whose (version → commit) changed → E_PACKAGE_INTEGRITY unless the version string also changed.
|
|
205
|
-
// An unchanged entry keeps its approval ONLY if executablesDigest(tree) still equals approved.executables
|
|
206
|
-
// (else E_PACKAGE_UNAPPROVED). A catalog `path` change at the same version is a new (unapproved) entry.
|
|
207
|
-
// `remote` defaults to lib/remote.mjs; `remoteOptions` is threaded through.
|
|
208
|
-
|
|
209
|
-
export function executablesDigest(packageTree) // sha256 over every manifest's `commands` targets' AND
|
|
210
|
-
// `hooks.*.command` targets' bytes (hooks run unattended at
|
|
211
|
-
// spawn/retire), codepoint order, locale-independent
|
|
212
|
-
// (clarified Phase B) a hook object without `command` is
|
|
213
|
-
// E_PACKAGE_MANIFEST — never an invisible no-op
|
|
214
|
-
export function approve(lock, id, digest, at) // records approval; returns new lock
|
|
215
|
-
export function packageProviding(lock, capName) // → { id, entry } | null; two providers → E_PACKAGE_MISSING { ambiguous }
|
|
216
|
-
// (a package declaring one capability twice → E_PACKAGE_MANIFEST)
|
|
250
|
+
{ lockfileVersion: 3, packages: { <id>: {
|
|
251
|
+
source: "catalog:<id>" | "git:<key>@<ref>", url, path, version, commit, integrity,
|
|
252
|
+
capabilities: [<cap>], souls?: [{ name, path, digest }] } } }
|
|
217
253
|
```
|
|
218
254
|
|
|
219
|
-
`
|
|
255
|
+
`readLock` (missing file: empty lock) and `writeLock` (atomic, canonical). `url` lets spawn work without
|
|
256
|
+
a catalog; `integrity` is the content digest of the tree at `path`. Any other `lockfileVersion` is
|
|
257
|
+
`E_LOCK_SCHEMA`. A legacy `approved` field is ignored and dropped on write.
|
|
258
|
+
|
|
259
|
+
### 4.2 `resolvePackages(workspace, { catalog, lock })`
|
|
260
|
+
|
|
261
|
+
A catalog version resolves through `package-catalog.json` shipped with the kernel
|
|
262
|
+
(`OATS_PACKAGE_CATALOG` overrides; unknown id: `E_PACKAGE_MISSING`); a git value reads `oats-package/`.
|
|
263
|
+
Each entry's manifests, souls and integrity are read at the observed commit →
|
|
264
|
+
`{ lock, changes: [{ id, from, to, commit }] }` (`to: null`: no longer declared).
|
|
265
|
+
|
|
266
|
+
- A branch is `E_PACKAGE_INTEGRITY { why: "branch" }`.
|
|
267
|
+
- An unchanged version, source and path is re-verified: a moved commit or different integrity,
|
|
268
|
+
capabilities or souls is `E_PACKAGE_INTEGRITY`.
|
|
269
|
+
- A malformed package is `E_PACKAGE_MANIFEST`.
|
|
270
|
+
|
|
271
|
+
`packageProviding(lock, cap)` → `{ id, entry }`, `null` or `E_PACKAGE_MISSING { ambiguous }`.
|
|
272
|
+
|
|
273
|
+
### 4.3 Trust and removed APIs
|
|
274
|
+
|
|
275
|
+
There is no approval step or record: declaring a package in `packages:` is the trust decision, and the
|
|
276
|
+
lock pins commit and integrity. `oats sync --approve` is `E_BAD_ARGS`. Pre-workspace exports remain as
|
|
277
|
+
shims that throw `E_REMOVED { name, contract }`, pointing at this record.
|
|
220
278
|
|
|
221
279
|
---
|
|
222
280
|
|
|
223
281
|
## 5. `lib/materialize.mjs` — copy whole, compose, record
|
|
224
282
|
|
|
225
|
-
|
|
283
|
+
### 5.1 `materialize(resolution, home, options)`
|
|
284
|
+
|
|
285
|
+
1. Fetch each module at `from.commit` from `module.dir` into `<home>/.oats/modules/<name>/`. A package
|
|
286
|
+
must match the lock's commit (`E_MATERIALIZE_INTEGRITY { why: "lock" }`); an unknown repo is
|
|
287
|
+
`E_MATERIALIZE_SOURCE`.
|
|
288
|
+
2. The copy's digest must equal the fetch's and any `module.digest` (`E_MATERIALIZE_INTEGRITY`).
|
|
289
|
+
3. Copy skills whole to `<home>/.agents/skills/<name>/<skill>/`.
|
|
290
|
+
4. Compose `<home>/AGENTS.md` = soul body (`options.soulAgentsMd` or `soulDir`) + kernel blocks + module
|
|
291
|
+
injects; operating guidance comes from a module such as `oats.core`. Keep `CLAUDE.md → AGENTS.md`
|
|
292
|
+
and `.claude/skills → ../.agents/skills`.
|
|
293
|
+
5. Record `modules: { <name>: { from, commit, digest, materializedAt } }`, `providers` (the payloads) and
|
|
294
|
+
`resolutionRevision` in `instance.json`.
|
|
295
|
+
|
|
296
|
+
Everything is staged, then renamed in with a rollback journal: any failure restores the previous files
|
|
297
|
+
(`E_MATERIALIZE_HOME { why }`; a concurrent run is `why: "busy"`).
|
|
298
|
+
|
|
299
|
+
### 5.2 Soul source and instance record
|
|
300
|
+
|
|
301
|
+
- A workspace soul's source goes to `agents/<agent>/souls/<commit12>/`, immutable and never removed;
|
|
302
|
+
`agents/<agent>/soul` is an atomically swapped symlink to the current one. A package soul must match
|
|
303
|
+
its locked digest (`why: "soul-digest"`); a source without `soul.yaml` or `AGENTS.md` is
|
|
304
|
+
`E_SOUL_INCOMPLETE`. A preview writes nothing.
|
|
305
|
+
- Homes carry no soul link. `instance.json` records `soulDir` (the instance's own commit directory),
|
|
306
|
+
`workspace: { key, name, deployment, commit, resolution, standalone, soul: { id, repoKey, commit },
|
|
307
|
+
layers }`, `teams`, `defaultTeam` and `capabilityMeta` (hook `meta`, merged at spawn and every launch;
|
|
308
|
+
retire reads it as `OATS_META`).
|
|
309
|
+
- Hooks get `OATS_SOUL` (`soulDir`) and `OATS_SOUL_ID`, a stable key for provider state:
|
|
310
|
+
`<repo key>#<soul>`, or `package:<id>#<soul>` for a package soul.
|
|
311
|
+
|
|
312
|
+
### 5.3 Drift
|
|
313
|
+
|
|
314
|
+
`driftOf(instanceJson, discovery, { lock })` → `[{ module, from, recorded, current, status, reason? }]`,
|
|
315
|
+
`status` ∈ `current | moved | missing`, against the member's commit or the lock. `soulDriftOf` does the
|
|
316
|
+
same for the recorded soul. Drift is shown, never prevented.
|
|
226
317
|
|
|
227
|
-
|
|
228
|
-
export async function materialize(resolution, home, { fetch = fetchRemoteTree } = {})
|
|
229
|
-
// For each module: fetch its capability dir — the resolver-recorded module.dir (the manifest-listed directory: member
|
|
230
|
-
// <repo>@<commit>/<dir>; package <pkg>@<commit>/<dir> where <dir> is the oats-package.json#capabilities[] entry, which
|
|
231
|
-
// need not equal <name>: capabilities/oats-okf → oats.okf) (clarified Phase B) —
|
|
232
|
-
// into <home>/.oats/modules/<name>/ ; verify contentDigest === module.digest (recorded); copy skills/* into
|
|
233
|
-
// <home>/.agents/skills/<name>/<skill>/ (full copy, not symlink).
|
|
234
|
-
// Compose <home>/AGENTS.md = soul AGENTS.md + each module inject (existing kernel composer; marker comments unchanged).
|
|
235
|
-
// Keep aliases: CLAUDE.md → AGENTS.md ; .claude/skills → ../.agents/skills (relative symlinks, as today).
|
|
236
|
-
// Write instance.json.modules = { <name>: { from, commit, digest, materializedAt } } and instance.json.providers = resolution.payloads.
|
|
237
|
-
// → { modules: […], skills: […], agentsMd: <path> } Any failure → nothing left behind (staging dir + rename).
|
|
238
|
-
// (clarified Phase B) The transaction includes the aliases and the AGENTS.md/instance.json swap: a failure at any
|
|
239
|
-
// commit step rolls back everything placed and restores the previous files (E_MATERIALIZE_HOME { why }); the home's
|
|
240
|
-
// shape (.oats, .agents, .claude and module targets: real directories or absent) is re-checked immediately before
|
|
241
|
-
// the renames; staging is unique per call; two materializations racing on one home → E_MATERIALIZE_HOME { why: "busy" }.
|
|
242
|
-
// (clarified Phase B) The soul body is the LOCAL soul directory — options.soulAgentsMd / options.soulDir, else
|
|
243
|
-
// <home>/soul/AGENTS.md through the instance's `soul` link into the member clone (decision 9: the work target is
|
|
244
|
-
// the only thing that needs a clone). fetchRemoteTree is not a soul-copy primitive; a soul's CLAUDE.md → AGENTS.md
|
|
245
|
-
// alias never crosses the remote.
|
|
246
|
-
|
|
247
|
-
export function driftOf(instanceJson, discovery)
|
|
248
|
-
// → [{ module, recorded: { repoKey, commit }, current: { commit } | null, status: "current"|"moved"|"missing" }]
|
|
249
|
-
```
|
|
318
|
+
---
|
|
250
319
|
|
|
251
|
-
|
|
320
|
+
## 6. CLI verbs and DTOs (`bin/oats.mjs`)
|
|
252
321
|
|
|
253
|
-
|
|
322
|
+
Verbs take `--dir` (walks up to `oats-local.yaml`) and `--json` (one envelope).
|
|
323
|
+
|
|
324
|
+
### 6.1 `oats onboard [<dir>] --workspace <repo ref>`
|
|
325
|
+
|
|
326
|
+
Writes `oats-local.yaml` and `agents/`, then runs the `sync` body. An existing `oats-local.yaml` there is
|
|
327
|
+
`E_ALREADY_ONBOARDED`; a failure before the workspace is read rolls back (`details.rolledBack: true`).
|
|
328
|
+
→ `{ onboardApi: 2, standalone?, local, dir, agents, lock, sync, hosting: { host, hostIsMember, rule },
|
|
329
|
+
next: { clone: [{ key, name, url, dir, present, host }], spawn, souls } }`.
|
|
330
|
+
|
|
331
|
+
### 6.2 `oats sync`
|
|
332
|
+
|
|
333
|
+
Discovers, resolves `packages:`, writes the lock, creates `agents/`, refreshes the automations
|
|
334
|
+
snapshot (standalone: only the catalog package providing `oats.core`). → `{ syncApi: 1, standalone?,
|
|
335
|
+
workspace: { name, key, url, commit, observedAt, local, lock }, members: [{ key, name, commit,
|
|
336
|
+
confirmed, status, detail, souls, capabilities, publishes }], packages: [{ id, version, source, commit,
|
|
337
|
+
integrity, capabilities, souls }], changes, problems, warnings, automations }`. Text: §8.
|
|
338
|
+
|
|
339
|
+
### 6.3 `oats package add <id> <value> | remove <id>`
|
|
340
|
+
|
|
341
|
+
Edits `packages:` only when `oats-workspace.yaml` is tracked by its checkout; otherwise prints the change
|
|
342
|
+
to make. Invalid value: `E_WORKSPACE_SCHEMA`; removing an undeclared id: `E_PACKAGE_MISSING`. Receipts:
|
|
343
|
+
`{ action, id, value, previous, edited: true, file }` or `{ action, id, value, edited: false, file: null,
|
|
344
|
+
line, hint }` (`line: null` for `remove`).
|
|
345
|
+
|
|
346
|
+
### 6.4 `oats workspace status`, `oats capabilities`, `oats souls`
|
|
347
|
+
|
|
348
|
+
- `workspace status` → `{ workspaceStatusApi: 1, standalone?, workspace: { name, key, url, commit,
|
|
349
|
+
observedAt, local, teams, file }, members, packages, declaredPackages, unsynced, stale, external,
|
|
350
|
+
automations, defaults, clones, disabledSouls, lock }`.
|
|
351
|
+
- `capabilities` / `souls` → `{ capabilitiesApi | soulsApi: 1, standalone?, workspace, capabilities |
|
|
352
|
+
souls, problems }`: every item of confirmed members, external and package souls, and locked package
|
|
353
|
+
capabilities, with `origin` and `kind`. Private capabilities carry `private: true`; soul rows carry
|
|
354
|
+
`teams` and `defaultTeam`.
|
|
355
|
+
|
|
356
|
+
### 6.5 `oats spawn <soul>`
|
|
254
357
|
|
|
255
|
-
|
|
358
|
+
- A bare name must be unique (`E_SOUL_UNKNOWN`, `E_SOUL_AMBIGUOUS`); `<member>/<soul>` or
|
|
359
|
+
`<package>/<soul>` qualifies it. A disabled soul is `E_SOUL_DISABLED`.
|
|
360
|
+
- Discover → resolve → materialize; `--provider <cap> <key>=<value>` is the spawn payload layer.
|
|
361
|
+
- `--preview` adds `modules` (`{ name, from, layer, private, declares, changedSince }`), `teams`,
|
|
362
|
+
`defaultTeam`, `resolution`, `declRevision`, `payloadRevision`, `providers` (as typed), `settings` and
|
|
363
|
+
`settingsOrigins`. The decision binds `revision` and `effective.providers` (`E_DECISION_STALE`).
|
|
364
|
+
- Member clone for `worktree | checkout`, first hit wins: `--repo`; `clones:`; `<deployment>/<member>`
|
|
365
|
+
(`agents` → `agents-repo`). None is `E_CLONE_MISSING`; a clone of another repo `E_CLONE_MISMATCH`.
|
|
366
|
+
- `work: workspace`: `./work` is the deployment directory; no branch is recorded.
|
|
256
367
|
|
|
257
|
-
|
|
258
|
-
- `oats package add <id> <version> | remove <id>` — edits `packages:` in the workspace file **when the workspace repo is the current checkout**; otherwise prints the line to add (the workspace file is shared through Git).
|
|
259
|
-
- `oats spawn <soul> …` — preview/apply unchanged in shape; the decision now embeds `resolution.revision`; `--provider <cap> k=v` (repeatable). Preview lists `modules[]` with `changedSince` (previous instance of the soul) and `team`.
|
|
260
|
-
- `oats capabilities` / `oats souls` — every non-private item of every confirmed member + packages, with `origin` (`member <key> @ <commit>` | `package <id> v<ver>`) and `team`. `--json`.
|
|
261
|
-
- `oats workspace status` — membership table (`confirmed` / `no-backlink` / `cannot-read` …), packages, approval state.
|
|
262
|
-
- `oats status` — per instance `modules` with `driftOf`.
|
|
263
|
-
- `oats version --json` — `workspaceApi: 2`, features `+workspace-v2`, `+instance-modules`, `+spawn-provider-payload`. Removed: `init`, `use`, `install`, `restore`.
|
|
264
|
-
*(clarified Phase B)* A feature string is listed only once the binary implements it: `instance-modules` and `spawn-provider-payload` appear when `oats spawn` runs on resolve/materialize (Phase C), not before. A removed verb answers `E_UNKNOWN_COMMAND` naming its replacement in BOTH text and `--json` (`details.removed`/`replacement`), checked before capability dispatch. `oats status` without a deployment is `E_NO_DEPLOYMENT` in both modes.
|
|
265
|
-
*(clarified Phase B)* `oats package add|remove` edits the file only when it is **tracked** by the checkout it sits in (`git ls-files`); an untracked copy gets "the line to add".
|
|
368
|
+
### 6.6 `oats status`
|
|
266
369
|
|
|
267
|
-
|
|
370
|
+
`--json` adds, per `agents[].instances[]`, `modules` and `soul` drift rows (§5.3) and `identity` from the
|
|
371
|
+
messaging capability's meta. An unreachable workspace gives `workspace: { reachable: false }`. Outside a
|
|
372
|
+
deployment: `E_NO_DEPLOYMENT`.
|
|
373
|
+
|
|
374
|
+
### 6.7 Capability commands from a deployment
|
|
375
|
+
|
|
376
|
+
Inside a home, `oats <ns> <cmd>` uses the home's modules. From a deployment, `lib/operator-dispatch.mjs`
|
|
377
|
+
resolves as a spawn of `--soul <name>` would (missing `--soul`: `E_BAD_ARGS`). The module whose
|
|
378
|
+
`manifest.command` is `<ns>` is fetched into `<deployment>/.oats/modules/<cap>@<commit12>/`, digest-verified
|
|
379
|
+
(`E_PACKAGE_INTEGRITY { why: "module-store" }`), and run with `OATS_SETTINGS` (its merged payload) and
|
|
380
|
+
`OATS_CLI_BIN`. Two claimants: `E_DUPLICATE_NAMESPACE`; none: `E_UNKNOWN_COMMAND`.
|
|
381
|
+
|
|
382
|
+
### 6.8 `oats version --json` and removed verbs
|
|
383
|
+
|
|
384
|
+
`workspaceApi: 2`; features are listed only once implemented (`workspace-v2`, `instance-modules`,
|
|
385
|
+
`spawn-provider-payload`, `packages-no-approval`, `package-souls`, `team-model-2`, among others). Removed
|
|
386
|
+
verbs (`prepare`, `create`, `type`, `install`, `restore`, `init`, `use`, `trust`, `list`, `catalog`,
|
|
387
|
+
`remove`, `migrate`, `config`, `inject`) answer `E_UNKNOWN_COMMAND` with `details.removed` and
|
|
388
|
+
`details.replacement`; `session recompose` is also `E_UNKNOWN_COMMAND` (re-spawn instead).
|
|
268
389
|
|
|
269
390
|
---
|
|
270
391
|
|
|
271
392
|
## 7. Test fixture — `test/fixtures/northwind/build.mjs`
|
|
272
393
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
394
|
+
`buildNorthwind(baseDir)` builds bare Git repos under `<baseDir>/remotes/`:
|
|
395
|
+
|
|
396
|
+
| Repo | Role |
|
|
397
|
+
|---|---|
|
|
398
|
+
| `agents` | host and member (`release-manager`, `support-triager`; `nw-release-tooling`, `nw-house-style`) |
|
|
399
|
+
| `platform`, `data`, `marketing` | members with souls and capabilities, some executable |
|
|
400
|
+
| `nw-tools` | member that also publishes package `nw.tools` v0.4.0 (`nw-lint`, `nw-deploy`) |
|
|
401
|
+
| `knowledge` | a store, not a member |
|
|
402
|
+
| `experts` | external soul `security-reviewer`, pinned by OID |
|
|
403
|
+
| `pkg-okf`, `pkg-framework` | catalog packages `oats.okf` v2.1.3 (knowledge layer, hooks, a `hostOnly` setting) and `oats.framework` v1.1.3 (`oats.core`) |
|
|
404
|
+
|
|
405
|
+
There are exactly two catalog package repos; `nw.tools` is pinned `git:<nw-tools ref>@v0.4.0`, which
|
|
406
|
+
exercises the non-collapse rule. The workspace declares three shared team labels without ids,
|
|
407
|
+
`defaults.messaging` and `defaults.tasks` as `none`, and `from:` values as `local/<abs-path>` keys.
|
|
408
|
+
|
|
409
|
+
It returns `{ baseDir, remotesDir, refs, urls, keys, commits, tags, catalog, moves }`, is deterministic
|
|
410
|
+
(fixed identity and dates, hermetic git config), and refuses a `baseDir` with whitespace or `@`
|
|
411
|
+
(`E_FIXTURE_BASEDIR`) or an existing `remotes/` (`E_FIXTURE_EXISTS`). Helpers: `moveMember`,
|
|
412
|
+
`dropBacklink` (→ `no-backlink`), `makeUnreadable` (→ `cannot-read`, returns `restore`).
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## 8. The human sync report
|
|
417
|
+
|
|
418
|
+
`oats sync` and `oats onboard` without `--json` print:
|
|
419
|
+
|
|
420
|
+
```text
|
|
421
|
+
workspace <name> (<key> @ <commit8>)
|
|
422
|
+
members <name> ✓↔ (@ <commit8>) <name> ✗ (<status>)
|
|
423
|
+
packages <id> <version> ✓ (@ <commit8>)
|
|
424
|
+
changed <id> <from|—> → <to> (@ <commit8>) <id> <from> → removed
|
|
425
|
+
souls N discovered (M members, E external, P package, D disabled here) · K private capabilities
|
|
426
|
+
teams <label>, … (shared) · this deployment's: oats teams
|
|
427
|
+
automations T triggers, S schedules in the members (oats trigger list · oats schedule list)
|
|
428
|
+
problem <code> <member>:<path> <message>
|
|
429
|
+
warning <code> <message>
|
|
430
|
+
|
|
431
|
+
lock <path to oats-lock.json>
|
|
287
432
|
```
|
|
288
433
|
|
|
289
|
-
|
|
434
|
+
`changed` reads `(nothing — the lock already described this workspace)` when empty. A standalone view
|
|
435
|
+
prints its note in place of the name. `automations` appears only when there are some; `problem` and
|
|
436
|
+
`warning` repeat per item.
|
|
290
437
|
|
|
291
438
|
---
|
|
292
439
|
|
|
293
|
-
##
|
|
294
|
-
|
|
295
|
-
Appended, not edited in place; each item names the section it refines. Decision record: `workspace-model-v2.md` decisions 10, 23, 25.
|
|
296
|
-
|
|
297
|
-
**§2 `standaloneRepo` / `discoverOrStandalone` — standalone requires membership (M6/M7).** A standalone view is a **member** whose workspace cannot be read: the repo MUST carry an `oats-membership.yaml` (`discoverRepo(ref).membership` non-null). A repo with no backlink is not a workspace host and not a member → `E_WORKSPACE_SCHEMA` (the original "not a host" error is rethrown), never a standalone view. The fallback engages **only** when reading the host fails for **access** reasons — `E_REMOTE_UNREADABLE` with `reason: "auth"` (which is also how a permission denial classifies) | `"not-found"`; a `"network"` or `"timeout"` failure propagates unchanged (an offline operator is not a public contributor). The discovery it returns carries `standalone: true`, `workspace: null`, one member row (the repo's own, `confirmed: false, reason: "cannot-read"`) and `standaloneReason: { code, reason, url }` — the access failure that triggered it — so `sync`/`status`/`spawn` can report *why* the view is standalone. `oats-local.yaml` `standalone: <repo ref>` asks for the view explicitly (no host lookup); it too requires the repo to be a member.
|
|
298
|
-
|
|
299
|
-
**§2/§3 `oats.core` default (S4).** In the standalone view the kernel adds `oats.core: { from: package }` only when the soul's `capabilities:` says **nothing** about `oats.core`. Any mention — any `from:` (`package`, `here`, a repo key) or `off` — suppresses the default and the soul's own line is what resolves.
|
|
300
|
-
|
|
301
|
-
**§3 payload keys — `byTeam` is reserved (decision 23).** `byTeam` is legal ONLY at the top level of `workspace.messaging` (merged `base ⊕ byTeam[soul.team]`, then stripped). In every other payload layer — a soul's `knowledge:` / `messaging:` / `tasks:`, `oats-local.yaml` `settings.<cap>`, `spawn.providers[cap]` / `--provider` — at **any depth**, it is `E_WORKSPACE_SCHEMA { reason: "reserved-key", path, key: "byTeam" }`, alongside the existing `poison-key` rule. A provider never receives a `byTeam`.
|
|
302
|
-
|
|
303
|
-
**§5/§6 `instance.json.workspace.standalone`.** A prepared spawn writes `instance.json.workspace = { key, commit, resolution, standalone: <boolean>, soul: { repoKey, commit, team } }` next to `modules{}`, `providers{}` and `capabilities[]` (the `toCapabilityRows(resolution, home)` rows). `standalone` is `true` exactly when `prepared.discovery.standalone === true` (then `key` is the member repo's key); `false` on a workspace spawn. `oats sync --json` marks the same view `standalone: true` with `workspace.name = "standalone:<repo>"`; `oats status`/`workspace status` show it in their workspace field.
|
|
304
|
-
|
|
305
|
-
**§6 `oats onboard [<dir>] --workspace <repo ref> [--json]` → `onboardApi: 2` (M13).** The bootstrap (decision 9): writes `<dir>/oats-local.yaml` `{ schemaVersion: 2, workspace: <ref> }` and `<dir>/agents/`, then runs exactly the `sync` body (§6 `oats sync`) over that directory. Result `{ onboardApi: 2, local, dir, agents, lock, sync: <syncApi 1 report>, hosting: { host, hostIsMember, rule }, next: { clone: [{ key, name, url, dir }], spawn: "<setup-expert spawn command>" } }`; exit `2` with `ok: true` when `sync.approvalNeeded` is non-empty. `--workspace` is parsed (`E_REPO_REF`) before anything is written; a second onboard of the same directory is `E_ALREADY_ONBOARDED { local, dir }` (only THIS directory's file counts — an enclosing deployment is a different deployment); a failure before the workspace has been read (unreadable remote, not a host, package/lock errors of the first resolve) removes what onboarding created and carries `details.rolledBack: true, details.dir`; a failure after the read keeps the files (a lock may exist) and carries `details.dir, details.local`. Creates no soul, installs nothing, spawns nothing, writes no `oats-config.yaml`.
|
|
306
|
-
|
|
307
|
-
**§6 `oats package remove <id>` (M15).** BOTH branches — the file tracked by the checkout (edited) and untracked/absent (not edited) — answer `E_PACKAGE_MISSING { id, path? }` when `<id>` is not declared in `packages:`. The tracked receipt is `{ action: "remove", id, value: null, previous: <old value>, edited: true, file }`; the untracked receipt is `{ action: "remove", id, value: null, edited: false, file: null, line: null, hint }` — `previous` is absent and `line` is `null` (a removal has no line to add).
|
|
308
|
-
|
|
309
|
-
**§6 features.** `catalog` is no longer advertised in `oats version --json` `features` (the verb is removed; the official catalog is reached through `packages:` + `sync`, not a command).
|
|
310
|
-
|
|
311
|
-
## Post-0.25.0 clarifications (team review, 2026-09-23)
|
|
312
|
-
|
|
313
|
-
**Capability commands outside an instance home (`oats <ns> <cmd>` from the
|
|
314
|
-
deployment).** Inside an instance home the dispatcher resolves the namespace from
|
|
315
|
-
the home's materialized modules (`instance.json.modules` → `<home>/.oats/modules`);
|
|
316
|
-
that shipped in 0.25.0. Outside a home — the operator acts a knowledge layer needs
|
|
317
|
-
before any instance exists (`oats okf init`, base migration) — the intended rule
|
|
318
|
-
is: **resolve exactly as a spawn of `--soul <name>` would** (`prepareInstance` →
|
|
319
|
-
the soul's Resolution), fetch the namespace's capability into the deployment's
|
|
320
|
-
module store `<deployment>/.oats/modules/<cap>@<commit>/` (the same per-commit
|
|
321
|
-
store capability-defined agents use), and dispatch to that copy with the soul's
|
|
322
|
-
merged payload as `OATS_SETTINGS`. Never "the newest instance's copy" (an
|
|
323
|
-
instance is not an authority for the deployment) and never an unlocked cache
|
|
324
|
-
read (the lock's approval is the gate, as for spawn). `--soul` is required when
|
|
325
|
-
the namespace's capability is not a workspace default. **Status: 0.25.x
|
|
326
|
-
follow-up** — 0.25.0 still answers `E_CAPABILITY_INACTIVE` there (the pre-v2
|
|
327
|
-
chain); the interim is to run the module binary directly with `OATS_SETTINGS`
|
|
328
|
-
and `OATS_CLI_BIN`, as the tarball smoke does.
|
|
329
|
-
|
|
330
|
-
**`work: workspace` is kept.** A coordination soul's `./work` is the deployment
|
|
331
|
-
boundary — the directory holding `oats-local.yaml` (whatever the operator
|
|
332
|
-
named it, member clones beside it or named in `clones:`) — read-only across member
|
|
333
|
-
clones, no branch recorded. The clone map in `oats-local.yaml` (`clones:`) is
|
|
334
|
-
how such a soul finds a member whose clone is elsewhere. **Status: the
|
|
335
|
-
directory link is the intent; 0.25.0's kernel still derives the boundary from
|
|
336
|
-
the classic `team:` scope (`docs/souls-and-instances.md` open thread) — 0.25.x
|
|
337
|
-
follow-up binds it to the `oats-local.yaml` directory.**
|
|
338
|
-
|
|
339
|
-
**`identity.source` (oats.aweb) is the absolute path of the `.aw` directory to
|
|
340
|
-
retain**, given per spawn (`--provider oats.aweb identity.source=/abs/.aw`) or
|
|
341
|
-
per machine (`oats-local.yaml settings.oats.aweb.identity.source`); the kernel
|
|
342
|
-
resolves no symbolic seat names. Absolute paths never enter the workspace file
|
|
343
|
-
(decision 14).
|
|
344
|
-
|
|
345
|
-
**Member capabilities are a code-execution boundary** (decision 2: membership is
|
|
346
|
-
the trust; hooks and scripts of every member's default branch run on every
|
|
347
|
-
operator's machine at spawn). For a mixed public/private organisation the
|
|
348
|
-
recommendation is: **souls only in public members; executable capabilities come
|
|
349
|
-
from packages (approved per version) or from private members.** The onboarding
|
|
350
|
-
skill (Phase E) states this beside the hosting rule (decision 26).
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
### 0.25.1 fix round (team review, 2026-09-23) — appended, not edited in place
|
|
354
|
-
|
|
355
|
-
Each item names the section it refines and the review finding it closes. The
|
|
356
|
-
implementation lands in kernel 0.25.1 (`docs/release-notes/v0.25.1.md`); no
|
|
357
|
-
API integer or feature name changes.
|
|
358
|
-
|
|
359
|
-
**§3 slot `none` (L1).** A soul's `none` for a slot **empties the slot and drops
|
|
360
|
-
any layer-bearing capability the WORKSPACE DEFAULTS contributed for that
|
|
361
|
-
layer** — whether it arrived through `defaults.<slot>`, `defaults.capabilities`
|
|
362
|
-
or `defaults.byTeam[team].capabilities`. A layer-bearing capability **the soul
|
|
363
|
-
itself declares** in its own `capabilities:` alongside `none` for that layer is
|
|
364
|
-
`E_SLOT_CONFLICT { reason: "none" }` (spell `<cap>: off` to remove it). This
|
|
365
|
-
replaces the Phase B reading under which a layered default arriving via
|
|
366
|
-
`defaults.capabilities` was itself a conflict: the workspace's choice is a
|
|
367
|
-
default and `none` is the soul's answer to it; only the soul contradicting
|
|
368
|
-
itself is loud.
|
|
369
|
-
|
|
370
|
-
**§2/§5 per-commit soul cache (M1).** `ensureWorkspaceSoul` fetches a soul's
|
|
371
|
-
source at `(repoKey, commit)` into `<deployment>/agents/<name>/souls/<commit12>/`
|
|
372
|
-
— **immutable once written** (staged, then renamed in; never removed by the
|
|
373
|
-
kernel) — and maintains `<deployment>/agents/<name>/soul` as a **symlink to the
|
|
374
|
-
current commit's directory**, swapped atomically (symlink to a temp name +
|
|
375
|
-
rename over) so classic readers (`findAgent`, `doctor`, the classic spawn path)
|
|
376
|
-
keep seeing "current". A spawned home's `<home>/soul` links **its own commit's
|
|
377
|
-
directory** (the realpath of `souls/<commit12>/`), never the swapped pointer:
|
|
378
|
-
a running instance's soul never changes under it (decision 7), a `--preview`
|
|
379
|
-
may fetch a new commit and swap the pointer without touching any directory an
|
|
380
|
-
instance links, and OKF 2's owner pin (`owners.json` = `realpath(<home>/soul)`)
|
|
381
|
-
stays valid for the instance that registered it. `.oats-soul-source.json`
|
|
382
|
-
remains the stamp of "current". A 0.25.0 layout (`agents/<name>/soul` a real
|
|
383
|
-
directory, no `souls/`) is migrated in place on first use: the directory moves
|
|
384
|
-
to `souls/<commit from the stamp, else unknown>/` and the pointer replaces it.
|
|
385
|
-
A soul symlink whose target lies inside the same `agents/<name>/souls/` is the
|
|
386
|
-
one kernel-owned symlink soul readers accept.
|
|
387
|
-
|
|
388
|
-
**§1 transport (M2).** `parseRepoRef(ref).key` is unchanged — `<host>/<path>`
|
|
389
|
-
is the identity everywhere and every comparison is by key. The **fetch url
|
|
390
|
-
honours the form written**: `git@host:org/repo(.git)` and `ssh://…` fetch over
|
|
391
|
-
SSH as written; `https://…` fetches over HTTPS; the bare scheme
|
|
392
|
-
`git:host/org/repo` fetches over HTTPS by default **unless
|
|
393
|
-
`remoteOptions.transport === "ssh"`** (a per-machine choice; `oats-local.yaml`
|
|
394
|
-
may carry it once the schema admits it — reported by lane 3, not landed here).
|
|
395
|
-
The operator's SSH access is therefore used when the operator wrote an SSH ref,
|
|
396
|
-
and a private repo no longer degrades to `not-found` → standalone through an
|
|
397
|
-
unintended HTTPS probe. When the standalone fallback engages the discovery
|
|
398
|
-
carries `standaloneReason`/`hostFailure { code, reason, url }` so the CLI can
|
|
399
|
-
print *why*.
|
|
400
|
-
|
|
401
|
-
**§3/§4 approval re-verified at spawn (M3).** For a `from: package` module
|
|
402
|
-
`resolveSoul` recomputes `executablesDigest` over the package tree **at the
|
|
403
|
-
locked `entry.commit`** and requires equality with `entry.approved.executables`
|
|
404
|
-
→ else `E_PACKAGE_UNAPPROVED { reason: "digest-mismatch", approved, actual }`.
|
|
405
|
-
The one digest definition is `lib/packages.mjs#executablesDigestAt(remote, ref,
|
|
406
|
-
commit, path, capabilities)`, shared by `sync` and `resolve`; a hand-edited lock
|
|
407
|
-
(same id/version, different commit, copied approval) can no longer materialize
|
|
408
|
-
and run unapproved hooks. Cached per `(id, commit)` within a process.
|
|
409
|
-
|
|
410
|
-
**§1 peeled commit OIDs (M4).** `observeRemote(ref, { at })` accepts an
|
|
411
|
-
annotated tag's OID (or name) but **records the peeled commit** (`<oid>^{commit}`)
|
|
412
|
-
as `commit` — in its result, in the lock, in `fetchRemoteTree`'s errors and in
|
|
413
|
-
every `instance.json` record. A tag OID is never stored where a commit is
|
|
414
|
-
expected.
|
|
415
|
-
|
|
416
|
-
**§1 listing hygiene (L3, L4).** `listRemoteTree` filters by depth **before**
|
|
417
|
-
asserting entry-name safety, so one unsafe deep name does not blank a member's
|
|
418
|
-
souls (unsafe names at the kept depth are still `E_REMOTE_TREE_UNSAFE`). A git
|
|
419
|
-
child killed for `maxBuffer` (`ENOBUFS`) is not reported as `timeout`; an
|
|
420
|
-
unclassified listing failure is wrapped as `E_REMOTE_UNREADABLE { reason:
|
|
421
|
-
"unknown" }` so discovery records a problem row instead of aborting.
|
|
422
|
-
|
|
423
|
-
**§2 `validateWorkspace` absolute paths (L2).** The absolute-path refusal
|
|
424
|
-
applies to **ref/path fields only** — `members[]`, `packages` values, `stores`
|
|
425
|
-
values, `external[].source` / `external[].soul`, `defaults.*.from` — never to
|
|
426
|
-
`teams.<label>.description` or to the opaque `messaging` payload.
|
|
427
|
-
|
|
428
|
-
**§3 revision (L6).** `Resolution.revision = hash(declRevision, payloadRevision)`
|
|
429
|
-
where `declRevision` covers the declarations (member/package commits, the
|
|
430
|
-
composed capability set, skills, injects) and `payloadRevision` covers the
|
|
431
|
-
payload layers (soul slot payloads ⊕ `oats-local.yaml settings` ⊕
|
|
432
|
-
`--provider`). Both are exposed on the Resolution; decision binding keeps using
|
|
433
|
-
`revision`, so a settings-only difference still refuses a stale apply, while
|
|
434
|
-
`spawn --preview` can say **`changed since: declarations | payload | both`**
|
|
435
|
-
instead of a bare `changedSince`.
|
|
436
|
-
|
|
437
|
-
**§6 operator-level dispatch (B3, implements the rule stated above).** Outside a
|
|
438
|
-
home, with `oats-local.yaml` present, `oats <ns> <cmd> … --soul <name>` runs
|
|
439
|
-
`prepareInstance(dir, name)`, picks the module whose `manifest.command === <ns>`,
|
|
440
|
-
ensures its tree in `<deployment>/.oats/modules/<cap>@<commit12>/` (member: the
|
|
441
|
-
member repo at `module.from.commit`, `module.dir`; package: the lock entry) and
|
|
442
|
-
dispatches to that copy with `OATS_SETTINGS = resolution.payloads[cap]` and
|
|
443
|
-
`OATS_CLI_BIN`. `--soul` absent → `E_BAD_ARGS` naming it; a namespace no module
|
|
444
|
-
provides → `E_UNKNOWN_COMMAND`. Trust is the resolution's (membership;
|
|
445
|
-
`E_PACKAGE_UNAPPROVED` for an unapproved package).
|
|
446
|
-
|
|
447
|
-
**§5/§6 `work: workspace` under v2 (B2, implements the rule stated above).**
|
|
448
|
-
With `prepared` present, a `work: workspace` soul's `./work` links the
|
|
449
|
-
deployment directory (`prepared.deployment`, the one holding `oats-local.yaml`);
|
|
450
|
-
no branch is recorded; the "needs a declared boundary" remedy names
|
|
451
|
-
`oats-local.yaml`, not `oats-config.yaml`. The classic root is unchanged.
|
|
452
|
-
|
|
453
|
-
**Decision 13 reach (L7).** "Harnesses start normally" is a property of the
|
|
454
|
-
0.25 **launcher**: every `pi` launch a 0.25 kernel performs — module homes and
|
|
455
|
-
classic 0.24 homes alike — starts pi with cwd = home, the composed `AGENTS.md`
|
|
456
|
-
appended, and pi's own skill/context discovery intact. Consequently `oats
|
|
457
|
-
session recompose` refuses a **module home** (`instance.json.modules` present)
|
|
458
|
-
with `E_UNSUPPORTED_MODE` ("re-spawn"); the `session-recompose` feature name
|
|
459
|
-
stays advertised because the verb still serves classic homes
|
|
460
|
-
(`docs/desktop-cli-api.md`).
|
|
461
|
-
|
|
462
|
-
### 0.25.2 operator-rebuild round (2026-09-24) — appended, not edited in place
|
|
463
|
-
|
|
464
|
-
Source: an operator's first rebuild of a real two-team deployment on 0.25.0,
|
|
465
|
-
following the 0.25 rebuild guide literally (removed in 0.26.0). **The guide is a contract the
|
|
466
|
-
kernel must honour**: where the guide claimed behaviour the kernel lacked, the
|
|
467
|
-
kernel changes; where the guide described keys no provider consumes, the guide
|
|
468
|
-
changes. Findings R1–R10; kernel side in 0.25.2 (`docs/release-notes/v0.25.2.md`).
|
|
469
|
-
No API integer or feature name changes; the only surface additions are
|
|
470
|
-
additive fields (`spawn --preview` `providers` / `settings`, `oats status
|
|
471
|
-
--json instances[].soul`) and the `sync --approve` flag.
|
|
472
|
-
|
|
473
|
-
**§2/§5 member clone lookup (R1).** For a `work: worktree | checkout` soul the
|
|
474
|
-
kernel finds the member clone in this order, first hit wins: (1) `oats spawn
|
|
475
|
-
--repo <abs path>`; (2) `oats-local.yaml` `clones: { <repo key>: <abs path> }`,
|
|
476
|
-
keys normalised through `parseRepoRef(...).key` so any spelling of the same
|
|
477
|
-
repo addresses one entry; (3) the convention `<deployment>/<member name>` where
|
|
478
|
-
`<member name>` is the last segment of the repo key — **a member named `agents`
|
|
479
|
-
is looked for at `<deployment>/agents-repo`** (`<deployment>/agents/` is the
|
|
480
|
-
instance root); (4) none → `E_CLONE_MISSING { repoKey, tried: [...], remedies }`
|
|
481
|
-
naming the three remedies. A directory found by (2) or (3) whose `origin` remote
|
|
482
|
-
resolves to a different repo key → `E_CLONE_MISMATCH { repoKey, path, origin }`
|
|
483
|
-
— the kernel never spawns into a clone that is not the member. This order was
|
|
484
|
-
stated by the guide and `docs/workspaces.md` before 0.25.2 and not implemented;
|
|
485
|
-
it is now normative.
|
|
486
|
-
|
|
487
|
-
**§6 `oats sync` creates `agents/` (R2).** `sync` (and therefore `onboard`,
|
|
488
|
-
which runs the sync body) creates `<deployment>/agents/` when absent. A
|
|
489
|
-
hand-written `oats-local.yaml` needs no `mkdir`.
|
|
490
|
-
|
|
491
|
-
**§5 one "You run on OATS" block (R3).** When `oats.core` resolves as a module
|
|
492
|
-
the composer suppresses the kernel's legacy `oats:kernel:oats` block; the
|
|
493
|
-
module's inject is the one such block. Without `oats.core` (a soul saying `off`)
|
|
494
|
-
the legacy block is composed as before, so no instance is left without the
|
|
495
|
-
briefing.
|
|
496
|
-
|
|
497
|
-
**§5/§6 soul-source drift (R4).** `driftOf` covers `instance.json.workspace.soul`
|
|
498
|
-
as well as `modules`: `oats status` prints `soul: <name> from <member> @ <c7>`
|
|
499
|
-
with `[member moved since …]` when the member's default branch is past the
|
|
500
|
-
recorded commit (`[member unconfirmed]` / `[soul no longer present]` for the
|
|
501
|
-
missing cases); `--json` adds `instances[].soul = { repoKey, commit, current:
|
|
502
|
-
<commit>|null, status: "current"|"moved"|"missing" }`. A moved soul is
|
|
503
|
-
information (decision 17): the instance keeps its own commit directory (M1).
|
|
504
|
-
|
|
505
|
-
**§6 preview payload visibility (R5).** `oats spawn --preview` (text and
|
|
506
|
-
`--json`) reports `providers` — the `--provider <cap> k=v` map exactly as given,
|
|
507
|
-
nested — and `settings.<cap>` — `resolution.payloads[cap]`, the merged payload
|
|
508
|
-
the provider's binding receives (`workspace.messaging` base ⊕ `byTeam[team]` ⊕
|
|
509
|
-
soul slot payload ⊕ `local.settings[cap]` ⊕ `providers[cap]`). Additive fields;
|
|
510
|
-
both empty objects when nothing applies.
|
|
511
|
-
|
|
512
|
-
**§5/§6 `work: workspace` (R6, closed in 0.25.1 as B2).** Documented in the
|
|
513
|
-
guide's §9: a coordination soul's `./work` is the deployment directory.
|
|
514
|
-
|
|
515
|
-
**Provider payload delivery vs provider consumption (R7 — oats.aweb 1.11.2).**
|
|
516
|
-
Decision 23 (`messaging.byTeam`) is **kernel semantics**: the kernel merges and
|
|
517
|
-
delivers; the provider consumes what its binding declares. oats.aweb 1.11.2's
|
|
518
|
-
spawn hook (a) locates the aweb root among `OATS_TEAM_SCOPE`, the home, the
|
|
519
|
-
home's git root, `OATS_CONTEXT` and its git root, and `OATS_WORKSPACE` (under
|
|
520
|
-
v2: the deployment directory) — none of which is a 0.24 team root; and (b)
|
|
521
|
-
resolves the target team from `OATS_TEAM_ID`/`OATS_TEAM_NAME` (the removed
|
|
522
|
-
`oats-config.yaml` `team:` block; empty under v2), else the **active team at
|
|
523
|
-
the root it found** — it does **not** read `team` from `OATS_SETTINGS`. So for
|
|
524
|
-
1.11.2 `byTeam` is delivered and recorded but a no-op; per-label minting is
|
|
525
|
-
obtained only by placing a per-team `.aw` inside each team's member clone
|
|
526
|
-
(gitignored) so it is found through the work repo, or one `.aw` at the
|
|
527
|
-
deployment directory for a single team. The guide states this (§8b) and
|
|
528
|
-
`docs/workspaces.md` states the general rule ("kernel-merged; whether a
|
|
529
|
-
provider honours it is the provider's"). **oats.aweb follow-up**: read `team`
|
|
530
|
-
(and honour `byTeam`'s result) from the payload; accept the deployment
|
|
531
|
-
directory as a first-class root. The kernel does not paper over this with a
|
|
532
|
-
`team:` env shim — the env block is removed with `oats-config.yaml`, and a
|
|
533
|
-
provider contract is the provider's to grow.
|
|
534
|
-
|
|
535
|
-
**OKF 2.1.3 reads `okf.json`, not a soul payload (R8 — corrects §2's `stores`
|
|
536
|
-
comment and decision 24's `root` example).** `oats.okf` 2.1.3's spawn hook
|
|
537
|
-
reads the soul's knowledge declaration from `<soul>/okf.json` (`{ version: 1,
|
|
538
|
-
owner, owns: ["<base>/<node>"], reads: [...] }`, `lib/config.mjs#validateDeclaration`)
|
|
539
|
-
and its settings from `OATS_SETTINGS`, admitting **only** `bindings-file`,
|
|
540
|
-
`state-dir`, `harvest-runtime`, `harvest-model` (`oats.json#settings`) — any
|
|
541
|
-
other key is `E_CONFIG unknown OATS_SETTINGS property`. Where a base lives
|
|
542
|
-
inside a store repository is the **bindings file's** `bases.<alias>.repository`
|
|
543
|
-
+ `root`, not a soul payload key. Therefore: a soul.yaml `knowledge:` payload for
|
|
544
|
-
OKF carries binding settings only (usually nothing — the workspace default
|
|
545
|
-
fills the slot; `none` opts out); `owns`/`reads`/`store`/`root` examples on
|
|
546
|
-
`soul.yaml` are removed from the guide, `workspaces.md`, `souls-and-instances.md`
|
|
547
|
-
and `knowledge.md`; `okf.json` stays in `souls/<name>/` and travels with the
|
|
548
|
-
soul into the per-commit cache (M1). A soul-payload grammar for OKF is an OKF
|
|
549
|
-
follow-up that lands with an `oats.okf` release declaring it in its binding.
|
|
550
|
-
The kernel's part — opaque forwarding of the merged payload — is unchanged and
|
|
551
|
-
correct. §7b's fresh `state-dir` rule is confirmed by the operator's run.
|
|
552
|
-
|
|
553
|
-
**§6 non-interactive approval (R9).** `oats sync --approve <id>@<version>`
|
|
554
|
-
(repeatable) approves exactly the entry the current resolution contains for
|
|
555
|
-
that id and version: the executables digest is always computed by `sync` over
|
|
556
|
-
the fetched tree (`executablesDigestAt`) and recorded — never typed. An
|
|
557
|
-
`--approve` naming an id/version the resolution does not contain → `E_BAD_ARGS`
|
|
558
|
-
(nothing approved); entries not covered stay unapproved (exit `2`). At the
|
|
559
|
-
interactive prompt **Ctrl+D (EOF) is a decline**: exit `2`, entry unapproved —
|
|
560
|
-
never treated as "yes", never a hang.
|
|
561
|
-
|
|
562
|
-
**§6 onboard next steps (R10).** `oats onboard` lists the workspace **host**
|
|
563
|
-
in `next.clone` like any member that lacks a clone at the convention (the host
|
|
564
|
-
is a member; a soul that lives in it may need a work clone). Under an explicit
|
|
565
|
-
`oats-local.yaml` `standalone:` header the next steps say the view is standalone
|
|
566
|
-
and list only that repo.
|
|
567
|
-
|
|
568
|
-
### 0.25.6 — decision 27: served identity is a messaging-layer fact (K1′, K1″, K2)
|
|
569
|
-
|
|
570
|
-
- **K1′** `decision.effective.providers` = the resolution's merged per-module payloads
|
|
571
|
-
(exactly what reaches `OATS_SETTINGS`), bound by the decision revision. No new flag.
|
|
572
|
-
- **K1″** manifest `settings.<key>.hostOnly: true` → the resolver refuses that key in
|
|
573
|
-
the workspace base, `byTeam[*]`, the soul's slot and `--provider` layers with
|
|
574
|
-
`E_WORKSPACE_SCHEMA { reason: "host-only-key", path, key, capability }`; only
|
|
575
|
-
`oats-local.yaml settings.<cap>` may carry it. Generalises decision 23's reserved
|
|
576
|
-
`byTeam` into a capability-declared attribute. Schema: `docs/capability-manifest.schema.json`.
|
|
577
|
-
- **K2** `oats status --json instances[].identity` and `oats inspect … selected.identity`
|
|
578
|
-
copy `capabilityMeta[<cap>].identity` (messaging-layer capability preferred; `provider`
|
|
579
|
-
added); text `identity: acts as <address> via grant, expires <t>` / `alias <a> on <team>`.
|
|
580
|
-
Layer contract shape in `docs/integrations.md`.
|
|
581
|
-
- `features[]` gains `served-identity`.
|
|
582
|
-
|
|
583
|
-
### 0.25.5 — launch-hook `meta` is persisted
|
|
584
|
-
|
|
585
|
-
`runLifecycleHooks("launch")` collected each capability's `meta` and the
|
|
586
|
-
start/restart path discarded it (only `contributions` and `env` were consumed).
|
|
587
|
-
From 0.25.5 a successful start merges `res.meta` per capability into
|
|
588
|
-
`instance.json.capabilityMeta` — the record spawn writes and retire reads as
|
|
589
|
-
`OATS_META`. A hook answering without `meta` keeps its prior entry; a failed
|
|
590
|
-
launch preparation writes nothing. No new field, flag or hook event; this is
|
|
591
|
-
the documented hook return finally honoured (decision 27, K3′). Driver: a
|
|
592
|
-
provider renewing a session grant at every start would otherwise leave the
|
|
593
|
-
original grant id on record and retire would revoke the wrong grant.
|
|
594
|
-
|
|
595
|
-
### 0.25.3 — `OATS_SOUL_ID` (stable soul identity for providers)
|
|
596
|
-
|
|
597
|
-
The per-commit soul cache (0.25.1, M1) made `realpath(<home>/soul)` change with every
|
|
598
|
-
member commit; a provider that keyed durable state on that path (OKF 2.1.3 `owners.json`)
|
|
599
|
-
refused the next spawn (`E_OWNER`). "Members are latest" and "the owner is a path" cannot
|
|
600
|
-
both hold, so the kernel now hands hooks a **stable identity**:
|
|
601
|
-
|
|
602
|
-
- `OATS_SOUL_ID` in the `spawn` / `retire` / `launch` hook environment: for a workspace soul
|
|
603
|
-
`<repo key>#<soul name>` exactly as the canonical key is spelled (e.g.
|
|
604
|
-
`github.com/awebai/aweb#aweb-protocol-expert`, local fixtures `local//abs/path.git#name`);
|
|
605
|
-
for a classic soul the realpath of `agents/<name>/soul` (today's value — 0.24 deployments
|
|
606
|
-
unchanged). Also recorded as `instance.json.workspace.soul.id`.
|
|
607
|
-
- `OATS_SOUL` is the **content** the home links — for a workspace soul the per-commit
|
|
608
|
-
directory `agents/<name>/souls/<commit12>/`, never the swappable `agents/<name>/soul`
|
|
609
|
-
pointer. Providers read content from `OATS_SOUL` and key state on `OATS_SOUL_ID`.
|
|
610
|
-
- Provider contract (OKF 2.1.4): `owners[owner] = OATS_SOUL_ID ?? realpath(OATS_SOUL ?? home/soul)`;
|
|
611
|
-
a prior row whose value is a path under `agents/<same soul name>/(soul|souls/<commit>)` is
|
|
612
|
-
migrated to the id once, not refused; any other mismatch stays `E_OWNER`.
|
|
440
|
+
## Post-0.25.0 clarifications
|
|
613
441
|
|
|
442
|
+
Folded into the sections above: operator-level capability commands (§6.7); `work: workspace` (§6.5);
|
|
443
|
+
host paths and `hostOnly` keys (§3.4); member capabilities as a code-execution boundary (§3.2, §4.3);
|
|
444
|
+
slot `none` (§3.3); the two revisions (§3.6); peeled commits and transport (§1); the per-commit soul
|
|
445
|
+
cache, `OATS_SOUL_ID` and persisted launch `meta` (§5.2); clone lookup and preview payloads (§6.5);
|
|
446
|
+
soul drift (§5.3).
|