@awebai/oats 0.29.3 → 0.30.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 +12 -6
- package/bin/oats.mjs +203 -54
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +538 -204
- package/capabilities/oats-aweb/injects/aweb.md +1 -1
- package/capabilities/oats-aweb/lib/binding-wire.mjs +31 -22
- package/capabilities/oats-aweb/oats.json +5 -12
- package/capabilities/oats-aweb/skills/VENDORED.md +4 -4
- package/capabilities/oats-aweb/skills/aweb-team-membership/SKILL.md +1 -1
- package/capabilities/oats-aweb/skills/oats-aweb/SKILL.md +83 -13
- package/capabilities/oats-code-review/injects/reviewer.md +26 -0
- package/capabilities/oats-code-review/oats.json +16 -0
- package/capabilities/oats-code-review/skills/adversarial-review/SKILL.md +66 -0
- package/capabilities/oats-code-review/skills/review-dev-docs/SKILL.md +30 -0
- package/capabilities/oats-code-review/skills/security-review/SKILL.md +56 -0
- package/capabilities/oats-code-review/skills/simplification-review/SKILL.md +34 -0
- package/capabilities/oats-developer/injects/developer.md +38 -0
- package/capabilities/oats-developer/oats.json +17 -0
- package/capabilities/oats-developer/skills/execution-strategy/SKILL.md +43 -0
- package/capabilities/oats-developer/skills/maintain-dev-docs/SKILL.md +47 -0
- package/capabilities/oats-developer/skills/run-the-review-loop/SKILL.md +65 -0
- package/capabilities/oats-developer/skills/understand-the-spec/SKILL.md +37 -0
- package/capabilities/oats-developer/skills/worktrees/SKILL.md +36 -0
- package/capabilities/oats-engineering-expert/injects/expert.md +37 -0
- package/capabilities/oats-engineering-expert/oats.json +17 -0
- package/capabilities/oats-engineering-expert/skills/coordinate-developers/SKILL.md +37 -0
- package/capabilities/oats-engineering-expert/skills/coordinate-experts/SKILL.md +52 -0
- package/capabilities/oats-engineering-expert/skills/land-your-prs/SKILL.md +50 -0
- package/capabilities/oats-engineering-expert/skills/plan-and-spec/SKILL.md +53 -0
- package/capabilities/oats-engineering-expert/skills/verify-developer-work/SKILL.md +49 -0
- package/capabilities/oats-okf/bin/oats-okf.mjs +16 -9
- package/capabilities/oats-okf/lib/binding-wire.mjs +47 -15
- package/capabilities/oats-okf/lib/config.mjs +2 -1
- package/capabilities/oats-okf/lib/consult.mjs +26 -4
- package/capabilities/oats-okf/lib/harvest-switch.mjs +16 -3
- package/capabilities/oats-okf/lib/inspection.mjs +26 -7
- package/capabilities/oats-okf/lib/io.mjs +9 -1
- package/capabilities/oats-okf/lib/sources.mjs +34 -3
- package/capabilities/oats-okf/lib/stores.mjs +8 -6
- package/capabilities/oats-okf/lib/worker.mjs +7 -17
- package/capabilities/oats-okf/oats.json +6 -3
- package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +2 -2
- package/capabilities/oats-okf-harvest/oats.json +3 -3
- package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +6 -6
- package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +37 -16
- package/capabilities/oats-okf-maintenance/injects/maintainer.md +1 -1
- package/capabilities/oats-okf-maintenance/lib/provenance.mjs +6 -1
- package/capabilities/oats-okf-maintenance/oats.json +2 -2
- package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +17 -2
- package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +11 -23
- package/capabilities/oats-workspace-experts/injects/oats-experts.md +26 -0
- package/capabilities/oats-workspace-experts/oats.json +9 -0
- package/docs/capabilities.md +160 -171
- package/docs/capability-manifest.schema.json +7 -10
- 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 +116 -0
- 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 +76 -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 +61 -0
- package/docs/release-lane.md +77 -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.29.4.md +90 -0
- package/docs/release-notes/v0.30.0.md +205 -0
- package/docs/schedules.md +280 -349
- 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 +132 -215
- package/lib/automations.mjs +28 -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 +2 -5
- package/lib/resolve.mjs +29 -87
- package/lib/schedule.mjs +32 -18
- package/lib/teams-verbs.mjs +195 -0
- package/lib/teams.mjs +190 -0
- package/lib/triggers.mjs +53 -17
- package/lib/workspace.mjs +54 -147
- package/package-catalog.json +9 -15
- package/package.json +1 -1
- package/skills/oats-getting-started/SKILL.md +25 -13
- 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
package/docs/workspaces.md
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
# Workspaces
|
|
1
|
+
# Workspaces: one workspace per organisation, members are trust, nothing is installed
|
|
2
2
|
|
|
3
|
-
This is the OATS workspace model
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
`agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md`; the module
|
|
7
|
-
contracts the kernel is built against are in
|
|
8
|
-
[design/2026-09-23-workspace-module-contracts.md](design/2026-09-23-workspace-module-contracts.md);
|
|
9
|
-
a full worked example (an imaginary company with three teams) is in
|
|
10
|
-
[design/2026-09-23-simplified-workspace-model.md](design/2026-09-23-simplified-workspace-model.md).
|
|
3
|
+
This is the OATS workspace model. The module contracts the kernel is built
|
|
4
|
+
against are in
|
|
5
|
+
[design/2026-09-23-workspace-module-contracts.md](design/2026-09-23-workspace-module-contracts.md).
|
|
11
6
|
|
|
12
7
|
## The rule
|
|
13
8
|
|
|
@@ -44,7 +39,7 @@ shapes; domain rules (declared teams, duplicate members, canonical `from:` keys,
|
|
|
44
39
|
the two `packages:` value forms) live in the kernel's `validateWorkspace` /
|
|
45
40
|
`validateSoul`, which are the authority.
|
|
46
41
|
|
|
47
|
-
### `oats-workspace.yaml
|
|
42
|
+
### `oats-workspace.yaml`: the one shared declaration
|
|
48
43
|
|
|
49
44
|
Lives in the repository that **hosts** the workspace (often a dedicated
|
|
50
45
|
`agents` repo, but any member can host it). One per organisation.
|
|
@@ -59,13 +54,13 @@ members: # repo refs, NO @revision (E_WORKSPAC
|
|
|
59
54
|
- git:github.com/acme/tools # a member that ALSO publishes a package (see below)
|
|
60
55
|
|
|
61
56
|
packages: # the ONLY versioned things
|
|
62
|
-
oats.framework: v1.
|
|
63
|
-
oats.okf: v4.0.
|
|
57
|
+
oats.framework: v1.4.0 # bare version → resolves through the official catalog
|
|
58
|
+
oats.okf: v4.0.4
|
|
64
59
|
acme.tools: git:github.com/acme/tools@v0.4.0 # outside the catalog → git:<repo>@<tag|OID>; still a package
|
|
65
60
|
|
|
66
|
-
teams: #
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
teams: # SHARED teams: the same provider team for everyone
|
|
62
|
+
engineering: { description: Platform and release automation, team: "engineering:acme.aweb.ai" }
|
|
63
|
+
reviewers: { description: Code review } # declared, not created yet (no `team` id): readiness team-unmapped
|
|
69
64
|
|
|
70
65
|
defaults:
|
|
71
66
|
capabilities:
|
|
@@ -74,9 +69,6 @@ defaults:
|
|
|
74
69
|
knowledge: { oats.okf: { from: package } } # one slot default at most; a soul may say `none`
|
|
75
70
|
messaging: none
|
|
76
71
|
tasks: none
|
|
77
|
-
byTeam:
|
|
78
|
-
engineering:
|
|
79
|
-
capabilities: { acme-release-tooling: { from: github.com/acme/agents } }
|
|
80
72
|
|
|
81
73
|
stores: # knowledge stores, declared once
|
|
82
74
|
org: git:github.com/acme/knowledge
|
|
@@ -97,92 +89,68 @@ exactly as the kernel spells it (`parseRepoRef(ref).key`: lowercase host,
|
|
|
97
89
|
file/bare-directory remote). Any other spelling is a schema error at
|
|
98
90
|
validation, not a late membership error.
|
|
99
91
|
|
|
100
|
-
### `oats-membership.yaml
|
|
92
|
+
### `oats-membership.yaml`: the backlink, in every member
|
|
101
93
|
|
|
102
94
|
```yaml
|
|
103
95
|
schemaVersion: 2
|
|
104
96
|
workspace: git:github.com/acme/agents # "I am a member of acme"
|
|
105
|
-
team: engineering # optional: default team label for this repo's items
|
|
106
97
|
```
|
|
107
98
|
|
|
108
|
-
Nothing else
|
|
99
|
+
Nothing else: there are no export lists, and team membership is local to each
|
|
100
|
+
deployment ([Teams](#teams)).
|
|
109
101
|
|
|
110
|
-
### `souls/<name>/soul.yaml
|
|
102
|
+
### `souls/<name>/soul.yaml`: where each capability comes from
|
|
111
103
|
|
|
112
104
|
```yaml
|
|
113
105
|
schemaVersion: 2
|
|
114
106
|
name: release-manager
|
|
115
107
|
description: Cuts, verifies and announces releases.
|
|
116
108
|
work: worktree # worktree | checkout | directory | workspace
|
|
117
|
-
team: engineering # optional; else the repo's default; else "unassigned"
|
|
118
109
|
|
|
119
110
|
capabilities:
|
|
120
111
|
acme-release-tooling: { from: here } # `here` = the repo this soul.yaml lives in
|
|
121
112
|
acme-deploy: { from: package } # provided by acme.tools, pinned in packages:
|
|
122
113
|
acme-house-style: off # removes a workspace default
|
|
123
114
|
|
|
124
|
-
knowledge: # provider payload, opaque to the kernel
|
|
125
|
-
harvest
|
|
115
|
+
knowledge: # provider payload, opaque to the kernel: the slot capability's settings
|
|
116
|
+
harvest: off # (what this soul owns and reads is in souls/<name>/okf.json, not here)
|
|
126
117
|
messaging:
|
|
127
118
|
channels: [acme-eng]
|
|
128
119
|
tasks: none # empties the slot
|
|
129
120
|
|
|
130
|
-
compatibility: # optional FLOORS on package versions
|
|
131
|
-
oats.okf: ">=
|
|
121
|
+
compatibility: # optional FLOORS on package versions: constraints, not sources
|
|
122
|
+
oats.okf: ">=4.0"
|
|
132
123
|
```
|
|
133
124
|
|
|
134
125
|
Beside it: `AGENTS.md` (canonical), `CLAUDE.md → AGENTS.md`, `skills/`, and
|
|
135
|
-
whatever the slot providers read from the soul directory
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
`name
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
The capability manifest is the one file that did not change (see
|
|
152
|
-
[capabilities.md](capabilities.md)). Discovery relies on `capability` (the same
|
|
126
|
+
whatever the slot providers read from the soul directory. For `oats.okf` that
|
|
127
|
+
is **`okf.json`** (`{ version: 1, owner, owns: ["<base>/<node>"], reads: […] }`),
|
|
128
|
+
the soul's knowledge declaration; it travels with the soul into the per-commit
|
|
129
|
+
soul cache ([knowledge.md](knowledge.md)). A slot payload on `soul.yaml` reaches
|
|
130
|
+
the provider as `OATS_SETTINGS` and may carry only the settings the provider's
|
|
131
|
+
manifest declares; the provider refuses any other key.
|
|
132
|
+
|
|
133
|
+
Every `souls/*/soul.yaml` in a member is listed and spawnable; souls have no
|
|
134
|
+
private mode. A soul's `name` must equal its directory name; the first of two
|
|
135
|
+
souls declaring one name (by path) is listed, the second is a problem.
|
|
136
|
+
|
|
137
|
+
### `capabilities/<name>/oats.json`: the manifest
|
|
138
|
+
|
|
139
|
+
The capability manifest is described in [capabilities.md](capabilities.md).
|
|
140
|
+
Discovery relies on `capability` (the same
|
|
153
141
|
`^[a-z0-9][a-z0-9._-]*$` grammar every `capabilities:` key uses), `version`,
|
|
154
|
-
`layer`, and may read `private: true` (a **repo-owned** capability)
|
|
155
|
-
`team: <label>`. `version` is
|
|
142
|
+
`layer`, and may read `private: true` (a **repo-owned** capability). `version` is
|
|
156
143
|
informational for member capabilities — a materialized copy is identified by
|
|
157
144
|
its content digest.
|
|
158
145
|
|
|
159
|
-
### `oats-local.yaml
|
|
146
|
+
### `oats-local.yaml`: the only per-machine file
|
|
160
147
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
github.com/acme/platform: /Users/ana/src/acme-platform
|
|
166
|
-
settings: # host-owned values the manifests ask for
|
|
167
|
-
oats.okf:
|
|
168
|
-
bindings-file: /Users/ana/.oats/okf-bindings.json
|
|
169
|
-
state-dir: /Users/ana/.oats/okf
|
|
170
|
-
souls:
|
|
171
|
-
disabled: [data-analyst] # not run on this machine (E_SOUL_DISABLED); oats.okf/knowledge-harvester names a package soul
|
|
172
|
-
host:
|
|
173
|
-
name: ana-laptop # this machine's name: runs the workspace triggers/schedules whose runsOn names it
|
|
174
|
-
triggers:
|
|
175
|
-
disabled: [knowledge/okf-harvest-review] # workspace triggers this host does not run (oats trigger disable)
|
|
176
|
-
schedules:
|
|
177
|
-
disabled: [platform/nightly-digest] # workspace schedules this host does not run (oats schedule disable)
|
|
178
|
-
```
|
|
148
|
+
Which workspace this machine realizes, where member clones live, host-owned
|
|
149
|
+
provider settings, this deployment's local teams and team membership, host
|
|
150
|
+
facts for automations, and launch configurations. The full reference is
|
|
151
|
+
[configuration.md](configuration.md).
|
|
179
152
|
|
|
180
|
-
|
|
181
|
-
see [schedules.md#workspace-triggers-and-schedules](schedules.md#workspace-triggers-and-schedules).
|
|
182
|
-
|
|
183
|
-
See [configuration.md](configuration.md). `oats-config.yaml` no longer exists.
|
|
184
|
-
|
|
185
|
-
### `oats-lock.json` — lock v3
|
|
153
|
+
### `oats-lock.json`: lock v3
|
|
186
154
|
|
|
187
155
|
Written by `oats sync`; the only persisted state at the deployment besides
|
|
188
156
|
`oats-local.yaml`. See [packages.md](packages.md).
|
|
@@ -194,16 +162,11 @@ Written by `oats sync`; the only persisted state at the deployment besides
|
|
|
194
162
|
the workspace back. One file per side; a copied backlink in a fork, or a folder
|
|
195
163
|
with the right name, is not admission.
|
|
196
164
|
|
|
197
|
-
**Membership is the whole trust decision for member capabilities
|
|
165
|
+
**Membership is the whole trust decision for member capabilities**, the same
|
|
198
166
|
model as a repo's committed `.agents/skills/`: whoever can push to the repo
|
|
199
|
-
decides what runs, and the branch's latest state is what runs.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
the trust decision** (human decision, 2026-09-24): people install a package only
|
|
203
|
-
when they trust it, so there is no second, per-version approval step. The lock
|
|
204
|
-
is reproducibility, not approval — it pins the exact commit and content
|
|
205
|
-
integrity, and `oats sync` refuses drift (a moved tag, changed content, an
|
|
206
|
-
edited capability list). A spawn admits only a locked package the workspace
|
|
167
|
+
decides what runs, and the branch's latest state is what runs. Packages come
|
|
168
|
+
from *outside* that boundary, and **declaring one in the workspace's
|
|
169
|
+
`packages:` is the trust decision** ([packages.md](packages.md#trust)). A spawn admits only a locked package the workspace
|
|
207
170
|
**still declares**: one removed from `packages:` but left in a stale lock is
|
|
208
171
|
`E_PACKAGE_MISSING { reason: "undeclared" }` until `oats sync` drops it.
|
|
209
172
|
|
|
@@ -237,15 +200,15 @@ no private mode: every soul of a confirmed member is listed and spawnable.
|
|
|
237
200
|
**not** a member, pinned to a full commit. No handshake is asked for and none is
|
|
238
201
|
read; the soul gets no member-tier capabilities of its own repo; it is
|
|
239
202
|
"source-complete" (its skills travel with it) and the workspace's defaults fill
|
|
240
|
-
its slots.
|
|
203
|
+
its slots.
|
|
241
204
|
|
|
242
|
-
**Package souls.** A package may ship souls (`souls:` in `oats-package.json
|
|
243
|
-
|
|
205
|
+
**Package souls.** A package may ship souls (`souls:` in `oats-package.json`):
|
|
206
|
+
they are listed from the lock for each package the workspace declares,
|
|
244
207
|
named `<package>/<soul>` (a bare name when unique), resolved like any soul
|
|
245
208
|
(`from: here` = their own package at the locked commit) and trusted as the
|
|
246
209
|
package is. See [packages](packages.md#package-souls).
|
|
247
210
|
|
|
248
|
-
## Member tier vs package tier
|
|
211
|
+
## Member tier vs package tier: the non-collapse rule
|
|
249
212
|
|
|
250
213
|
A repository may be a **member** (it completed the handshake; its `souls/*` and
|
|
251
214
|
`capabilities/*` are member-tier: latest state, trusted by membership) **and** a
|
|
@@ -264,44 +227,24 @@ A repository may be a **member** (it completed the handshake; its `souls/*` and
|
|
|
264
227
|
package's capabilities as member capabilities.
|
|
265
228
|
|
|
266
229
|
So the framework's own souls say `oats.okf: { from: package }` even though
|
|
267
|
-
`oats-okf` is a member of the OATS workspace
|
|
268
|
-
member soul that is the expert in that capability (`okf-expert`,
|
|
269
|
-
…), discoverable at latest state like any member soul.
|
|
230
|
+
`oats-okf` is a member of the OATS workspace, and every package repo carries a
|
|
231
|
+
member soul that is the expert in that capability (`oats-okf-expert`,
|
|
232
|
+
`oats-aweb-expert`, …), discoverable at latest state like any member soul.
|
|
270
233
|
|
|
271
234
|
## Packages, lock, catalog
|
|
272
235
|
|
|
273
|
-
`packages:` values have
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
understands (`github.com/org/repo`, `https://…`, `git@host:…`, `/abs/bare.git`,
|
|
281
|
-
`file:///…`), `<ref>` a tag name or a full commit OID. The package is read at
|
|
282
|
-
`oats-package/` inside that repo.
|
|
283
|
-
|
|
284
|
-
Both are packages: versioned and locked. A ref that resolves to a **branch** is
|
|
285
|
-
refused (`E_PACKAGE_INTEGRITY { why: "branch" }`) — versions are immutable. A
|
|
286
|
-
tag that moved (same version string, different commit), or content that no
|
|
287
|
-
longer matches the locked integrity, fails with `E_PACKAGE_INTEGRITY` on the
|
|
288
|
-
next `oats sync`.
|
|
289
|
-
|
|
290
|
-
**There is no package approval** (human decision, 2026-09-24). Declaring a
|
|
291
|
-
package in `packages:` is the trust decision; `oats sync` asks nothing and
|
|
292
|
-
`--approve` is `E_BAD_ARGS`. `oats sync` confirms membership, resolves every
|
|
293
|
-
`packages:` entry to a commit + content digest, writes `oats-lock.json`
|
|
294
|
-
(lockfileVersion 3), creates `agents/` if absent, reports what changed and
|
|
295
|
-
exits `0`. A lock written by an earlier kernel may still carry an `approved`
|
|
296
|
-
record per entry: it is ignored, and the next write drops it. `oats package add <id> <version|git:…@…>` / `oats package remove <id>` edit
|
|
297
|
-
`packages:` in the workspace file when it is tracked by the current checkout,
|
|
298
|
-
else print the line to add — the workspace file is shared through Git. Details:
|
|
299
|
-
[packages.md](packages.md).
|
|
236
|
+
`packages:` values have two forms, a **bare version** resolved through the
|
|
237
|
+
official catalog and a **`git:<repo>@<ref>`** direct ref; both are versioned
|
|
238
|
+
and locked, and a ref that resolves to a branch is refused. `oats sync`
|
|
239
|
+
confirms membership, resolves every entry to a commit and content digest,
|
|
240
|
+
writes `oats-lock.json`, creates `agents/` if absent and reports what changed.
|
|
241
|
+
`oats package add | remove` edit `packages:`. The grammar, the lock, trust and
|
|
242
|
+
the catalog are in [packages.md](packages.md).
|
|
300
243
|
|
|
301
244
|
## Resolution, spelled out
|
|
302
245
|
|
|
303
246
|
For each `(name, from)` in
|
|
304
|
-
`defaults.<slot>` ⊕ `defaults.capabilities` ⊕
|
|
247
|
+
`defaults.<slot>` ⊕ `defaults.capabilities` ⊕
|
|
305
248
|
`soul.capabilities` (later wins; `off` removes; a soul `<slot>: none` drops
|
|
306
249
|
the workspace's slot default):
|
|
307
250
|
|
|
@@ -326,30 +269,13 @@ preview and apply is `E_DECISION_STALE`, not a silent drift.
|
|
|
326
269
|
|
|
327
270
|
## Materialization and the instance home
|
|
328
271
|
|
|
329
|
-
At spawn every resolved capability is **copied whole** into the instance home
|
|
330
|
-
skills, injects, scripts, hooks
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
│ # (the "You run on OATS" block is oats.core's inject; the kernel ships no copy)
|
|
337
|
-
├── CLAUDE.md → AGENTS.md
|
|
338
|
-
├── .agents/skills/<capability>/<skill>/SKILL.md # full copies; where pi/codex look
|
|
339
|
-
├── .claude/skills → ../.agents/skills
|
|
340
|
-
├── .oats/modules/<capability>/ # the full capability copy: oats.json, bin/, injects/, skills/
|
|
341
|
-
├── instance.json # modules{}, providers{}, workspace{} recorded here
|
|
342
|
-
├── TASK.md
|
|
343
|
-
└── work/
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
`instance.json.modules.<cap>` records `from` (`{ kind: "member", repoKey,
|
|
347
|
-
commit }` or `{ kind: "package", package, version, commit, integrity, repoKey }`),
|
|
348
|
-
`commit`, `digest` (sha256 of the copied tree) and `materializedAt`;
|
|
349
|
-
`instance.json.providers.<cap>` records the merged provider payload. A running
|
|
350
|
-
instance never changes under itself: a member moving or `packages:` being
|
|
351
|
-
bumped affects only new spawns. Details and DTOs:
|
|
352
|
-
[souls-and-instances.md](souls-and-instances.md), [desktop-cli-api.md](desktop-cli-api.md).
|
|
272
|
+
At spawn every resolved capability is **copied whole** into the instance home
|
|
273
|
+
(skills, injects, scripts, hooks) from the remote at the recorded commit, and
|
|
274
|
+
`instance.json` records each module's source, commit and digest. Nothing is
|
|
275
|
+
symlinked or shared between instances, and a running instance never changes
|
|
276
|
+
under itself: a member moving or `packages:` being bumped affects only new
|
|
277
|
+
spawns. The home's layout and records are in
|
|
278
|
+
[souls-and-instances.md](souls-and-instances.md).
|
|
353
279
|
|
|
354
280
|
**Drift is shown, not prevented.** `oats status` compares each instance's
|
|
355
281
|
recorded modules — and its recorded **soul source** (`instance.json.workspace.soul`)
|
|
@@ -361,29 +287,61 @@ carries `instances[].soul`. `oats spawn --preview` lists `changedSince` the
|
|
|
361
287
|
newest previous instance of the same soul, plus `providers` (the `--provider`
|
|
362
288
|
map as given) and `settings.<cap>` (the merged payload each provider receives).
|
|
363
289
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
(`~/.pi/agent/skills`, `.agents/skills` up the tree, `.claude/`, …); machine-
|
|
367
|
-
and repo-level skills resolve exactly as they would without OATS. OATS
|
|
368
|
-
composes instructions (`AGENTS.md`) and pins model/provider settings; it does
|
|
369
|
-
not exclude anything.
|
|
290
|
+
Harnesses start normally, with their own skill discovery intact
|
|
291
|
+
([souls-and-instances.md](souls-and-instances.md)).
|
|
370
292
|
|
|
371
293
|
## Teams
|
|
372
294
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
`oats-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
`
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
295
|
+
A team is a messaging-provider team (for oats.aweb, an
|
|
296
|
+
aweb team id `<team>:<namespace>`) under a **label**. Two files declare them:
|
|
297
|
+
|
|
298
|
+
- **Shared teams**: the committed `oats-workspace.yaml` `teams.<label> =
|
|
299
|
+
{ description?, team? }`: the same provider team for everyone, edited by a PR.
|
|
300
|
+
A shared team without `team` is declared but not created yet (readiness
|
|
301
|
+
`team-unmapped`): its owner creates it with the messaging provider, then
|
|
302
|
+
commits the id.
|
|
303
|
+
- **Local teams**: the deployment's `oats-local.yaml` `teams.<label> = { team,
|
|
304
|
+
description? }`: a team only this deployment uses (a personal team). A label in
|
|
305
|
+
both files is `team-label-collision` (a warning); the **shared** definition
|
|
306
|
+
wins, and the fix is renaming the local label.
|
|
307
|
+
|
|
308
|
+
`oats-local.yaml` also says which teams each soul belongs to **here**:
|
|
309
|
+
|
|
310
|
+
- `defaultTeam: <label>`: the team every instance of this deployment lives in
|
|
311
|
+
(its default-team identity);
|
|
312
|
+
- `souls.teams`: `"*"` for every soul, and a soul's own entry (its bare name, or
|
|
313
|
+
`<package>/<soul>` for a package soul) adds to it;
|
|
314
|
+
- `souls.default`: a per-soul override of `defaultTeam`; it must be one of that
|
|
315
|
+
soul's teams (`E_TEAM_NOT_ELIGIBLE`).
|
|
316
|
+
|
|
317
|
+
A soul's default is `souls.default[soul] ?? defaultTeam`; its teams are that
|
|
318
|
+
default ∪ `souls.teams["*"]` ∪ `souls.teams[soul]`. A label no file declares is
|
|
319
|
+
`E_TEAM_UNKNOWN` (a spawn, preview or `inspect --soul` of that soul is
|
|
320
|
+
refused). At spawn an instance joins its **default** only; the others are
|
|
321
|
+
eligible: offered, and joined on request through the provider (`join=` at
|
|
322
|
+
spawn, or its own verbs later). Nothing committed besides the shared `teams:`
|
|
323
|
+
says anything about teams, and capabilities compose from the workspace defaults
|
|
324
|
+
and the soul only, the same for everyone. **A label never gates, restricts,
|
|
325
|
+
changes trust or partitions the knowledge store.**
|
|
326
|
+
|
|
327
|
+
The verbs edit `oats-local.yaml` in place; they never call a provider:
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
oats teams [--json] # this deployment's teams, ids, the default, problems
|
|
331
|
+
oats teams add <label> --team <id> [--description d] # declare a local team (the first one becomes the default)
|
|
332
|
+
oats teams remove <label> # refused while referenced (E_TEAM_IN_USE) or shared (E_TEAM_SHARED)
|
|
333
|
+
oats teams default <label>
|
|
334
|
+
oats soul teams <soul>|'*' [--add a,b] [--remove a,b] [--default <label> | --clear-default] [--json]
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
The messaging provider's own setup creates provider teams and records them with
|
|
338
|
+
`oats teams add` (see the provider's documentation). The spawn preview, `inspect` and `oats souls` report a soul's
|
|
339
|
+
`teams` and `defaultTeam`; readiness reports the team problems in
|
|
340
|
+
`checks.configured` (`E_TEAM_UNCONFIGURED` when a messaging layer is active and
|
|
341
|
+
there is no default; `team-unmapped`, blocking when it is the default;
|
|
342
|
+
`default-team-changed` for a running instance). The provider receives them in
|
|
343
|
+
its environment — see [capabilities.md](capabilities.md#teams-in-the-provider-environment).
|
|
344
|
+
Exact shapes: [desktop-cli-api.md](desktop-cli-api.md#team-model-v2-feature-team-model-2-oats-0300-replaces-feature-teams).
|
|
387
345
|
|
|
388
346
|
## Provider payloads have three homes
|
|
389
347
|
|
|
@@ -393,46 +351,17 @@ store** — it organises and can supply defaults. The messaging provider's paylo
|
|
|
393
351
|
| A fact about this machine | `oats-local.yaml` → `settings.<cap>.<key>` (absolute paths are refused in the workspace file) | `settings.oats.okf.state-dir: /Users/ana/.oats/okf` |
|
|
394
352
|
| 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` |
|
|
395
353
|
|
|
396
|
-
The merged payload is `workspace.messaging` (messaging slot only
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
set one; empty means the provider's own default. The provider's own `binding` contract
|
|
404
|
-
(`normalize → bind → check`) runs over the merged payload exactly as before.
|
|
405
|
-
Two teams, two messaging identities, one workspace:
|
|
354
|
+
The merged payload is `workspace.messaging` (messaging slot only) ⊕ soul slot
|
|
355
|
+
payload ⊕ `local.settings[cap]` ⊕ `spawn.providers[cap]` — objects deep-merge,
|
|
356
|
+
later wins on scalars and arrays. The provider's own `binding` contract
|
|
357
|
+
(its readiness check) runs over the merged payload. Teams are **not** settings:
|
|
358
|
+
they reach the provider beside them, in its environment ([Teams](#teams)).
|
|
359
|
+
Where a provider keeps its own state (for oats.aweb, its identity roots) is the
|
|
360
|
+
provider's concern; see its documentation.
|
|
406
361
|
|
|
407
|
-
```yaml
|
|
408
|
-
teams: { oss: { description: Open protocol }, cloud: { description: Hosted application } }
|
|
409
|
-
messaging:
|
|
410
|
-
byTeam:
|
|
411
|
-
oss: { team: aweb:example.oss }
|
|
412
|
-
cloud: { team: aweb:example.cloud }
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
A soul with `team: cloud` hands its messaging provider the eligible team
|
|
416
|
-
`{ label: cloud, team: aweb:example.cloud, mapped: true, payload: { team: aweb:example.cloud, … } }`
|
|
417
|
-
in `OATS_TEAMS`; its settings carry no `team` unless the host, soul or spawn set
|
|
418
|
-
one. A label under `byTeam` that is not declared in `teams:` is
|
|
419
|
-
`E_WORKSPACE_SCHEMA`. **Joining an eligible team is the provider's explicit
|
|
420
|
-
act.** `spawn --preview` shows the merged `settings.<cap>` and the `teams`, so
|
|
421
|
-
the delivery is verifiable, and `instance.json` records both. With oats.aweb
|
|
422
|
-
1.13.1 (which reads `team` from its settings and ignores `OATS_TEAMS`) the
|
|
423
|
-
primary identity therefore mints into the workspace's default team: the `.aw` root's
|
|
424
|
-
active team, or the one the host set. What the payload does not change is
|
|
425
|
-
**where the `.aw` root is found**: the hook still searches
|
|
426
|
-
bounded candidates, first hit wins — the instance home, the Git repository
|
|
427
|
-
containing it, the soul's work repository and the Git repository containing
|
|
428
|
-
it, then the deployment directory (`OATS_WORKSPACE`); never the user home or
|
|
429
|
-
above the deployment — and that root must hold a membership of the named team (the deployment's `.aw`
|
|
430
|
-
joined to every team its labels name is the simple layout). On oats.aweb
|
|
431
|
-
1.11.2 `team` was ignored (the root's active team won), so `byTeam` there is
|
|
432
|
-
a recorded intent only.
|
|
433
362
|
A store (`stores: { <name>: <repo ref> }`) names a repository; where a
|
|
434
|
-
knowledge base lives inside it is the knowledge provider's own concern
|
|
435
|
-
|
|
363
|
+
knowledge base lives inside it is the knowledge provider's own concern. For
|
|
364
|
+
oats.okf that is the **bindings file** (`bases.<alias>.repository` + `root`,
|
|
436
365
|
`oats-local.yaml settings.oats.okf.bindings-file`), not a soul payload key; a
|
|
437
366
|
repo ref never carries a `#path`.
|
|
438
367
|
|
|
@@ -460,11 +389,11 @@ different repo → `E_CLONE_MISMATCH`. Spawning a soul whose repo is not yet
|
|
|
460
389
|
cloned is a guided clone-then-spawn, a job for the onboarding skill, not the
|
|
461
390
|
kernel.
|
|
462
391
|
|
|
463
|
-
The deployment directory is **yours to choose
|
|
392
|
+
The deployment directory is **yours to choose**: an existing folder that already holds your member clones is the usual case, and `oats onboard <dir>` adds what the kernel needs and nothing else ([configuration.md](configuration.md#the-deployment-directory)):
|
|
464
393
|
|
|
465
394
|
```
|
|
466
395
|
~/acme/ ← the directory you chose
|
|
467
|
-
├── oats-local.yaml ← which workspace this machine realizes
|
|
396
|
+
├── oats-local.yaml ← which workspace this machine realizes, and host facts
|
|
468
397
|
├── oats-lock.json ← exact commit + integrity per package
|
|
469
398
|
├── agents/ ← instance homes (each self-contained) + fetched soul sources
|
|
470
399
|
├── platform/ ← clone of github.com/acme/platform (only if someone works IN it; may live elsewhere — see clones:)
|
|
@@ -500,7 +429,7 @@ that view explicitly). `oats onboard` lists the host among the clones to make
|
|
|
500
429
|
like any member (the host is a member; its souls may need a work clone); under
|
|
501
430
|
an explicit `standalone:` header its next steps say so and name that one repo.
|
|
502
431
|
|
|
503
|
-
**Executables from public members.** Membership is the trust
|
|
432
|
+
**Executables from public members.** Membership is the trust: a
|
|
504
433
|
member capability's hooks and command scripts run on every operator's machine at
|
|
505
434
|
spawn, gated by nothing but the handshake. In a mixed public/private
|
|
506
435
|
organisation keep **souls only** in public members and let executable
|
|
@@ -520,27 +449,15 @@ onboarding skill asks this question first.
|
|
|
520
449
|
- **Members.** A member is always its latest state; there is no `@revision`
|
|
521
450
|
on `members:`. A team that wants frozen capabilities publishes them as a
|
|
522
451
|
package and pins that.
|
|
523
|
-
- **Member capabilities' `version` field
|
|
452
|
+
- **Member capabilities' `version` field**: informational; the content digest
|
|
524
453
|
recorded at spawn identifies a copy.
|
|
525
454
|
- **Souls in members.** A soul is spawned from its repo's current state; the
|
|
526
455
|
commit is recorded in `instance.json.workspace.soul`.
|
|
527
|
-
- **The deployment layout
|
|
456
|
+
- **The deployment layout**: the operator's; only the convention is taught.
|
|
528
457
|
|
|
529
458
|
What **is** versioned: `packages:` (the workspace's one list), the lock's exact
|
|
530
459
|
commits and digests, and `external:` pins (a stranger's repo is never "latest").
|
|
531
460
|
|
|
532
|
-
## Removed
|
|
533
|
-
|
|
534
|
-
Per-soul `source: git:…@v#…` lines and the `git:`/`repo:`/`path:` grammar;
|
|
535
|
-
`imports:` of member souls; `exports:` lists; `oats.yaml`; the
|
|
536
|
-
installed-capability tier (`.agents/capabilities/installed/`) and
|
|
537
|
-
`oats-config.yaml` entirely; `oats init` / `use` / `install` / `restore` /
|
|
538
|
-
`trust` / `list` / `catalog` / `remove` / `migrate` / `config` (each answers
|
|
539
|
-
`E_UNKNOWN_COMMAND` naming its replacement); per-soul `stores.<x>.inherit`;
|
|
540
|
-
ambient-skill exclusion at launch. Lock v1/v2 files are `E_LOCK_SCHEMA`.
|
|
541
|
-
There is no converter and no dual-schema reader: a 0.24.x kernel keeps
|
|
542
|
-
spawning 0.24.x deployments.
|
|
543
|
-
|
|
544
461
|
## Related
|
|
545
462
|
|
|
546
463
|
- [Souls and instances](souls-and-instances.md) · [Packages](packages.md) ·
|
package/lib/automations.mjs
CHANGED
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
* E_AUTOMATION_SCHEMA), `id:` or the filename stem, `runsOn` (a host name), `owner` (a GitHub
|
|
22
22
|
* account, `<host>/<login>`), `description?`, `enabled?`. A duplicate id within one member and
|
|
23
23
|
* one kind is E_AUTOMATION_DUPLICATE.
|
|
24
|
-
* - PLACEMENT: a host runs one ONLY when `runsOn` is its `host.name` (oats-local.yaml)
|
|
25
|
-
* authenticated `gh` account is `owner
|
|
26
|
-
*
|
|
24
|
+
* - PLACEMENT: a host runs one ONLY when `runsOn` is its `host.name` (oats-local.yaml), its
|
|
25
|
+
* authenticated `gh` account is `owner` AND its `automations.trust` admits it (0.30: both
|
|
26
|
+
* names come from a commit; trust is the operator's own yes); otherwise it is listed with the
|
|
27
|
+
* reason (`host-unnamed`, `assigned-elsewhere`, `owner-mismatch`, `untrusted`). Each kind's own list in
|
|
27
28
|
* oats-local.yaml (`triggers.disabled`, `schedules.disabled`) stops a named host from running
|
|
28
29
|
* one.
|
|
29
30
|
* - THE SNAPSHOT: discovery reads the remotes (async), the host tick is synchronous; so
|
|
@@ -38,11 +39,15 @@ import { parseConfigData } from "./config-data.mjs";
|
|
|
38
39
|
|
|
39
40
|
export const AUTOMATIONS_API = 1;
|
|
40
41
|
export const SNAPSHOT_MAX_AGE_MS = 10 * 60_000;
|
|
41
|
-
export const PLACEMENT_REASONS = Object.freeze(["host-unnamed", "assigned-elsewhere", "owner-mismatch"]);
|
|
42
|
+
export const PLACEMENT_REASONS = Object.freeze(["host-unnamed", "assigned-elsewhere", "owner-mismatch", "untrusted"]);
|
|
42
43
|
/** The kinds, in the order the snapshot keeps them (`triggers`, `schedules`). */
|
|
43
44
|
export const KIND_NAMES = Object.freeze(["trigger", "schedule"]);
|
|
44
45
|
const NEVER_SCANNED = new Set(["oats-package", ".git", "node_modules"]);
|
|
45
46
|
export const AUTOMATION_ID_RE = /^[a-z0-9-]{1,40}$/;
|
|
47
|
+
/** A model id an automation may pass to `oats spawn --model`: a provider/model id, never an option.
|
|
48
|
+
* The first character is alphanumeric (or the spawn CLI's own `@native-default`), so no value can
|
|
49
|
+
* be read as a flag (re-review B #1). `[` `]` admit a harness's context-size alias (`opus[1m]`). */
|
|
50
|
+
export const MODEL_RE = /^(?:@native-default|[A-Za-z0-9][A-Za-z0-9._:/@+\[\]-]{0,127})$/;
|
|
46
51
|
export const HOST_NAME_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
47
52
|
const OWNER_RE = /^([a-z0-9-]+(?:\.[a-z0-9-]+)+)\/([A-Za-z0-9](?:[A-Za-z0-9-]{0,38}))$/;
|
|
48
53
|
const HEADER_KEYS = ["kind", "schemaVersion", "id", "description", "runsOn", "owner", "enabled"];
|
|
@@ -123,6 +128,9 @@ export async function discoverAutomations(discovery, { remote, memberName, kinds
|
|
|
123
128
|
const names = new Map();
|
|
124
129
|
for (const m of (discovery?.members || []).filter((x) => x.confirmed && x.commit)) {
|
|
125
130
|
const name = memberName(m.key);
|
|
131
|
+
// `local/<id>` names this host's own automations (and their state, live counts and CLI edits):
|
|
132
|
+
// a member labelled `local` would collide with them (re-review B #3).
|
|
133
|
+
if (name === LOCAL) { problems.push({ code: "E_AUTOMATION_DUPLICATE", repoKey: m.key, path: "", message: `member name ${JSON.stringify(name)} is reserved for this host's own triggers and schedules (local/<id>), so ${m.key}'s are not listed — rename the repository to list them` }); continue; }
|
|
126
134
|
if (names.has(name)) { problems.push({ code: "E_AUTOMATION_DUPLICATE", repoKey: m.key, path: "", message: `member name ${JSON.stringify(name)} is also ${names.get(name)}'s: triggers and schedules are named <member>/<id>, so ${m.key}'s are not listed` }); continue; }
|
|
127
135
|
names.set(name, m.key);
|
|
128
136
|
let candidates;
|
|
@@ -218,7 +226,20 @@ const snapshotAge = (snap, now) => (snap?.takenAt ? now.getTime() - Date.parse(s
|
|
|
218
226
|
export function hostOf(local) {
|
|
219
227
|
const name = isObject(local?.host) && typeof local.host.name === "string" ? local.host.name : null;
|
|
220
228
|
const disabled = Object.fromEntries(KIND_NAMES.map((k) => [k, new Set(Array.isArray(local?.[`${k}s`]?.disabled) ? local[`${k}s`].disabled : [])]));
|
|
221
|
-
|
|
229
|
+
const t = isObject(local?.automations) ? local.automations.trust : undefined;
|
|
230
|
+
const trust = t === "*" ? "*" : new Set(Array.isArray(t) ? t : []);
|
|
231
|
+
return { name, disabled, trust };
|
|
232
|
+
}
|
|
233
|
+
/** Whether this host's `automations.trust` admits a workspace automation (`<member>/<id>`). */
|
|
234
|
+
export const trusted = (host, id) => host.trust === "*" || !!host.trust?.has(id);
|
|
235
|
+
/** What an operator adds to trust `id`: the exact list line under `automations:` `trust:`. */
|
|
236
|
+
export const trustRemedy = (id) => `add the line "- ${id}" under automations: trust: in oats-local.yaml`;
|
|
237
|
+
/** `automations.trust` entries that name no workspace trigger or schedule in `automations`
|
|
238
|
+
* (the snapshot's): stale, or a member not synced yet. A warning, never an error. */
|
|
239
|
+
export function staleTrust(host, automations) {
|
|
240
|
+
if (host.trust === "*") return [];
|
|
241
|
+
const known = new Set(automations.map((a) => a.id));
|
|
242
|
+
return [...host.trust].filter((id) => !known.has(id));
|
|
222
243
|
}
|
|
223
244
|
/** The account the host's `gh` is logged in as on one GitHub host (`gh api user`), memoized in
|
|
224
245
|
* `cache` (one per tick). `io.gh(args)` is the seam. → { ok, login } | { ok: false, error } */
|
|
@@ -237,7 +258,7 @@ export function ghLogin(ghHost, { io, cache } = {}) {
|
|
|
237
258
|
return out;
|
|
238
259
|
}
|
|
239
260
|
/** Where a workspace trigger or schedule runs, from this host's point of view.
|
|
240
|
-
* → { runsHere, reason: null | host-unnamed | assigned-elsewhere | owner-mismatch, detail?, enabledHere } */
|
|
261
|
+
* → { runsHere, reason: null | host-unnamed | assigned-elsewhere | owner-mismatch | untrusted, detail?, enabledHere } */
|
|
241
262
|
export function placementOf(a, { host, io, cache }) {
|
|
242
263
|
const enabledHere = a.enabled !== false && !host.disabled[a.kind]?.has(a.id);
|
|
243
264
|
let reason = null, detail;
|
|
@@ -248,6 +269,7 @@ export function placementOf(a, { host, io, cache }) {
|
|
|
248
269
|
const who = owner ? ghLogin(owner.host, { io, cache }) : { ok: false, error: "owner is not <host>/<login>" };
|
|
249
270
|
if (!who.ok) { reason = "owner-mismatch"; detail = `acts as ${a.owner}, but this host's gh is not logged in on ${owner?.host ?? "?"}: ${who.error}`; }
|
|
250
271
|
else if (who.login.toLowerCase() !== owner.login.toLowerCase()) { reason = "owner-mismatch"; detail = `acts as ${a.owner}; this host's gh is logged in as ${owner.host}/${who.login}`; }
|
|
272
|
+
else if (!trusted(host, a.id)) { reason = "untrusted"; detail = `declared for this host, not trusted here; to run it, ${trustRemedy(a.id)}`; }
|
|
251
273
|
}
|
|
252
274
|
return { runsHere: reason === null && enabledHere && !a.invalid && !!a.definition, reason, ...(detail ? { detail } : {}), enabledHere };
|
|
253
275
|
}
|