@awebai/oats 0.30.0 → 0.30.2

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.
Files changed (128) hide show
  1. package/bin/oats.mjs +1 -1
  2. package/docs/capabilities.md +3 -3
  3. package/docs/design/2026-09-23-workspace-module-contracts.md +2 -1
  4. package/docs/desktop-cli-api.md +23 -11
  5. package/docs/first-team.md +1 -1
  6. package/docs/implementation.md +2 -1
  7. package/docs/integrations.md +1 -1
  8. package/docs/knowledge-capability-authoring.md +1 -1
  9. package/docs/knowledge.md +4 -4
  10. package/docs/official-catalog.md +4 -4
  11. package/docs/packages.md +13 -13
  12. package/docs/plans/0.30-close-out.md +24 -2
  13. package/docs/release-lane.md +7 -2
  14. package/docs/release-notes/v0.30.1.md +123 -0
  15. package/docs/release-notes/v0.30.2.md +85 -0
  16. package/docs/souls-and-instances.md +6 -5
  17. package/docs/workspaces.md +7 -2
  18. package/lib/core.mjs +42 -28
  19. package/lib/instance-inspect.mjs +1 -1
  20. package/lib/instance-resolution.mjs +15 -8
  21. package/lib/materialize.mjs +33 -19
  22. package/lib/packages.mjs +1 -1
  23. package/lib/resolve.mjs +1 -1
  24. package/package-catalog.json +3 -3
  25. package/package.json +1 -3
  26. package/skills/oats-getting-started/SKILL.md +2 -2
  27. package/capabilities/oats-authoring/LICENSE +0 -21
  28. package/capabilities/oats-authoring/oats-package.json +0 -11
  29. package/capabilities/oats-authoring/oats.json +0 -12
  30. package/capabilities/oats-authoring/skills/integration-authoring/SKILL.md +0 -84
  31. package/capabilities/oats-authoring/skills/skill-craft/SKILL.md +0 -109
  32. package/capabilities/oats-authoring/skills/soul-craft/SKILL.md +0 -116
  33. package/capabilities/oats-aweb/bin/oats-aweb-binding.mjs +0 -11
  34. package/capabilities/oats-aweb/bin/oats-aweb.mjs +0 -1672
  35. package/capabilities/oats-aweb/injects/aweb.md +0 -47
  36. package/capabilities/oats-aweb/lib/binding-wire.mjs +0 -365
  37. package/capabilities/oats-aweb/lib/captured-execution.mjs +0 -91
  38. package/capabilities/oats-aweb/lib/captured-native.mjs +0 -91
  39. package/capabilities/oats-aweb/lib/grant-custody.mjs +0 -38
  40. package/capabilities/oats-aweb/lib/invocation-shape.mjs +0 -135
  41. package/capabilities/oats-aweb/lib/portable-binding.mjs +0 -146
  42. package/capabilities/oats-aweb/lib/session-readiness.mjs +0 -56
  43. package/capabilities/oats-aweb/lib/wake-receive.mjs +0 -56
  44. package/capabilities/oats-aweb/oats.json +0 -201
  45. package/capabilities/oats-aweb/skills/LICENSE +0 -21
  46. package/capabilities/oats-aweb/skills/VENDORED.md +0 -31
  47. package/capabilities/oats-aweb/skills/aweb-identity/SKILL.md +0 -201
  48. package/capabilities/oats-aweb/skills/aweb-messaging/SKILL.md +0 -161
  49. package/capabilities/oats-aweb/skills/aweb-messaging/references/messaging-scenarios.md +0 -61
  50. package/capabilities/oats-aweb/skills/aweb-team-membership/SKILL.md +0 -116
  51. package/capabilities/oats-aweb/skills/aweb-team-membership/references/team-membership-reference.md +0 -74
  52. package/capabilities/oats-aweb/skills/oats-aweb/SKILL.md +0 -286
  53. package/capabilities/oats-code-review/injects/reviewer.md +0 -26
  54. package/capabilities/oats-code-review/oats.json +0 -16
  55. package/capabilities/oats-code-review/skills/adversarial-review/SKILL.md +0 -66
  56. package/capabilities/oats-code-review/skills/review-dev-docs/SKILL.md +0 -30
  57. package/capabilities/oats-code-review/skills/security-review/SKILL.md +0 -56
  58. package/capabilities/oats-code-review/skills/simplification-review/SKILL.md +0 -34
  59. package/capabilities/oats-developer/injects/developer.md +0 -38
  60. package/capabilities/oats-developer/oats.json +0 -17
  61. package/capabilities/oats-developer/skills/execution-strategy/SKILL.md +0 -43
  62. package/capabilities/oats-developer/skills/maintain-dev-docs/SKILL.md +0 -47
  63. package/capabilities/oats-developer/skills/run-the-review-loop/SKILL.md +0 -65
  64. package/capabilities/oats-developer/skills/understand-the-spec/SKILL.md +0 -37
  65. package/capabilities/oats-developer/skills/worktrees/SKILL.md +0 -36
  66. package/capabilities/oats-engineering-expert/injects/expert.md +0 -37
  67. package/capabilities/oats-engineering-expert/oats.json +0 -17
  68. package/capabilities/oats-engineering-expert/skills/coordinate-developers/SKILL.md +0 -37
  69. package/capabilities/oats-engineering-expert/skills/coordinate-experts/SKILL.md +0 -52
  70. package/capabilities/oats-engineering-expert/skills/land-your-prs/SKILL.md +0 -50
  71. package/capabilities/oats-engineering-expert/skills/plan-and-spec/SKILL.md +0 -53
  72. package/capabilities/oats-engineering-expert/skills/verify-developer-work/SKILL.md +0 -49
  73. package/capabilities/oats-jira/bin/oats-jira.mjs +0 -40
  74. package/capabilities/oats-jira/injects/jira.md +0 -10
  75. package/capabilities/oats-jira/oats.json +0 -22
  76. package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +0 -179
  77. package/capabilities/oats-linear/bin/oats-linear-hook.mjs +0 -34
  78. package/capabilities/oats-linear/bin/oats-linear.mjs +0 -344
  79. package/capabilities/oats-linear/injects/linear.md +0 -8
  80. package/capabilities/oats-linear/oats.json +0 -24
  81. package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +0 -223
  82. package/capabilities/oats-okf/bin/oats-okf-binding.mjs +0 -14
  83. package/capabilities/oats-okf/bin/oats-okf.mjs +0 -213
  84. package/capabilities/oats-okf/injects/okf.md +0 -42
  85. package/capabilities/oats-okf/lib/binding-wire.mjs +0 -380
  86. package/capabilities/oats-okf/lib/captured-worker.mjs +0 -109
  87. package/capabilities/oats-okf/lib/config.mjs +0 -124
  88. package/capabilities/oats-okf/lib/consult.mjs +0 -518
  89. package/capabilities/oats-okf/lib/harvest-status.mjs +0 -88
  90. package/capabilities/oats-okf/lib/harvest-switch.mjs +0 -94
  91. package/capabilities/oats-okf/lib/inspection.mjs +0 -138
  92. package/capabilities/oats-okf/lib/invocation-context.mjs +0 -111
  93. package/capabilities/oats-okf/lib/invocation-shape.mjs +0 -135
  94. package/capabilities/oats-okf/lib/io.mjs +0 -118
  95. package/capabilities/oats-okf/lib/migration.mjs +0 -137
  96. package/capabilities/oats-okf/lib/okf-validate.mjs +0 -123
  97. package/capabilities/oats-okf/lib/portable-binding.mjs +0 -199
  98. package/capabilities/oats-okf/lib/source-contract.mjs +0 -46
  99. package/capabilities/oats-okf/lib/sources.mjs +0 -438
  100. package/capabilities/oats-okf/lib/stores.mjs +0 -473
  101. package/capabilities/oats-okf/lib/worker.mjs +0 -486
  102. package/capabilities/oats-okf/oats.json +0 -151
  103. package/capabilities/oats-okf/schemas/okf-base.schema.json +0 -46
  104. package/capabilities/oats-okf/schemas/okf-bindings.schema.json +0 -112
  105. package/capabilities/oats-okf/schemas/okf-portable-declaration.schema.json +0 -87
  106. package/capabilities/oats-okf/schemas/okf-portable-payload.schema.json +0 -113
  107. package/capabilities/oats-okf/schemas/okf-soul.schema.json +0 -37
  108. package/capabilities/oats-okf/skills/okf-consultation/SKILL.md +0 -144
  109. package/capabilities/oats-okf/skills/okf-consultation/references/consult.md +0 -86
  110. package/capabilities/oats-okf/skills/okf-instance-knowledge/SKILL.md +0 -104
  111. package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +0 -140
  112. package/capabilities/oats-okf-harvest/injects/harvester.md +0 -12
  113. package/capabilities/oats-okf-harvest/oats.json +0 -26
  114. package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +0 -168
  115. package/capabilities/oats-okf-harvest/skills/knowledge-theory/SKILL.md +0 -192
  116. package/capabilities/oats-okf-harvest/skills/okf-authoring/SKILL.md +0 -151
  117. package/capabilities/oats-okf-harvest/skills/okf-authoring/scripts/okf-validate.mjs +0 -123
  118. package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +0 -170
  119. package/capabilities/oats-okf-maintenance/injects/maintainer.md +0 -12
  120. package/capabilities/oats-okf-maintenance/lib/provenance.mjs +0 -50
  121. package/capabilities/oats-okf-maintenance/oats.json +0 -21
  122. package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +0 -159
  123. package/capabilities/oats-okf-maintenance/skills/knowledge-theory/SKILL.md +0 -192
  124. package/capabilities/oats-okf-maintenance/skills/okf-authoring/SKILL.md +0 -151
  125. package/capabilities/oats-okf-maintenance/skills/okf-authoring/scripts/okf-validate.mjs +0 -123
  126. package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +0 -134
  127. package/capabilities/oats-workspace-experts/injects/oats-experts.md +0 -26
  128. package/capabilities/oats-workspace-experts/oats.json +0 -9
