@awebai/oats 0.25.4 → 0.25.6
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/bin/oats.mjs +7 -3
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +156 -9
- package/capabilities/oats-aweb/injects/aweb.md +7 -0
- package/capabilities/oats-aweb/oats.json +15 -2
- package/capabilities/oats-okf/bin/oats-okf.mjs +3 -1
- package/capabilities/oats-okf/lib/config.mjs +5 -1
- package/capabilities/oats-okf/lib/io.mjs +1 -1
- package/capabilities/oats-okf/lib/migration.mjs +24 -3
- package/capabilities/oats-okf/lib/sources.mjs +52 -16
- package/capabilities/oats-okf/lib/stores.mjs +37 -15
- package/capabilities/oats-okf/oats.json +4 -1
- package/capabilities/oats-okf/skills/okf/SKILL.md +3 -2
- package/docs/capabilities.md +19 -0
- package/docs/capability-manifest.schema.json +4 -0
- package/docs/design/2026-09-20-redesign-program-board.md +1 -1
- package/docs/design/2026-09-23-workspace-module-contracts.md +27 -0
- package/docs/design/2026-09-24-desktop-phase-f-boundary.md +180 -0
- package/docs/design/2026-09-24-phase-d-plan.md +129 -0
- package/docs/desktop-cli-api.md +35 -7
- package/docs/integrations.md +73 -7
- package/docs/rebuild-to-v2.md +11 -10
- package/docs/release-notes/v0.25.5.md +42 -0
- package/docs/release-notes/v0.25.6.md +37 -0
- package/docs/workspace-adoption.md +1 -1
- package/docs/workspaces.md +11 -8
- package/lib/core.mjs +45 -7
- package/lib/resolve.mjs +28 -5
- package/package-catalog.json +2 -2
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { fs, join, dirname, safePath, readJSON, save, atomic, tree, materialize, digest, hash, withLock, exec, cleanEnv, fail, relPath, overlaps, resolve } from './io.mjs';
|
|
6
|
-
import { metadata, noGit } from './config.mjs';
|
|
6
|
+
import { metadata, noGit, gitTimeoutMs } from './config.mjs';
|
|
7
7
|
const validator = fileURLToPath(new URL('../skills/okf/scripts/okf-validate.mjs', import.meta.url));
|
|
8
8
|
// Never let local replace refs reinterpret frozen OIDs, including inside Git's
|
|
9
9
|
// transport subprocesses. Override even an explicitly supplied command env.
|
|
@@ -17,11 +17,19 @@ export function validateBase(root, base) {
|
|
|
17
17
|
if(result.errors.length || result.warnings.length) fail('E_VALIDATION', [...result.errors,...result.warnings].join('; '));
|
|
18
18
|
return {files,meta,digest:digest(files)};
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
/** Write a blob straight to a file descriptor: no in-memory buffer, so object
|
|
21
|
+
* size never limits what a base may hold. The remote budget applies because a
|
|
22
|
+
* partial clone fetches a missing blob on its first read. */
|
|
23
|
+
function writeBlob(cwd,oid,target,mode) {
|
|
24
|
+
const fd=fs.openSync(target,'wx',mode);
|
|
25
|
+
let result; try { result=spawnSync('git',['--no-replace-objects','-c','core.hooksPath=/dev/null','-C',cwd,'cat-file','blob',oid],{cwd,env:gitEnv(),timeout:gitTimeoutMs(),stdio:['ignore',fd,'pipe']}); } finally { fs.closeSync(fd); }
|
|
22
26
|
if(result.error || result.status!==0) fail('E_COMMAND','Git object read failed');
|
|
23
|
-
|
|
27
|
+
fs.chmodSync(target,mode);
|
|
24
28
|
}
|
|
29
|
+
/** A tree entry the store never materializes: outside the knowledge base root.
|
|
30
|
+
* Its absence from a staging tree is by construction; its presence is a
|
|
31
|
+
* worker's doing and is judged like any other change. */
|
|
32
|
+
const outsideBase=(base,p)=>!(base.root==='.' || p===base.root || p.startsWith(base.root+'/'));
|
|
25
33
|
function materializeGitObjects(base,dest,head) {
|
|
26
34
|
immutableCommit(dest,head);
|
|
27
35
|
const entries=gitTreeEntries(dest,head);
|
|
@@ -35,20 +43,32 @@ function materializeGitObjects(base,dest,head) {
|
|
|
35
43
|
// Index/HEAD updates do not apply content filters. They preserve the normal
|
|
36
44
|
// Git worktree needed by existing diff/publication guards without checkout.
|
|
37
45
|
git(dest,['read-tree',head]);git(dest,['update-ref','--no-deref','HEAD',head]);
|
|
46
|
+
// Only the knowledge base is materialized: the index carries the whole tree
|
|
47
|
+
// for publication, but bytes outside the base root are never read, so the
|
|
48
|
+
// size of the rest of the repository does not matter. The scope checks know
|
|
49
|
+
// that an entry outside the root is expected to be absent (see outsideBase).
|
|
38
50
|
for(const [p,entry] of entries) {
|
|
51
|
+
if(outsideBase(base,p)) continue;
|
|
39
52
|
const target=safePath(join(dest,p));fs.mkdirSync(dirname(target),{recursive:true});
|
|
40
|
-
|
|
41
|
-
const bytes=rawBlob(dest,entry.oid);
|
|
42
|
-
if(entry.mode==='120000') fs.symlinkSync(bytes.toString('utf8'),target);
|
|
43
|
-
else {fs.writeFileSync(target,bytes,{flag:'wx',mode:entry.mode==='100755'?0o755:0o644});fs.chmodSync(target,entry.mode==='100755'?0o755:0o644);}
|
|
53
|
+
writeBlob(dest,entry.oid,target,entry.mode==='100755'?0o755:0o644);
|
|
44
54
|
}
|
|
45
55
|
}
|
|
46
56
|
function clone(base, dest, selectedHead) {
|
|
47
57
|
safePath(dest);
|
|
48
58
|
if(fs.existsSync(dest)) fail('E_PATH',`staging destination exists: ${dest}`);
|
|
49
59
|
fs.mkdirSync(dirname(dest),{recursive:true});
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
// Fetch only what the store reads: the accepted branch, trees now and blobs
|
|
61
|
+
// on demand. Only a remote that does not offer object filtering gets a plain
|
|
62
|
+
// single-branch clone instead; any other failure is the failure it is. The
|
|
63
|
+
// ancestry the publication checks walk is present either way.
|
|
64
|
+
const cloneArgs=['clone','--no-hardlinks','--no-checkout','--single-branch','--branch',base.acceptedBranch];
|
|
65
|
+
try { git(dirname(dest),[...cloneArgs,'--filter=blob:none','--',base.repository,dest],{timeout:gitTimeoutMs()}); }
|
|
66
|
+
catch(e) {
|
|
67
|
+
if(e.code!=='E_COMMAND' || !/filter/i.test(e.message)) throw e;
|
|
68
|
+
fs.rmSync(dest,{recursive:true,force:true});
|
|
69
|
+
git(dirname(dest),[...cloneArgs,'--',base.repository,dest],{timeout:gitTimeoutMs()});
|
|
70
|
+
}
|
|
71
|
+
git(dest,['fetch','origin',`refs/heads/${base.acceptedBranch}`],{timeout:gitTimeoutMs()});
|
|
52
72
|
const head=selectedHead ?? git(dest,['rev-parse','FETCH_HEAD']);
|
|
53
73
|
verifyRemote(base,dest);
|
|
54
74
|
materializeGitObjects(base,dest,head);
|
|
@@ -142,7 +162,9 @@ function withVerificationIndex(cwd,fn) {
|
|
|
142
162
|
}
|
|
143
163
|
export function verifyGitScope(base, dest, baseline, { checkModes = true } = {}) {
|
|
144
164
|
return withVerificationIndex(dest,read=>{
|
|
145
|
-
|
|
165
|
+
// Entries outside the root are never materialized, so their absence is
|
|
166
|
+
// not a deletion; a present outside file that differs still is a change.
|
|
167
|
+
const names=read(['diff','--name-only','-z',baseline,'--']).split('\0').filter(Boolean).filter(p=>!(outsideBase(base,p) && !fs.existsSync(join(dest,p))));
|
|
146
168
|
// A restored working file can conceal a staged, unauthorized index entry.
|
|
147
169
|
names.push(...read(['diff','--cached','--name-only','-z',baseline,'--']).split('\0').filter(Boolean));
|
|
148
170
|
names.push(...read(['ls-files','--others','--exclude-standard','-z']).split('\0').filter(Boolean));
|
|
@@ -303,7 +325,7 @@ export function gitPublish(base, stage, proposal, receipt, persist, {beforePubli
|
|
|
303
325
|
const baseline=immutableCommit(cwd,stage.head);
|
|
304
326
|
verifyPublicationTree(base,cwd,baseline.tree,baseline.tree,proposal.before);
|
|
305
327
|
// Baseline must still be accepted. Never rebase model output without rejudging it.
|
|
306
|
-
git(cwd,['fetch','origin',`refs/heads/${base.acceptedBranch}`]);
|
|
328
|
+
git(cwd,['fetch','origin',`refs/heads/${base.acceptedBranch}`],{timeout:gitTimeoutMs()});
|
|
307
329
|
const accepted=git(cwd,['rev-parse','FETCH_HEAD']);
|
|
308
330
|
if(accepted!==stage.head) {
|
|
309
331
|
// A known or uncertain previously created PR may be reconciled, but a
|
|
@@ -350,13 +372,13 @@ export function gitPublish(base, stage, proposal, receipt, persist, {beforePubli
|
|
|
350
372
|
const publication=immutableCommit(cwd,receipt.commit);
|
|
351
373
|
if(publication.parents.length!==1 || publication.parents[0]!==stage.head) fail('E_CONFIRM','publication commit must have exactly the frozen baseline as its parent');
|
|
352
374
|
verifyPublicationTree(base,cwd,baseline.tree,publication.tree,proposal.after,proposal.before);
|
|
353
|
-
const remoteTip=()=>git(cwd,['ls-remote','--heads','origin',`refs/heads/${branch}`]).split(/\s/)[0] || null;
|
|
375
|
+
const remoteTip=()=>git(cwd,['ls-remote','--heads','origin',`refs/heads/${branch}`],{timeout:gitTimeoutMs()}).split(/\s/)[0] || null;
|
|
354
376
|
let tip=remoteTip();
|
|
355
377
|
if(tip && tip!==receipt.commit) fail('E_PR','publication branch has unexpected commit; never force push');
|
|
356
378
|
if(tip!==receipt.commit) {
|
|
357
379
|
beforePublish();
|
|
358
380
|
receipt.status='push-intent'; persist();
|
|
359
|
-
try { verifyRemote(base,cwd);git(cwd,['push','--no-follow-tags','--recurse-submodules=no','origin',`${receipt.commit}:refs/heads/${branch}`]); }
|
|
381
|
+
try { verifyRemote(base,cwd);git(cwd,['push','--no-follow-tags','--recurse-submodules=no','origin',`${receipt.commit}:refs/heads/${branch}`],{timeout:gitTimeoutMs()}); }
|
|
360
382
|
catch(e) { receipt.status='push-unknown'; receipt.error=e.message; persist(); throw e; }
|
|
361
383
|
tip=remoteTip(); if(tip!==receipt.commit) {receipt.status='push-unknown';persist();fail('E_CONFIRM','pushed head not confirmed');}
|
|
362
384
|
}
|
|
@@ -375,7 +397,7 @@ export function gitPublish(base, stage, proposal, receipt, persist, {beforePubli
|
|
|
375
397
|
const pr=matching[0];receipt.pr=pr;receipt.status='delivered';receipt.deliveredAt ||= new Date().toISOString();persist();
|
|
376
398
|
if(pr.state==='CLOSED' && !pr.mergedAt) {receipt.status='rejected';persist();fail('E_PR','PR closed without merge; retained proposal requires operator review');}
|
|
377
399
|
if(pr.mergedAt) {
|
|
378
|
-
git(cwd,['fetch','origin',`refs/heads/${base.acceptedBranch}`]);
|
|
400
|
+
git(cwd,['fetch','origin',`refs/heads/${base.acceptedBranch}`],{timeout:gitTimeoutMs()});
|
|
379
401
|
const merge=pr.mergeCommit?.oid;
|
|
380
402
|
if(!merge) fail('E_CONFIRM','merged PR lacks merge commit');
|
|
381
403
|
git(cwd,['merge-base','--is-ancestor',merge,'FETCH_HEAD']);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"capability": "oats.okf",
|
|
3
3
|
"command": "okf",
|
|
4
|
-
"version": "2.1.
|
|
4
|
+
"version": "2.1.4",
|
|
5
5
|
"compatibility": {
|
|
6
6
|
"oats": ">=0.24.4"
|
|
7
7
|
},
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
},
|
|
27
27
|
"state-dir": {
|
|
28
28
|
"description": "Absolute host-owned durable state directory for portable bindings. It is never derived from an instance home."
|
|
29
|
+
},
|
|
30
|
+
"git-timeout": {
|
|
31
|
+
"description": "Seconds allowed for each Git operation that talks to a remote (clone, fetch, push, ls-remote); default 600. Local object reads keep a short fixed limit. Raise it for large repositories behind slow links."
|
|
29
32
|
}
|
|
30
33
|
},
|
|
31
34
|
"agents": [
|
|
@@ -121,8 +121,9 @@ captured ProviderBinding as execution authority:
|
|
|
121
121
|
`payload.bindings`. Workspace, adoption, and operator values remain separate
|
|
122
122
|
inputs to the shared resolver; OKF does not select their precedence.
|
|
123
123
|
- Durable placement is explicit selected settings: physical absolute
|
|
124
|
-
`bindings-file` and `state-dir`, plus selected `harvest-runtime
|
|
125
|
-
`harvest-model
|
|
124
|
+
`bindings-file` and `state-dir`, plus selected `harvest-runtime`, optional
|
|
125
|
+
`harvest-model` and optional `git-timeout` (seconds for remote Git
|
|
126
|
+
operations, default 600). Never derive state from an instance home.
|
|
126
127
|
- Provider codecs run only after exact retained executable approval. Their
|
|
127
128
|
populated binding is not proof of readiness, enrollment, credentials, privacy
|
|
128
129
|
or publication authority. Respect typed non-ready results.
|
package/docs/capabilities.md
CHANGED
|
@@ -262,6 +262,17 @@ return `meta`, `brief`, `warning`, or runtime-specific `launch` arguments. A
|
|
|
262
262
|
**spawn hook only** may also return an `env` object for the launched process;
|
|
263
263
|
returning `env` from retire or soul-scaffold is an explicit contract error.
|
|
264
264
|
|
|
265
|
+
A **launch hook** runs at every start and restart of a home for each provider
|
|
266
|
+
captured at spawn (under its captured settings). Its `launch` arguments and
|
|
267
|
+
`env` replace that provider's previous contribution whole. Its `meta`, when
|
|
268
|
+
returned, replaces that provider's entry in `instance.json.capabilityMeta`
|
|
269
|
+
after the start succeeds — the same record the spawn hook wrote and the retire
|
|
270
|
+
hook later reads as `OATS_META` — so a provider that re-issues a credential at
|
|
271
|
+
start (a renewed session grant, for example) leaves the CURRENT one on record. A
|
|
272
|
+
launch hook that answers without `meta` keeps its previous entry; a start whose
|
|
273
|
+
preparation fails changes nothing. (Kernel ≥ 0.25.5; earlier kernels collected
|
|
274
|
+
launch `meta` and discarded it.)
|
|
275
|
+
|
|
265
276
|
Hook environment values are strings, at most 8192 UTF-8 bytes, with no NUL or
|
|
266
277
|
newlines. Names use the portable environment grammar and must belong to an
|
|
267
278
|
unambiguous vendor namespace. Only a dotted capability ID participates: its
|
|
@@ -271,6 +282,14 @@ for this contract. Hyphenated vendors are also excluded because translating a
|
|
|
271
282
|
hyphen to `_` would let `aweb-evil.*` collide with names already inside
|
|
272
283
|
`aweb.*`'s `AWEB_*` namespace.
|
|
273
284
|
|
|
285
|
+
A manifest's `settings.<key>` may carry `hostOnly: true` (decision 27). Such a
|
|
286
|
+
key is a fact about the machine — a custody directory, a state root — and the
|
|
287
|
+
resolver accepts it only from the deployment's own `oats-local.yaml`
|
|
288
|
+
`settings.<capability>`; a committed workspace or soul file or a `--provider`
|
|
289
|
+
flag carrying it is refused (`E_WORKSPACE_SCHEMA`, reason `host-only-key`).
|
|
290
|
+
Declare it for any key whose value points at something a committed file must
|
|
291
|
+
never be able to choose.
|
|
292
|
+
|
|
274
293
|
A hook may return only names in its manifest's exact `environment` declaration.
|
|
275
294
|
For package capabilities that declaration is part of the integrity-locked tree
|
|
276
295
|
and of what the per-version approval showed; for member capabilities it is
|
|
@@ -263,6 +263,10 @@
|
|
|
263
263
|
},
|
|
264
264
|
"description": {
|
|
265
265
|
"type": "string"
|
|
266
|
+
},
|
|
267
|
+
"hostOnly": {
|
|
268
|
+
"type": "boolean",
|
|
269
|
+
"description": "When true, this key is a host fact (a custody path, a state directory): the resolver accepts it only from the deployment's oats-local.yaml settings.<capability> and refuses it in the workspace file, byTeam payloads, a soul's slot payload and --provider flags (E_WORKSPACE_SCHEMA reason host-only-key). Decision 27."
|
|
266
270
|
}
|
|
267
271
|
},
|
|
268
272
|
"additionalProperties": false
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Purpose:** the one accurate view of every work stream in the redesign, what is on main, what is in flight, who owns it, and what blocks it. Lead: `oats-expert` (redesign lead). Updated whenever anything merges, is returned, or reality changes. Older per-lane boards are superseded by this file.
|
|
4
4
|
|
|
5
|
-
**Last update:** 2026-09-23 20:00Z · **0.25.0–0.25.
|
|
5
|
+
**Last update:** 2026-09-23 20:00Z · **0.25.0–0.25.5 PUBLISHED** (workspace model A–C; team-review fixes; operator-rebuild round; OATS_SOUL_ID; quarantine-retry fix; launch meta + catalog pins) · **OKF v2.1.4 + oats.aweb v1.12.0 TAGGED and pinned** · **decision 27 accepted; K1′/K1″/K2 kernel PR next** · **oats.aweb 1.13.0 (#110) queued** · **Desktop 10B-0 resumed** · Phase D plan next · **Desktop engineer paused by the human** (10B-0 uncommitted foundation preserved; resume is the human's call; 0.24.14 to be cut from a maintenance branch off `e5cdaf95` when its PR lands) · parity pipeline ⏸.
|
|
6
6
|
|
|
7
7
|
Legend: ✅ on main/published · 🔄 in flight (PR/branch) · 🟡 preserved, not adopted · ⬜ not started · ⛔ blocked
|
|
8
8
|
|
|
@@ -565,6 +565,33 @@ is a member; a soul that lives in it may need a work clone). Under an explicit
|
|
|
565
565
|
`oats-local.yaml` `standalone:` header the next steps say the view is standalone
|
|
566
566
|
and list only that repo.
|
|
567
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
|
+
|
|
568
595
|
### 0.25.3 — `OATS_SOUL_ID` (stable soul identity for providers)
|
|
569
596
|
|
|
570
597
|
The per-commit soul cache (0.25.1, M1) made `realpath(<home>/soul)` change with every
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Desktop Phase F — the Desktop is built FOR workspace model v2
|
|
2
|
+
|
|
3
|
+
**Status**: boundary for the Desktop engineer, issued 2026-09-24 by the lead under
|
|
4
|
+
the human's direction: *"the desktop should not just adapt to the new version,
|
|
5
|
+
it should be natively built for it."* Supersedes the Phase 3 parity plan's
|
|
6
|
+
assumptions about what the Desktop reads; keeps its visual deliverable (the
|
|
7
|
+
redesign frames, excl. 05/06).
|
|
8
|
+
|
|
9
|
+
**Human's second directive, verbatim intent**: make ultra sure the Desktop is set
|
|
10
|
+
up to work in the new setup — new versions, new CLI, new deployment shape.
|
|
11
|
+
|
|
12
|
+
## 0. Why this is a rebuild of the model, not a patch
|
|
13
|
+
|
|
14
|
+
The Desktop today is a 0.24 product that *tolerates* 0.25 kernels: its
|
|
15
|
+
`ACCEPT_RANGE` admits `0.25.x`, so it launches, and the few CLI verbs it drives
|
|
16
|
+
(`version`, `session *`, `spawn`, `retire`, `schedule`, `catalog`) still answer.
|
|
17
|
+
But its **model of a deployment is 0.24's**, reimplemented in
|
|
18
|
+
`packages/desktop/server/deployment.mjs`: it reads `oats-config.yaml`,
|
|
19
|
+
`agents/<name>/soul`, `local-agents/`, and capability manifests from
|
|
20
|
+
`.agents/capabilities/installed/`, and derives the roster itself. None of these
|
|
21
|
+
is how a v2 deployment is shaped:
|
|
22
|
+
|
|
23
|
+
| 0.24 (what the Desktop reads) | v2 (what a deployment IS) |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `oats-config.yaml` at the repo root | `oats-local.yaml` at the deployment directory → `workspace:` URL |
|
|
26
|
+
| souls at `agents/<name>/soul` | souls at `souls/<name>` **in member repos**, materialised per commit into `agents/<name>/souls/<commit>` |
|
|
27
|
+
| capabilities installed into `.agents/capabilities/installed/` | packages resolved through `oats-workspace.yaml` + the official catalog, locked in `oats-lock.json`, **copied whole into each instance home** (`.oats/modules/<cap>`) |
|
|
28
|
+
| `team:` block | `oats-membership.yaml` `team:` label per member; `messaging.byTeam.<label>` payloads |
|
|
29
|
+
| `oats catalog` DTO | removed; the catalog is `package-catalog.json` resolved by `oats sync` |
|
|
30
|
+
| roster derived by the Desktop | `oats status --json` is the roster (agents, instances, `modules[]` drift, `soul` source drift, `identity`) |
|
|
31
|
+
|
|
32
|
+
A Desktop that keeps the left column and adds a few right-column fields is the
|
|
33
|
+
"adapt" outcome the human rejected. Phase F replaces the left column.
|
|
34
|
+
|
|
35
|
+
## 1. The principle: the kernel is the model; the Desktop renders and drives it
|
|
36
|
+
|
|
37
|
+
- **Read model**: every fact the Desktop shows about a deployment comes from
|
|
38
|
+
the kernel's JSON surfaces — `oats status --json`, `oats inspect --json`,
|
|
39
|
+
`oats workspace status --json`, `oats spawn --preview --json`, `oats
|
|
40
|
+
readiness --json`, `oats version --json`, `oats sync --json`. The Desktop
|
|
41
|
+
does not parse `oats-config.yaml`, `oats-local.yaml`, `soul.yaml`,
|
|
42
|
+
manifests or lock files itself. Where a fact is missing from a kernel
|
|
43
|
+
surface, the fix is a kernel PR (lead's lane), not a Desktop-side parser.
|
|
44
|
+
- **Write model**: every mutation is a kernel verb with `--json`: `sync`,
|
|
45
|
+
`sync --approve`, `spawn` (preview → `--expect-decision` apply), `retire`,
|
|
46
|
+
`session start|restart|recompose`, `schedule *`, `onboard`. The Desktop
|
|
47
|
+
never writes a deployment file.
|
|
48
|
+
- **Version contract**: `oats version --json` `features[]` is the capability
|
|
49
|
+
probe. The Desktop's `ACCEPT_RANGE` moves to `>=0.25.6 <0.27.0` (the first
|
|
50
|
+
kernel with `served-identity`), and each feature the UI depends on is gated
|
|
51
|
+
on its `features[]` name, not on a version number.
|
|
52
|
+
|
|
53
|
+
## 2. Deliverables (slices; each is one PR against main, each reviewed by the lead)
|
|
54
|
+
|
|
55
|
+
**F1 — Deployment model on kernel JSON.** Replace
|
|
56
|
+
`server/deployment.mjs`'s own readers with `oats status --json` (+ `oats
|
|
57
|
+
workspace status --json` for the workspace header: name, key, members, packages,
|
|
58
|
+
lock state, `approvalNeeded`). Roster rows carry `modules[]` drift, `soul`
|
|
59
|
+
source (`repo: <member> @ <c7>`, "member moved since"), `identity`. Legacy
|
|
60
|
+
`local-agents/`/`tmp-agents/` paths are dropped. Remove `server/catalog.mjs`'s
|
|
61
|
+
`oats catalog` DTO validation (the verb no longer exists).
|
|
62
|
+
|
|
63
|
+
**F2 — Workspace onboarding and sync.** A "Open deployment" flow that: detects a
|
|
64
|
+
directory with `oats-local.yaml` (v2), or offers `oats onboard` for one without
|
|
65
|
+
(the kernel asks for the deployment directory and the workspace URL — the
|
|
66
|
+
Desktop collects both, never invents a folder name; decision 9). A "Sync"
|
|
67
|
+
action runs `oats sync --json`; exit 2 with `approvalNeeded[]` renders an
|
|
68
|
+
approval sheet showing each package's `executables` digest and applies with
|
|
69
|
+
`oats sync --approve <id>@<version>` (the version string is what
|
|
70
|
+
`approvalNeeded[].version` reports — for a git-pinned package, the full OID).
|
|
71
|
+
Lock drift and `E_PACKAGE_INTEGRITY` are surfaced verbatim.
|
|
72
|
+
|
|
73
|
+
**F3 — Spawn dialog on the v2 preview.** The preview already carries
|
|
74
|
+
`modules`, `providers`, `settings.<cap>`, `team`, `resolution`, `decision`.
|
|
75
|
+
Render: which modules the instance will get and from where (package vs member,
|
|
76
|
+
commit); the merged `settings.<cap>` per provider (read-only); **Identity**
|
|
77
|
+
select (local | global) with a Resident field for global, prefilled from
|
|
78
|
+
`settings.<messaging cap>.identity`, forwarded as `--provider <cap>
|
|
79
|
+
identity.mode=… identity.resident=…` (decision 27 — there is no kernel flag);
|
|
80
|
+
`decision.effective.providers` is what the confirm binds. `cli-adapter.mjs`
|
|
81
|
+
`SPAWN_ARG_RULES` gains one `provider` rule (capability id, dotted key, value
|
|
82
|
+
grammar); no identity-named rules. Work mode select includes `workspace`.
|
|
83
|
+
|
|
84
|
+
**F4 — Instance card and roster on v2 facts.** The served-identity line (`acts
|
|
85
|
+
as <address> via grant, expires <t>` / `alias <a> on <team>`); module rows with
|
|
86
|
+
"moved since" markers and a **re-spawn** action (preview → apply, then retire
|
|
87
|
+
the old instance — module homes answer `E_UNSUPPORTED_MODE` to
|
|
88
|
+
`session recompose` by design; an instance never changes under itself); the
|
|
89
|
+
soul-source row; quarantine state from `rollbackIncomplete` with the retry
|
|
90
|
+
action (`oats retire`) and `--force` behind a confirm.
|
|
91
|
+
|
|
92
|
+
**F5 — Redesign frames** (the original Phase 3 deliverable, excl. 05/06),
|
|
93
|
+
implemented on top of F1–F4 rather than on the 0.24 model.
|
|
94
|
+
|
|
95
|
+
**F6 — Version and doctor surface.** `oats version --json` and `oats doctor
|
|
96
|
+
--json` in an About/Health pane; `ACCEPT_RANGE` and the three pins move to
|
|
97
|
+
`>=0.25.6`; a kernel below the floor is refused with the upgrade command shown.
|
|
98
|
+
|
|
99
|
+
Order: F1 → F2 → F3 → F4 → F5 → F6, or F1 then F3/F4 in parallel if the engineer
|
|
100
|
+
spawns children (its call; the lead reviews each PR).
|
|
101
|
+
|
|
102
|
+
## 3. What the engineer must LEARN first (before F1)
|
|
103
|
+
|
|
104
|
+
Read, in this order, in the checked-out main:
|
|
105
|
+
1. `docs/workspaces.md` — the v2 model end to end (deployment vs workspace,
|
|
106
|
+
members, packages, payloads, `byTeam`, hosting).
|
|
107
|
+
2. `docs/rebuild-to-v2.md` — how an operator builds a deployment (this is the
|
|
108
|
+
flow F2 wraps).
|
|
109
|
+
3. `docs/desktop-cli-api.md` — every JSON surface, with examples; note
|
|
110
|
+
`features[]`, `decision.effective.providers`, `instances[].identity`.
|
|
111
|
+
4. `docs/design/2026-09-23-workspace-module-contracts.md` §0.25.x — what
|
|
112
|
+
changed per release and why.
|
|
113
|
+
5. `agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md` and
|
|
114
|
+
`…/served-identity-is-a-messaging-layer-fact.md` — the decisions.
|
|
115
|
+
6. `test/fixtures/northwind/build.mjs` — the two-team fixture workspace; run
|
|
116
|
+
`node --test test/spawn-workspace.test.mjs` once and read what a v2 spawn
|
|
117
|
+
produces on disk (`.oats/modules/`, `instance.json` `modules`/`providers`/
|
|
118
|
+
`workspace.soul.id`).
|
|
119
|
+
|
|
120
|
+
Then build a scratch deployment by hand with the CLI (`oats onboard`, `oats
|
|
121
|
+
sync`, `oats sync --approve`, `oats spawn --preview`, `oats spawn --no-launch`,
|
|
122
|
+
`oats status`, `oats inspect`, `oats retire`) against the Northwind fixture
|
|
123
|
+
remotes, and keep the transcript: F1's tests are written against exactly those
|
|
124
|
+
JSON shapes.
|
|
125
|
+
|
|
126
|
+
## 3b. Native rework, not a compatibility layer (human, 2026-09-24)
|
|
127
|
+
|
|
128
|
+
The human's rule, verbatim intent: *"do a native rework — do not keep v1-specific
|
|
129
|
+
things, and no v1 modules calling v2 modules."* Concretely:
|
|
130
|
+
|
|
131
|
+
- **Remove, do not wrap.** A 0.24 reader (`server/deployment.mjs`'s
|
|
132
|
+
`oats-config.yaml`/`soul.yaml`/manifest parsing, `local-agents/`,
|
|
133
|
+
`.agents/capabilities/installed/`, the `oats catalog` DTO) is deleted in the
|
|
134
|
+
slice that replaces it — never kept behind a flag, a fallback branch, or an
|
|
135
|
+
"if the kernel is old" path. The Desktop supports one kernel line
|
|
136
|
+
(`ACCEPT_RANGE` from 0.25.6) and refuses older ones with the upgrade command.
|
|
137
|
+
- **No adapters between generations.** No module whose job is to translate a
|
|
138
|
+
v1-shaped object into a v2-shaped one or vice versa (no `legacyRosterToV2()`,
|
|
139
|
+
no `toOldCard()`); the v2 kernel JSON is consumed where it is read and shaped
|
|
140
|
+
once for rendering. If a v1 module still needs a v2 fact, the v1 module is
|
|
141
|
+
the thing being replaced — replace it, do not feed it.
|
|
142
|
+
- **Names and types follow v2.** Types, fields and UI labels use the kernel's
|
|
143
|
+
vocabulary (workspace, member, package, module, deployment, soul source,
|
|
144
|
+
served identity); 0.24 vocabulary (installed capability, config chain, team
|
|
145
|
+
block, agents root as identity) leaves the codebase with the code that used
|
|
146
|
+
it. `git grep` for the old terms is part of each slice's exit check.
|
|
147
|
+
- **Tests follow the same rule.** Fixtures shaped like 0.24 deployments are
|
|
148
|
+
deleted with the readers; new fixtures are v2 deployments produced by the
|
|
149
|
+
kernel (Northwind or a hand-built scratch deployment), not hand-written
|
|
150
|
+
JSON imitating old shapes.
|
|
151
|
+
- **One exception, stated per case.** Where a 0.24 concept has a genuine v2
|
|
152
|
+
successor with the same meaning and the Desktop code is already correct for
|
|
153
|
+
it (a terminal broker, a tmux target admission), it stays — the PR names it
|
|
154
|
+
as "unchanged, v2-agnostic", not as "kept for compatibility".
|
|
155
|
+
|
|
156
|
+
Exit check for Phase F as a whole: no file under `packages/desktop/` reads a
|
|
157
|
+
deployment file, names a 0.24 concept, or contains a code path that exists
|
|
158
|
+
only for a kernel below the floor.
|
|
159
|
+
|
|
160
|
+
## 4. Rules that do not change
|
|
161
|
+
|
|
162
|
+
- `packages/desktop/**` only; kernel gaps go to the lead as a written ask
|
|
163
|
+
(they become kernel PRs; the engineer never adds a Desktop-side parser to
|
|
164
|
+
work around one).
|
|
165
|
+
- No native tmux/PTY/Electron execution by the agent; the lead's native gate
|
|
166
|
+
at review time is the acceptance.
|
|
167
|
+
- Every slice: focused tests + the intended mutants on the new code; the
|
|
168
|
+
Desktop suite green; one PR per slice against main; lead's pr-review.
|
|
169
|
+
- The design frames are the visual authority; the kernel JSON is the data
|
|
170
|
+
authority; where a frame shows a 0.24 concept (an "installed capability"
|
|
171
|
+
list, a `team:` block), the frame is adapted to the v2 concept and the
|
|
172
|
+
adaptation noted in the PR.
|
|
173
|
+
|
|
174
|
+
## 5. Acceptance for Phase F as a whole
|
|
175
|
+
|
|
176
|
+
The lead builds a fresh v2 deployment from the Northwind fixture using ONLY the
|
|
177
|
+
Desktop (open → onboard → sync → approve → spawn with a global identity →
|
|
178
|
+
inspect → retire) on kernel 0.25.6+, and every fact shown matches `oats status
|
|
179
|
+
--json` / `oats inspect --json` byte for byte. Nothing in
|
|
180
|
+
`packages/desktop/server` reads a deployment file.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Phase D — the OATS project runs on the architecture it offers (plan)
|
|
2
|
+
|
|
3
|
+
**Status**: plan, 2026-09-24, lead. Decisions 18–22 of the workspace model, the
|
|
4
|
+
five-soul roster and its 2026-09-24 amendment, the human's sequencing
|
|
5
|
+
("knowledge centralisation first"; "do not retire live souls until their
|
|
6
|
+
instances retire"). Mailed to the human and the OSS coordinator before the
|
|
7
|
+
first swarm. Method per standing instruction: write → swarm build →
|
|
8
|
+
adversarial-review swarm → PR → main; releases under delegated authority.
|
|
9
|
+
|
|
10
|
+
## Slices, in order
|
|
11
|
+
|
|
12
|
+
### D1 — Knowledge centralisation (IN PROGRESS)
|
|
13
|
+
|
|
14
|
+
Goal: every roster soul's knowledge lives in the central base
|
|
15
|
+
`awebai/oats-knowledge` (OKF 2.1.4), one owned node each; the in-repo
|
|
16
|
+
`agents/*/soul/knowledge` bundles stop receiving writes and are decommissioned
|
|
17
|
+
when no live instance links them.
|
|
18
|
+
|
|
19
|
+
Done: nodes `oats-operator-expert` and `integrations-expert` chartered
|
|
20
|
+
(oats-knowledge PR #3); eight attributed seeds landed in
|
|
21
|
+
`agents/oats-expert/soul/knowledge/inbox` (oats PR #121); the roster amendment
|
|
22
|
+
accepted; the rule "no per-soul knowledge merges" in force.
|
|
23
|
+
|
|
24
|
+
Work:
|
|
25
|
+
1. **Copy-migrate by judgement** (the 2026-09-21 method: two-part test plus the
|
|
26
|
+
2026-09-24 recipe refinement; not copying): `agents/oats-expert/soul/knowledge`
|
|
27
|
+
(~130 files) → node `oats-expert`; `agents/oats-desktop-engineer/soul/knowledge`
|
|
28
|
+
(~120) → `oats-desktop-expert`; `agents/cli-dev/soul/knowledge` (~155) →
|
|
29
|
+
`oats-kernel-expert`; `agents/integrations-expert/soul/knowledge` (13) →
|
|
30
|
+
`integrations-expert`. Others (`dev-coordinator`, `docs-expert`, `ux-designer`,
|
|
31
|
+
`lead`, `oats-coordinator`) are assessed for the few universal concepts they
|
|
32
|
+
hold and otherwise not carried. One PR per node on oats-knowledge, reviewed
|
|
33
|
+
by the lead; the OSS coordinator reviews the operator and integrations PRs.
|
|
34
|
+
2. **Seed the operator node** — attributed to `oats-expert-antares`:
|
|
35
|
+
MOVE (not copy) from the oats-expert bundle: `lessons/the-aweb-team-root-must-sit-where-the-spawn-hook-looks`,
|
|
36
|
+
`lessons/okf-state-directory-must-sit-outside-every-work-tree`,
|
|
37
|
+
`lessons/capability-trust-hash-covers-the-installation-record`,
|
|
38
|
+
`lessons/a-locally-minted-oats-identity-has-no-cross-team-first-contact-address`,
|
|
39
|
+
`lessons/stale-checkout-serves-stale-soul`,
|
|
40
|
+
`playbooks/rebuild-a-deployment-in-scratch-against-local-bare-remotes`;
|
|
41
|
+
generalise the R1–R10 rebuild findings and the published-combination
|
|
42
|
+
verifications from `stewardship/delivery-log` (the log keeps the record).
|
|
43
|
+
From the inbox: `check-the-record-before-redesigning-identity`,
|
|
44
|
+
`grant-custody-service-and-renewal-belong-on-the-custody-host` (operator
|
|
45
|
+
half), `the-wake-broker-accepts-a-grant-home`.
|
|
46
|
+
3. **Seed the integrations node** — from the inbox:
|
|
47
|
+
`a-merged-provider-payload-cannot-enforce-host-only-keys`,
|
|
48
|
+
`aw-grant-commands-resolve-the-identity-from-cwd-only` (discipline half),
|
|
49
|
+
`oats-runtime-requirements-for-grant-backed-resident-operation`; from the
|
|
50
|
+
integrations-expert bundle: `fake-aw-must-model-real-refusals` and the rest
|
|
51
|
+
of this week's harvested lessons.
|
|
52
|
+
4. **Route the remainder of the inbox**: aweb package expert (D3) gets
|
|
53
|
+
`aweb-grants-are-team-bound-to-the-custody-identitys-active-team`,
|
|
54
|
+
`a-grant-signed-send-must-name-the-subject-as-sender` and the aw halves;
|
|
55
|
+
kernel expert gets `path-keyed-owner-registry-breaks-under-per-commit-soul-copies`.
|
|
56
|
+
5. **Rebind the souls** in `souls/<name>/`: the `knowledge:` grammar there is
|
|
57
|
+
still 0.24's (`capability` + `source: git:…@v2.1.2#oats-package`); v2 is
|
|
58
|
+
`capabilities: { oats.okf: { from: package } }` plus `okf.json`
|
|
59
|
+
(`{ version: 1, owner: <node owner uuid>, owns: ["oats/<node>"], reads: [...] }`)
|
|
60
|
+
and the store `oats` naming `awebai/oats-knowledge` / `knowledge` / `main`.
|
|
61
|
+
Add `souls/oats-operator-expert` (rename of `oats-setup-expert`, keeps the
|
|
62
|
+
`oats.setup` charter) and `souls/integrations-expert`.
|
|
63
|
+
6. **Do NOT delete** `agents/<n>/soul/knowledge` or the legacy souls while a
|
|
64
|
+
live instance links them (human rule). Record which are live; decommission
|
|
65
|
+
as they retire; new spawns use `souls/<n>`.
|
|
66
|
+
7. **Release playbook** (`oats-expert` node, stewardship area): landing order
|
|
67
|
+
for provider PRs (tag → pin on the branch → squash; a bundled provider never
|
|
68
|
+
lands ahead of its tag), the version literals to bump on a catalog bump,
|
|
69
|
+
the mirror checker, the bump-PR step. First entries: today's two lessons.
|
|
70
|
+
|
|
71
|
+
### D2 — The OATS workspace as a v2 workspace (decisions 18, 19, 21)
|
|
72
|
+
|
|
73
|
+
`oats-workspace.yaml` at the `oats` repo (name `oats`; members = the seven
|
|
74
|
+
framework repos incl. `oats` itself; `packages:` = oats.framework / okf / aweb /
|
|
75
|
+
jira / linear / authoring / dev pinned from the catalog; `defaults.capabilities`
|
|
76
|
+
= `oats.core` from package + knowledge `oats.okf`); `oats-membership.yaml` ×7
|
|
77
|
+
(each package repo is a member AND a package publisher — non-collapse:
|
|
78
|
+
`packages:` resolves it as a package, membership only grants trust and a soul).
|
|
79
|
+
`package-catalog.json` stays the official marketplace (decision 21); the `oats`
|
|
80
|
+
repo keeps `oats-dev` as dev capabilities. A fresh deployment directory (asked
|
|
81
|
+
for, never named by convention — decision 9) is the acceptance: `oats onboard`
|
|
82
|
+
→ `sync` → `approve` → spawn every roster soul `--no-launch`.
|
|
83
|
+
|
|
84
|
+
### D3 — Souls to `souls/<name>` and the six package-expert souls (decision 20)
|
|
85
|
+
|
|
86
|
+
Each package repo carries `souls/<pkg>-expert` (okf-expert, aweb-expert,
|
|
87
|
+
jira-expert, linear-expert, authoring-expert, dev-expert), the expert in that
|
|
88
|
+
package, with a node in the central base from day one. **Seams named in the
|
|
89
|
+
charters** (roster amendment): `aweb-expert` READS
|
|
90
|
+
`aweb-protocol-expert` in base `aweb-oss-knowledge` (repo
|
|
91
|
+
`github.com/awebai/aweb`, root `knowledge/`, branch `main`, OKF 2.1.x
|
|
92
|
+
descriptor at `knowledge/okf-base.json`; owner `1913b77b-…`) through a
|
|
93
|
+
read-only store reference; `okf-expert` names its seam to the knowledge-theory
|
|
94
|
+
material in `oats-expert`. Whether the aweb bookshelf decisions the program
|
|
95
|
+
rests on are published into that node is the aweb side's call (asked).
|
|
96
|
+
|
|
97
|
+
### D4 — `oats.core` and `oats.setup` rewritten (decision 22, W9b)
|
|
98
|
+
|
|
99
|
+
Not patched: written for the v2 world. `oats.core`: home layout, `oats status`
|
|
100
|
+
with modules/soul/identity rows, spawn preview → apply, `sync --approve`, the
|
|
101
|
+
two-directory boundary, what a module is. `oats.setup` (held by
|
|
102
|
+
`oats-operator-expert`): onboarding that ASKS for the deployment directory and
|
|
103
|
+
the workspace URL, the hosting rule (decision 26), the public-member executable
|
|
104
|
+
rule, the rebuild guide as procedure with the operator node as rationale.
|
|
105
|
+
Developer souls in `oats.dev` gain `promotesTo: <node>` (roster amendment);
|
|
106
|
+
the harvester delivers to that node as a PR the owning expert reviews.
|
|
107
|
+
|
|
108
|
+
### D5 — Catalog update and 0.26.0
|
|
109
|
+
|
|
110
|
+
Catalog pins for the new package versions; **widen Desktop `ACCEPT_RANGE` and
|
|
111
|
+
the three pins FIRST** (minor bump rule); release notes; tag; the fresh
|
|
112
|
+
deployment from D2 rebuilt on the published artefacts by the OSS coordinator
|
|
113
|
+
(outsider verification) before the program board marks Phase D done.
|
|
114
|
+
|
|
115
|
+
## Adversarial review (per slice)
|
|
116
|
+
|
|
117
|
+
Each slice's swarm is followed by a review swarm with the standing lenses
|
|
118
|
+
(direction against the decisions; correctness by reproduction; security —
|
|
119
|
+
trust at acquisition, hoisted paths, hook approval, host-only keys; docs as
|
|
120
|
+
contract — every guide claim has a test), plus two Phase-D-specific ones: **the
|
|
121
|
+
outsider** (can a reader who was not in the room set OATS up from `oats.setup`
|
|
122
|
+
alone?) and **the seam** (does every cross-project read resolve to a real node
|
|
123
|
+
with a real owner?).
|
|
124
|
+
|
|
125
|
+
## Out of scope
|
|
126
|
+
|
|
127
|
+
Desktop (Phase F, its own boundary); oats.aweb 1.13.0 re-land (held on the
|
|
128
|
+
aweb release); legacy `~/OATS` deployment cutover (the operator's, on the
|
|
129
|
+
published 0.26.0).
|
package/docs/desktop-cli-api.md
CHANGED
|
@@ -376,12 +376,32 @@ the pre-fix marker and is never accepted for dispatch.
|
|
|
376
376
|
import; `--instructions-file`/`--def-file` refused with `E_BAD_ARGS`). Test:
|
|
377
377
|
the deployment tree is byte-identical after a success, a refusal and an
|
|
378
378
|
unknown-soul preview.
|
|
379
|
+
**Workspace deployments (0.25.1+) — one stated exception**: the first preview
|
|
380
|
+
of a workspace soul may populate the deployment's per-commit soul cache
|
|
381
|
+
(`agents/<soul>/souls/<commit>/`, the swappable `agents/<soul>/soul` pointer,
|
|
382
|
+
`soulFetched: true` in the result). That cache is derived, content-addressed
|
|
383
|
+
and idempotent — the same member commit yields the same bytes, a later
|
|
384
|
+
preview of the same commit writes nothing — and nothing else moves: no lock,
|
|
385
|
+
no event, no home, no instance. A Desktop treats a preview as
|
|
386
|
+
side-effect-free for everything it shows; it must not assume the deployment
|
|
387
|
+
directory's byte-identity across the FIRST preview of a soul or commit.
|
|
379
388
|
- **Exact root**: `spawn <soul> --agents-root <abs>` binds the soul to that root
|
|
380
389
|
(as inspect/readiness take it) — no team-soul / capability-agent / importable-
|
|
381
390
|
def fallback; mismatch → `E_SOUL_UNKNOWN`. The preview echoes
|
|
382
391
|
`subject {soul, agentsRoot|null, dir|null}` **as given, byte-exact**.
|
|
383
392
|
- **Decision binding**: `decision {instance, home, branch, base{ref,oid},
|
|
384
|
-
revision}` (24-hex).
|
|
393
|
+
effective{…, providers}, resolution, revision}` (24-hex). From 0.25.6
|
|
394
|
+
(`features: served-identity`) `effective.providers` is the merged
|
|
395
|
+
per-module payload each provider will receive (`{ "<cap>": {…} }`, exactly
|
|
396
|
+
`settings.<cap>` of the preview) — so a confirmed apply binds every
|
|
397
|
+
provider fact (an identity choice, a delivery mode) **by value**; a Desktop
|
|
398
|
+
that changes a provider field re-previews. `instances[].identity` (status)
|
|
399
|
+
and `selected.identity` (inspect) carry the served principal a messaging
|
|
400
|
+
provider reported: `{ mode: "local"|"global", alias, team, address|null,
|
|
401
|
+
resident|null, grant?: { id, expiresAt, scopes }, provider }`; absent when
|
|
402
|
+
no provider emitted one. A Desktop offers the identity choice as
|
|
403
|
+
`--provider <cap> identity.mode=… identity.resident=…` — there is no
|
|
404
|
+
kernel flag for it. Apply with `spawn … --expect-decision <revision>`: the
|
|
385
405
|
kernel recomputes name/home/branch/base under the same placement path and
|
|
386
406
|
refuses **`E_DECISION_STALE`** with `details.decision` (the fresh one) on ANY
|
|
387
407
|
drift — no auto-suffix, no silent re-base, nothing created. A GUI re-previews
|
|
@@ -774,9 +794,12 @@ written. Captured selectors are refused (`E_BAD_ARGS`).
|
|
|
774
794
|
|
|
775
795
|
- `sync` is the `syncApi: 1` report of the first sync (members, packages,
|
|
776
796
|
changes, `approvalNeeded`, `problems`); `lock` is the lock it wrote.
|
|
777
|
-
- **Exit `2` with `ok: true`** when `sync.approvalNeeded` is non-empty
|
|
778
|
-
|
|
779
|
-
|
|
797
|
+
- **Exit `2` with `ok: true`** when `sync.approvalNeeded` is non-empty. Approve
|
|
798
|
+
non-interactively with `oats sync --dir <dir> --approve <id>@<version> …`
|
|
799
|
+
(0.25.2+; `<version>` is `approvalNeeded[].version` verbatim — a catalog
|
|
800
|
+
version, or the full commit OID for a git-pinned package); a Desktop renders
|
|
801
|
+
`approvalNeeded[].executables` (the digest of the executable set it is
|
|
802
|
+
approving) and `targets`, then runs that command. Exit `0` otherwise.
|
|
780
803
|
- `hosting` states decision 26 (the kernel cannot see forge visibility, so it
|
|
781
804
|
reports `hostIsMember` and the rule rather than judging).
|
|
782
805
|
- `next.clone[]` is one row per **confirmed** member (`url` = what the
|
|
@@ -801,9 +824,10 @@ written. Captured selectors are refused (`E_BAD_ARGS`).
|
|
|
801
824
|
|
|
802
825
|
Discovers, confirms membership, resolves `packages:` to commits, writes
|
|
803
826
|
`oats-lock.json` (lockfileVersion 3), reports. **Exit `2` with `ok: true`** when
|
|
804
|
-
the lock was written but approvals are pending (`approvalNeeded` non-empty)
|
|
805
|
-
|
|
806
|
-
|
|
827
|
+
the lock was written but approvals are pending (`approvalNeeded` non-empty).
|
|
828
|
+
Approve with repeatable `--approve <id>@<version>` (0.25.2+, non-interactive;
|
|
829
|
+
`<version>` = `approvalNeeded[].version` verbatim); the interactive prompt is
|
|
830
|
+
the TTY fallback, not the contract. Exit `0` otherwise.
|
|
807
831
|
|
|
808
832
|
```json
|
|
809
833
|
{"syncApi":1,
|
|
@@ -1052,6 +1076,10 @@ refreshes the home in place:
|
|
|
1052
1076
|
materialized modules is not supported yet; re-spawn"): its `AGENTS.md` was
|
|
1053
1077
|
composed from the soul at a recorded member commit plus the materialized
|
|
1054
1078
|
modules' injects, and the instance never changes under itself (decision 7).
|
|
1079
|
+
**Desktop contract (Phase F, F4)**: for a module home, show the drift rows
|
|
1080
|
+
and offer *re-spawn* (preview → apply of the same soul/purpose, then retire
|
|
1081
|
+
the old instance); do not offer "recompose". A kernel recompose for module
|
|
1082
|
+
homes is not planned — an instance never changes under itself.
|
|
1055
1083
|
The refresh path for such a home is a new spawn (the soul is re-fetched at
|
|
1056
1084
|
the member's current commit). `session-recompose` **stays advertised** in
|
|
1057
1085
|
`features[]` because the verb still works for classic homes; gate the UI
|