@awebai/oats 0.24.4 → 0.24.5
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 +19 -5
- package/capabilities/oats-aweb/oats.json +44 -3
- package/capabilities/oats-okf/lib/binding-wire.mjs +57 -7
- package/capabilities/oats-okf/oats.json +12 -3
- package/docs/design/2026-09-20-redesign-program-board.md +40 -4
- package/docs/first-team.md +8 -1
- package/docs/layers.md +1 -1
- package/docs/official-marketplace.md +1 -1
- package/docs/packages.md +2 -2
- package/docs/release-notes/oats-framework-v1.1.3.md +11 -0
- package/docs/release-notes/v0.24.5.md +12 -0
- package/docs/workspace-adoption.md +6 -6
- package/docs/workspaces.md +1 -1
- package/lib/captured-launch-request.mjs +22 -2
- package/lib/core.mjs +25 -3
- package/lib/helper-injection-policy.mjs +7 -1
- package/lib/prepare-composition.mjs +21 -2
- package/lib/setup-expert-source.mjs +25 -1
- package/package-catalog.json +3 -3
- package/package.json +1 -1
package/bin/oats.mjs
CHANGED
|
@@ -4328,7 +4328,14 @@ function onboardCmd() {
|
|
|
4328
4328
|
if (findAgent(root, SETUP_EXPERT)) throw Object.assign(new Error("oats-setup-expert already exists; it will not be overwritten"), { code: "E_AGENT_EXISTS" });
|
|
4329
4329
|
const edition = loadSetupExpertEdition(values.get("workspace"));
|
|
4330
4330
|
for (const key of ["description", "runtime", "model"]) if (edition.declaration[key] !== undefined) assertSafeConfigValue(edition.declaration[key], `setup edition ${key}`);
|
|
4331
|
-
const catalog = officialPackageCatalog()
|
|
4331
|
+
const catalog = officialPackageCatalog();
|
|
4332
|
+
// Catalog precedence: an explicit OATS_PACKAGE_CATALOG override, else the entry the WORKSPACE
|
|
4333
|
+
// publishes at the edition's revision, else the kernel's bundled snapshot. The bundled copy lags
|
|
4334
|
+
// every oats.framework release cut after this kernel's tag, and a second operator has no main
|
|
4335
|
+
// checkout to point an override at (0.24.5; second-operator finding).
|
|
4336
|
+
const bundledEntry = catalog["oats.framework"];
|
|
4337
|
+
const entry = process.env.OATS_PACKAGE_CATALOG ? bundledEntry : (edition.catalogEntry ?? bundledEntry);
|
|
4338
|
+
const catalogOrigin = process.env.OATS_PACKAGE_CATALOG ? "override" : edition.catalogEntry ? "workspace" : "bundled";
|
|
4332
4339
|
if (!Object.hasOwn(catalog, "oats.framework") || !entry?.url || !entry.ref
|
|
4333
4340
|
|| SETUP_CAPABILITIES.some(id => { const m = officialCapabilityPackage(id); return !m.available || m.package !== "oats.framework" || m.migratedCapability !== id; })) {
|
|
4334
4341
|
throw Object.assign(new Error("official oats.framework with core/setup aliases and a published revision is required"), { code: "needs-configuration" });
|
|
@@ -4360,10 +4367,17 @@ function onboardCmd() {
|
|
|
4360
4367
|
const text = replaceCapabilitiesBlock(before ?? `name: ${scaffoldConfigName(deployment)}\n`, caps);
|
|
4361
4368
|
mkdirSync(deployment, { recursive: true });
|
|
4362
4369
|
acquired = acquirePackage(deployment, "oats.framework", { expectPackage: "oats.framework",
|
|
4363
|
-
catalog(id, selector) {
|
|
4370
|
+
catalog(id, selector) {
|
|
4371
|
+
const selected = id === "oats.framework" ? entry : (Object.hasOwn(catalog, id) ? catalog[id] : null);
|
|
4372
|
+
return selected?.url ? { url: selected.url, ref: selector || selected.ref, path: selected.path } : undefined;
|
|
4373
|
+
},
|
|
4364
4374
|
assertCommittable(plan) {
|
|
4365
4375
|
const pkg = plan.packages.find(p => p.package === "oats.framework");
|
|
4366
|
-
if (edition.packageIntegrity && pkg?.integrity !== edition.packageIntegrity)
|
|
4376
|
+
if (edition.packageIntegrity && pkg?.integrity !== edition.packageIntegrity) {
|
|
4377
|
+
const lag = catalogOrigin === "bundled" ? " (the kernel's bundled catalog entry lags the edition's package; onboard from the workspace or point OATS_PACKAGE_CATALOG at the reviewed list)" : "";
|
|
4378
|
+
throw Object.assign(new Error(`selected edition's same-repository package differs from the official acquisition; align the reviewed source and catalog explicitly${lag}`),
|
|
4379
|
+
{ code: "integrity-drift", details: { catalogOrigin, catalogRef: entry.ref, acquiredIntegrity: pkg?.integrity ?? null, editionPackageIntegrity: edition.packageIntegrity } });
|
|
4380
|
+
}
|
|
4367
4381
|
for (const id of SETUP_CAPABILITIES) {
|
|
4368
4382
|
const cap = plan.capabilities.find(c => c.capability === id);
|
|
4369
4383
|
if (!cap || cap.package !== "oats.framework" || cap.layer || Object.values(cap.executableSurface || {}).some(value => Array.isArray(value) && value.length)) {
|
|
@@ -4394,7 +4408,7 @@ function onboardCmd() {
|
|
|
4394
4408
|
const agent = findAgent(root, SETUP_EXPERT), composition = composeInstanceAgentsMd(created.soul, deployment, SETUP_EXPERT, "directory", "local");
|
|
4395
4409
|
planInstanceResources({ resolved: composition.resolved, soulDir: created.soul, agent, contextDir: deployment, composition });
|
|
4396
4410
|
const argv = [process.execPath, CLI_BIN, "spawn", SETUP_EXPERT, "--dir", deployment, "--no-yolo", "--task", "Help me configure this deployment and adopt my workspace with explicit approvals."];
|
|
4397
|
-
const result = { mode: "classic", captured: false, deployment, agentsRoot: root, ...created, source: edition.source,
|
|
4411
|
+
const result = { mode: "classic", captured: false, deployment, agentsRoot: root, ...created, source: edition.source, catalog: { origin: catalogOrigin, ref: entry.ref },
|
|
4398
4412
|
package: { id: pkg.package, version: pkg.version, commit: pkg.commit, path: pkg.path }, lockFile: acquired.lockFile,
|
|
4399
4413
|
capabilities: [...SETUP_CAPABILITIES], launched: false, next: { argv, command: argv.map(shellQuote).join(" ") } };
|
|
4400
4414
|
if (JSON_MODE) jsonOk(result);
|
|
@@ -4411,7 +4425,7 @@ function onboardCmd() {
|
|
|
4411
4425
|
}
|
|
4412
4426
|
} catch { /* never erase another writer's change or hide a failed rollback */ }
|
|
4413
4427
|
}
|
|
4414
|
-
fail(error.code || "E_ONBOARD_FAILED", error.message, { deployment, agentsRoot: root, packageAcquired: !!acquired, soul: created?.soul, configRestored, launched: false });
|
|
4428
|
+
fail(error.code || "E_ONBOARD_FAILED", error.message, { deployment, agentsRoot: root, packageAcquired: !!acquired, soul: created?.soul, configRestored, launched: false, ...(error.details && typeof error.details === "object" ? error.details : {}) });
|
|
4415
4429
|
}
|
|
4416
4430
|
}
|
|
4417
4431
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"capability": "oats.aweb",
|
|
3
3
|
"command": "aweb",
|
|
4
|
-
"version": "1.11.
|
|
4
|
+
"version": "1.11.2",
|
|
5
5
|
"compatibility": {
|
|
6
|
-
"oats": ">=0.24.
|
|
6
|
+
"oats": ">=0.24.4"
|
|
7
7
|
},
|
|
8
8
|
"layer": "messaging",
|
|
9
9
|
"description": "Messaging layer via aweb: per-instance team identities + native aw mail/chat skills + cross-machine team roster.",
|
|
@@ -62,6 +62,10 @@
|
|
|
62
62
|
"skills/aweb-identity"
|
|
63
63
|
],
|
|
64
64
|
"inject": "injects/aweb.md",
|
|
65
|
+
"helperInjection": {
|
|
66
|
+
"version": 1,
|
|
67
|
+
"mode": "omit"
|
|
68
|
+
},
|
|
65
69
|
"commands": {
|
|
66
70
|
"roster": "bin/oats-aweb.mjs roster",
|
|
67
71
|
"setup": "bin/oats-aweb.mjs setup",
|
|
@@ -73,7 +77,44 @@
|
|
|
73
77
|
"version": 1,
|
|
74
78
|
"normalize": "binding-normalize",
|
|
75
79
|
"bind": "binding-bind",
|
|
76
|
-
"check": "binding-check"
|
|
80
|
+
"check": "binding-check",
|
|
81
|
+
"keys": [
|
|
82
|
+
"responsibleHuman",
|
|
83
|
+
"privateTeam",
|
|
84
|
+
"wider"
|
|
85
|
+
],
|
|
86
|
+
"reasons": [
|
|
87
|
+
"messaging-enabled standalone preparation needs an explicit context key",
|
|
88
|
+
"messaging binding needs one soul declaration",
|
|
89
|
+
"messaging workspace must declare private: per-human",
|
|
90
|
+
"an explicit responsible-human binding is required",
|
|
91
|
+
"an explicit wider-membership consent list is required",
|
|
92
|
+
"a selected wider-team binding is required",
|
|
93
|
+
"a selected wider alias needs an explicit workspace team mapping",
|
|
94
|
+
"multiple soul messaging declarations",
|
|
95
|
+
"adoption team aliases have conflicting mappings",
|
|
96
|
+
"messaging settings and explicit binding selections are required",
|
|
97
|
+
"messaging declarations contain incompatible requirements",
|
|
98
|
+
"messaging input must match the supported binding contract",
|
|
99
|
+
"explicit native messaging authorization is required",
|
|
100
|
+
"a required native messaging host resource is unavailable",
|
|
101
|
+
"the selected messaging provider is unavailable",
|
|
102
|
+
"the requested messaging configuration is not qualified",
|
|
103
|
+
"messaging response exceeds the supported wire limits",
|
|
104
|
+
"an admitted captured instance intent is required for execution",
|
|
105
|
+
"an explicit private-team binding is required",
|
|
106
|
+
"selected binding and inline captured invocation are required",
|
|
107
|
+
"an explicit captured instance home is required",
|
|
108
|
+
"captured messaging requires explicit delivery: session",
|
|
109
|
+
"selected wider memberships need their explicitly qualified native setup; they were not omitted",
|
|
110
|
+
"caller-owned OATS_CLI_BIN and readable kernel version are required",
|
|
111
|
+
"oats >=0.24.2 is required for captured HOME custody and retained runtime inspection",
|
|
112
|
+
"the exact retained runtime profile must be readable",
|
|
113
|
+
"the kernel must report the exact retained resolution",
|
|
114
|
+
"a retained launchSelection runtime/model observation is required",
|
|
115
|
+
"Pi strict print does not support session input; retain messaging and configure an input-capable profile",
|
|
116
|
+
"a supported input-capable ordinary Claude/Codex profile is required"
|
|
117
|
+
]
|
|
77
118
|
},
|
|
78
119
|
"hooks": {
|
|
79
120
|
"spawn": {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
KNOWLEDGE_CONTRACT,
|
|
8
8
|
KNOWLEDGE_CONTRACT_VERSION,
|
|
9
9
|
bindKnowledgeDomain,
|
|
10
|
+
bindingChoiceKey,
|
|
10
11
|
checkKnowledgeRuntime,
|
|
11
12
|
normalizeKnowledgeBindingCandidates,
|
|
12
13
|
normalizeKnowledgeDeclaration,
|
|
@@ -24,6 +25,22 @@ const declarationKinds=new Set(['soul','workspace','adoption','operator']);
|
|
|
24
25
|
const errorCodes=new Set(['needs-configuration','requirement-conflict','invalid-binding','authorization-required','host-requirement-missing','provider-unavailable','provider-not-qualified']);
|
|
25
26
|
const obj=value=>value!==null && typeof value==='object' && !Array.isArray(value);
|
|
26
27
|
const wireError=code=>{throw Object.assign(new Error(code),{wireCode:code});};
|
|
28
|
+
// Private diagnostic marker and closed literal vocabulary. Never serialize a
|
|
29
|
+
// caught exception's message, supplied value, path, alias or unknown key.
|
|
30
|
+
const settingDiagnostic=Symbol('runtime-setting-diagnostic');
|
|
31
|
+
const settingMessages=Object.freeze({
|
|
32
|
+
'bindings-file:missing':'setting bindings-file is required (absolute host path)',
|
|
33
|
+
'bindings-file:invalid':'setting bindings-file must be a normalized absolute host path',
|
|
34
|
+
'state-dir:missing':'setting state-dir is required (absolute host path)',
|
|
35
|
+
'state-dir:invalid':'setting state-dir must be a normalized absolute host path',
|
|
36
|
+
'harvest-runtime:missing':'setting harvest-runtime is required (pi, claude or codex)',
|
|
37
|
+
'harvest-runtime:invalid':'setting harvest-runtime must be pi, claude or codex',
|
|
38
|
+
'harvest-model:invalid':'setting harvest-model must be null or a non-empty string',
|
|
39
|
+
});
|
|
40
|
+
function settingError(reason) {
|
|
41
|
+
if(!Object.hasOwn(settingMessages,reason)) wireError('invalid-binding');
|
|
42
|
+
throw Object.assign(new Error('needs-configuration'),{wireCode:'needs-configuration',[settingDiagnostic]:settingMessages[reason]});
|
|
43
|
+
}
|
|
27
44
|
function keys(value,allowed,required,label) {
|
|
28
45
|
if(!obj(value)) wireError('invalid-binding');
|
|
29
46
|
for(const key of Object.keys(value)) if(!allowed.includes(key)) wireError('invalid-binding');
|
|
@@ -100,13 +117,29 @@ function contract(value,{required=false}={}) {
|
|
|
100
117
|
function runtimeSettings(settings) {
|
|
101
118
|
keys(settings,['bindings-file','state-dir','harvest-runtime','harvest-model'],[], 'OKF settings');
|
|
102
119
|
const descriptorFile=settings['bindings-file'],stateDir=settings['state-dir'],runtime=settings['harvest-runtime'],model=settings['harvest-model'] ?? null;
|
|
103
|
-
|
|
120
|
+
for(const name of ['bindings-file','state-dir']) {
|
|
121
|
+
if(settings[name]===undefined) settingError(`${name}:missing`);
|
|
122
|
+
if(!absolute(settings[name])) settingError(`${name}:invalid`);
|
|
123
|
+
}
|
|
124
|
+
if(runtime===undefined) settingError('harvest-runtime:missing');
|
|
125
|
+
if(!['pi','claude','codex'].includes(runtime)) settingError('harvest-runtime:invalid');
|
|
126
|
+
if(model!==null && (typeof model!=='string' || !model.trim())) settingError('harvest-model:invalid');
|
|
104
127
|
return {descriptorFile,stateDir,execution:{runtime,model}};
|
|
105
128
|
}
|
|
129
|
+
// Shared maps contain other providers' opaque values. Ownership comes from
|
|
130
|
+
// this source's declared/default/required knowledge addresses, not from a value
|
|
131
|
+
// looking like a locator or a blanket stores.* prefix. Validate OWN values later.
|
|
132
|
+
function ownedBindings(bindings,ownedKeys) {
|
|
133
|
+
return Object.fromEntries(Object.entries(bindings).filter(([name])=>{
|
|
134
|
+
let key;try{key=bindingChoiceKey(name);}catch{return false;}
|
|
135
|
+
return ownedKeys.has(key);
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
106
138
|
function normalizePhase(req) {
|
|
107
139
|
keys(req.input,['declarations','context'],['declarations','context'],'normalize input');
|
|
108
140
|
if(!Array.isArray(req.input.declarations) || !obj(req.input.context)) wireError('invalid-binding');
|
|
109
|
-
const requirements=[],candidates=[];let domain=null;
|
|
141
|
+
const requirements=[],candidates=[],bindingInputs=[];let domain=null;
|
|
142
|
+
const collect=input=>{if(!obj(input.bindings)) wireError('invalid-binding');bindingInputs.push(input);};
|
|
110
143
|
for(const raw of req.input.declarations) {
|
|
111
144
|
const item=declaration(raw);
|
|
112
145
|
if(item.kind==='soul') {
|
|
@@ -124,14 +157,19 @@ function normalizePhase(req) {
|
|
|
124
157
|
if(rawStore.contract!==KNOWLEDGE_CONTRACT) return;
|
|
125
158
|
const store=contract(rawStore);
|
|
126
159
|
keys(store.payload,['bindings'],['bindings'],'workspace OKF payload');
|
|
127
|
-
|
|
160
|
+
collect({bindings:store.payload.bindings,kind:'workspace-default',origin:item.origin,origins:item.origins,pointer:`/knowledge/stores/${index}/payload/bindings`});
|
|
128
161
|
});
|
|
129
162
|
continue;
|
|
130
163
|
}
|
|
131
164
|
if(item.value.bindings===undefined) continue;
|
|
132
|
-
|
|
165
|
+
collect({bindings:item.value.bindings,kind:item.kind==='adoption'?'import-adoption':'operator',origin:item.origin,origins:item.origins,pointer:'/bindings'});
|
|
133
166
|
}
|
|
134
167
|
if(!domain) wireError('needs-configuration');
|
|
168
|
+
// Collect first so declaration order cannot change who owns a binding. Keep
|
|
169
|
+
// all declared addresses, including dotted aliases and explicit/implicit
|
|
170
|
+
// write.default or custom inheritance; the kernel still resolves precedence.
|
|
171
|
+
const ownedKeys=new Set([...domain.requirements,...domain.candidates].map(item=>item.key));
|
|
172
|
+
for(const input of bindingInputs) candidates.push(...normalizeKnowledgeBindingCandidates({...input,bindings:ownedBindings(input.bindings,ownedKeys)}));
|
|
135
173
|
requirements.push(...domain.requirements);candidates.unshift(...domain.candidates);
|
|
136
174
|
return {requirements,candidates,model:{domain,runtime:runtimeSettings(req.settings)}};
|
|
137
175
|
}
|
|
@@ -152,12 +190,20 @@ function bindPhase(req) {
|
|
|
152
190
|
const runtime=renderKnowledgeRuntime({domain:bound.payload,...req.input.model.runtime});
|
|
153
191
|
return {payloadContract:bound.contract,payloadVersion:bound.version,payload:{...bound.payload,runtime,execution:req.input.model.runtime.execution},credentialRefs:bound.credentialRefs,provenance:bound.provenance};
|
|
154
192
|
}
|
|
155
|
-
function bindingPayload(binding) {
|
|
193
|
+
function bindingPayload(binding,{diagnoseSettings=false}={}) {
|
|
156
194
|
keys(binding,['schemaVersion','capability','payloadContract','payloadVersion','payload','credentialRefs','provenance'],['schemaVersion','capability','payloadContract','payloadVersion','payload','credentialRefs','provenance'],'provider binding');
|
|
157
195
|
if(binding.schemaVersion!==1 || binding.capability!==CAPABILITY || binding.payloadContract!==KNOWLEDGE_CONTRACT || binding.payloadVersion!==KNOWLEDGE_CONTRACT_VERSION || !Array.isArray(binding.provenance)) wireError('invalid-binding');
|
|
158
196
|
if(!obj(binding.credentialRefs) || Object.keys(binding.credentialRefs).length) wireError('invalid-binding');
|
|
159
197
|
keys(binding.payload,['owner','stores','reads','owns','runtime','execution'],['owner','stores','reads','owns','runtime','execution'],'OKF binding payload');
|
|
160
198
|
const domain={owner:binding.payload.owner,stores:binding.payload.stores,reads:binding.payload.reads,owns:binding.payload.owns};
|
|
199
|
+
if(diagnoseSettings) {
|
|
200
|
+
const bound=binding.payload;
|
|
201
|
+
if(!obj(bound.runtime) || !obj(bound.runtime.bindings) || !obj(bound.execution)) wireError('invalid-binding');
|
|
202
|
+
// Check the retained values, not mutable request settings. Existing full
|
|
203
|
+
// envelope/canonical-runtime/execution guards still run below on success.
|
|
204
|
+
runtimeSettings({'bindings-file':bound.runtime.descriptorFile,'state-dir':bound.runtime.bindings.stateDir,
|
|
205
|
+
'harvest-runtime':bound.execution.runtime,'harvest-model':bound.execution.model});
|
|
206
|
+
}
|
|
161
207
|
const runtime=renderKnowledgeRuntime({domain,stateDir:binding.payload.runtime?.bindings?.stateDir,descriptorFile:binding.payload.runtime?.descriptorFile});
|
|
162
208
|
if(!sameJson(runtime,binding.payload.runtime)) wireError('invalid-binding');
|
|
163
209
|
keys(binding.payload.execution,['runtime','model'],['runtime','model'],'OKF worker execution');
|
|
@@ -211,7 +257,7 @@ function checkPhase(req) {
|
|
|
211
257
|
keys(req.input,['binding','context','action','invocation'],['binding','context','action'],'check input');
|
|
212
258
|
if(!obj(req.input.context) || !obj(req.input.action)) wireError('invalid-binding');
|
|
213
259
|
if(Object.hasOwn(req.input,'invocation')) validateInvocationShape(req.input.invocation,{capability:CAPABILITY,context:req.input.context,action:req.input.action});
|
|
214
|
-
const {runtime}=bindingPayload(req.input.binding),action=req.input.action,name=providerActionName(action);
|
|
260
|
+
const {runtime}=bindingPayload(req.input.binding,{diagnoseSettings:true}),action=req.input.action,name=providerActionName(action);
|
|
215
261
|
const harvestInvocation=req.input.invocation;
|
|
216
262
|
const admittedHarvest=name==='harvest' && action.kind==='operation' && action.slot==='knowledge' && action.name==='harvest'
|
|
217
263
|
&& harvestInvocation?.subject.kind==='persistent' && harvestInvocation.instance!==null && !!harvestInvocation.intent;
|
|
@@ -244,6 +290,10 @@ export function handleBindingRequest(phase,value) {
|
|
|
244
290
|
}
|
|
245
291
|
function response(phase,body) {return {schemaVersion:1,phase,slot:SLOT,capability:CAPABILITY,...body};}
|
|
246
292
|
function errorCode(error) {if(errorCodes.has(error?.wireCode)) return error.wireCode;if(error?.code==='E_OWNER') return 'requirement-conflict';return 'invalid-binding';}
|
|
293
|
+
function errorProblem(error) {
|
|
294
|
+
const code=errorCode(error),message=error?.[settingDiagnostic];
|
|
295
|
+
return {code,...(code==='needs-configuration' && Object.values(settingMessages).includes(message)?{message}:{})};
|
|
296
|
+
}
|
|
247
297
|
function enforceOutputLimits(value,depth=1,state={entries:0}) {
|
|
248
298
|
if(depth>BINDING_WIRE_LIMITS.depth) wireError('provider-not-qualified');
|
|
249
299
|
if(value===null || typeof value!=='object') return;
|
|
@@ -259,7 +309,7 @@ export async function runBindingWire(phase,input=process.stdin,output=process.st
|
|
|
259
309
|
for await(const chunk of input) {const bytes=Buffer.from(chunk);length+=bytes.length;if(length>BINDING_WIRE_LIMITS.bytes) wireError('invalid-binding');chunks.push(bytes);}
|
|
260
310
|
const result=handleBindingRequest(phase,parseBindingJson(Buffer.concat(chunks,length)));
|
|
261
311
|
answer=response(phase,{ok:true,result});
|
|
262
|
-
} catch(error) {answer=response(phases.has(phase)?phase:'check',{ok:false,error:
|
|
312
|
+
} catch(error) {answer=response(phases.has(phase)?phase:'check',{ok:false,error:errorProblem(error)});}
|
|
263
313
|
let bytes;
|
|
264
314
|
try {enforceOutputLimits(answer);bytes=Buffer.from(JSON.stringify(answer)+'\n');if(bytes.length>BINDING_WIRE_LIMITS.bytes) wireError('provider-not-qualified');}
|
|
265
315
|
catch {bytes=Buffer.from(JSON.stringify(response(phases.has(phase)?phase:'check',{ok:false,error:{code:'provider-not-qualified'}}))+'\n');}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"capability": "oats.okf",
|
|
3
3
|
"command": "okf",
|
|
4
|
-
"version": "2.1.
|
|
4
|
+
"version": "2.1.2",
|
|
5
5
|
"compatibility": {
|
|
6
|
-
"oats": ">=0.24.
|
|
6
|
+
"oats": ">=0.24.4"
|
|
7
7
|
},
|
|
8
8
|
"layer": "knowledge",
|
|
9
9
|
"description": "External OKF bases with owned nodes, immutable reader views, durable per-source evidence, independent workers, verified Git PR and recoverable non-Git directory delivery.",
|
|
@@ -54,7 +54,16 @@
|
|
|
54
54
|
"version": 1,
|
|
55
55
|
"normalize": "binding-normalize",
|
|
56
56
|
"bind": "binding-bind",
|
|
57
|
-
"check": "binding-check"
|
|
57
|
+
"check": "binding-check",
|
|
58
|
+
"reasons": [
|
|
59
|
+
"setting bindings-file is required (absolute host path)",
|
|
60
|
+
"setting bindings-file must be a normalized absolute host path",
|
|
61
|
+
"setting state-dir is required (absolute host path)",
|
|
62
|
+
"setting state-dir must be a normalized absolute host path",
|
|
63
|
+
"setting harvest-runtime is required (pi, claude or codex)",
|
|
64
|
+
"setting harvest-runtime must be pi, claude or codex",
|
|
65
|
+
"setting harvest-model must be null or a non-empty string"
|
|
66
|
+
]
|
|
58
67
|
},
|
|
59
68
|
"inject": "injects/okf.md",
|
|
60
69
|
"helperInjection": {
|
|
@@ -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-21
|
|
5
|
+
**Last update:** 2026-09-21 23:40Z · main `cd3d4913`+ · **oats-knowledge main `7148a36` = centralised base (58 concepts)** · OATS v0.24.4 · OKF v2.1.2 · aweb v1.11.2 · framework v1.1.3 · imports @ `7416d84e`
|
|
6
6
|
|
|
7
7
|
Legend: ✅ on main/published · 🔄 in flight (PR/branch) · 🟡 preserved, not adopted · ⬜ not started · ⛔ blocked
|
|
8
8
|
|
|
@@ -11,12 +11,12 @@ Legend: ✅ on main/published · 🔄 in flight (PR/branch) · 🟡 preserved, n
|
|
|
11
11
|
| # | Stream | State | Owner | Next action |
|
|
12
12
|
|---|---|---|---|---|
|
|
13
13
|
| S1 | Knowledge capability contract rework (kernel↔provider boundary, OKF 2.x) | ✅ OATS 0.24.1 / OKF 2.1.1 published | P | done for this phase |
|
|
14
|
-
| S2 | Workspace/Portable Souls adoption of the OATS repos | ✅ 0.24.
|
|
14
|
+
| S2 | Workspace/Portable Souls adoption of the OATS repos | ✅ **FIRST SECOND-OPERATOR PUBLICATION (735296c5, 2026-09-21)**: fresh machine, published definition alone, no `launch` → `status: prepared`, resolution `sha256-f11c433c…`, zero problems. ❌ then OKF `check` → `needs-configuration / provider-not-qualified`, **no message/key/origins**, identical for `harvest-runtime` pi and claude — OKF's check phase is code-only and folds ≥4 causes into one code (P, OKF 2.1.3). ⏳ Pi `launch` blocked by kernel ifInstalled defect (L, 0.24.5). Earlier: ✅ knowledge: bound to public `oats-knowledge` via OKF 2.1.2 from a fresh machine, four kernel versions · ✅ messaging normalizes; `wider` deadlock gone; named reasons in one run · ❌ **operator's amended verdict (9816b8ec): no resolution can publish — behind `responsibleHuman`, helper composition refuses because `oats.core` and `oats.aweb` ship `inject` without `helperInjection`** (OKF adopted the contract; its siblings did not). Both remaining blockers are packaging, not operator input. Fix: [decision](https://github.com/awebai/oats/blob/main/agents/oats-expert/soul/knowledge/decisions/helper-injection-policy-on-every-injecting-capability.md) — core `inherit`, aweb `omit`, framework release check, attributed early refusal · then unchanged re-run | lead, Antares | oats.framework 1.1.3 + aweb 1.11.2 + pins; re-run |
|
|
15
15
|
| S3 | Messaging capability readiness on the new infrastructure (aweb) | ✅ aweb 1.11.0 released · ✅ **catalog + six editions pin v1.11.0 (0.24.2)** · ⬜ second-operator re-run | P, lead | Antares re-run |
|
|
16
16
|
| S4 | Official capabilities `oats.core` / `oats.setup` + explicit default + onboarding `oats-setup-expert` | ✅ D1, D2, **D3 merged (PR35)**: `oats onboard` verified live (acquire 1.1.1 → setup expert with both caps → scaffold composes the five capability skills, no legacy) · `oats.framework` 1.1.1 tagged | P, L | done; Desktop surfaces → S8 |
|
|
17
17
|
| S5 | Official marketplace = reviewed list in oats repo | ✅ D4 merged · ✅ `oats.framework` 1.1.1 listed (`oats.core`, `oats.setup`, `oats.knowledge-theory` aliases) | M | Desktop view → S8 |
|
|
18
18
|
| S6 | Five expert souls created in the oats repo (`souls/<name>/`) | ✅ five + `oats-setup-expert` on main, all declaring `oats.core`, exported + imported | M, L, lead | legacy `agents/` cutover after S7 proof |
|
|
19
|
-
| S7 | Centralised per-soul knowledge in `oats-knowledge` (migration + PR-only learning) | ✅ **
|
|
19
|
+
| S7 | Centralised per-soul knowledge in `oats-knowledge` (migration + PR-only learning) | ✅ **MIGRATED — PR #2 merged → main `7148a36`, 58 concepts / 35k words from 399 legacy** (kernel 26, expert 18, desktop 13, assistant 1, market-research chartered empty); option A roster; theory + no-code-teaching rule; two workflow passes (24 agents) + lead review; validator 58/0/0; ownership 14/14 · ⏸ harvest OFF until new souls run from the base (human) | lead | next: legacy `agents/` decommission with the `~/OATS` cutover |
|
|
20
20
|
| S8 | Desktop parity (marketplace view, soul creation with `oats.core`, onboarding flow) | ⬜ after D3 · **host offered: Juan's machine (has `claude`)** — accepted | fresh Desktop engineer on Juan's host | brief + spawn params after D3 |
|
|
21
21
|
|
|
22
22
|
## S1 — Knowledge capability contract rework
|
|
@@ -50,13 +50,49 @@ Fresh dir, local `@awebai/oats@0.24.1`, no prior state. `inspect --request` →
|
|
|
50
50
|
- With `bindings-file`/`state-dir`/`harvest-runtime: pi` and the public `oats-knowledge` bound as base `oats`: **the knowledge slot binds** (OKF problem gone). `harvest-model` is optional per manifest; aweb 1.11.0 declares exactly one setting (`delivery`).
|
|
51
51
|
- **aweb 1.11.0 is the only remaining hold** (`needs-configuration`, identical for `delivery: session` and `channel` on 0.24.2's unattributed output). No resolution publishes → OKF `check` probe not reachable yet. 0.24.3 run will show the attributed aweb message (expected: private-team binding / Pi session-input).
|
|
52
52
|
|
|
53
|
+
## Cutover started: `~/OATS` onboarded from the public workspace (lead, 2026-09-21 23:55Z)
|
|
54
|
+
|
|
55
|
+
Kernel 0.24.4 installed globally; `oats onboard --dir ~/OATS --workspace git:github.com/awebai/oats` → `oats.framework` 1.1.3 acquired and exact-locked (lock v2), `oats.core` + `oats.setup` selected, `oats-setup-expert` copied from the workspace's pinned edition (`7416d84e`, workspace revision `c82a96af`), layers `none`. First attempt refused `integrity-drift` because the kernel's **bundled catalog snapshot** still listed framework 1.1.1 — correct refusal, misleading read; remedy `OATS_PACKAGE_CATALOG=<main's list>`; lesson `lessons/bundled-catalog-lags-the-reviewed-list.md` (proposes onboarding read the catalog from the workspace revision). Next: spawn the setup expert to adopt the workspace (knowledge → public `oats-knowledge` via OKF 2.1.2; messaging → aweb 1.11.2 or none) with explicit approvals; then the five expert souls run from the central base and this checkout becomes secondary. Harvest stays off until then.
|
|
56
|
+
|
|
57
|
+
## S2 — FIRST SECOND-OPERATOR PUBLICATION + `check` finding (Antares, 735296c5, 2026-09-21)
|
|
58
|
+
|
|
59
|
+
Verified wave (aweb v1.11.2, framework v1.1.3, main cd3d4913, imports 7416d84e). Fresh deployment, unchanged request minus `launch`, synthetic `responsibleHuman`, `wider: []` → `{"status":"prepared","resolution":{"id":"sha256-f11c433c…"},"executionBinding":{…},"problems":[]}`, exit 0, empty stderr. **An operator holding none of the authoring state produced an executable resolution from the published workspace definition — the first.** Helper-injection fix confirmed from outside; `ignored` shrank to `["operator"]` correctly.
|
|
60
|
+
|
|
61
|
+
Then P's OKF `check` probe against the published resolution (contacted the public repository): `{"status":"needs-configuration","problems":[{"code":"provider-not-qualified"}]}` — bare; the operator dumped the shape to prove nothing was elided. Second resolution with `harvest-runtime: claude`, no model (`sha256-1aa5ab9a…`, prepared) → identical. Traced by lead: OKF 2.1.2 `checkPhase` (`binding-wire.mjs` L256–282) returns `problem(code)` only and maps unadmitted action / >64 bases / `E_OWNER|E_BASE|E_VALIDATION|E_DIRECTORY_GIT|E_CONFIRM` all to `provider-not-qualified`; the kernel's check path already passes declared reasons. Fifth of the family. Operator's verdict adopted: *"A second operator reaches, resolves, approves and publishes an executable resolution from the public definition alone … The OKF check readiness probe then refuses as provider-not-qualified with no reason, no key and no origins, and the refusal is independent of the harvest runtime."* Decision amended: every check problem carries a fixed reason distinct per cause. **Assigned P: OKF 2.1.3** — ahead of 0.24.5 by the operator's priority, which the lead accepts (publication works; check is the next thing every operator hits). **Operator narrowed the cause from outside (9c9dc404), each elimination tested:** not action kind, not base count, not `validateBase` (validator from the 2.1.2 artifact against the public `knowledge/`: 25 concepts, 0/0), not directory-git, not runtime; staging IS reached (nonexistent repo → `unavailable/provider-unavailable` in 0.65s; real → `needs-configuration/provider-not-qualified` in 1.66s). Left: `verifyPublicationTree` (E_CONFIRM/E_BASE) or `checkKnowledgeRuntime` (E_OWNER; not a naive owner mismatch — public base owner = edition owner). Forwarded to P so 2.1.3 reasons split exactly these. Sharp edge noted: the validator at the repository root fails on bundle-root-absolute links — a wrong root will look like an invalid base. **Acceptance plan corrected by the operator (bbbb6bdd):** the two retained resolutions captured and approved the exact 2.1.2 artifact and `runCapturedProviderBinding` executes exactly that, so re-probing them must still return code-only — that is immutability working. After 2.1.3 is pinned: keep the old resolutions as before-evidence, prepare NEW resolutions from the same two request shapes with only the OKF source moved to v2.1.3, approve their exact artifact sets, run the same probe → valid before/after.
|
|
62
|
+
|
|
63
|
+
## Helper-injection gap FIXED + next blocker found (lead, 2026-09-21 20:30Z)
|
|
64
|
+
|
|
65
|
+
- ✅ **oats.framework 1.1.3** (`oats-framework/v1.1.3` @ `6108bfbb`): `oats.core` 1.0.1 declares `helperInjection: inherit`. ✅ **aweb 1.11.2** (`v1.11.2` @ `a671884`): `helperInjection: omit`, manifest-only. Bundled copy byte-synced; catalog `oats.aweb` → v1.11.2, `oats.framework` → v1.1.3; six editions → aweb v1.11.2; six imports → `7416d84e`. Release check added: every injecting framework capability declares its helper policy (jira/linear/review pending upstream, not required by any edition). Notes: `docs/release-notes/oats-framework-v1.1.3.md`.
|
|
66
|
+
- **Lead probe from the published 0.24.4 tarball, operator's request shape + synthetic `responsibleHuman` + `wider: []`:** helper composition now passes. **Without a `launch` block the resolution PUBLISHES** (`status: prepared`, resolution + execution binding) — first end-to-end publication from public definitions. **With the Pi `launch` block it refuses** on a bare `needs-configuration: runtime package requirements need retained runtime roots and a qualified loader`: kernel defect — the captured path treats aweb's `ifInstalled: true` Pi floor (`npm:@awebai/pi ≥0.3.10`, only if present) as a hard block, and refuses with no `details`. `delivery: channel` gives the byte-identical message. Lesson: `lessons/captured-launch-ignores-ifinstalled-and-refuses-bare.md`. **Assigned to L with the retirement defect → 0.24.5 (kernel), plus attribution for both bare refusals.** Operator's re-run: after 0.24.5, or now without `launch` to witness publication.
|
|
67
|
+
|
|
68
|
+
## S2 — operator's self-correction (9816b8ec, 2026-09-21): packaging gap behind `responsibleHuman`
|
|
69
|
+
|
|
70
|
+
The operator supplied a deliberately synthetic `responsibleHuman` (`gate-probe-not-a-real-human`; accepted — `humanRef` checks only provider + non-empty id, i.e. an accountability claim the code records but does not verify) and preparation refused: `needs-configuration: new helper injection requires an explicit capability policy` (`lib/helper-injection-policy.mjs:38`), a bare top-level error with no `details`/attribution. Cause verified from the published manifests: `oats.okf` declares `helperInjection {omit}`; **`oats.core` (`injects/oats.md`) and `oats.aweb` (`injects/aweb.md`) declare none**. Every edition declares `oats.core`, so no edition could publish once its OKF harvest helper composed. Fourth finding of the same family (contract adopted by one provider, not its siblings). Operator's restated verdict adopted verbatim: *"A second operator reaches, resolves and approves the entire graph from the public definition, binds knowledge to the public `oats-knowledge` base, and normalizes messaging. Preparation cannot publish a resolution … Both remaining blockers are packaging, not operator input."* The earlier "EXIT GATE MET" entry below is superseded by this one.
|
|
71
|
+
|
|
72
|
+
## Kernel defect found at peer retirement (2026-09-21) — 0.25 lifecycle item
|
|
73
|
+
|
|
74
|
+
`oats retire oats-expert-scheduler-peer` (self and spawner retry) fails closed: *recovered Git index/status disagreed with the source*. Cause: `preserveRetirementWork` clones the branch recorded in `instance.json` (`feat/portable-scheduler-captures`, tip e0c3232c) while the worktree is checked out on `docs/okf-host-runtime-settings` (5919a547); the status comparison cannot agree. Verified by hand: worktree clean, both branches pushed, every deliverable merged (oats PR39, oats-okf PR5, oats-aweb PR4); the only dirt is two untracked files in a nested scratch clone that are already on aweb main since 1.11.0. **Nothing unpreserved.** Instance left `RETIRING`; no branch/metadata/force surgery. Fix scoped to **0.25 (L)**: derive the recovery branch from the worktree, record drift as a typed observation. Lesson: `lessons/retire-recovery-uses-recorded-branch-not-checked-out-branch.md`.
|
|
75
|
+
|
|
76
|
+
## S2 — acceptance run on the 0.24.4 wave (Antares, Juan's machine, 2026-09-21) — superseded by the self-correction above (knowledge result stands)
|
|
77
|
+
|
|
78
|
+
Fresh directory and deployment, `@awebai/oats@0.24.4`, policy sources v2.1.2 / v1.11.1, `wider: []`, request otherwise unchanged from the 0.24.3 run; wave verified independently first (npm, both provider tags, main `86b5f924`, six imports @ `08c68ece`).
|
|
79
|
+
|
|
80
|
+
- inspect: exit 0, `ignored: ["operator","launch"]`, six imports @ `08c68ece`, `ready-for-preparation` — **seam 1 closed**.
|
|
81
|
+
- prepare → two `approval-required` → approved via the printed `--artifact-set` commands → prepare: **knowledge binds with `wider: []` present (deadlock fixed)**; the single remaining problem is `{code: needs-configuration, slot: messaging, key: /bindings/messaging/responsibleHuman}`.
|
|
82
|
+
- `bindings-file` removed → `setting bindings-file is required (absolute host path)` in one run — the named-reason criterion ("one run instead of six") met. `wider` removed → both messaging keys attributed separately.
|
|
83
|
+
- Operator's observation, adopted: `required value has no concrete binding` is generic text but the `key` carries the address; sufficient — no more spend on the string.
|
|
84
|
+
- **Operator's verdict (verbatim):** *"An outside operator on a fresh machine, holding none of the authoring state, now reaches a single, fully-named, legitimately-operator-owned requirement: `/bindings/messaging/responsibleHuman`. Everything else — source discovery, workspace membership, artifact resolution, exact approval, knowledge binding to the public `oats-knowledge` base through OKF 2.1.2, messaging normalize — resolves from the published definitions alone. That is the S2 exit gate met … not 'it works', but 'every remaining item is named and belongs to the operator'."*
|
|
85
|
+
- Coda: the operator has put `responsibleHuman` to Juan as a real decision; if supplied, R publishes and the OKF `check` probe runs (typed status/problems only). If declined, that is a legitimate end state and is recorded as such.
|
|
86
|
+
- Four acceptance trees retained on the operator's side (0.24.1–0.24.4) — every later check is a repeat, not a rebuild.
|
|
87
|
+
|
|
53
88
|
## S2 — 0.24.3 gate run (Antares, Juan's machine, 2026-09-21) — verdict
|
|
54
89
|
- Seams 1, 3, 4 **fixed**; seam 2 fixed as typed (message does not echo the path — deliberate, consistent with no host diagnostics; expectation corrected); seam 5 **half**: attribution (slot/capability/full origins) fixed; naming the missing item only where the kernel knows it. The specificity half is provider-side: OKF 2.1.2 (assigned) and an aweb follow-up.
|
|
55
90
|
- Six imports resolve at the repinned revision; workspace route selects aweb 1.11.0; **the knowledge slot fully resolves through the workspace route** with `bindings-file`/`state-dir`/`harvest-runtime` (`harvest-model` confirmed optional).
|
|
56
91
|
- **Messaging was a SOURCE defect, not an operator gap**: aweb 1.11.0 normalize requires the workspace to declare `teams: {private: per-human}` and the soul a messaging declaration; neither was published, so the slot was unreachable by any input. Fixed on main **906b1558** (workspace policy + `teams: []` on the five messaging editions; verified against aweb v1.11.0 normalize) and imports repinned **f3ee31e0**. Remaining operator inputs after the fix: `responsibleHuman` (required) and `wider` (required, may be empty) — the private team is a later `check` outcome.
|
|
57
92
|
- **S2 exit-gate verdict (amended per the operator):** knowledge — an outside operator on 0.24.3 discovers, resolves, approves and binds from public sources, remaining item attributed: **PASS**. Messaging — on 0.24.3 as published the operator hit a source defect wearing a configuration error; fixed on main 906b1558 and to be re-proven on the repinned imports before it is written as PASS.
|
|
58
93
|
- **Re-run on 906b1558 (operator, b875669f):** messaging normalize passes; remaining problem is `responsibleHuman`, attributed. But end-to-end is **deadlocked**: `wider: []` (messaging requires) makes OKF 2.1.1 return `invalid-binding` on knowledge (it validates every `operator.bindings` key as a store locator); without it messaging needs `wider`. Operator's wording adopted for S2: *a second operator reaches a published, resolvable configuration boundary for knowledge; messaging is blocked by a kernel-level binding-namespace collision, not by operator input or identity.* Not Juan's to unblock; no identity requested. [Decision](https://github.com/awebai/oats/blob/main/agents/oats-expert/soul/knowledge/decisions/operator-bindings-ownership.md): flat map with declared ownership (`binding.keys`); providers ignore foreign keys (OKF 2.1.2, aweb 1.11.1); kernel attributes/refuses stray keys by name — **deferred to 0.25 by L's scope call (accepted): overlap refusal, filtered forwarding, owned/unowned attribution and the undeclared-provider path need their own regression matrix; the provider ignore rule is the floor and ships in 0.24.4's provider releases.**
|
|
59
|
-
- ✅ **
|
|
94
|
+
- ✅ **Provider wave published (2026-09-21 17:00Z):** OKF **2.1.2** (oats-okf PR5 → `f02a0d9a`, tag v2.1.2: named-setting reasons, source-owned operator bindings, `binding.reasons`, floor >=0.24.4), aweb **1.11.1** (oats-aweb PR4 → `d404da76`, tag v1.11.1: manifest-only reasons + keys, floor >=0.24.4), **oats.framework 1.1.2** (`oats-framework/v1.1.2` @ `6f98c7c9`), OKF mirror finalized + bundled aweb byte-synced (`08c68ece`), catalog refs, six editions repinned, six imports → `08c68ece`. **Lead's end-to-end probe from the published 0.24.4 tarball via the workspace route, Antares' exact request shape with `wider: []`:** inspect accepts the complete file (`ignored: [operator, launch]`), six imports resolve, both providers approve via the printed `trust` commands, **knowledge binds and messaging normalizes — the deadlock is gone**; the single remaining problem is `key: /bindings/messaging/responsibleHuman` (the operator's own decision). Named reasons cross in one run: `setting bindings-file is required (absolute host path)`; dropping `wider` yields both messaging keys attributed. Awaiting the independent operator's unchanged re-run to write it as PASS. **Seam 1 independently confirmed closed by the operator on 0.24.4** (same file that failed on 0.24.3 → exit 0, `ignored: [operator, launch]`).
|
|
95
|
+
- ✅ **PR41 merged `ef211d3e`** (L): inspect accepts prepare's superset (`ignored`), provider reasons cross the wire by exact match (manifest `binding.reasons` / bundled lists), `binding.keys` shape-only, CLI renders `key`. Full gate 1657/1653/0/4 twice (L + maintainer). → ✅ **v0.24.4 PUBLISHED** (npm both packages, GH release 7 assets, tarball probe: installed validator accepts `binding.reasons`/`keys`, `inspect --request` accepts `operator`/`launch`), kernel-first; OKF 2.1.2 / aweb 1.11.1 floor `>=0.24.4` (closed validator on ≤0.24.3 rejects the fields — P's finding).
|
|
60
96
|
- Two further kernel findings from the repeat (assigned to L, 0.24.4): (a) seam 1 converged one way only — `inspect --request` still refuses prepare's `operator`/`launch`; (b) **the kernel discards the adapter's reason** at the wire (`provider-binding-wire.mjs:23`) and templates it; aweb already sends whitelisted safe reasons. New [decision](https://github.com/awebai/oats/blob/main/agents/oats-expert/soul/knowledge/decisions/provider-problem-reasons-cross-the-wire.md): whitelisted fixed reasons cross the wire (`binding.reasons`), free text still refused.
|
|
61
97
|
|
|
62
98
|
## S3 — Messaging (aweb) on the new infrastructure
|
package/docs/first-team.md
CHANGED
|
@@ -79,7 +79,14 @@ the observed revision. Missing or incompatible explicit sources refuse; they do
|
|
|
79
79
|
not fall back to the packaged default. The copied edition's package must match
|
|
80
80
|
the official acquisition; workspace policy, teams and provider adoption values
|
|
81
81
|
are not silently adopted. Without this option, only the packaged definition and
|
|
82
|
-
instruction text are used—no knowledge corpus is bundled.
|
|
82
|
+
instruction text are used—no knowledge corpus is bundled. From 0.24.5, a
|
|
83
|
+
`--workspace` onboarding also reads `package-catalog.json` **from the workspace
|
|
84
|
+
repository at its observed revision** and acquires the `oats.framework` that
|
|
85
|
+
catalog names; the kernel's bundled catalog is only the fallback (it is a
|
|
86
|
+
snapshot at the kernel's own release and lags every framework release cut
|
|
87
|
+
afterwards). `OATS_PACKAGE_CATALOG` still overrides both. The result reports
|
|
88
|
+
`catalog.origin` (`workspace` | `bundled` | `override`), and an integrity
|
|
89
|
+
refusal names the lag when the bundled entry caused it.
|
|
83
90
|
|
|
84
91
|
The manual path below retains its stated older integration/version scope.
|
|
85
92
|
|
package/docs/layers.md
CHANGED
|
@@ -80,7 +80,7 @@ Messaging is conversation, not automatically task state. Accepted knowledge may
|
|
|
80
80
|
|
|
81
81
|
The current slot name is **`messaging`**. The capability owns native identity, addressing, team membership, transport, wake delivery and qualification. A team alias in a workspace is a declaration, not proof that an actor is enrolled or a privacy property is enforced.
|
|
82
82
|
|
|
83
|
-
aweb 1.10.3 supports its legacy setup/lifecycle path but lacks the captured provider-binding interface. **aweb 1.11.0** (OATS >=0.24.2) adds it: `check` qualifies HOME-route operational custody for an input-capable Claude/Codex primary with an explicit private team and `delivery: session`; a strict-Pi print primary reports `needs-configuration` rather than dropping the requirement. Qualification is not account delegation, broker delivery or model consumption.
|
|
83
|
+
aweb 1.10.3 supports its legacy setup/lifecycle path but lacks the captured provider-binding interface. **aweb 1.11.0** (OATS >=0.24.2) adds it (1.11.2, OATS >=0.24.4, is code-identical and declares its fixed reasons and `helperInjection: omit`): `check` qualifies HOME-route operational custody for an input-capable Claude/Codex primary with an explicit private team and `delivery: session`; a strict-Pi print primary reports `needs-configuration` rather than dropping the requirement. Qualification is not account delegation, broker delivery or model consumption.
|
|
84
84
|
|
|
85
85
|
The earlier proposed `reach` ladder is **not an enforced universal field**. In particular, aweb's `team_and_contacts` includes verified same-team senders; the compatibility spellings `contacts-only` and `contacts_only` do not establish owner-only admission. A config command succeeding proves neither inbound/outbound restrictions nor knowledge visibility. See the [identity/membership amendment](design/2026-09-08-expert-assisted-deployment-proposal.md#membership-reach-and-visibility-are-separate) and [messaging boundary](design/2026-09-16-messaging-capability-contract.md).
|
|
86
86
|
|
|
@@ -62,7 +62,7 @@ this policy does not invent new catalog or manifest fields.
|
|
|
62
62
|
|
|
63
63
|
- Listed capabilities: `oats.okf`, `oats.aweb`, `oats.authoring`, `oats.jira`,
|
|
64
64
|
`oats.linear`, `oats.dev`, `oats.knowledge-theory`, `oats.core` and `oats.setup`.
|
|
65
|
-
- **`oats.framework` 1.1.
|
|
65
|
+
- **`oats.framework` 1.1.3** is listed at tag `oats-framework/v1.1.3` in
|
|
66
66
|
`awebai/oats`, payload root `oats-package`. The `oats.core`, `oats.setup` and
|
|
67
67
|
`oats.knowledge-theory` aliases select that distribution; package identity is
|
|
68
68
|
distinct from capability identity. Core supplies operation/soul guidance;
|
package/docs/packages.md
CHANGED
|
@@ -402,7 +402,7 @@ sources; installing a kernel does not advance existing package locks:
|
|
|
402
402
|
{
|
|
403
403
|
"packages": {
|
|
404
404
|
"oats.okf": { "url": "https://github.com/awebai/oats-okf.git", "ref": "v2.0.0", "path": "oats-package" },
|
|
405
|
-
"oats.framework": { "url": "https://github.com/awebai/oats.git", "ref": "oats-framework/v1.1.
|
|
405
|
+
"oats.framework": { "url": "https://github.com/awebai/oats.git", "ref": "oats-framework/v1.1.3", "path": "oats-package" },
|
|
406
406
|
"oats.dev": { "url": "https://github.com/awebai/oats-dev.git", "ref": "v1.0.0", "path": "oats-package" }
|
|
407
407
|
},
|
|
408
408
|
"capabilities": { "oats.review": "oats.dev" }
|
|
@@ -429,7 +429,7 @@ integrity checks. Git transport preserves the canonical source alias.
|
|
|
429
429
|
|
|
430
430
|
The `oats.framework` distribution package is a separate Git payload in this
|
|
431
431
|
repository's `oats-package/`, excluded from the kernel npm tarball. The catalog
|
|
432
|
-
entry selects the published `oats-framework/v1.1.
|
|
432
|
+
entry selects the published `oats-framework/v1.1.3` tag, which exports three
|
|
433
433
|
capabilities: `oats.core` (day-to-day operation: `oats-operate`, `oats-souls`
|
|
434
434
|
and the "you run on OATS" briefing — declared explicitly on every soul by
|
|
435
435
|
default at creation and removable), `oats.setup` (OATS Soul Setup: `oats-config`,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# oats.framework 1.1.3 · aweb 1.11.2 — helper composition for every edition
|
|
2
|
+
|
|
3
|
+
Packaging fix found by the independent second operator on the 0.24.4 wave: behind the operator's `responsibleHuman` requirement, preparation refused with `needs-configuration: new helper injection requires an explicit capability policy`. `oats.okf` had adopted the helper-injection contract (`omit`); its siblings **`oats.core`** and **`oats.aweb`** shipped an `inject` without a `helperInjection` policy, so no edition's OKF harvest helper could compose and **no edition could publish a resolution**. Both remaining blockers were packaging, not operator input.
|
|
4
|
+
|
|
5
|
+
- **`oats.core` 1.0.1** (in oats.framework 1.1.3): `helperInjection: {version: 1, mode: inherit}` — a helper is still an OATS instance and keeps the "you run on OATS" briefing.
|
|
6
|
+
- **aweb 1.11.2**: `helperInjection: {version: 1, mode: omit}` — a harvest helper has no messaging identity. Manifest-only; code identical to 1.11.0.
|
|
7
|
+
- **Release check**: `test/release-packaging.test.mjs` now asserts every framework-shipped capability with an `inject` declares a `helperInjection` policy; the theory-package check pins core's `inherit`. The framework's own packages must pass the contracts the kernel imposes.
|
|
8
|
+
- Catalog: `oats.aweb` → `v1.11.2`, `oats.framework` → `oats-framework/v1.1.3`; six editions and workspace imports repinned.
|
|
9
|
+
- Still open (kernel, 0.25 unless a 0.24.5 is cut): this early refusal is a bare top-level error with no `details`/attribution — it must route through the same problem shape as every other preparation problem. Also noted: `responsibleHuman` is a captured accountability claim the code validates only by shape.
|
|
10
|
+
|
|
11
|
+
Decision: `agents/oats-expert/soul/knowledge/decisions/helper-injection-policy-on-every-injecting-capability.md`.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# OATS v0.24.5 — four small fixes from the second-operator wave
|
|
2
|
+
|
|
3
|
+
Kernel/Pi/Desktop **0.24.5**. No contract, schema or authority change. Every item was found while an independent operator (and then the lead) drove the published 0.24.4 wave to its first end-to-end resolution.
|
|
4
|
+
|
|
5
|
+
- **Retirement recovers a worktree on the branch it actually has.** `oats retire` derived the recovery clone's branch from `instance.json` (the spawn-time branch); a worktree that had legitimately switched branches during its task could never pass recovery verification and became unretirable by the normal path, even with nothing unpreserved. Recovery now derives the branch from the worktree while it exists (detached HEADs recover at their exact commit), falls back to the recorded branch only when the worktree is gone, and records the drift in `recovery.json` (`branchDrift`). Fail-closed behaviour is unchanged; the source of truth moved to the object.
|
|
6
|
+
- **Captured launch: `ifInstalled` runtime rows are not hard blocks, and refusals are attributed.** A provider's `requires` row marked `ifInstalled: true` (a version floor for an ambient package, if present) was treated as a hard requirement, so a Pi launch with aweb `delivery: session` could never publish a resolution — the same request without a `launch` block published fine. Such rows are now satisfied by absence in captured preparation. Remaining hard rows refuse through the ordinary preparation problem shape (`slot`, `capability`, `runtime`, `package`, the manifest's own `install` text) instead of one bare `needs-configuration`.
|
|
7
|
+
- **Helper-injection refusal is attributed.** A capability that ships an `inject` without a `helperInjection` policy now yields a preparation problem naming that capability (with `origins`), so the operator learns *which* sibling has not adopted the contract rather than only that one has.
|
|
8
|
+
- **`oats onboard --workspace` acquires the framework the workspace's catalog names.** The kernel tarball ships `package-catalog.json` as a snapshot at the kernel's tag, so it lags every `oats.framework` release cut afterwards; onboarding a current workspace edition against it refused as `integrity-drift` — correct, but a fresh machine has no reviewed list to point `OATS_PACKAGE_CATALOG` at. Onboarding now reads the workspace repository's `package-catalog.json` at the observed revision and uses its `oats.framework` entry; the bundled entry is the fallback, `OATS_PACKAGE_CATALOG` still overrides. The result reports `catalog.origin`, and a drift refusal names the lag and both integrities.
|
|
9
|
+
|
|
10
|
+
Rule going forward (recorded in the maintainer's knowledge): **`prepare` never ends in a bare `needs-configuration` after selection** — every refusal is a problem with a slot/capability, or it is a kernel defect. Remaining bare sites are pre-selection input errors, record-shape guards and the skill-name collision, none reachable from a published edition.
|
|
11
|
+
|
|
12
|
+
Not in this release: `binding.keys` enforcement and the dotted-exact-key grammar (0.25); OKF `check` per-cause reasons (OKF 2.1.3, deferred with harvest).
|
|
@@ -106,26 +106,26 @@ onboarding and legacy roster/knowledge cutover remain separate.
|
|
|
106
106
|
Store `oats` requires the explicit `stores.oats` binding; no publisher writer,
|
|
107
107
|
production store or grants are supplied. An acceptance fixture is parent-owned
|
|
108
108
|
and cannot be counted as production knowledge adoption.
|
|
109
|
-
- Current authored expert editions require knowledge **oats.okf@2.1.
|
|
110
|
-
messaging **oats.aweb@1.11.0
|
|
109
|
+
- Current authored expert editions require knowledge **oats.okf@2.1.2** and
|
|
110
|
+
messaging **oats.aweb@1.11.2** (both OATS >=0.24.4), not optional defaults. These published revisions
|
|
111
111
|
are **not proof that their combined bindings/runtime profile is ready**. The provider
|
|
112
112
|
owner supplies that evidence and any subsequently reviewed compatible revision.
|
|
113
113
|
Do not replace either requirement with none or erase a read edge to launch.
|
|
114
114
|
|
|
115
115
|
At those authored revisions, the provider boundary is concrete:
|
|
116
116
|
|
|
117
|
-
- Published
|
|
117
|
+
- Published OKF 2.1.2 supports `inherit: stores.oats`, normalized to
|
|
118
118
|
`/bindings/knowledge/stores/oats`. The explicit `destination: oats` preserves
|
|
119
119
|
routing; omitting it would instead require `write.default`. No new schema,
|
|
120
120
|
owner or production locator is needed for this declaration.
|
|
121
121
|
- aweb 1.10.3 (`24efa6f9`) has no portable binding interface; **aweb 1.11.0**
|
|
122
|
-
(`v1.11.0`, OATS >=0.24.2) adds it and the five editions
|
|
122
|
+
(`v1.11.0`, OATS >=0.24.2) adds it; **1.11.1/1.11.2** (OATS >=0.24.4) are code-identical and declare its fixed reasons, owned operator keys and `helperInjection: omit` (a harvest helper has no messaging identity); the five editions pin 1.11.2. Its `check`
|
|
123
123
|
qualifies only an input-capable Claude/Codex primary with an explicit private team
|
|
124
124
|
and `delivery: session`; strict-Pi print reports `needs-configuration`. Status is on
|
|
125
125
|
the [program board](design/2026-09-20-redesign-program-board.md). Qualification
|
|
126
126
|
is HOME-route operational custody only: not human/native-principal delegation,
|
|
127
127
|
private grants, broker delivery or model consumption.
|
|
128
|
-
- Published
|
|
128
|
+
- Published OKF 2.1.2 accepts retained Claude/Codex helpers with the complete approved
|
|
129
129
|
capability closure and native-default model intent. Strict Pi still requires an
|
|
130
130
|
explicit model and the sole-OKF profile; Pi plus messaging remains unqualified.
|
|
131
131
|
This provider release alone is not combined-profile acceptance. Do not silently
|
|
@@ -143,7 +143,7 @@ workspace declares the per-human private team policy (`teams: {private: per-huma
|
|
|
143
143
|
and every messaging edition carries its `teams: []` declaration — without them the
|
|
144
144
|
aweb provider cannot normalize in workspace context, as the second operator found.
|
|
145
145
|
Workspace update `f3ee31e0`. The five knowledge-owning experts therefore select
|
|
146
|
-
|
|
146
|
+
OKF 2.1.2 and aweb 1.11.2 with explicit core; setup remains provider-independent,
|
|
147
147
|
requiring core/setup and defaulting all three fundamental layers to none. It is
|
|
148
148
|
not a sixth knowledge owner. This deliberate repin, not a catalog/kernel upgrade
|
|
149
149
|
alone, advances the selected source requirements. Successful source inspection,
|
package/docs/workspaces.md
CHANGED
|
@@ -71,7 +71,7 @@ name: domain-expert
|
|
|
71
71
|
requires:
|
|
72
72
|
knowledge:
|
|
73
73
|
capability: oats.okf
|
|
74
|
-
source: git:github.com/awebai/oats-okf@v2.1.
|
|
74
|
+
source: git:github.com/awebai/oats-okf@v2.1.2#oats-package
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
This illustrates software selection, not complete OKF provisioning: the chosen capability also needs its own valid knowledge declaration, bindings and accepted base.
|
|
@@ -32,8 +32,28 @@ export function validateCapturedLaunchRequest(value, validateConfig) {
|
|
|
32
32
|
export function compileCapturedLaunchRequest(request,{artifacts,manifests,settings,resources},kernel) {
|
|
33
33
|
if(request===undefined)return null;
|
|
34
34
|
validateCapturedLaunchRequest(request,kernel.validateLaunchConfig);
|
|
35
|
-
const
|
|
36
|
-
|
|
35
|
+
const providers=[...manifests].filter(([id])=>Object.hasOwn(artifacts.capabilities,id)).map(([id,manifest])=>({id,manifest,settings:settings[id]}));
|
|
36
|
+
// `ifInstalled: true` rows constrain a package that may legitimately be absent (a version floor
|
|
37
|
+
// for an ambient extension, if any). Captured preparation retains no ambient packages, so such a
|
|
38
|
+
// row is satisfied by absence — treating it as a hard requirement made a Pi launch with aweb
|
|
39
|
+
// `delivery: session` unpublishable (second-operator finding, 2026-09-21). Hard rows refuse
|
|
40
|
+
// through the preparation problem shape, naming the capability and package; the remedy text is
|
|
41
|
+
// the manifest's own `install` string — declared data, never free text.
|
|
42
|
+
// Resolve each applicable row back to its declaration, honouring the same `when` predicate the
|
|
43
|
+
// kernel's requirement selection uses, so two rows for one package (channel vs session) stay distinct.
|
|
44
|
+
const holds=(provider,row)=>!row.when || Object.entries(row.when).every(([k,v])=>String(provider.settings?.[k] ?? '')===String(v));
|
|
45
|
+
const declarationOf=(row)=>{const provider=providers.find(p=>p.id===row.capability);
|
|
46
|
+
return {provider,declared:provider?.manifest?.requires?.find(r=>r && typeof r==='object' && r.runtime===request.runtime && r.package===row.package && holds(provider,r))};};
|
|
47
|
+
const required=kernel.runtimeRequirements(request.runtime,providers).filter(row=>declarationOf(row).declared?.ifInstalled!==true);
|
|
48
|
+
if(required.length){
|
|
49
|
+
const error=oatsError('needs-configuration','runtime package requirements need retained runtime roots and a qualified loader; no ambient package discovery was used');
|
|
50
|
+
error.problems=required.map(row=>{
|
|
51
|
+
const {provider,declared}=declarationOf(row);
|
|
52
|
+
return {code:'needs-configuration',message:`${request.runtime} launch requires runtime package ${row.package}, which captured preparation has not retained`,capability:row.capability,
|
|
53
|
+
...(provider?.manifest?.layer?{slot:provider.manifest.layer}:{}),runtime:request.runtime,package:row.package,...(typeof declared?.install==='string'?{install:declared.install}:{})};
|
|
54
|
+
});
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
37
57
|
const base={version:1,runtime:request.runtime,args:request.args,env:request.env,model:request.model,yolo:request.yolo,
|
|
38
58
|
hooks:{launch:{},env:{},contributions:[],pending:true},prompt:{kind:'task-file',file:'TASK.md'}};
|
|
39
59
|
if(typeof request.executable==='string')return {...base,executable:request.executable,executableResolvedFrom:'explicit-host'};
|
package/lib/core.mjs
CHANGED
|
@@ -7080,6 +7080,15 @@ function fingerprintTree(root, { excludeRoot = new Set(), excludeGitMetadata = f
|
|
|
7080
7080
|
return `sha256:${hash.digest("hex")}`;
|
|
7081
7081
|
}
|
|
7082
7082
|
|
|
7083
|
+
/** The ref a worktree actually has checked out: `{branch, oid}` with
|
|
7084
|
+
* `branch === null` when HEAD is detached. Recovery derives truth from the
|
|
7085
|
+
* object, never from spawn-time metadata. */
|
|
7086
|
+
function worktreeRef(work) {
|
|
7087
|
+
const oid = execFileSync("git", ["-C", work, "rev-parse", "HEAD"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: GIT_MAX_BUFFER }).trim();
|
|
7088
|
+
let branch = null;
|
|
7089
|
+
try { branch = execFileSync("git", ["-C", work, "symbolic-ref", "--quiet", "--short", "HEAD"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: GIT_MAX_BUFFER }).trim() || null; } catch { branch = null; }
|
|
7090
|
+
return { branch, oid };
|
|
7091
|
+
}
|
|
7083
7092
|
function worktreeStatus(repo) {
|
|
7084
7093
|
try {
|
|
7085
7094
|
return execFileSync("git", ["-C", repo, "status", "--porcelain=v1", "-z", "--untracked-files=all", "--ignored=matching", "--ignore-submodules=none"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] , maxBuffer: GIT_MAX_BUFFER });
|
|
@@ -8304,9 +8313,21 @@ function preserveRetirementWork(observation, meta, instance) {
|
|
|
8304
8313
|
if (fingerprintTree(observation.home, { excludeRoot: new Set(["work"]) }) !== fingerprintTree(recoveredHome)) {
|
|
8305
8314
|
throw new Error("home recovery verification disagreed with the source");
|
|
8306
8315
|
}
|
|
8316
|
+
let branchDrift;
|
|
8307
8317
|
if (!homeOnly && meta.work === "worktree" && meta.repo && meta.branch && (existsSync(observation.work) || observation.branchExists)) {
|
|
8308
8318
|
const recoveredRepo = join(staging, "repo");
|
|
8309
|
-
|
|
8319
|
+
// The branch is derived from the worktree while it exists: an instance
|
|
8320
|
+
// that legitimately switched branches during its task must still be
|
|
8321
|
+
// recoverable, and the recorded spawn-time branch is only the fallback
|
|
8322
|
+
// when the worktree is gone. Detached HEADs recover at their exact OID.
|
|
8323
|
+
const ref = existsSync(observation.work) ? worktreeRef(observation.work) : { branch: meta.branch, oid: null };
|
|
8324
|
+
if (ref.branch !== meta.branch) branchDrift = { recordedBranch: meta.branch, worktreeBranch: ref.branch, detachedAt: ref.branch === null ? ref.oid : null };
|
|
8325
|
+
if (ref.branch !== null) execFileSync("git", ["clone", "--no-local", "--quiet", "--branch", ref.branch, meta.repo, recoveredRepo], { stdio: ["ignore", "pipe", "pipe"] , maxBuffer: GIT_MAX_BUFFER });
|
|
8326
|
+
else {
|
|
8327
|
+
execFileSync("git", ["clone", "--no-local", "--quiet", "--no-checkout", meta.repo, recoveredRepo], { stdio: ["ignore", "pipe", "pipe"] , maxBuffer: GIT_MAX_BUFFER });
|
|
8328
|
+
execFileSync("git", ["-C", recoveredRepo, "fetch", "--quiet", observation.work, ref.oid], { stdio: ["ignore", "pipe", "pipe"], maxBuffer: GIT_MAX_BUFFER });
|
|
8329
|
+
execFileSync("git", ["-C", recoveredRepo, "checkout", "--quiet", "--detach", ref.oid], { stdio: ["ignore", "pipe", "pipe"], maxBuffer: GIT_MAX_BUFFER });
|
|
8330
|
+
}
|
|
8310
8331
|
const sourceGitContext = existsSync(observation.work) ? observation.work : meta.repo;
|
|
8311
8332
|
detachRecoveryClone(sourceGitContext, recoveredRepo);
|
|
8312
8333
|
if (existsSync(observation.work)) {
|
|
@@ -8325,7 +8346,8 @@ function preserveRetirementWork(observation, meta, instance) {
|
|
|
8325
8346
|
if (worktreeStatus(observation.work) !== worktreeStatus(recoveredRepo)) throw new Error("recovered Git index/status disagreed with the source");
|
|
8326
8347
|
}
|
|
8327
8348
|
const recoveredHead = execFileSync("git", ["-C", recoveredRepo, "rev-parse", "HEAD"], { encoding: "utf8" , maxBuffer: GIT_MAX_BUFFER }).trim();
|
|
8328
|
-
const sourceHead =
|
|
8349
|
+
const sourceHead = ref.branch === null ? ref.oid
|
|
8350
|
+
: execFileSync("git", ["-C", meta.repo, "rev-parse", `refs/heads/${ref.branch}`], { encoding: "utf8" , maxBuffer: GIT_MAX_BUFFER }).trim();
|
|
8329
8351
|
if (recoveredHead !== sourceHead) throw new Error("recovery clone does not retain the instance branch tip");
|
|
8330
8352
|
}
|
|
8331
8353
|
if (observation.directory && observation.directoryFingerprint) {
|
|
@@ -8336,7 +8358,7 @@ function preserveRetirementWork(observation, meta, instance) {
|
|
|
8336
8358
|
}
|
|
8337
8359
|
}
|
|
8338
8360
|
const repoCopy = homeOnly ? { copied: false, reason: "Only instance-home bytes changed; no work state requires a repository copy", source: meta.repo, branch: meta.branch } : undefined;
|
|
8339
|
-
writeFileSync(join(staging, "recovery.json"), JSON.stringify({ version: 1, instance, classes: observation.classes, sourceHome: observation.home, createdAt: new Date().toISOString(), ...(repoCopy ? { repoCopy } : {}) }, null, 2) + "\n", { mode: 0o600 });
|
|
8361
|
+
writeFileSync(join(staging, "recovery.json"), JSON.stringify({ version: 1, instance, classes: observation.classes, sourceHome: observation.home, createdAt: new Date().toISOString(), ...(repoCopy ? { repoCopy } : {}), ...(branchDrift ? { branchDrift } : {}) }, null, 2) + "\n", { mode: 0o600 });
|
|
8340
8362
|
mkdirSync(dirname(recovery), { recursive: true });
|
|
8341
8363
|
renameSync(staging, recovery);
|
|
8342
8364
|
return { path: recovery, classes: observation.classes, ...(repoCopy ? { repoCopy } : {}) };
|
|
@@ -35,7 +35,13 @@ export function captureHelperInjectionChoices(plan, definitions) {
|
|
|
35
35
|
const policy = helperInjectionFact(definition), id = definition.artifact.capability;
|
|
36
36
|
if (policies.has(id)) throw oatsError('invalid-resolution', 'duplicate helper policy owner');
|
|
37
37
|
policies.set(id, policy);
|
|
38
|
-
if (!policy.fact && policy.manifest.inject)
|
|
38
|
+
if (!policy.fact && policy.manifest.inject) {
|
|
39
|
+
// Attributed like every other preparation problem: the operator learns WHICH
|
|
40
|
+
// capability lacks the declaration, not only that one does (second-operator finding).
|
|
41
|
+
const error = oatsError('needs-configuration', 'new helper injection requires an explicit capability policy');
|
|
42
|
+
error.problems = [{ code: 'needs-configuration', message: 'capability ships an inject without a helperInjection policy', capability: id, ...(policy.manifest.layer ? { slot: policy.manifest.layer } : {}) }];
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
39
45
|
if (policy.fact) requirements.push(policy.fact);
|
|
40
46
|
}
|
|
41
47
|
const resolved = resolveChoices({ requirements, candidates: plan.candidates });
|
|
@@ -31,6 +31,16 @@ function packageSubset(artifacts, root) {
|
|
|
31
31
|
|
|
32
32
|
/** Repositories and kernel callbacks belong to this operation; the caller owns
|
|
33
33
|
* their lifetime/scratch cleanup. No hook, launch, enrollment or approval here. */
|
|
34
|
+
/** Origins of the choices that selected a capability — the same derivation preparation
|
|
35
|
+
* bindings use, so completion refusals render beside binding problems. */
|
|
36
|
+
function originsOf(plan, capability) {
|
|
37
|
+
const seen = new Map();
|
|
38
|
+
for (const key of plan.capabilities?.[capability]?.choiceKeys ?? []) {
|
|
39
|
+
const origin = plan.choices?.[key]?.selectedBy;
|
|
40
|
+
if (origin) seen.set(canonicalJson(origin), origin);
|
|
41
|
+
}
|
|
42
|
+
return [...seen.values()];
|
|
43
|
+
}
|
|
34
44
|
export function prepareComposition(input, { repositories, kernel, previous: suppliedPrevious }) {
|
|
35
45
|
canonicalJson(input);
|
|
36
46
|
objectAt(input, ["deployment", "directory", "source", "origin", "workspace", "member", "operator", "mode", "allowLocalPaths", "standaloneContextKey", "launch", "helperLaunches"], ["deployment", "directory", "source", "origin"]);
|
|
@@ -114,8 +124,17 @@ export function prepareComposition(input, { repositories, kernel, previous: supp
|
|
|
114
124
|
if (operator) declarations.push(operatorBindingDeclaration(operator));
|
|
115
125
|
const bound = prepareProviderBindings({ seed, plan, manifests, declarations }, options => kernel.binding({ ...options, deployment }));
|
|
116
126
|
plan = bound.plan; seed = bound.seed;
|
|
117
|
-
|
|
118
|
-
|
|
127
|
+
let completion;
|
|
128
|
+
if (bound.problems.length) completion = { record: null, problems: bound.problems };
|
|
129
|
+
else {
|
|
130
|
+
try { completion = kernel.complete({ seed, plan, manifests, mode, deployment, directory, launch: input.launch, helperLaunches: input.helperLaunches }); }
|
|
131
|
+
catch (error) {
|
|
132
|
+
// A typed completion refusal that names its capability is a preparation problem like any
|
|
133
|
+
// other (slot/capability/origins), never a bare top-level error. Anything else propagates.
|
|
134
|
+
if (!Array.isArray(error?.problems) || !["needs-configuration", "requirement-conflict"].includes(error.code)) throw error;
|
|
135
|
+
completion = { record: null, problems: error.problems.map(problem => ({ ...problem, origins: problem.origins ?? originsOf(plan, problem.capability) })) };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
119
138
|
let resolution = null;
|
|
120
139
|
if (completion.record) resolution = commitCapturedResolution(deployment, completion.record);
|
|
121
140
|
// Publishing valid immutable inputs/records need not roll back on a later
|
|
@@ -37,11 +37,28 @@ function repositoryRequest(source) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/** `packages["oats.framework"]` from the repository's own `package-catalog.json` at the observed
|
|
41
|
+
* revision, or null when the file or entry is absent. Data only; the acquisition still verifies
|
|
42
|
+
* bytes, and the edition/package integrity comparison still applies. */
|
|
43
|
+
function frameworkCatalogEntry(transaction, observation) {
|
|
44
|
+
const file = transaction.readFile(observation, 'package-catalog.json', { optional: true });
|
|
45
|
+
if (!file) return null;
|
|
46
|
+
let doc;
|
|
47
|
+
try { doc = JSON.parse(file.bytes.toString('utf8')); } catch { throw oatsError('invalid-source', 'workspace package-catalog.json is not valid JSON'); }
|
|
48
|
+
const entry = doc && typeof doc === 'object' && !Array.isArray(doc) && doc.packages && typeof doc.packages === 'object' && !Array.isArray(doc.packages)
|
|
49
|
+
? doc.packages['oats.framework'] : undefined;
|
|
50
|
+
if (entry === undefined) return null;
|
|
51
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry) || typeof entry.url !== 'string' || typeof entry.ref !== 'string' || (entry.path !== undefined && typeof entry.path !== 'string')) {
|
|
52
|
+
throw oatsError('invalid-source', 'workspace package-catalog.json has an invalid oats.framework entry');
|
|
53
|
+
}
|
|
54
|
+
return { url: entry.url, ref: entry.ref, ...(entry.path === undefined ? {} : { path: entry.path }), revision: observation.source.commit };
|
|
55
|
+
}
|
|
56
|
+
|
|
40
57
|
export function loadSetupExpertEdition(workspace, repositoryOptions = {}) {
|
|
41
58
|
if (workspace === undefined) {
|
|
42
59
|
const root = fileURLToPath(new URL(`../${EXPORT}/`, import.meta.url));
|
|
43
60
|
return { declaration: validateEdition(readFileSync(join(root, 'soul.yaml'))), instructions: readFileSync(join(root, 'AGENTS.md'), 'utf8'),
|
|
44
|
-
source: { kind: 'packaged-definition', captured: false }, packageIntegrity: null };
|
|
61
|
+
source: { kind: 'packaged-definition', captured: false }, packageIntegrity: null, catalogEntry: null };
|
|
45
62
|
}
|
|
46
63
|
const request = repositoryRequest(workspace), scratch = realpathSync(mkdtempSync(join(tmpdir(), 'oats-setup-source-'))), owned = lstatSync(scratch);
|
|
47
64
|
let transaction;
|
|
@@ -64,7 +81,14 @@ export function loadSetupExpertEdition(workspace, repositoryOptions = {}) {
|
|
|
64
81
|
|| readdirSync(soulRoot).some(name => !['soul.yaml', 'AGENTS.md', 'CLAUDE.md'].includes(name))) {
|
|
65
82
|
throw oatsError('source-incomplete', 'classic setup edition must have canonical AGENTS.md/CLAUDE.md and no omitted private skill or knowledge trees');
|
|
66
83
|
}
|
|
84
|
+
// The workspace publishes the reviewed catalog: read the oats.framework entry at the WORKSPACE's
|
|
85
|
+
// observed revision (falling back to the edition's own revision when the source has no workspace
|
|
86
|
+
// document), so onboarding acquires the framework the workspace currently names instead of
|
|
87
|
+
// whatever the kernel tarball snapshotted at its own tag. The edition/package integrity check
|
|
88
|
+
// below still decides whether that acquisition matches the copied edition.
|
|
89
|
+
const catalogEntry = frameworkCatalogEntry(transaction, observed) ?? frameworkCatalogEntry(transaction, imported.observation);
|
|
67
90
|
return { declaration, instructions: readFileSync(body, 'utf8'), packageIntegrity: packageIntegrity(join(projection, 'oats-package')),
|
|
91
|
+
catalogEntry,
|
|
68
92
|
source: { kind: 'exported-edition-copy', source: imported.reference.source, revision: imported.observation.source.commit,
|
|
69
93
|
path: imported.reference.soul, workspaceRevision: view?.source.commit ?? null, captured: false } };
|
|
70
94
|
} finally {
|
package/package-catalog.json
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"packages": {
|
|
4
4
|
"oats.okf": {
|
|
5
5
|
"url": "https://github.com/awebai/oats-okf.git",
|
|
6
|
-
"ref": "v2.1.
|
|
6
|
+
"ref": "v2.1.2",
|
|
7
7
|
"path": "oats-package"
|
|
8
8
|
},
|
|
9
9
|
"oats.aweb": {
|
|
10
10
|
"url": "https://github.com/awebai/oats-aweb.git",
|
|
11
|
-
"ref": "v1.11.
|
|
11
|
+
"ref": "v1.11.2",
|
|
12
12
|
"path": "oats-package"
|
|
13
13
|
},
|
|
14
14
|
"oats.jira": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"oats.framework": {
|
|
35
35
|
"url": "https://github.com/awebai/oats.git",
|
|
36
|
-
"ref": "oats-framework/v1.1.
|
|
36
|
+
"ref": "oats-framework/v1.1.3",
|
|
37
37
|
"path": "oats-package"
|
|
38
38
|
}
|
|
39
39
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awebai/oats",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.5",
|
|
4
4
|
"description": "OATS (Open Agent Team Specification) — durable souls, disposable instances, targetable capability packages, and the runtime-neutral oats CLI/kernel.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|