@@ -1,380 +0,0 @@
1
- import { TextDecoder } from 'node:util';
2
- import { tmpdir } from 'node:os';
3
- import { isAbsolute, join, resolve } from 'node:path';
4
- import { fs, safePath } from './io.mjs';
5
- import {
6
- KNOWLEDGE_CONTRACT,
7
- KNOWLEDGE_CONTRACT_VERSION,
8
- bindKnowledgeDomain,
9
- bindingChoiceKey,
10
- checkKnowledgeRuntime,
11
- normalizeKnowledgeBindingCandidates,
12
- normalizeKnowledgeDeclaration,
13
- renderKnowledgeRuntime,
14
- sameJson,
15
- } from './portable-binding.mjs';
16
- import { declaration as soulDeclaration, loadBindings, validateBindings } from './config.mjs';
17
- import { stageBase, validateBase } from './stores.mjs';
18
-
19
- export const BINDING_WIRE_LIMITS=Object.freeze({bytes:1024*1024,depth:32,entries:16384});
20
- const CAPABILITY='oats.okf',SLOT='knowledge';
21
- const phases=new Set(['normalize','bind','check']);
22
- const unsupportedCapturedCommands=new Set(['setup','init','migrate','unlock']);
23
- const declarationKinds=new Set(['soul','workspace','adoption','operator']);
24
- const errorCodes=new Set(['needs-configuration','requirement-conflict','invalid-binding','authorization-required','host-requirement-missing','provider-unavailable','provider-not-qualified']);
25
- const obj=value=>value!==null && typeof value==='object' && !Array.isArray(value);
26
- const wireError=code=>{throw Object.assign(new Error(code),{wireCode:code});};
27
- // Private diagnostic marker and closed literal vocabulary. Never serialize a
28
- // caught exception's message, supplied value, path, alias or unknown key.
29
- const settingDiagnostic=Symbol('runtime-setting-diagnostic');
30
- const settingMessages=Object.freeze({
31
- 'bindings-file:missing':'setting bindings-file is required (absolute host path)',
32
- 'bindings-file:invalid':'setting bindings-file must be a normalized absolute host path',
33
- 'state-dir:missing':'setting state-dir is required (absolute host path)',
34
- 'state-dir:invalid':'setting state-dir must be a normalized absolute host path',
35
- 'harvest-runtime:missing':'setting harvest-runtime is required (pi, claude or codex)',
36
- 'harvest-runtime:invalid':'setting harvest-runtime must be pi, claude or codex',
37
- 'harvest-model:invalid':'setting harvest-model must be null or a non-empty string',
38
- });
39
- function settingError(reason) {
40
- if(!Object.hasOwn(settingMessages,reason)) wireError('invalid-binding');
41
- throw Object.assign(new Error('needs-configuration'),{wireCode:'needs-configuration',[settingDiagnostic]:settingMessages[reason]});
42
- }
43
- function keys(value,allowed,required,label) {
44
- if(!obj(value)) wireError('invalid-binding');
45
- for(const key of Object.keys(value)) if(!allowed.includes(key)) wireError('invalid-binding');
46
- for(const key of required) if(!Object.hasOwn(value,key)) wireError('invalid-binding');
47
- return value;
48
- }
49
- function absolute(value) {return typeof value==='string' && isAbsolute(value) && resolve(value)===value;}
50
-
51
- class StrictJsonParser {
52
- constructor(text,{depth,entries}) {this.text=text;this.maxDepth=depth;this.maxEntries=entries;this.at=0;this.entries=0;}
53
- whitespace() {while(/[\u0009\u000a\u000d\u0020]/.test(this.text[this.at] || '')) this.at++;}
54
- count() {if(++this.entries>this.maxEntries) wireError('invalid-binding');}
55
- string() {
56
- if(this.text[this.at]!=='"') wireError('invalid-binding');
57
- const start=this.at++;
58
- while(this.at<this.text.length) {
59
- const code=this.text.charCodeAt(this.at++);
60
- if(code===0x22) {try{return JSON.parse(this.text.slice(start,this.at));}catch{wireError('invalid-binding');}}
61
- if(code<0x20) wireError('invalid-binding');
62
- if(code===0x5c) {
63
- const escaped=this.text[this.at++];
64
- if(escaped==='u') {if(!/^[0-9a-fA-F]{4}$/.test(this.text.slice(this.at,this.at+4))) wireError('invalid-binding');this.at+=4;}
65
- else if(!'"\\/bfnrt'.includes(escaped || '')) wireError('invalid-binding');
66
- }
67
- }
68
- wireError('invalid-binding');
69
- }
70
- value(depth=1) {
71
- this.whitespace();const char=this.text[this.at];
72
- if(char==='"') return this.string();
73
- if(char==='{') {
74
- if(depth>this.maxDepth) wireError('invalid-binding');this.at++;this.whitespace();
75
- const result=Object.create(null),seen=new Set();if(this.text[this.at]==='}') {this.at++;return result;}
76
- while(true) {
77
- this.whitespace();const key=this.string();if(seen.has(key)) wireError('invalid-binding');seen.add(key);this.count();
78
- this.whitespace();if(this.text[this.at++]!==':') wireError('invalid-binding');result[key]=this.value(depth+1);this.whitespace();
79
- const next=this.text[this.at++];if(next==='}') return result;if(next!==',') wireError('invalid-binding');
80
- }
81
- }
82
- if(char==='[') {
83
- if(depth>this.maxDepth) wireError('invalid-binding');this.at++;this.whitespace();
84
- const result=[];if(this.text[this.at]===']') {this.at++;return result;}
85
- while(true) {this.count();result.push(this.value(depth+1));this.whitespace();const next=this.text[this.at++];if(next===']') return result;if(next!==',') wireError('invalid-binding');}
86
- }
87
- for(const [token,value] of [['true',true],['false',false],['null',null]]) if(this.text.startsWith(token,this.at)) {this.at+=token.length;return value;}
88
- const match=/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/.exec(this.text.slice(this.at));
89
- if(!match) wireError('invalid-binding');this.at+=match[0].length;const number=Number(match[0]);if(!Number.isFinite(number)) wireError('invalid-binding');return number;
90
- }
91
- parse() {this.whitespace();const value=this.value();this.whitespace();if(this.at!==this.text.length) wireError('invalid-binding');return value;}
92
- }
93
-
94
- export function parseBindingJson(bytes,limits=BINDING_WIRE_LIMITS) {
95
- if(!Buffer.isBuffer(bytes)) bytes=Buffer.from(bytes);
96
- if(bytes.length>limits.bytes) wireError('invalid-binding');
97
- let text;try{text=new TextDecoder('utf-8',{fatal:true}).decode(bytes);}catch{wireError('invalid-binding');}
98
- return new StrictJsonParser(text,limits).parse();
99
- }
100
-
101
- function request(value,phase) {
102
- keys(value,['schemaVersion','phase','slot','capability','settings','input'],['schemaVersion','phase','slot','capability','settings','input'],'binding request');
103
- if(value.schemaVersion!==1 || value.phase!==phase || value.slot!==SLOT || value.capability!==CAPABILITY || !obj(value.settings)) wireError('invalid-binding');
104
- return value;
105
- }
106
- function declaration(value) {
107
- keys(value,['kind','value','origin','origins'],['kind','value','origin','origins'],'provider declaration');
108
- if(!declarationKinds.has(value.kind) || !obj(value.value) || !obj(value.origin) || !obj(value.origins)) wireError('invalid-binding');
109
- return value;
110
- }
111
- function contract(value,{required=false}={}) {
112
- if(value===undefined) {if(required) wireError('needs-configuration');return null;}
113
- if(!obj(value) || value.contract!==KNOWLEDGE_CONTRACT || value.version!==KNOWLEDGE_CONTRACT_VERSION) wireError('invalid-binding');
114
- return value;
115
- }
116
- function runtimeSettings(settings) {
117
- // `harvest` (on|off) is read by the lifecycle hooks, not bound: accept it here
118
- // so a deployment can set it, and refuse any other value.
119
- keys(settings,['bindings-file','state-dir','harvest-runtime','harvest-model','harvest'],[], 'OKF settings');
120
- if(settings.harvest!==undefined && !['on','off'].includes(settings.harvest)) wireError('invalid-binding');
121
- const descriptorFile=settings['bindings-file'],stateDir=settings['state-dir'],runtime=settings['harvest-runtime'],model=settings['harvest-model'] ?? null;
122
- for(const name of ['bindings-file','state-dir']) {
123
- if(settings[name]===undefined) settingError(`${name}:missing`);
124
- if(!absolute(settings[name])) settingError(`${name}:invalid`);
125
- }
126
- if(runtime===undefined) settingError('harvest-runtime:missing');
127
- if(!['pi','claude','codex'].includes(runtime)) settingError('harvest-runtime:invalid');
128
- if(model!==null && (typeof model!=='string' || !model.trim())) settingError('harvest-model:invalid');
129
- return {descriptorFile,stateDir,execution:{runtime,model}};
130
- }
131
- // Shared maps contain other providers' opaque values. Ownership comes from
132
- // this source's declared/default/required knowledge addresses, not from a value
133
- // looking like a locator or a blanket stores.* prefix. Validate OWN values later.
134
- function ownedBindings(bindings,ownedKeys) {
135
- return Object.fromEntries(Object.entries(bindings).filter(([name])=>{
136
- let key;try{key=bindingChoiceKey(name);}catch{return false;}
137
- return ownedKeys.has(key);
138
- }));
139
- }
140
- function normalizePhase(req) {
141
- keys(req.input,['declarations','context'],['declarations','context'],'normalize input');
142
- if(!Array.isArray(req.input.declarations) || !obj(req.input.context)) wireError('invalid-binding');
143
- const requirements=[],candidates=[],bindingInputs=[];let domain=null;
144
- const collect=input=>{if(!obj(input.bindings)) wireError('invalid-binding');bindingInputs.push(input);};
145
- for(const raw of req.input.declarations) {
146
- const item=declaration(raw);
147
- if(item.kind==='soul') {
148
- const envelope=contract(item.value.knowledge,{required:true});
149
- if(domain) wireError('requirement-conflict');
150
- domain=normalizeKnowledgeDeclaration(envelope,{origin:item.origin,origins:item.origins,pointer:'/knowledge/payload'});
151
- continue;
152
- }
153
- if(item.kind==='workspace') {
154
- const stores=item.value.knowledge?.stores;
155
- if(stores===undefined) continue;
156
- if(!Array.isArray(stores)) wireError('invalid-binding');
157
- stores.forEach((rawStore,index)=>{
158
- if(!obj(rawStore)) wireError('invalid-binding');
159
- if(rawStore.contract!==KNOWLEDGE_CONTRACT) return;
160
- const store=contract(rawStore);
161
- keys(store.payload,['bindings'],['bindings'],'workspace OKF payload');
162
- collect({bindings:store.payload.bindings,kind:'workspace-default',origin:item.origin,origins:item.origins,pointer:`/knowledge/stores/${index}/payload/bindings`});
163
- });
164
- continue;
165
- }
166
- if(item.value.bindings===undefined) continue;
167
- collect({bindings:item.value.bindings,kind:item.kind==='adoption'?'import-adoption':'operator',origin:item.origin,origins:item.origins,pointer:'/bindings'});
168
- }
169
- if(!domain) wireError('needs-configuration');
170
- // Collect first so declaration order cannot change who owns a binding. Keep
171
- // all declared addresses, including dotted aliases and explicit/implicit
172
- // write.default or custom inheritance; the kernel still resolves precedence.
173
- const ownedKeys=new Set([...domain.requirements,...domain.candidates].map(item=>item.key));
174
- for(const input of bindingInputs) candidates.push(...normalizeKnowledgeBindingCandidates({...input,bindings:ownedBindings(input.bindings,ownedKeys)}));
175
- requirements.push(...domain.requirements);candidates.unshift(...domain.candidates);
176
- return {requirements,candidates,model:{domain,runtime:runtimeSettings(req.settings)}};
177
- }
178
- function choiceMap(value) {
179
- if(!obj(value)) wireError('invalid-binding');
180
- for(const [key,choice] of Object.entries(value)) {
181
- if(!key.startsWith('/bindings/knowledge/') || key.includes('//') || /~(?![01])/.test(key)) wireError('invalid-binding');
182
- keys(choice,['value','selectedBy','constraints','considered'],['value','selectedBy','constraints','considered'],'knowledge choice');
183
- if(!Array.isArray(choice.constraints) || !Array.isArray(choice.considered) || (choice.selectedBy!==null && !obj(choice.selectedBy))) wireError('invalid-binding');
184
- }
185
- return value;
186
- }
187
- function bindPhase(req) {
188
- keys(req.input,['model','choices','context'],['model','choices','context'],'bind input');if(!obj(req.input.context)) wireError('invalid-binding');
189
- keys(req.input.model,['domain','runtime'],['domain','runtime'],'OKF binding model');
190
- keys(req.input.model.runtime,['stateDir','descriptorFile','execution'],['stateDir','descriptorFile','execution'],'OKF runtime model');
191
- let bound;try{bound=bindKnowledgeDomain({model:req.input.model.domain,choices:choiceMap(req.input.choices)});}catch(error){if(/unresolved knowledge binding/.test(error.message)) wireError('needs-configuration');throw error;}
192
- const runtime=renderKnowledgeRuntime({domain:bound.payload,...req.input.model.runtime});
193
- return {payloadContract:bound.contract,payloadVersion:bound.version,payload:{...bound.payload,runtime,execution:req.input.model.runtime.execution},credentialRefs:bound.credentialRefs,provenance:bound.provenance};
194
- }
195
- function bindingPayload(binding,{diagnoseSettings=false}={}) {
196
- keys(binding,['schemaVersion','capability','payloadContract','payloadVersion','payload','credentialRefs','provenance'],['schemaVersion','capability','payloadContract','payloadVersion','payload','credentialRefs','provenance'],'provider binding');
197
- if(binding.schemaVersion!==1 || binding.capability!==CAPABILITY || binding.payloadContract!==KNOWLEDGE_CONTRACT || binding.payloadVersion!==KNOWLEDGE_CONTRACT_VERSION || !Array.isArray(binding.provenance)) wireError('invalid-binding');
198
- if(!obj(binding.credentialRefs) || Object.keys(binding.credentialRefs).length) wireError('invalid-binding');
199
- keys(binding.payload,['owner','stores','reads','owns','runtime','execution'],['owner','stores','reads','owns','runtime','execution'],'OKF binding payload');
200
- const domain={owner:binding.payload.owner,stores:binding.payload.stores,reads:binding.payload.reads,owns:binding.payload.owns};
201
- if(diagnoseSettings) {
202
- const bound=binding.payload;
203
- if(!obj(bound.runtime) || !obj(bound.runtime.bindings) || !obj(bound.execution)) wireError('invalid-binding');
204
- // Check the retained values, not mutable request settings. Existing full
205
- // envelope/canonical-runtime/execution guards still run below on success.
206
- runtimeSettings({'bindings-file':bound.runtime.descriptorFile,'state-dir':bound.runtime.bindings.stateDir,
207
- 'harvest-runtime':bound.execution.runtime,'harvest-model':bound.execution.model});
208
- }
209
- const runtime=renderKnowledgeRuntime({domain,stateDir:binding.payload.runtime?.bindings?.stateDir,descriptorFile:binding.payload.runtime?.descriptorFile});
210
- if(!sameJson(runtime,binding.payload.runtime)) wireError('invalid-binding');
211
- keys(binding.payload.execution,['runtime','model'],['runtime','model'],'OKF worker execution');
212
- if(!['pi','claude','codex'].includes(binding.payload.execution.runtime) || (binding.payload.execution.model!==null && (typeof binding.payload.execution.model!=='string' || !binding.payload.execution.model.trim()))) wireError('invalid-binding');
213
- return {domain,runtime,execution:binding.payload.execution};
214
- }
215
-
216
- /** Pure projection for durable source registration and later source-free work. */
217
- export function sourceRuntimeFromKnowledgeBinding(binding) {
218
- const {domain,runtime,execution}=bindingPayload(binding);
219
- return {owner:domain.owner,bindings:{file:runtime.descriptorFile,...runtime.bindings},decl:runtime.declaration,execution:{...execution}};
220
- }
221
-
222
- const invocationError=()=>{throw Object.assign(new Error('invalid captured provider binding snapshot'),{code:'E_BINDING'});};
223
- export function readPrivateInvocationJson(file) {
224
- if(!absolute(file)) invocationError();
225
- let fd;
226
- try {
227
- if(safePath(file)!==file) invocationError();
228
- const before=fs.lstatSync(file),uid=typeof process.getuid==='function'?process.getuid():null;
229
- if(!before.isFile() || before.nlink!==1 || before.size>BINDING_WIRE_LIMITS.bytes || (before.mode&0o077)!==0 || (uid!==null && before.uid!==uid)) invocationError();
230
- fd=fs.openSync(file,fs.constants.O_RDONLY|fs.constants.O_NOFOLLOW|fs.constants.O_NONBLOCK);
231
- const opened=fs.fstatSync(fd);
232
- if(!opened.isFile() || opened.nlink!==1 || opened.dev!==before.dev || opened.ino!==before.ino || opened.size>BINDING_WIRE_LIMITS.bytes || (opened.mode&0o077)!==0 || (uid!==null && opened.uid!==uid)) invocationError();
233
- const bytes=Buffer.alloc(Math.min(BINDING_WIRE_LIMITS.bytes+1,opened.size+1));let length=0;
234
- while(length<bytes.length) {const count=fs.readSync(fd,bytes,length,bytes.length-length,null);if(!count) break;length+=count;}
235
- const after=fs.fstatSync(fd);
236
- if(length>BINDING_WIRE_LIMITS.bytes || after.size!==opened.size || after.mtimeMs!==opened.mtimeMs) invocationError();
237
- return parseBindingJson(bytes.subarray(0,length));
238
- } catch(error) {if(error?.code==='E_BINDING') throw error;invocationError();}
239
- finally {if(fd!==undefined) fs.closeSync(fd);}
240
- }
241
-
242
- /** Load only the parent-owned transient ProviderBinding1 snapshot when present.
243
- * Absence is an explicit legacy mode; every present-file defect fails closed. */
244
- export function loadInvocationKnowledgeBinding(env=process.env) {
245
- if(!Object.hasOwn(env,'OATS_BINDING_FILE')) return {kind:'legacy'};
246
- const file=env.OATS_BINDING_FILE,binding=readPrivateInvocationJson(file);
247
- let runtime;try{runtime=sourceRuntimeFromKnowledgeBinding(binding);}catch{invocationError();}
248
- return {kind:'captured',file,binding,runtime};
249
- }
250
- // Closed vocabulary of check-phase reasons: one fixed literal per cause, so the
251
- // operator learns WHICH qualification failed without any value, path, alias or
252
- // caught message reaching the wire. Every literal is also in oats.json
253
- // binding.reasons (byte-exact) — the manifest test pins that.
254
- const checkReasons=Object.freeze({
255
- 'action:not-admitted':'check action is not an admitted knowledge operation',
256
- 'settings:missing':'OKF settings missing bindings-file: set settings.oats.okf bindings-file, or deactivate oats.okf',
257
- 'soul:missing':'OKF soul declaration missing: create okf.json for this soul, or deactivate oats.okf',
258
- 'harvest-runtime:missing':settingMessages['harvest-runtime:missing'],
259
- 'harvest-runtime:invalid':settingMessages['harvest-runtime:invalid'],
260
- 'harvest-model:invalid':settingMessages['harvest-model:invalid'],
261
- 'binding:not-configured':'OKF binding missing or invalid: provision soul OKF declaration and bindings file, or deactivate oats.okf',
262
- 'bindings:invalid':'OKF bindings file missing or invalid: repair configured bindings file, or deactivate oats.okf',
263
- 'bases:too-many':'more than 64 git knowledge bases declared',
264
- 'base:stage-failed':'declared knowledge base could not be staged from its git source',
265
- 'base:not-validated':'declared knowledge base is not a validated knowledge tree',
266
- 'base:owner-unmet':'knowledge base owner or remote custody requirement not met',
267
- 'base:source-mismatch':'staged git source does not match the declared knowledge base',
268
- 'runtime:command-missing':'harvest runtime command is not installed on this host',
269
- 'runtime:not-qualified':'harvest runtime is not qualified: install/configure the selected harvest runtime or set harvest off',
270
- });
271
- function problem(code,reason) {
272
- if(reason===undefined) return {code};
273
- if(!Object.hasOwn(checkReasons,reason)) wireError('invalid-binding');
274
- return {code,message:checkReasons[reason]};
275
- }
276
- export const CHECK_REASONS=Object.values(checkReasons);
277
- function providerActionName(action) {
278
- if(action.kind==='operation' && action.slot===SLOT) return action.name;
279
- if(action.kind!=='command') return null;
280
- if(action.namespace==='okf' || action.capability===CAPABILITY) return action.name;
281
- if(typeof action.name==='string' && action.name.startsWith('okf:')) return action.name.slice(4);
282
- return null;
283
- }
284
- function readinessRuntime(settings,env=process.env) {
285
- if(Object.hasOwn(settings,'bindings-file')) {
286
- try {
287
- if(typeof settings['bindings-file']!=='string' || !absolute(settings['bindings-file'])) return {problem:problem('needs-configuration','settings:missing')};
288
- if(settings.harvest!==undefined && !['on','off'].includes(settings.harvest)) return {problem:problem('needs-configuration','runtime:not-qualified')};
289
- if(settings.harvest!=='off') {
290
- if(settings['harvest-runtime']===undefined) return {problem:problem('needs-configuration','harvest-runtime:missing')};
291
- if(!['pi','claude','codex'].includes(settings['harvest-runtime'])) return {problem:problem('needs-configuration','harvest-runtime:invalid')};
292
- if(settings['harvest-model']!==undefined && (typeof settings['harvest-model']!=='string' || !settings['harvest-model'].trim())) return {problem:problem('needs-configuration','harvest-model:invalid')};
293
- }
294
- const bindings=loadBindings(settings['bindings-file']);
295
- if(!env.OATS_SOUL) return {problem:problem('needs-configuration','soul:missing')};
296
- const decl=soulDeclaration(env.OATS_SOUL);
297
- return {runtime:{descriptorFile:bindings.file,bindings:{version:1,stateDir:bindings.stateDir,bases:bindings.bases},declaration:decl}};
298
- } catch(error) {
299
- const message=String(error?.message || '');
300
- if(error?.code==='E_CONFIG' && /okf\.json|soul has no/.test(message)) return {problem:problem('needs-configuration','soul:missing')};
301
- return {problem:problem('needs-configuration','bindings:invalid')};
302
- }
303
- }
304
- if(settings.schemaVersion!==undefined) {
305
- try { return {runtime:bindingPayload(settings,{diagnoseSettings:true}).runtime}; }
306
- catch(error) {if(['invalid-binding','needs-configuration'].includes(error?.wireCode)) return {problem:problem('needs-configuration','binding:not-configured')};throw error;}
307
- }
308
- return {problem:problem('needs-configuration','settings:missing')};
309
- }
310
- function checkPhase(req) {
311
- keys(req.input,['context','action'],['context','action'],'check input');
312
- if(!obj(req.input.context) || !obj(req.input.action)) wireError('invalid-binding');
313
- const action=req.input.action,name=providerActionName(action);
314
- const prepared=readinessRuntime(req.settings);
315
- if(prepared.problem) return {status:'needs-configuration',problems:[prepared.problem]};
316
- const runtime=prepared.runtime;
317
- if(name && (unsupportedCapturedCommands.has(name) || name==='run-source' || name==='harvest')) return {status:'needs-configuration',problems:[problem('provider-not-qualified','action:not-admitted')]};
318
- if(action.kind==='hook' && action.name==='soul-scaffold') return {status:'ready',problems:[]};
319
- let bindings;try{bindings=validateBindings(runtime.bindings,runtime.descriptorFile);}catch{return {status:'needs-configuration',problems:[problem('needs-configuration','bindings:invalid')]};}
320
- const accepted={},gitBases=Object.entries(bindings.bases).filter(([,base])=>base.kind==='git');
321
- if(gitBases.length>64) return {status:'unavailable',problems:[problem('provider-not-qualified','bases:too-many')]};
322
- let scratch=null,stage='base';
323
- try {
324
- if(gitBases.length) scratch=fs.mkdtempSync(join(fs.realpathSync(tmpdir()),'oats-okf-binding-check-'));
325
- for(const [alias,base] of Object.entries(bindings.bases)) {
326
- stage=base.kind==='directory'?'validate':'stage';
327
- accepted[alias]=(base.kind==='directory'?validateBase(base.path,base):stageBase(base,join(scratch,alias),{alias})).meta;
328
- }
329
- if(req.settings.harvest!=='off') {
330
- stage='runtime';
331
- checkKnowledgeRuntime({rendered:runtime,accepted});
332
- }
333
- } catch(error) {
334
- if(error.code==='E_COMMAND') return {status:'needs-configuration',problems:[problem('needs-configuration','runtime:command-missing')]};
335
- if(error.code==='E_OWNER') return {status:'needs-configuration',problems:[problem('provider-not-qualified','base:owner-unmet')]};
336
- if(error.code==='E_CONFIRM') return {status:'needs-configuration',problems:[problem('provider-not-qualified','base:source-mismatch')]};
337
- if(['E_BASE','E_VALIDATION','E_DIRECTORY_GIT'].includes(error.code)) return {status:'needs-configuration',problems:[problem('provider-not-qualified',stage==='stage'?'base:stage-failed':'base:not-validated')]};
338
- if(stage==='runtime') return {status:'needs-configuration',problems:[problem('needs-configuration','runtime:not-qualified')]};
339
- return {status:'unavailable',problems:[problem('provider-unavailable')]};
340
- } finally {
341
- if(scratch) fs.rmSync(scratch,{recursive:true,force:true});
342
- }
343
- return {status:'ready',problems:[]};
344
- }
345
-
346
- export function handleBindingRequest(phase,value) {
347
- if(!phases.has(phase)) wireError('invalid-binding');
348
- const req=request(value,phase);
349
- if(phase==='normalize') return normalizePhase(req);
350
- if(phase==='bind') return bindPhase(req);
351
- return checkPhase(req);
352
- }
353
- function response(phase,body) {return {schemaVersion:1,phase,slot:SLOT,capability:CAPABILITY,...body};}
354
- function errorCode(error) {if(errorCodes.has(error?.wireCode)) return error.wireCode;if(error?.code==='E_OWNER') return 'requirement-conflict';return 'invalid-binding';}
355
- function errorProblem(error) {
356
- const code=errorCode(error),message=error?.[settingDiagnostic];
357
- return {code,...(code==='needs-configuration' && Object.values(settingMessages).includes(message)?{message}:{})};
358
- }
359
- function enforceOutputLimits(value,depth=1,state={entries:0}) {
360
- if(depth>BINDING_WIRE_LIMITS.depth) wireError('provider-not-qualified');
361
- if(value===null || typeof value!=='object') return;
362
- for(const child of Array.isArray(value)?value:Object.values(value)) {
363
- if(++state.entries>BINDING_WIRE_LIMITS.entries) wireError('provider-not-qualified');
364
- enforceOutputLimits(child,depth+1,state);
365
- }
366
- }
367
- export async function runBindingWire(phase,input=process.stdin,output=process.stdout) {
368
- let answer;
369
- try {
370
- const chunks=[];let length=0;
371
- 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);}
372
- const result=handleBindingRequest(phase,parseBindingJson(Buffer.concat(chunks,length)));
373
- answer=response(phase,{ok:true,result});
374
- } catch(error) {answer=response(phases.has(phase)?phase:'check',{ok:false,error:errorProblem(error)});}
375
- let bytes;
376
- try {enforceOutputLimits(answer);bytes=Buffer.from(JSON.stringify(answer)+'\n');if(bytes.length>BINDING_WIRE_LIMITS.bytes) wireError('provider-not-qualified');}
377
- catch {bytes=Buffer.from(JSON.stringify(response(phases.has(phase)?phase:'check',{ok:false,error:{code:'provider-not-qualified'}}))+'\n');}
378
- output.write(bytes);
379
- return answer.ok;
380
- }
@@ -1,109 +0,0 @@
1
- // Captured worker PUBLIC consumer. No kernel imports/index reads, legacy source
2
- // discovery, fake admission, auth handling, backend defaults or history repair.
3
- import {fs,join,resolve,safePath,exec,cliPath,hash,fail} from './io.mjs';
4
- import {isAbsolute} from 'node:path';
5
- import {parseBindingJson,BINDING_WIRE_LIMITS} from './binding-wire.mjs';
6
- import {assertOkfSourceContext,requireOkfAdmittedAction} from './invocation-context.mjs';
7
- import {sameInvocationJson as same} from './invocation-shape.mjs';
8
- export const CAPTURED_WORKER_KEY='oats.okf:memory-harvest';
9
- const obj=v=>v!==null&&typeof v==='object'&&!Array.isArray(v);
10
- const abs=v=>typeof v==='string'&&isAbsolute(v)&&resolve(v)===v&&!v.includes('\0');
11
- const uuid=v=>typeof v==='string'&&/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/.test(v);
12
- export const capturedSource=s=>['providerBinding','executionBinding','registration'].some(k=>Object.hasOwn(s,k));
13
- function binding(v){if(!obj(v)||v.schemaVersion!==1||!abs(v.deployment)||v.resolution?.schemaVersion!==1||!/^sha256-[a-f0-9]{64}$/.test(v.resolution.id))fail('E_CAPTURED_HELPER','invalid public execution binding');return v;}
14
- export function workerCallEnvironment(inherited){
15
- // Preserve native HOME/profile/auth/Git helpers and the active OATS data
16
- // scope, not invoking instance selectors (including legacy PI_AGENT aliases).
17
- const env={...inherited};for(const key of Object.keys(env))if(/^(OATS_(?!HOME_DIR$)|OAS_|PI_AGENT_)/.test(key)||key==='PI_AGENTS_ROOT')delete env[key];
18
- return env;
19
- }
20
- export function publicWorkerCall(source,args){
21
- let text,status=0;
22
- const env=workerCallEnvironment(process.env);
23
- try{text=exec(cliPath(),args,{cwd:source.context,env,timeout:90000});}
24
- catch(error){text=error.stdout;status=error.status??null;}
25
- let envelope;try{envelope=parseBindingJson(Buffer.from(text||''),BINDING_WIRE_LIMITS);}catch{fail('E_CAPTURED_HELPER_UNKNOWN','public helper outcome is unknown; retain run/home, never re-scaffold');}
26
- if(envelope.schemaVersion!==1||typeof envelope.ok!=='boolean')fail('E_CAPTURED_HELPER_UNKNOWN','invalid public helper response');
27
- if(status!==0||!envelope.ok){const error=Object.assign(new Error('public captured helper action refused or is uncertain; retain custody'),{code:'E_CAPTURED_HELPER_UNKNOWN',publicObservation:{status,envelope}});throw error;}
28
- if(!obj(envelope.result))fail('E_CAPTURED_HELPER_UNKNOWN','public helper result missing');return envelope.result;
29
- }
30
- export function readWorkerEndpoint(file){
31
- if(!abs(file))fail('E_CAPTURED_HELPER','captured harvest needs an explicit absolute native-request');safePath(file);let fd;
32
- try{
33
- fd=fs.openSync(file,fs.constants.O_RDONLY|fs.constants.O_NOFOLLOW|fs.constants.O_NONBLOCK);
34
- const before=fs.fstatSync(fd);if(!before.isFile()||before.size>BINDING_WIRE_LIMITS.bytes)fail('E_CAPTURED_HELPER','invalid bounded native request');
35
- const buffer=Buffer.alloc(before.size+1);let n=0,k;while(n<buffer.length&&(k=fs.readSync(fd,buffer,n,buffer.length-n,null)))n+=k;
36
- const after=fs.fstatSync(fd);if(n!==before.size||before.size!==after.size||before.mtimeMs!==after.mtimeMs||before.ctimeMs!==after.ctimeMs)fail('E_CAPTURED_HELPER','native request changed');
37
- const value=parseBindingJson(buffer.subarray(0,n),BINDING_WIRE_LIMITS);
38
- if(!obj(value)||value.schemaVersion!==1||!obj(value.backend)||Object.keys(value).some(k=>!['schemaVersion','backend','stopGraceMs'].includes(k)))fail('E_CAPTURED_HELPER','worker native-request allows backend/stopGraceMs only; provider owns task');
39
- const b=value.backend,allowed=b.backend==='tmux'?['backend','binary','socket','session']:b.backend==='herdr'?['backend','binary','socket','protocol']:[];
40
- if(!allowed.length||Object.keys(b).length!==allowed.length||!allowed.every(k=>Object.hasOwn(b,k))||!abs(b.binary)||!abs(b.socket))fail('E_CAPTURED_HELPER','unsupported exact backend request');
41
- if(b.backend==='tmux'&&(typeof b.session!=='string'||!b.session||/[\r\n\0]/.test(b.session)))fail('E_CAPTURED_HELPER','invalid tmux endpoint');
42
- if(b.backend==='herdr'&&![20,22].includes(b.protocol))fail('E_CAPTURED_HELPER','unknown Herdr protocol');
43
- if(value.stopGraceMs!==undefined&&(!Number.isSafeInteger(value.stopGraceMs)||value.stopGraceMs<1||value.stopGraceMs>300000))fail('E_CAPTURED_HELPER','invalid stop grace');
44
- return JSON.parse(JSON.stringify(value));
45
- }finally{if(fd!==undefined)fs.closeSync(fd);}
46
- }
47
- export function qualifyCapturedWorker(source,{context,nativeRequest}={},call=publicWorkerCall){
48
- if(!context||source.registration?.schemaVersion!==1||source.registration.kind!=='captured')fail('E_CAPTURED_HELPER','captured worker needs registered SOURCE and current admitted operation');
49
- requireOkfAdmittedAction(context);assertOkfSourceContext(source,context,source.providerBinding);
50
- if(context.action.kind!=='operation'||context.action.slot!=='knowledge'||context.action.name!=='harvest'||context.subject.kind!=='persistent')fail('E_CAPTURED_HELPER','only admitted SOURCE knowledge:harvest may create a captured worker');
51
- const manifest=JSON.parse(fs.readFileSync(new URL('../oats.json',import.meta.url),'utf8'));
52
- if((manifest.requires||[]).some(r=>obj(r)&&r.runtime==='pi')||manifest.hooks?.launch)fail('E_CAPTURED_HELPER','required Pi plugin/launch behavior is not qualified by this captured slice');
53
- const request=readWorkerEndpoint(nativeRequest),sourceBinding=binding(source.executionBinding);
54
- const inspected=call(source,['inspect','--deployment',sourceBinding.deployment,'--resolution',sourceBinding.resolution.id,'--helper',CAPTURED_WORKER_KEY,'--json']);
55
- return validateWorkerSelection(source,context,request,inspected);
56
- }
57
- export function validateWorkerSelection(source,context,request,inspected){
58
- const selected=inspected?.helperSelection;
59
- if(selected?.schemaVersion!==1||!same(selected.sourceExecutionBinding,source.executionBinding)||selected.workMode!=='directory'||!same(selected.context,context.context)||!same(selected.responsibleHuman,source.responsibleHuman))fail('E_CAPTURED_HELPER','helper SOURCE/context/human/work correspondence is unqualified');
60
- const h=selected.helper,subject=h?.subject;
61
- if(h?.key!==CAPTURED_WORKER_KEY||h.name!=='memory-harvest'||subject?.kind!=='helper'||subject.name!==h.name||subject.provider?.kind!=='capability'||subject.provider.capability!=='oats.okf'||!same(subject.provider,subject.definition?.owner))fail('E_CAPTURED_HELPER','wrong qualified helper identity');
62
- const api={schemaVersion:1,api:{contract:'oats.captured-session',version:2,available:true,backends:['tmux','herdr']},readiness:{status:'not-checked'}};
63
- if(!same(selected.launch,api)||!same(inspected.nativeSession,api))fail('E_CAPTURED_HELPER','unknown captured native public API');
64
- // Preserve the exact retained selection. Ordinary Claude/Codex may select
65
- // null to use their own native model configuration; Pi's strict SDK slice
66
- // still needs an explicit model and its previously qualified sole-OKF profile.
67
- // API availability/approval is NOT native readiness. The unchanged public
68
- // scaffold/start path enforces the entire retained resource/hook closure.
69
- const launch=selected.launchSelection;
70
- if(!obj(launch)||launch.runtime!==source.execution.runtime||!['pi','claude','codex'].includes(launch.runtime)
71
- || !(launch.model===null?launch.runtime!=='pi':typeof launch.model==='string'&&!!launch.model.trim())
72
- || (source.execution.model!==null&&launch.model!==source.execution.model))fail('E_CAPTURED_HELPER','retained helper launch runtime/model missing or mismatched');
73
- const capabilities=inspected.capabilities;
74
- if(!Array.isArray(capabilities)||!capabilities.length||capabilities.some(c=>!obj(c)||typeof c.id!=='string'||!/^(?:@?[a-z0-9][a-z0-9._-]*[./])[a-z0-9][a-z0-9._/-]*$/.test(c.id)||!['approved','not-required'].includes(c.approval))
75
- || new Set(capabilities.map(c=>c.id)).size!==capabilities.length||!capabilities.some(c=>c.id==='oats.okf'&&c.approval==='approved'))fail('E_CAPTURED_HELPER','complete selected helper capability closure requires current approval');
76
- if(launch.runtime==='pi'&&capabilities.length!==1)fail('E_CAPTURED_HELPER','enriched strict-Pi helper profile is not qualified; required capabilities were not removed');
77
- if(binding(selected.executionBinding).deployment!==source.executionBinding.deployment||!same(inspected.resolution,selected.executionBinding.resolution))fail('E_CAPTURED_HELPER','helper inspection resolution/deployment differs');
78
- return {schemaVersion:1,sourceBinding:JSON.parse(JSON.stringify(source.executionBinding)),helperBinding:JSON.parse(JSON.stringify(selected.executionBinding)),helper:JSON.parse(JSON.stringify(h)),request,requestHash:hash(request),sourceIntent:JSON.parse(JSON.stringify(context.intent)),sourceIncarnationId:context.instance.incarnationId,launchSelection:JSON.parse(JSON.stringify(launch))};
79
- }
80
- export function assertCapturedRun(source,run,plan){
81
- const prior=run.capturedWorker;
82
- if(!prior||prior.schemaVersion!==1||!same(prior.sourceBinding,source.executionBinding)||!same(prior.helperBinding,plan.helperBinding)||!same(prior.helper,plan.helper)||prior.sourceIncarnationId!==plan.sourceIncarnationId||prior.requestHash!==plan.requestHash||hash(prior.request)!==prior.requestHash||!same(prior.request,plan.request)||!same(prior.launchSelection,plan.launchSelection))fail('E_CAPTURED_HELPER','existing run authority/endpoint changed; no replacement worker');
83
- if(['spawn-intent','scaffold-unknown','scaffolded','launch-intent','launch-unknown'].includes(run.status))fail('E_CAPTURED_HELPER_UNKNOWN','existing worker outcome requires explicit custody recovery; no duplicate dispatch');
84
- }
85
- export function capturedScaffold(source,run,home,call=publicWorkerCall){
86
- const p=run.capturedWorker,b=p.helperBinding;
87
- const result=call(source,['spawn',p.helper.name,'--deployment',b.deployment,'--resolution',b.resolution.id,'--home',home,'--no-launch','--json']);
88
- if(result.home!==home||!uuid(result.incarnationId)||!same(result.executionBinding,b)||result.launchPending!==true||result.hooksPending!==false||result.cleanupRequired!==false||result.agent!==p.helper.name||result.work!=='directory'||typeof result.instance!=='string')fail('E_CAPTURED_HELPER_UNKNOWN','scaffold receipt lacks exact completed helper custody');
89
- return result;
90
- }
91
- function directoryCustody(home){
92
- const result={};for(const [key,path] of [['home',home],['work',join(home,'work')]]){safePath(path);const s=fs.lstatSync(path);if(!s.isDirectory()||!Number.isSafeInteger(s.dev)||!Number.isSafeInteger(s.ino))fail('E_WORKER','original worker directories unavailable');result[key]={dev:s.dev,ino:s.ino};}return result;
93
- }
94
- export function retainCapturedWorkerCustody(run,meta){
95
- // Observe the EXISTING kernel home/work proof after the real public scaffold;
96
- // do not mint a new incarnation or read/modify its private kernel index.
97
- const actual=directoryCustody(run.worker.home);
98
- if(!same(meta.captured?.custody,actual)||meta.incarnationId!==run.worker.incarnationId||!same(meta.executionBinding,run.capturedWorker.helperBinding))fail('E_WORKER','scaffold metadata/directory proof mismatch');
99
- return JSON.parse(JSON.stringify(meta.captured.custody));
100
- }
101
- export function assertCapturedWorkerHome(source,run,meta,home){
102
- const p=run.capturedWorker;if(!p||home!==run.worker.home||!same(p.sourceBinding,source.executionBinding)||!same(meta.executionBinding,p.helperBinding)||meta.incarnationId!==run.worker.incarnationId||meta.kind!=='helper'||meta.agent!==p.helper.name||!same(meta.captured?.custody,p.workerDirectoryCustody)||!same(directoryCustody(home),p.workerDirectoryCustody))fail('E_WORKER','captured worker incarnation/binding/subject/directory mismatch');
103
- }
104
- export function capturedStart(source,run,requestFile,call=publicWorkerCall){
105
- const p=run.capturedWorker,b=p.sourceBinding;
106
- const result=call(source,['session','start','--deployment',b.deployment,'--resolution',b.resolution.id,'--helper',p.helper.key,'--home',run.worker.home,'--request',requestFile,'--json']);
107
- if(result.home!==run.worker.home||result.incarnationId!==run.worker.incarnationId||!same(result.executionBinding,p.helperBinding)||!same(result.sourceExecutionBinding,b)||!same(result.helper,p.helper)||result.dispatchAccepted!==true||result.intent?.incarnationId!==run.worker.incarnationId||typeof result.intent?.executionId!=='string'||result.runtime!==p.launchSelection.runtime||result.model!==p.launchSelection.model)fail('E_CAPTURED_HELPER_UNKNOWN','native result lacks exact helper dispatch custody');
108
- return result;
109
- }
@@ -1,124 +0,0 @@
1
- import { isAbsolute } from 'node:path';
2
- import { fs, join, resolve, dirname, fail, readJSON, safePath, relPath, identifier, overlaps, hash, embedsCredential } from './io.mjs';
3
- const obj = v => v && typeof v === 'object' && !Array.isArray(v);
4
- function keys(value, allowed, label, code='E_CONFIG') {
5
- if(!obj(value)) fail(code, `${label} must be an object`);
6
- for(const key of Object.keys(value)) if(!allowed.includes(key)) fail(code, `unknown ${label} property: ${key}`);
7
- }
8
- export function settings() {
9
- const s = JSON.parse(process.env.OATS_SETTINGS || '{}');
10
- keys(s,['bindings-file','state-dir','harvest-runtime','harvest-model','git-timeout','consult-max-age','harvest'],'OATS_SETTINGS');
11
- if(s.harvest!==undefined && !['on','off'].includes(s.harvest)) fail('E_CONFIG','harvest must be on or off');
12
- if(s['git-timeout']!==undefined && (!Number.isInteger(s['git-timeout']) || s['git-timeout']<1)) fail('E_CONFIG','git-timeout must be a positive integer number of seconds');
13
- if(s['consult-max-age']!==undefined && (!Number.isInteger(s['consult-max-age']) || s['consult-max-age']<0)) fail('E_CONFIG','consult-max-age must be a non-negative integer number of seconds');
14
- if(s['state-dir']!==undefined && (typeof s['state-dir']!=='string' || !isAbsolute(s['state-dir']) || resolve(s['state-dir'])!==s['state-dir'])) fail('E_CONFIG','state-dir must be a normalized absolute path');
15
- if(s['harvest-runtime']!==undefined && !['pi','claude','codex'].includes(s['harvest-runtime'])) fail('E_CONFIG','invalid harvest-runtime');
16
- if(s['harvest-model']!==undefined && (typeof s['harvest-model']!=='string' || !s['harvest-model'].trim())) fail('E_CONFIG','harvest-model must be a nonempty string');
17
- return s;
18
- }
19
- /** Time budget for Git operations that talk to a remote (clone, fetch, push,
20
- * ls-remote). Local object reads keep the short exec default. */
21
- export function gitTimeoutMs() { return (settings()['git-timeout'] ?? 600)*1000; }
22
- /** How old (seconds) the host cache's accepted commit may be before a consult
23
- * read refetches the accepted branch; 0 fetches on every read. */
24
- export function consultMaxAgeMs() { return (settings()['consult-max-age'] ?? 300)*1000; }
25
- export function noGit(path) {
26
- for (let p = safePath(path); ; p = dirname(p)) {
27
- if (fs.existsSync(join(p, '.git')) || (fs.existsSync(join(p, 'HEAD')) && fs.existsSync(join(p, 'objects')) && fs.existsSync(join(p, 'refs')))) fail('E_DIRECTORY_GIT', `directory store is in Git custody: ${p}; use kind git`);
28
- if (dirname(p) === p) break;
29
- }
30
- }
31
- export function validateBindings(doc, file, { sourceHome, sourceWork } = {}) {
32
- keys(doc,['version','stateDir','bases','cron','tz'],'bindings');
33
- if (!obj(doc) || doc.version !== 1 || !obj(doc.bases) || !Object.keys(doc.bases).length || (typeof doc.stateDir !== 'string' || !doc.stateDir)) fail('E_CONFIG', 'bindings require {version:1,stateDir,bases}');
34
- for(const key of ['cron','tz']) if(doc[key]!==undefined && (typeof doc[key]!=='string' || !doc[key].trim())) fail('E_CONFIG', `${key} must be a nonempty string`);
35
- const stateDir = safePath(resolve(dirname(file), doc.stateDir));
36
- const bases = {}; const ids = new Set(); const paths = [];
37
- for (const [alias, raw] of Object.entries(doc.bases)) {
38
- identifier(alias); if (!obj(raw)) fail('E_CONFIG', 'invalid base');
39
- const id = identifier(raw.id); if(ids.has(id)) fail('E_ID', `duplicate base identity ${id}`); ids.add(id);
40
- if (raw.kind === 'directory') {
41
- keys(raw,['id','kind','path'],'directory base');
42
- if(typeof raw.path !== 'string' || !raw.path || ['repository','root','acceptedBranch','pr'].some(k=>k in raw)) fail('E_CONFIG', 'directory base requires only path custody');
43
- const path = safePath(resolve(dirname(file), raw.path)); noGit(path);
44
- bases[alias] = { id, kind: 'directory', path }; paths.push(path);
45
- for(const artifact of [`${path}.okf-lock`,`${path}.okf-publication.json`]) if(overlaps(artifact,stateDir) || [sourceHome,sourceWork,file].filter(Boolean).some(p=>overlaps(artifact,p))) fail('E_PATH','coordination artifacts overlap state/source/bindings');
46
- if ([sourceHome, sourceWork].filter(Boolean).some(p=>overlaps(path,p))) fail('E_PATH', 'directory base overlaps source home/work');
47
- } else if (raw.kind === 'git') {
48
- keys(raw,['id','kind','repository','root','acceptedBranch','pr'],'git base');
49
- keys(raw.pr,['repository'],'git pr');
50
- if(typeof raw.repository !== 'string' || !raw.repository || raw.repository.startsWith('-') || /[\r\n\0]/.test(raw.repository) || 'path' in raw) fail('E_CONFIG', 'git base requires repository');
51
- if(embedsCredential(raw.repository)) fail('E_CONFIG', `git base "${alias}": the repository locator embeds a credential (user:token@); bind the plain URL and let a Git credential helper or SSH key supply access`);
52
- const root = relPath(raw.root, true);
53
- if (typeof raw.acceptedBranch !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/.test(raw.acceptedBranch) || raw.acceptedBranch.includes('..') || raw.acceptedBranch.endsWith('/') || raw.acceptedBranch.endsWith('.lock')) fail('E_CONFIG','invalid acceptedBranch');
54
- if (!obj(raw.pr) || typeof raw.pr.repository !== 'string' || !/^[\w.-]+\/[\w.-]+$/.test(raw.pr.repository)) fail('E_CONFIG','git pr requires repository: owner/repo (same-repository PRs)');
55
- let repository = raw.repository;
56
- if(!/^(https:\/\/|ssh:\/\/|git@)/.test(repository)) {
57
- repository = safePath(resolve(dirname(file), repository));
58
- if(sourceHome && overlaps(repository,sourceHome) && repository.startsWith(sourceHome)) fail('E_PATH','Git locator is disposable source custody');
59
- if(fs.existsSync(join(repository,'.git')) && fs.lstatSync(join(repository,'.git')).isFile()) fail('E_PATH','Git repository locator is a linked worktree; bind the durable canonical repository/remote');
60
- paths.push(safePath(resolve(repository,root)));
61
- }
62
- bases[alias] = { id, kind:'git', repository, root, acceptedBranch:raw.acceptedBranch, pr:{repository:raw.pr.repository} };
63
- } else fail('E_CONFIG', `unsupported base kind ${raw.kind}`);
64
- }
65
- for (const p of paths) if (overlaps(p,stateDir)) fail('E_PATH', 'state overlaps accepted base');
66
- for (let i=0;i<paths.length;i++) for(let j=0;j<i;j++) if(overlaps(paths[i],paths[j])) fail('E_PATH','overlapping bases');
67
- const gitBases=Object.values(bases).filter(b=>b.kind==='git');
68
- for(let i=0;i<gitBases.length;i++) for(let j=0;j<i;j++) if(gitBases[i].repository===gitBases[j].repository && overlaps(resolve('/',gitBases[i].root),resolve('/',gitBases[j].root))) fail('E_PATH','overlapping Git base namespaces');
69
- for (const p of [sourceHome,sourceWork].filter(Boolean)) if(overlaps(stateDir,p)) fail('E_PATH','state overlaps source home/work');
70
- if(overlaps(stateDir,file) || paths.some(p=>overlaps(p,file))) fail('E_PATH','bindings document must be outside state and bases');
71
- return { version:1, stateDir, bases, cron:doc.cron?.trim() ?? '*/15 * * * *', tz:doc.tz?.trim() ?? 'UTC' };
72
- }
73
- export function loadBindings(file = settings()['bindings-file'], opts = {}) {
74
- if(typeof file !== 'string' || !isAbsolute(file)) fail('E_CONFIG','set one absolute bindings-file; explicit provisioning/migration is required');
75
- safePath(file); return { file, ...validateBindings(readJSON(file),file,opts) };
76
- }
77
- export function declaration(soul) {
78
- if(fs.existsSync(join(soul,'.okf-cutover.json'))) fail('E_MIGRATION','incomplete explicit migration cutover: rerun its recorded migrate --cutover command');
79
- if(fs.existsSync(join(soul,'knowledge'))) fail('E_MIGRATION','legacy soul/knowledge exists: use oats okf migrate to preserve and stage it, then explicit cutover; no automatic loss');
80
- if(!fs.existsSync(join(soul,'okf.json'))) fail('E_CONFIG',`soul has no okf.json: this soul reads/owns no knowledge yet. Provision it explicitly (oats okf init, or oats okf migrate for a legacy soul), or deactivate oats.okf for this soul; nothing was created`);
81
- return validateDeclaration(readJSON(join(soul,'okf.json')));
82
- }
83
- export function validateDeclaration(d) {
84
- keys(d,['version','owner','owns','reads'],'soul declaration');
85
- if(!obj(d) || d.version!==1) fail('E_CONFIG','soul/okf.json requires version:1');
86
- identifier(d.owner);
87
- for(const key of ['owns','reads']) {
88
- if(!Array.isArray(d[key]) || new Set(d[key]).size !== d[key].length) fail('E_CONFIG', `${key} must be a unique array`);
89
- for(const ref of d[key]) splitRef(ref);
90
- }
91
- return { version:1, owner:d.owner, owns:d.owns, reads:d.reads };
92
- }
93
- export function splitRef(ref) {
94
- if(typeof ref !== 'string' || ref.split('/').length !== 2) fail('E_CONFIG', `node reference must be base/node: ${ref}`);
95
- return ref.split('/').map(identifier);
96
- }
97
- export function metadata(files, base) {
98
- let m; try { m=JSON.parse(Buffer.from(files['okf-base.json'],'base64').toString()); } catch { fail('E_BASE','missing/invalid accepted okf-base.json; run explicit init, never bootstrap on read'); }
99
- keys(m,['version','id','nodes'],'base metadata','E_BASE');
100
- if(!base || typeof base.id!=='string') fail('E_BASE','invalid bound base identity');
101
- if(!obj(m) || m.version !==1 || m.id!==base.id || !obj(m.nodes) || !Object.keys(m.nodes).length) fail('E_BASE','base identity/nodes mismatch');
102
- const paths=[];
103
- for(const [node, spec] of Object.entries(m.nodes)) {
104
- identifier(node); keys(spec,['path','owner'],'node','E_BASE'); identifier(spec.owner); relPath(spec.path);
105
- if(spec.path.split('/').some(p=>p.startsWith('.'))) fail('E_BASE','hidden node paths are not valid knowledge');
106
- if(paths.some(p=>overlaps(resolve('/',p),resolve('/',spec.path)))) fail('E_BASE','overlapping nodes'); paths.push(spec.path);
107
- if(!Object.hasOwn(files,`${spec.path}/index.md`) || !Object.hasOwn(files,`${spec.path}/log.md`)) fail('E_BASE',`missing node index/log ${node}`);
108
- }
109
- if(!files['index.md'] || !files['log.md']) fail('E_BASE','base index.md and log.md required');
110
- for(const p of Object.keys(files)) {
111
- if(p.split('/').some(part=>part.startsWith('.'))) fail('E_BASE','hidden knowledge files would evade the OKF validator');
112
- if(['okf-base.json','index.md','log.md'].includes(p)) continue;
113
- if(!p.endsWith('.md') || !paths.some(n=>p.startsWith(n+'/'))) fail('E_BASE',`file outside node knowledge: ${p}`);
114
- }
115
- return m;
116
- }
117
- export function resolveNodes(d, bindings, accepted) {
118
- for(const ref of [...d.owns,...d.reads]) {
119
- const [alias,node]=splitRef(ref);
120
- if(!Object.hasOwn(bindings.bases,alias) || !accepted[alias] || !Object.hasOwn(accepted[alias].nodes,node)) fail('E_CONFIG', `unresolved node: ${ref}`);
121
- if(d.owns.includes(ref) && accepted[alias].nodes[node].owner!==d.owner) fail('E_OWNER',`owner mismatch: ${ref}`);
122
- }
123
- }
124
- export const bindingFingerprint = b => hash({stateDir:b.stateDir,bases:b.bases});