@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,94 +0,0 @@
1
- // The harvest switch (okf 4.0.0; plan §2.4). `harvest: on|off` is a host
2
- // fact, default off. A soul may only opt OUT (`knowledge: { harvest: off }` in
3
- // soul.yaml), and that opt-out is absolute. The kernel merges soul ⊕ host ⊕
4
- // spawn with later-wins, so a host `on` would hide a soul `off` in the merged
5
- // settings. The soul's own value is therefore read from its soul.yaml
6
- // ($OATS_SOUL), with a deliberately small reader. Anything it cannot read with
7
- // certainty counts as off (fail closed): a private transcript is never kept
8
- // because a file was ambiguous.
9
- import { fs, join } from './io.mjs';
10
-
11
- const VALUES = ['on', 'off'];
12
- const scalar = (raw) => {
13
- const v = raw.replace(/\s+#.*$/, '').trim();
14
- const q = /^(['"])(.*)\1$/.exec(v);
15
- return q ? q[2] : v;
16
- };
17
- /** The soul's own `knowledge.harvest` → { value: 'on'|'off'|null, readable, why }. */
18
- export function soulHarvest(soulDir) {
19
- if (!soulDir) return { value: null, readable: false, why: 'the soul directory is not known to this command (OATS_SOUL unset)' };
20
- let text;
21
- try { text = fs.readFileSync(join(soulDir, 'soul.yaml'), 'utf8'); } catch (e) { return { value: null, readable: false, why: `soul.yaml unreadable (${e.code || e.message})` }; }
22
- const lines = text.split(/\r?\n/);
23
- const at = lines.findIndex((l) => /^knowledge\s*:/.test(l));
24
- if (at < 0) return { value: null, readable: true, why: 'no knowledge: payload' };
25
- const rest = lines[at].replace(/^knowledge\s*:/, '').replace(/\s+#.*$/, '').trim();
26
- if (rest === 'none' || rest === "'none'" || rest === '"none"') return { value: null, readable: true, why: 'knowledge: none' };
27
- if (rest.startsWith('{')) {
28
- if (!rest.endsWith('}') || /[{}]/.test(rest.slice(1, -1))) return { value: null, readable: false, why: 'knowledge: flow mapping is not a single flat line' };
29
- const pairs = rest.slice(1, -1).split(',').map((p) => p.trim()).filter(Boolean);
30
- let value = null;
31
- for (const pair of pairs) {
32
- const m = /^(['"]?)([A-Za-z0-9._-]+)\1\s*:\s*(.*)$/.exec(pair);
33
- if (!m) return { value: null, readable: false, why: 'knowledge: flow mapping entry is not key: value' };
34
- if (m[2] === 'harvest') value = scalar(m[3]);
35
- }
36
- return shaped(value);
37
- }
38
- if (rest !== '') return { value: null, readable: false, why: 'knowledge: is neither none, a flow mapping nor a block mapping' };
39
- let value = null, indent = null;
40
- for (const line of lines.slice(at + 1)) {
41
- if (/^\s*(#.*)?$/.test(line)) continue;
42
- const lead = /^(\s*)/.exec(line)[1].length;
43
- if (lead === 0) break;
44
- if (/\t/.test(line.slice(0, lead))) return { value: null, readable: false, why: 'tab indentation under knowledge:' };
45
- if (indent === null) indent = lead;
46
- if (lead < indent) return { value: null, readable: false, why: 'inconsistent indentation under knowledge:' };
47
- if (lead > indent) continue; // a nested value of another key
48
- const m = /^\s*(['"]?)([A-Za-z0-9._-]+)\1\s*:(.*)$/.exec(line);
49
- if (!m) return { value: null, readable: false, why: 'a knowledge: entry is not key: value' };
50
- if (m[2] === 'harvest') value = scalar(m[3]);
51
- }
52
- return shaped(value);
53
- }
54
- function shaped(value) {
55
- if (value === null) return { value: null, readable: true, why: 'the soul does not set harvest' };
56
- if (!VALUES.includes(value)) return { value: null, readable: false, why: `the soul's harvest is ${JSON.stringify(value)}, not on or off` };
57
- return { value, readable: true, why: `soul.yaml knowledge.harvest: ${value}` };
58
- }
59
- /** Effective = on only if the deployment says on AND the soul does not say off.
60
- * A soul `on` is ignored and reported: a soul cannot switch a host on. It also
61
- * hides the host's value (the kernel's merged `harvest` is then the soul's),
62
- * so with a soul `on` the switch stays off until the soul drops the line. */
63
- /** OATS_SETTINGS_ORIGINS (kernel 0.29.0): JSON pointer → { kind, at } of the LAST
64
- * layer that set each leaf. {} when absent or unreadable. */
65
- export function parseOrigins(text = process.env.OATS_SETTINGS_ORIGINS) {
66
- try { const o = JSON.parse(text || '{}'); return o && typeof o === 'object' && !Array.isArray(o) ? o : {}; } catch { return {}; }
67
- }
68
- /** okf 4.0.1: origins name the last layer only, and the host's settings merge
69
- * AFTER the soul's slot, so a host `harvest: on` hides a soul's `off` there.
70
- * soul.yaml therefore stays the authority for the absolute opt-out; origins
71
- * are an extra signal: a merged value whose origin is the soul is never the
72
- * deployment switching harvest on (a soul `on` is ignored, a soul `off` is off). */
73
- export function harvestSwitch({ settings = {}, soulDir = process.env.OATS_SOUL, origins = parseOrigins() } = {}) {
74
- const origin = origins?.['/harvest'] && typeof origins['/harvest'] === 'object' ? origins['/harvest'] : null;
75
- const fromSoul = origin?.kind === 'soul';
76
- const deployment = !fromSoul && VALUES.includes(settings.harvest) ? settings.harvest : 'off';
77
- const soul = soulHarvest(soulDir);
78
- if (fromSoul && VALUES.includes(settings.harvest) && soul.value === null) Object.assign(soul, { value: settings.harvest, readable: true, why: `OATS_SETTINGS_ORIGINS: harvest: ${settings.harvest} came from the soul (${String(origin.at || 'soul.yaml')})` });
79
- const rows = [
80
- { layer: 'deployment', value: deployment, ...(origin ? { origin: origin.kind } : {}), why: fromSoul ? 'the merged harvest value came from the soul, so the deployment does not switch harvest on' : settings.harvest === undefined ? 'harvest is not set (default off)' : `settings.oats.okf.harvest: ${settings.harvest}` },
81
- { layer: 'soul', value: soul.value, readable: soul.readable, why: soul.why },
82
- ];
83
- const warnings = [];
84
- if (soul.value === 'on') warnings.push('the soul says harvest: on, which is ignored: only the deployment can switch harvest on; a soul may only opt out with harvest: off');
85
- let effective = 'off', reason;
86
- if (soul.value === 'on') reason = "the soul's harvest: on is ignored and hides the deployment's own value; remove it from soul.yaml (a soul may only opt out)";
87
- else if (deployment !== 'on') reason = 'the deployment does not switch harvest on (oats-local.yaml settings.oats.okf.harvest)';
88
- else if (!soul.readable) reason = `the soul's opt-out could not be read (${soul.why}); unreadable counts as off`;
89
- else if (soul.value === 'off') reason = 'the soul opts out (knowledge: { harvest: off }), which the deployment cannot override';
90
- else { effective = 'on'; reason = 'the deployment switches harvest on and the soul does not opt out'; }
91
- return { effective, reason, rows, warnings };
92
- }
93
- /** The home's record that it was spawned with harvest off (no source registered). */
94
- export const instanceRecordPath = (home) => join(home, '.okf-instance.json');
@@ -1,138 +0,0 @@
1
- import { fs, join, dirname, safePath, fail, oats } from './io.mjs';
2
- import { markerPath, loadStatus } from './sources.mjs';
3
- import { qualifiedSoulIdentity } from './source-contract.mjs';
4
-
5
- // Keep the v1 labeled-document contract, including its explicit per-document
6
- // preview cap. The JSON envelope itself must drain in full through stdout.
7
- const DOCUMENT_BYTES = 256 * 1024;
8
- const missing = e => e.code === 'ENOENT' || e.code === 'ENOTDIR';
9
- function regular(file, limit) {
10
- safePath(file);
11
- let stat;try { stat=fs.lstatSync(file); } catch(e) { if(missing(e)) return null;throw e; }
12
- if(!stat.isFile() || stat.nlink!==1) fail('E_PATH',`inspection requires a single-link regular file: ${file}`);
13
- const fd=fs.openSync(file,fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
14
- try {
15
- const opened=fs.fstatSync(fd);
16
- if(!opened.isFile() || opened.nlink!==1 || opened.dev!==stat.dev || opened.ino!==stat.ino) fail('E_PATH',`inspection file changed: ${file}`);
17
- if(limit===undefined) return {text:fs.readFileSync(fd,'utf8')};
18
- const bytes=Buffer.alloc(Math.min(opened.size,limit));let length=0;
19
- while(length<bytes.length) { const n=fs.readSync(fd,bytes,length,bytes.length-length,null);if(!n) break;length+=n; }
20
- const truncated=opened.size>limit;
21
- let text=bytes.subarray(0,length).toString('utf8');
22
- if(truncated && text.endsWith('\uFFFD')) text=text.slice(0,-1);
23
- return {text,...(truncated?{truncated:true,bytes:opened.size}:{})};
24
- } finally {fs.closeSync(fd);}
25
- }
26
- function identityJSON(text,label) {
27
- // JSON.parse diagnostics may include raw input from a replacement home.
28
- // Report the failed verification, never those untrusted bytes.
29
- try {return JSON.parse(text);} catch {fail('E_SOURCE',`invalid ${label} JSON in source home`);}
30
- }
31
- function liveHome(source,status) {
32
- if(status.retired) return {available:false,reason:'retired'};
33
- try {
34
- safePath(source.home);
35
- const stat=fs.lstatSync(source.home);
36
- if(!stat.isDirectory()) return {available:false,reason:'identity-mismatch'};
37
- const marker=regular(markerPath(source.home));
38
- if(!marker) return {available:false,reason:'missing-marker'};
39
- const m=identityJSON(marker.text,'source marker');
40
- // Never load a replacement descriptor or trust the invoking home/env when
41
- // inspecting an explicitly selected durable source.
42
- if(m?.version!==1 || m.id!==source.id || m.source!==source.file) return {available:false,reason:'identity-mismatch'};
43
- const metadata=regular(join(source.home,'instance.json'));
44
- if(metadata) {
45
- const meta=identityJSON(metadata.text,'instance metadata');
46
- if(meta?.instance!==source.instance || meta.agent!==source.agent) return {available:false,reason:'identity-mismatch'};
47
- }
48
- return {available:true,reason:'live',dev:stat.dev,ino:stat.ino};
49
- } catch(e) {
50
- if(missing(e)) return {available:false,reason:'missing-home'};
51
- // Unsafe/unreadable identity is not proof of a live source. Durable state
52
- // remains inspectable, but no document from that home may be returned.
53
- return {available:false,reason:'unverified-home',error:{code:e.code || 'E_SOURCE',message:e.message}};
54
- }
55
- }
56
- /** The home's working memory (STATE.md, log.md, notes/*.md), each capped. */
57
- function homeDocuments(home) {
58
- const documents=[];
59
- const doc=(label,file)=>{
60
- const content=regular(file,DOCUMENT_BYTES);
61
- if(content) documents.push({label,kind:'markdown',path:file,...content});
62
- };
63
- let error;
64
- try {
65
- doc('Working state (STATE.md)',join(home,'STATE.md'));
66
- doc('Log (log.md)',join(home,'log.md'));
67
- const notes=safePath(join(home,'notes'));
68
- function walk(dir,prefix='') {
69
- let entries;try {entries=fs.readdirSync(dir,{withFileTypes:true});} catch(e) {if(e.code==='ENOENT') return;throw e;}
70
- for(const entry of entries.sort((a,b)=>a.name<b.name?-1:a.name>b.name?1:0)) {
71
- const name=prefix+entry.name,file=join(dir,entry.name);
72
- // No symlink traversal, including directories or non-Markdown aliases.
73
- safePath(file);
74
- if(entry.isDirectory()) walk(file,name+'/');
75
- else if(entry.name.endsWith('.md')) doc(`Pending note: ${name}`,file);
76
- }
77
- }
78
- walk(notes);
79
- } catch(e) {error=e;}
80
- return {documents,error};
81
- }
82
- export function workingDocuments(source,status=loadStatus(source)) {
83
- const before=liveHome(source,status),observedAt=new Date().toISOString();
84
- const unavailable=state=>({liveMemory:{available:false,reason:state.reason,observedAt,...(state.error?{error:state.error}:{})},documents:[]});
85
- if(!before.available) return unavailable(before);
86
- const {documents,error}=homeDocuments(source.home);
87
- const after=liveHome(source,loadStatus(source));
88
- if(!after.available) return unavailable(after);
89
- if(before.dev!==after.dev || before.ino!==after.ino) return unavailable({reason:'home-changed'});
90
- if(error) fail(error.code==='E_PATH'?'E_PATH':'E_INSPECT_FAILED',error.message);
91
- return {liveMemory:{available:true,reason:'live',observedAt},documents};
92
- }
93
- export function capturedAuthority(source) {
94
- const fields=['registration','providerBinding','sourceIdentity','executionBinding','responsibleHuman'],present=fields.filter(key=>Object.hasOwn(source,key));
95
- const base={schemaVersion:1};
96
- if(!present.length) return {...base,registration:'legacy',capture:'unknown',migrationRequired:true,responsibleHuman:{status:'unknown'}};
97
- const binding=source.executionBinding,identity=source.sourceIdentity,complete=source.registration?.schemaVersion===1 && source.registration.kind==='captured'
98
- && source.providerBinding && typeof source.providerBinding==='object' && identity && typeof identity==='object' && !Array.isArray(identity)
99
- && binding?.schemaVersion===1 && typeof binding.deployment==='string' && binding.resolution?.schemaVersion===1 && typeof binding.resolution.id==='string'
100
- && Object.hasOwn(source,'responsibleHuman');
101
- const invalid={...base,registration:'invalid',capture:'invalid',migrationRequired:true,responsibleHuman:{status:'unknown'}};
102
- if(!complete) return invalid;
103
- try {if(Buffer.byteLength(JSON.stringify({identity,binding}))>64*1024) return invalid;qualifiedSoulIdentity(identity);} catch {return invalid;}
104
- return {...base,registration:'captured',capture:'recorded',migrationRequired:false,sourceIdentity:JSON.parse(JSON.stringify(identity)),executionBinding:JSON.parse(JSON.stringify(binding)),
105
- responsibleHuman:{status:source.responsibleHuman===null?'disabled':'specified'}};
106
- }
107
- /** A ./knowledge/ snapshot written by okf 2.x. okf 3.0.0 never reads it
108
- * (knowledge is consulted remotely) and never deletes it. */
109
- export function legacyLocalView(source,status=loadStatus(source)) {
110
- if(!liveHome(source,status).available) return null;
111
- const path=join(safePath(source.home),'knowledge');let stat;
112
- try {stat=fs.lstatSync(path);} catch(e) {if(missing(e)) return null;throw e;} // never follow it
113
- return {status:'legacy-local-view',path,ignored:true,...(stat.isSymbolicLink()?{symlink:true}:{}),note:'okf 3.0.0 ignores this okf 2.x snapshot and reads knowledge remotely; it is safe to delete by hand'};
114
- }
115
- export function inspect(source) {
116
- const status=loadStatus(source),working=workingDocuments(source,status),legacy=legacyLocalView(source,status);
117
- let health;try {health=oats(['schedule','list','--dir',source.context,'--json'],source.context).scheduler;} catch(e) {health={active:false,error:e.message};}
118
- const documents=[...working.documents,{label:'Durable processing receipts',kind:'text',path:join(dirname(source.file),'status.json'),text:JSON.stringify(status,null,2)}];
119
- return {
120
- summary:`OKF ${source.id}: ${status.captured.inputs.length-status.processed.length} unprocessed inputs; ${status.retired?'source retired':'source not retired'}; ${working.liveMemory.available?`${working.documents.length} working-memory documents`:`live memory unavailable (${working.liveMemory.reason})`}${legacy?'; legacy-local-view ./knowledge/ (ignored, safe to delete)':''}`,
121
- source:source.file,owns:source.decl.owns,reads:source.decl.reads,bases:source.bindings.bases,authority:capturedAuthority(source),
122
- acceptedView:source.acceptedView,legacyLocalView:legacy,status,scheduler:health,liveMemory:working.liveMemory,documents
123
- };
124
- }
125
- /** okf 4.0.3: inspect a harvest-off home (consultSource): its declaration, the
126
- * bases it reads, and its own working memory. There is no source, custody or
127
- * schedule to report; `oats okf bases` shows the accepted state it reads. */
128
- export function inspectConsultOnly(source) {
129
- const {documents,error}=homeDocuments(source.home),observedAt=new Date().toISOString();
130
- if(error) fail(error.code==='E_PATH'?'E_PATH':'E_INSPECT_FAILED',error.message);
131
- const path=join(source.home,'knowledge');let legacy=null;
132
- try {const stat=fs.lstatSync(path);legacy={status:'legacy-local-view',path,ignored:true,...(stat.isSymbolicLink()?{symlink:true}:{}),note:'okf 3.0.0 ignores this okf 2.x snapshot and reads knowledge remotely; it is safe to delete by hand'};} catch(e) {if(!missing(e)) throw e;}
133
- return {
134
- summary:`OKF harvest off for this instance (${source.harvest.reason}): no source is registered and nothing is captured; ${documents.length} working-memory documents${legacy?'; legacy-local-view ./knowledge/ (ignored, safe to delete)':''}`,
135
- source:null,harvest:source.harvest,owns:source.decl.owns,reads:source.decl.reads,bases:source.bindings.bases,
136
- acceptedView:null,legacyLocalView:legacy,status:null,scheduler:null,liveMemory:{available:true,reason:'live',observedAt},documents
137
- };
138
- }
@@ -1,111 +0,0 @@
1
- // Execution-only consumer of the kernel-owned generic snapshot. This module
2
- // never reads a kernel index, mints identity, discovers source/config, or grants
3
- // native authority from structural validation alone. Check remains stdin-only.
4
- import fs from 'node:fs';
5
- import { dirname, isAbsolute, resolve } from 'node:path';
6
- import { BINDING_WIRE_LIMITS, parseBindingJson, sourceRuntimeFromKnowledgeBinding } from './binding-wire.mjs';
7
- import { INVOCATION_LIMITS, validateInvocationShape, sameInvocationJson } from './invocation-shape.mjs';
8
- import { validateCapturedReceipt } from './sources.mjs';
9
-
10
- const CAPABILITY='oats.okf';
11
- export const INVOCATION_CONTEXT_LIMITS=INVOCATION_LIMITS;
12
- const SOURCE_RECEIPT_LIMITS=Object.freeze({bytes:256*1024,depth:32,entries:16384});
13
- const invalid=()=>{throw Object.assign(new Error('invalid captured OKF invocation context'),{code:'E_INVOCATION'});};
14
- function absolute(value) {return typeof value==='string' && !value.includes('\0') && isAbsolute(value) && resolve(value)===value;}
15
- function noLinks(file) {
16
- for(let path=file;;path=dirname(path)) {
17
- const stat=fs.lstatSync(path);
18
- if(stat.isSymbolicLink() || (path!==file && !stat.isDirectory())) invalid();
19
- if(dirname(path)===path) return;
20
- }
21
- }
22
- function readPrivateJson(file,limits) {
23
- if(!absolute(file)) invalid();let fd;
24
- try {
25
- noLinks(file);
26
- const uid=typeof process.getuid==='function'?process.getuid():null;
27
- const privateFile=stat=>stat.isFile() && stat.nlink===1n && stat.size<=BigInt(limits.bytes) && (stat.mode&0o7777n)===0o600n && (uid===null || stat.uid===BigInt(uid));
28
- const before=fs.lstatSync(file,{bigint:true});
29
- const same=stat=>privateFile(stat) && ['dev','ino','size','mtimeNs','ctimeNs'].every(key=>stat[key]===before[key]);
30
- if(!privateFile(before) || typeof fs.constants.O_NOFOLLOW!=='number' || typeof fs.constants.O_NONBLOCK!=='number') invalid();
31
- fd=fs.openSync(file,fs.constants.O_RDONLY|fs.constants.O_NOFOLLOW|fs.constants.O_NONBLOCK);
32
- noLinks(file);
33
- if(!same(fs.fstatSync(fd,{bigint:true})) || !same(fs.lstatSync(file,{bigint:true}))) invalid();
34
- const bytes=Buffer.alloc(Number(before.size));
35
- for(let offset=0;offset<bytes.length;) {
36
- const count=fs.readSync(fd,bytes,offset,bytes.length-offset,offset);
37
- if(!count) invalid();offset+=count;
38
- }
39
- noLinks(file);
40
- if(!same(fs.fstatSync(fd,{bigint:true})) || !same(fs.lstatSync(file,{bigint:true}))) invalid();
41
- return parseBindingJson(bytes,limits);
42
- } catch {invalid();}
43
- finally {if(fd!==undefined) fs.closeSync(fd);}
44
- }
45
- export function validateOkfInvocationContext(value,binding) {
46
- try {
47
- sourceRuntimeFromKnowledgeBinding(binding);
48
- return validateInvocationShape(value,{capability:CAPABILITY});
49
- } catch {invalid();}
50
- }
51
- export function loadCapturedOkfInvocation(env=process.env) {
52
- const hasContext=Object.hasOwn(env,'OATS_INVOCATION_CONTEXT_FILE'),hasBinding=Object.hasOwn(env,'OATS_BINDING_FILE');
53
- if(!hasContext && !hasBinding && !Object.hasOwn(env,'OATS_SOURCE_RECEIPT_FILE')) return {kind:'legacy'};
54
- if(!hasContext || !hasBinding || env.OATS_INVOCATION_CONTEXT_FILE===env.OATS_BINDING_FILE) invalid();
55
- if(Object.hasOwn(env,'OATS_SOURCE_RECEIPT_FILE') && [env.OATS_INVOCATION_CONTEXT_FILE,env.OATS_BINDING_FILE].includes(env.OATS_SOURCE_RECEIPT_FILE)) invalid();
56
- const binding=readPrivateJson(env.OATS_BINDING_FILE,BINDING_WIRE_LIMITS);
57
- const context=validateOkfInvocationContext(readPrivateJson(env.OATS_INVOCATION_CONTEXT_FILE,INVOCATION_CONTEXT_LIMITS),binding);
58
- return {kind:'captured',file:env.OATS_INVOCATION_CONTEXT_FILE,bindingFile:env.OATS_BINDING_FILE,binding,context};
59
- }
60
- export function loadOkfSourceReceiptInput(loaded,env=process.env) {
61
- if(!Object.hasOwn(env,'OATS_SOURCE_RECEIPT_FILE')) return {mode:'absent'};
62
- if([loaded.file,loaded.bindingFile].includes(env.OATS_SOURCE_RECEIPT_FILE)) invalid();
63
- const receipt=readPrivateJson(env.OATS_SOURCE_RECEIPT_FILE,SOURCE_RECEIPT_LIMITS);
64
- assertOkfSourceReceiptContext(receipt,loaded.context,loaded.binding);
65
- return {mode:'captured',receipt};
66
- }
67
- // Explicit compatibility for a source already validated/loaded from durable
68
- // storage. This cannot construct a source or supply generic admission authority.
69
- export function assertOkfRegisteredSourceReplay(source,binding) {
70
- try {sourceRuntimeFromKnowledgeBinding(binding);} catch {invalid();}
71
- if(!source?.file || source.registration?.schemaVersion!==1 || source.registration.kind!=='captured' || !sameInvocationJson(source.providerBinding,binding)) invalid();
72
- }
73
- export function assertOkfInvocationAction(context,event,manifest) {
74
- try {validateInvocationShape(context,{capability:CAPABILITY});} catch {invalid();}
75
- const action=context.action;
76
- if(manifest?.capability!==CAPABILITY) invalid();
77
- if(action.kind==='hook') {
78
- if(action.capability!==CAPABILITY || action.name!==event || !Object.hasOwn(manifest.hooks||{},event)) invalid();
79
- } else if(action.kind==='command') {
80
- if(action.name!==event || !Object.hasOwn(manifest.commands||{},event) || (action.capability!==CAPABILITY && action.namespace!==manifest.command)) invalid();
81
- } else if(action.kind==='operation') {
82
- if(action.slot!==manifest.layer || !Object.hasOwn(manifest.operations||{},action.name) || manifest.operations[action.name].command!==event) invalid();
83
- } else invalid();
84
- }
85
- export function requireOkfAdmittedAction(context) {
86
- try {validateInvocationShape(context,{capability:CAPABILITY});} catch {invalid();}
87
- if(!context.instance || !context.intent) throw Object.assign(new Error('captured action requires an admitted instance intent'),{code:'E_ADMISSION'});
88
- // The shared shape enforces incarnation equality; the kernel, not this
89
- // provider, proves that the supplied attempt is currently admitted/running.
90
- }
91
- export function assertOkfSourceReceiptContext(receipt,context,binding) {
92
- validateOkfInvocationContext(context,binding);
93
- try {validateCapturedReceipt(context.instance?.home,receipt);} catch {invalid();}
94
- const instance=context.instance;
95
- if(!instance || context.action.kind!=='hook' || !['spawn','retire'].includes(context.action.name)
96
- || receipt.kind!==context.subject.kind || receipt.home!==instance.home || receipt.work!==instance.work
97
- || receipt.agent!==instance.agent || receipt.instance!==instance.name || receipt.context!==context.executionBinding.deployment
98
- || !sameInvocationJson(receipt.executionBinding,context.executionBinding) || !sameInvocationJson(receipt.responsibleHuman,context.responsibleHuman)
99
- || !sameInvocationJson(receipt.binding,binding)) invalid();
100
- const identity=context.subject.kind==='persistent'?context.subject.soul.identity:null;
101
- if(!sameInvocationJson(receipt.sourceIdentity,identity)) invalid();
102
- }
103
- export function assertOkfSourceContext(source,context,binding) {
104
- validateOkfInvocationContext(context,binding);
105
- if(!source) invalid();
106
- if(context.subject.kind!=='persistent' || source.agent!==context.subject.soul.alias
107
- || !sameInvocationJson(source.sourceIdentity,context.subject.soul.identity) || !sameInvocationJson(source.providerBinding,binding)
108
- || !sameInvocationJson(source.executionBinding,context.executionBinding) || source.context!==context.executionBinding.deployment
109
- || !sameInvocationJson(source.responsibleHuman,context.responsibleHuman)) invalid();
110
- if(context.instance && (source.home!==context.instance.home || source.work!==context.instance.work || source.instance!==context.instance.name || source.agent!==context.instance.agent)) invalid();
111
- }
@@ -1,135 +0,0 @@
1
- // Provider-side consumption checks for the kernel-owned CapturedInvocationContext1.
2
- // Decoded JSON only: no source/config parsing, artifact lookup or authorization.
3
- // The kernel must verify retained authority before invoking provider code.
4
- import { isAbsolute, join, resolve } from 'node:path';
5
- export const INVOCATION_LIMITS=Object.freeze({bytes:512*1024,depth:32,entries:16384});
6
- export const PRIOR_RECEIPT_LIMITS=Object.freeze({bytes:128*1024,depth:24,entries:8192});
7
- const fail=()=>{throw Object.assign(new Error('invalid captured invocation'),{code:'invalid-binding'});};
8
- const obj=value=>value!==null && typeof value==='object' && !Array.isArray(value);
9
- const canonical=value=>value===null || typeof value!=='object'?JSON.stringify(value):Array.isArray(value)?`[${value.map(canonical).join(',')}]`:`{${Object.keys(value).sort().map(key=>`${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`;
10
- export const sameInvocationJson=(a,b)=>canonical(a)===canonical(b);
11
- const same=sameInvocationJson;
12
- function keys(value,allowed,required=allowed) {if(!obj(value)) fail();for(const key of Object.keys(value)) if(!allowed.includes(key)) fail();for(const key of required) if(!Object.hasOwn(value,key)) fail();}
13
- function text(value,empty=false) {if(typeof value!=='string' || (!empty && !value.length) || value.includes('\0')) fail();}
14
- function list(value) {if(!Array.isArray(value)) fail();return value;}
15
- function absolute(value) {text(value);if(!isAbsolute(value) || resolve(value)!==value) fail();}
16
- function path(value,root=false) {text(value);if(root && value==='.') return;if(value.startsWith('/') || value.includes('\\') || value.split('/').some(part=>!part || part==='.' || part==='..')) fail();}
17
- function hash(value) {if(typeof value!=='string' || !/^sha256-[a-f0-9]{64}$/.test(value)) fail();}
18
- function integrity(value,format) {keys(value,['format','value']);if(value.format!==format) fail();hash(value.value);}
19
- function capability(value) {text(value);if(!/^(?:@?[a-z0-9][a-z0-9._-]*[./])[a-z0-9][a-z0-9._/-]*$/.test(value)) fail();}
20
- function repository(value) {
21
- if(value?.kind==='canonical-remote') {keys(value,['kind','remote']);text(value.remote);}
22
- else if(value?.kind==='provider-repository') {keys(value,['kind','provider','host','id']);text(value.provider);text(value.host);text(value.id);}
23
- else fail();
24
- }
25
- function soulIdentity(value) {
26
- if(value?.kind==='git-soul') {keys(value,['kind','repository','exportPath']);repository(value.repository);path(value.exportPath);}
27
- else if(value?.kind==='local-soul') {keys(value,['kind','source','exportPath']);text(value.source);if(!value.source.startsWith('path:')) fail();path(value.exportPath,true);}
28
- else fail();
29
- }
30
- function artifact(value) {
31
- if(value?.kind==='soul') {keys(value,['kind','identity','integrity']);soulIdentity(value.identity);}
32
- else if(value?.kind==='capability') {keys(value,['kind','capability','integrity']);capability(value.capability);}
33
- else if(value?.kind==='resource') keys(value,['kind','integrity']);
34
- else fail();
35
- integrity(value.integrity,'oats.tree-exec.v1');
36
- }
37
- function resolution(value) {keys(value,['schemaVersion','id']);if(value.schemaVersion!==1) fail();hash(value.id);}
38
- function incarnation(value) {if(typeof value!=='string' || !/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/.test(value)) fail();}
39
- function intent(value,instance) {
40
- if(value===null) return;
41
- keys(value,['schemaVersion','executionId','incarnationId','attempt']);incarnation(value.incarnationId);
42
- if(value.schemaVersion!==1 || typeof value.executionId!=='string' || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value.executionId) || !Number.isSafeInteger(value.attempt) || value.attempt<1) fail();
43
- if(instance===null || value.incarnationId!==instance.incarnationId) fail();
44
- }
45
- function origin(value) {
46
- keys(value,['kind','document','pointer','span'],['kind','document','pointer']);
47
- if(!['soul-requirement','soul-default','workspace-default','import-adoption','operator','package-dependency','work-target','migration-evidence','provider-binding','workspace-admission','member-backlink','source-export','manifest-default'].includes(value.kind)) fail();
48
- text(value.pointer,true);if(!/^(?:\/(?:[^~]|~[01])*)*$/.test(value.pointer)) fail();
49
- const doc=value.document;
50
- if(doc?.kind==='source') {keys(doc,['kind','source','revision','path','integrity']);text(doc.source);text(doc.revision);path(doc.path);integrity(doc.integrity,'oats.bytes.v1');}
51
- else if(doc?.kind==='deployment') {keys(doc,['kind','path','integrity']);path(doc.path);integrity(doc.integrity,'oats.bytes.v1');}
52
- else if(doc?.kind==='operator') {keys(doc,['kind','id']);text(doc.id);}
53
- else if(doc?.kind==='record') {keys(doc,['kind','ref']);resolution(doc.ref);}
54
- else if(doc?.kind==='artifact') {keys(doc,['kind','owner','path','integrity']);artifact(doc.owner);path(doc.path);integrity(doc.integrity,'oats.bytes.v1');}
55
- else fail();
56
- if(Object.hasOwn(value,'span')) {keys(value.span,['start','end']);if(!Number.isSafeInteger(value.span.start) || !Number.isSafeInteger(value.span.end) || value.span.start<0 || value.span.end<value.span.start) fail();}
57
- }
58
- function origins(value) {list(value).forEach(origin);}
59
- function subject(value) {
60
- if(value?.kind==='helper') {
61
- keys(value,['kind','provider','definition','name']);artifact(value.provider);
62
- keys(value.definition,['owner','path','kind']);artifact(value.definition.owner);path(value.definition.path,true);
63
- if(value.provider.kind!=='capability' || !same(value.provider,value.definition.owner) || !['file','directory','manifest','skill','runtime-package'].includes(value.definition.kind)) fail();
64
- if(typeof value.name!=='string' || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value.name)) fail();return value.name;
65
- }
66
- if(value?.kind!=='persistent') fail();keys(value,['kind','soul']);const s=value.soul;
67
- keys(s,['identity','revision','alias','sourceArtifact','definition','projection']);soulIdentity(s.identity);artifact(s.sourceArtifact);
68
- if(s.sourceArtifact.kind!=='soul' || !same(s.identity,s.sourceArtifact.identity)) fail();
69
- if(typeof s.alias!=='string' || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(s.alias)) fail();path(s.definition);
70
- keys(s.projection,['roots']);list(s.projection.roots).forEach(value=>path(value,true));
71
- if(!s.projection.roots.length || !s.projection.roots.some(root=>root==='.' || s.definition===root || s.definition.startsWith(`${root}/`))) fail();
72
- for(let i=1;i<s.projection.roots.length;i++) if(Buffer.compare(Buffer.from(s.projection.roots[i-1]),Buffer.from(s.projection.roots[i]))>=0) fail();
73
- if(s.identity.exportPath!=='.' && !s.definition.startsWith(`${s.identity.exportPath}/`)) fail();
74
- const r=s.revision;
75
- if(r?.kind==='local') {
76
- keys(r,['kind','source','integrity','provenance','repository'],['kind','source','integrity','provenance']);text(r.source);if(!r.source.startsWith('path:')) fail();integrity(r.integrity,'oats.tree-exec.v1');
77
- if(!same(r.integrity,s.sourceArtifact.integrity)) fail();
78
- if(s.identity.kind==='local-soul') {if(r.source!==s.identity.source || Object.hasOwn(r,'repository')) fail();}
79
- else {repository(r.repository);if(!same(r.repository,s.identity.repository)) fail();}
80
- } else {
81
- keys(r,['identity','remote','selector','commit','provenance']);repository(r.identity);text(r.remote);text(r.selector);
82
- if(s.identity.kind!=='git-soul' || !same(r.identity,s.identity.repository) || !/^[a-f0-9]{40}$/.test(r.commit)) fail();
83
- if(r.identity.kind==='canonical-remote' && r.identity.remote!==r.remote) fail();
84
- }
85
- origins(r.provenance);if(!r.provenance.length) fail();return s.alias;
86
- }
87
- function context(value) {
88
- if(value?.kind==='workspace') {keys(value,['kind','identity','observation']);keys(value.identity,['repository','path']);repository(value.identity.repository);if(value.identity.path!=='oats-workspace.yaml') fail();origin(value.observation);return {kind:'workspace',identity:value.identity};}
89
- if(value?.kind==='standalone') {keys(value,['kind','key']);if(value.key!==null) text(value.key);return value;}
90
- fail();
91
- }
92
- function human(value) {if(value===null) return;keys(value,['provider','id']);text(value.provider);text(value.id);}
93
- function messaging(value,selectedContext,responsibleHuman) {
94
- if(value?.enabled===false) {keys(value,['schemaVersion','enabled']);if(responsibleHuman!==null) fail();}
95
- else {
96
- keys(value,['schemaVersion','enabled','privateKey','wider','provenance']);if(value.enabled!==true) fail();
97
- keys(value.privateKey,['provider','human','context']);capability(value.privateKey.provider);human(value.privateKey.human);
98
- if(value.privateKey.human===null || !same(value.privateKey.human,responsibleHuman) || !same(value.privateKey.context,selectedContext) || (selectedContext.kind==='standalone' && selectedContext.key===null)) fail();
99
- list(value.wider).forEach(row=>{keys(row,['provider','id']);text(row.id);if(row.provider!==value.privateKey.provider) fail();});
100
- for(let i=1;i<value.wider.length;i++) if(Buffer.compare(Buffer.from(canonical(value.wider[i-1])),Buffer.from(canonical(value.wider[i])))>=0) fail();
101
- origins(value.provenance);
102
- }
103
- if(value.schemaVersion!==1) fail();
104
- }
105
- function action(value) {
106
- if(!obj(value)) fail();
107
- if(value.kind==='command') {keys(value,['kind','capability','namespace','name'],['kind','name']);if(Object.hasOwn(value,'capability')===Object.hasOwn(value,'namespace')) fail();if(value.capability!==undefined) capability(value.capability);else text(value.namespace);text(value.name);}
108
- else if(value.kind==='hook') {keys(value,['kind','capability','name']);capability(value.capability);text(value.name);}
109
- else if(value.kind==='operation') {keys(value,['kind','slot','name']);if(!['knowledge','messaging','tasks'].includes(value.slot)) fail();text(value.name);}
110
- else if(['inspect','compose'].includes(value.kind)) keys(value,['kind']);
111
- else fail();
112
- }
113
- export function assertInvocationBounds(value,limits=INVOCATION_LIMITS) {
114
- let entries=0;const active=new Set();
115
- const visit=(v,depth)=>{
116
- if(v===null || typeof v==='string' || typeof v==='boolean') return;
117
- if(typeof v==='number') {if(!Number.isFinite(v)) fail();return;}
118
- if(typeof v!=='object' || depth>limits.depth || active.has(v)) fail();
119
- if(!Array.isArray(v) && Object.getPrototypeOf(v)!==Object.prototype && Object.getPrototypeOf(v)!==null) fail();
120
- active.add(v);for(const child of Object.values(v)) {if(++entries>limits.entries) fail();visit(child,depth+1);}active.delete(v);
121
- };
122
- visit(value,1);if(Buffer.byteLength(JSON.stringify(value),'utf8')>limits.bytes) fail();
123
- }
124
- export function validateInvocationShape(value,expected={}) {
125
- assertInvocationBounds(value);keys(value,['schemaVersion','executionBinding','subject','instance','intent','context','responsibleHuman','messagingChoice','capability','action','priorReceipt']);
126
- if(value.schemaVersion!==1) fail();keys(value.executionBinding,['schemaVersion','deployment','resolution']);if(value.executionBinding.schemaVersion!==1) fail();absolute(value.executionBinding.deployment);resolution(value.executionBinding.resolution);
127
- const alias=subject(value.subject);
128
- if(value.instance!==null) {keys(value.instance,['home','work','name','agent','incarnationId']);incarnation(value.instance.incarnationId);absolute(value.instance.home);absolute(value.instance.work);text(value.instance.name);text(value.instance.agent);if(value.instance.agent!==alias || value.instance.work!==join(value.instance.home,'work')) fail();}
129
- intent(value.intent,value.instance);
130
- const selectedContext=context(value.context);human(value.responsibleHuman);messaging(value.messagingChoice,selectedContext,value.responsibleHuman);capability(value.capability);action(value.action);assertInvocationBounds(value.priorReceipt,PRIOR_RECEIPT_LIMITS);
131
- if(value.action.capability!==undefined && value.action.capability!==value.capability) fail();
132
- if(value.action.kind==='hook' && value.instance===null) fail();
133
- for(const field of ['capability','context','action']) if(Object.hasOwn(expected,field) && !same(value[field],expected[field])) fail();
134
- return value;
135
- }
@@ -1,118 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import { dirname, resolve, relative, isAbsolute, join, sep, basename } from 'node:path';
3
- import { createHash, randomUUID } from 'node:crypto';
4
- import { hostname } from 'node:os';
5
- import { spawnSync } from 'node:child_process';
6
- export { fs, join, resolve, dirname };
7
- export const fail = (code, message) => { throw Object.assign(new Error(message), { code }); };
8
- export const hash = value => createHash('sha256').update(typeof value === 'string' || Buffer.isBuffer(value) ? value : JSON.stringify(value)).digest('hex');
9
- export const readJSON = path => JSON.parse(fs.readFileSync(path, 'utf8'));
10
- export const within = (root, path) => { const r = relative(root, path); return r === '' || (!r.startsWith('..' + sep) && r !== '..' && !isAbsolute(r)); };
11
- // okf 4.0.1 #2: a URL's userinfo is a credential (https://user:token@host).
12
- // Strip it from anything shown to agents or users; an SSH user alone
13
- // (ssh://git@host) is not a secret and stays.
14
- const USERINFO = /\b([a-z][a-z0-9+.-]*:\/\/)([^\s/@'"]+)@/gi;
15
- export const redactUrls = text => String(text).replace(USERINFO, (m, scheme, info) => /^ssh:/i.test(scheme) && !info.includes(':') ? m : scheme);
16
- export const displayRepo = repository => typeof repository === 'string' ? redactUrls(repository) : repository;
17
- /** Whether a repository locator embeds a credential (any userinfo except a bare SSH user). */
18
- export const embedsCredential = repository => { USERINFO.lastIndex = 0; return typeof repository === 'string' && redactUrls(repository) !== repository; };
19
- export const overlaps = (a, b) => within(a, b) || within(b, a);
20
- export function safePath(path) {
21
- path = resolve(path);
22
- let part = path;
23
- while (true) {
24
- try { if (fs.lstatSync(part).isSymbolicLink()) fail('E_PATH', `symlink not allowed: ${part}`); }
25
- catch (e) { if (e.code !== 'ENOENT') throw e; }
26
- if (dirname(part) === part) break;
27
- part = dirname(part);
28
- }
29
- return path;
30
- }
31
- export function relPath(p, dot = false) {
32
- if (typeof p !== 'string' || (!dot && p === '.') || !p || p.includes('\\') || p.includes('\0') || isAbsolute(p) || p.split('/').some(x => !x || x === '..' || (x === '.' && p !== '.')) || p.split('/').some(x => x === '.git')) fail('E_PATH', `noncanonical relative path: ${p}`);
33
- return p;
34
- }
35
- export function identifier(value) {
36
- if (typeof value !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,95}$/.test(value) || ['constructor','__proto__','prototype','toString','valueOf'].includes(value)) fail('E_ID', `invalid identity: ${value}`);
37
- return value;
38
- }
39
- export function syncDir(dir) { const fd = fs.openSync(dir, 'r'); try { fs.fsyncSync(fd); } finally { fs.closeSync(fd); } }
40
- export function atomic(path, bytes, { tempDir = dirname(path) } = {}) {
41
- safePath(path); fs.mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
42
- // Publication may stage in its owned lock directory, outside accepted data.
43
- safePath(tempDir);
44
- const temp = join(tempDir, `${basename(path)}.tmp-${randomUUID()}`);
45
- const fd = fs.openSync(temp, 'wx', 0o600);
46
- try { fs.writeFileSync(fd, bytes); fs.fsyncSync(fd); } finally { fs.closeSync(fd); }
47
- fs.renameSync(temp, path); syncDir(dirname(path));
48
- }
49
- export const save = (path, obj) => atomic(path, JSON.stringify(obj, null, 2) + '\n');
50
- export function tree(root, { git = false } = {}) {
51
- safePath(root); const files = {};
52
- function walk(dir) {
53
- for (const d of fs.readdirSync(dir, { withFileTypes: true }).sort((a,b) => a.name.localeCompare(b.name))) {
54
- if (git && dir === root && d.name === '.git') continue;
55
- const path = join(dir, d.name); safePath(path);
56
- if (d.isDirectory()) walk(path);
57
- else if (d.isFile()) { if (fs.statSync(path).nlink !== 1) fail('E_PATH', `hardlink not allowed: ${path}`); files[relative(root, path).split(sep).join('/')] = fs.readFileSync(path).toString('base64'); }
58
- else fail('E_PATH', `unsupported entry: ${path}`);
59
- }
60
- }
61
- walk(root); return files;
62
- }
63
- export const digest = files => hash(Object.fromEntries(Object.entries(files).sort(([a],[b])=>a.localeCompare(b))));
64
- export function materialize(root, files) {
65
- safePath(root); fs.mkdirSync(root, { recursive: true });
66
- for (const [p, content] of Object.entries(files)) atomic(join(root, relPath(p)), Buffer.from(content, 'base64'));
67
- }
68
- const pause = ms => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
69
- /** Cooperative directory lock. `waitMs` lets a reader queue behind a live
70
- * holder for a bounded time; an abandoned lock is still never reclaimed. */
71
- export function withLock(path, fn, { waitMs = 0 } = {}) {
72
- safePath(path); fs.mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
73
- const deadline = Date.now() + waitMs;
74
- for (let delay = 20;; delay = Math.min(delay * 2, 250)) {
75
- try { fs.mkdirSync(path, { mode: 0o700 }); break; }
76
- catch(e) { if(e.code !== 'EEXIST') throw e; if(Date.now() >= deadline) fail('E_LOCKED', `busy or abandoned lock: ${path}; inspect owner.json, never reclaim by age`); pause(delay); }
77
- }
78
- const owner = { token: randomUUID(), pid: process.pid, host: hostname() };
79
- save(join(path, 'owner.json'), owner);
80
- try { return fn(); } finally { if (readJSON(join(path, 'owner.json')).token === owner.token) { fs.rmSync(path, { recursive: true }); syncDir(dirname(path)); } }
81
- }
82
- export function unlock(path, token) {
83
- safePath(path); const o = readJSON(join(path, 'owner.json'));
84
- if (!token || o.token !== token || o.host !== hostname()) fail('E_LOCKED', 'explicit matching local lock token required');
85
- try { process.kill(o.pid, 0); fail('E_LOCKED', 'lock owner is still alive'); } catch (e) { if(e.code !== 'ESRCH') throw e; }
86
- fs.rmSync(path, { recursive: true }); syncDir(dirname(path)); return { unlocked: path };
87
- }
88
- export const identityKeys = ['OATS_INSTANCE','OATS_INSTANCE_HOME','OATS_HOME','PI_AGENT_HOME','PI_AGENT_NAME','PI_AGENT_INSTANCE','PI_AGENTS_ROOT','OATS_ROOT','OATS_SOUL','OATS_SOUL_ID','OATS_AGENT','OATS_KIND','OATS_EVENT','OATS_CONTEXT','OATS_REPO','OATS_WORK','OATS_BRANCH','OATS_META','OATS_SETTINGS','OATS_SETTINGS_ORIGINS','OATS_BINDING_FILE','OATS_SOURCE_RECEIPT_FILE','OATS_INVOCATION_CONTEXT_FILE','OATS_DEPLOYMENT','OATS_RESOLUTION'];
89
- export function cleanEnv(env = process.env) {
90
- return Object.fromEntries(Object.entries(env).filter(([k]) => !/^(OATS_(?!HOME_DIR$|PACKAGE_CATALOG$)|PI_AGENT|GIT_)/.test(k)));
91
- }
92
- export function exec(bin, args, { cwd, env = cleanEnv(), timeout = 30000, maxBuffer = 16*1024*1024, acceptedStatus = [0] } = {}) {
93
- const r = spawnSync(bin, args, { cwd, env, encoding: 'utf8', timeout, maxBuffer });
94
- if(r.error || !acceptedStatus.includes(r.status)) throw Object.assign(new Error(`${bin} ${args[0]} failed: ${r.error?.message || r.stderr || `exit ${r.status}`}`),{code:'E_COMMAND',stdout:r.stdout,status:r.status});
95
- return r.stdout.trim();
96
- }
97
- export function cliPath() {
98
- const p = process.env.OATS_CLI_BIN;
99
- if (!p || !isAbsolute(p)) fail('E_RUNTIME', 'absolute OATS_CLI_BIN required; never resolve oats on PATH');
100
- return p;
101
- }
102
- export function oats(args, cwd, { native = false, ...opts } = {}) {
103
- let text;
104
- try {text = exec(cliPath(), args, { cwd, ...opts });}
105
- catch(e) {
106
- let response;try {response=JSON.parse(e.stdout);} catch { /* unknown side effect, preserve command failure */ }
107
- if(response?.schemaVersion===1 && response.ok===false) fail(response.error?.code || 'E_RUNTIME',response.error?.message || 'CLI failed');
108
- throw e;
109
- }
110
- let o; try { o = JSON.parse(text); } catch { fail('E_RUNTIME', 'unsupported CLI JSON response'); }
111
- if(native) return o;
112
- if(o.schemaVersion !== 1 || o.ok !== true) fail(o.error?.code || 'E_RUNTIME', o.error?.message || 'unsupported CLI envelope');
113
- return o.result;
114
- }
115
- export const quote = v => `'${String(v).replaceAll("'", "'\\''")}'`;
116
- export function command(cwd, argv) {
117
- return `cd ${quote(cwd)} && env ${identityKeys.map(k => `-u ${quote(k)}`).join(' ')} ${[cliPath(), ...argv].map(quote).join(' ')}`;
118
- }