@awebai/oats 0.29.4 → 0.30.1

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 (263) hide show
  1. package/README.md +12 -6
  2. package/bin/oats.mjs +194 -50
  3. package/docs/capabilities.md +160 -171
  4. package/docs/capability-manifest.schema.json +6 -11
  5. package/docs/configuration.md +213 -64
  6. package/docs/design/2026-09-16-knowledge-capability-contract.md +36 -50
  7. package/docs/design/2026-09-23-workspace-module-contracts.md +377 -544
  8. package/docs/design/2026-09-26-okf-knowledge-operations.md +132 -357
  9. package/docs/design/2026-09-27-team-model-v2.md +97 -117
  10. package/docs/design/2026-09-28-automations-trust.md +38 -0
  11. package/docs/design/2026-09-28-soul-launch-preference.md +63 -0
  12. package/docs/design/HISTORY.md +65 -0
  13. package/docs/design/README.md +23 -54
  14. package/docs/desktop-cli-api.md +1787 -1777
  15. package/docs/desktop.md +30 -91
  16. package/docs/execution-targets.md +146 -292
  17. package/docs/first-team.md +31 -17
  18. package/docs/implementation.md +77 -288
  19. package/docs/integrations.md +118 -320
  20. package/docs/knowledge-capability-authoring.md +25 -52
  21. package/docs/knowledge-reference/acceptance.md +3 -3
  22. package/docs/knowledge-reference/adoption.md +1 -1
  23. package/docs/knowledge-reference/harvester.md +2 -2
  24. package/docs/knowledge-reference/package-craft.md +3 -3
  25. package/docs/knowledge-reference/provider-mapping.md +3 -6
  26. package/docs/knowledge-reference/reader-capture.md +3 -3
  27. package/docs/knowledge-theory.md +62 -166
  28. package/docs/knowledge.md +225 -404
  29. package/docs/layers.md +42 -97
  30. package/docs/oats-local.schema.json +58 -5
  31. package/docs/oats-membership.schema.json +1 -8
  32. package/docs/oats-package.schema.json +5 -5
  33. package/docs/oats-workspace.schema.json +8 -22
  34. package/docs/official-catalog.md +25 -28
  35. package/docs/packages.md +45 -63
  36. package/docs/plans/0.30-close-out.md +83 -0
  37. package/docs/release-lane.md +82 -0
  38. package/docs/release-notes/oats-framework-v1.1.3.md +10 -8
  39. package/docs/release-notes/v0.19.0.md +48 -147
  40. package/docs/release-notes/v0.19.1.md +2 -3
  41. package/docs/release-notes/v0.19.3.md +2 -15
  42. package/docs/release-notes/v0.20.0.md +0 -15
  43. package/docs/release-notes/v0.22.0.md +71 -138
  44. package/docs/release-notes/v0.22.1.md +42 -90
  45. package/docs/release-notes/v0.22.10.md +1 -1
  46. package/docs/release-notes/v0.22.11.md +1 -47
  47. package/docs/release-notes/v0.22.12.md +4 -13
  48. package/docs/release-notes/v0.22.13.md +1 -42
  49. package/docs/release-notes/v0.22.14.md +3 -11
  50. package/docs/release-notes/v0.22.15.md +1 -46
  51. package/docs/release-notes/v0.22.16.md +6 -8
  52. package/docs/release-notes/v0.22.18.md +1 -99
  53. package/docs/release-notes/v0.22.19.md +3 -14
  54. package/docs/release-notes/v0.22.2.md +6 -15
  55. package/docs/release-notes/v0.22.3.md +0 -1
  56. package/docs/release-notes/v0.22.4.md +1 -14
  57. package/docs/release-notes/v0.22.5.md +2 -12
  58. package/docs/release-notes/v0.22.6.md +0 -3
  59. package/docs/release-notes/v0.23.0.md +9 -25
  60. package/docs/release-notes/v0.23.1.md +9 -25
  61. package/docs/release-notes/v0.23.2.md +2 -4
  62. package/docs/release-notes/v0.24.0.md +56 -97
  63. package/docs/release-notes/v0.24.1.md +7 -11
  64. package/docs/release-notes/v0.24.10.md +34 -45
  65. package/docs/release-notes/v0.24.11.md +12 -20
  66. package/docs/release-notes/v0.24.12.md +35 -48
  67. package/docs/release-notes/v0.24.13.md +34 -41
  68. package/docs/release-notes/v0.24.2.md +9 -13
  69. package/docs/release-notes/v0.24.3.md +7 -11
  70. package/docs/release-notes/v0.24.4.md +6 -6
  71. package/docs/release-notes/v0.24.5.md +6 -10
  72. package/docs/release-notes/v0.24.6.md +2 -5
  73. package/docs/release-notes/v0.24.7.md +46 -75
  74. package/docs/release-notes/v0.24.8.md +58 -96
  75. package/docs/release-notes/v0.24.9.md +38 -54
  76. package/docs/release-notes/v0.25.0.md +59 -76
  77. package/docs/release-notes/v0.25.1.md +57 -81
  78. package/docs/release-notes/v0.25.2.md +51 -70
  79. package/docs/release-notes/v0.25.3.md +11 -13
  80. package/docs/release-notes/v0.25.4.md +9 -13
  81. package/docs/release-notes/v0.25.5.md +3 -5
  82. package/docs/release-notes/v0.25.6.md +20 -29
  83. package/docs/release-notes/v0.25.7.md +5 -7
  84. package/docs/release-notes/v0.25.8.md +26 -39
  85. package/docs/release-notes/v0.26.0.md +175 -646
  86. package/docs/release-notes/v0.27.0.md +4 -5
  87. package/docs/release-notes/v0.27.1.md +4 -6
  88. package/docs/release-notes/v0.27.2.md +1 -1
  89. package/docs/release-notes/v0.28.0.md +57 -124
  90. package/docs/release-notes/v0.29.0.md +89 -208
  91. package/docs/release-notes/v0.29.1.md +1 -1
  92. package/docs/release-notes/v0.29.2.md +3 -4
  93. package/docs/release-notes/v0.30.0.md +205 -0
  94. package/docs/release-notes/v0.30.1.md +123 -0
  95. package/docs/schedules.md +280 -363
  96. package/docs/servers.md +99 -117
  97. package/docs/soul.schema.json +2 -9
  98. package/docs/souls-and-instances.md +145 -158
  99. package/docs/workspaces.md +137 -215
  100. package/lib/automations.mjs +21 -6
  101. package/lib/core.mjs +226 -74
  102. package/lib/instance-events.mjs +1 -1
  103. package/lib/instance-inspect.mjs +109 -34
  104. package/lib/instance-lifecycle.mjs +14 -1
  105. package/lib/instance-resolution.mjs +26 -27
  106. package/lib/launch-preference.mjs +87 -0
  107. package/lib/materialize.mjs +3 -3
  108. package/lib/packages.mjs +1 -1
  109. package/lib/resolve.mjs +30 -88
  110. package/lib/schedule.mjs +1 -1
  111. package/lib/teams-verbs.mjs +195 -0
  112. package/lib/teams.mjs +190 -0
  113. package/lib/triggers.mjs +2 -2
  114. package/lib/workspace.mjs +54 -147
  115. package/package-catalog.json +10 -16
  116. package/package.json +1 -3
  117. package/skills/oats-getting-started/SKILL.md +25 -13
  118. package/capabilities/oats-authoring/LICENSE +0 -21
  119. package/capabilities/oats-authoring/oats-package.json +0 -11
  120. package/capabilities/oats-authoring/oats.json +0 -12
  121. package/capabilities/oats-authoring/skills/integration-authoring/SKILL.md +0 -84
  122. package/capabilities/oats-authoring/skills/skill-craft/SKILL.md +0 -109
  123. package/capabilities/oats-authoring/skills/soul-craft/SKILL.md +0 -116
  124. package/capabilities/oats-aweb/bin/oats-aweb-binding.mjs +0 -11
  125. package/capabilities/oats-aweb/bin/oats-aweb.mjs +0 -1338
  126. package/capabilities/oats-aweb/injects/aweb.md +0 -47
  127. package/capabilities/oats-aweb/lib/binding-wire.mjs +0 -356
  128. package/capabilities/oats-aweb/lib/captured-execution.mjs +0 -91
  129. package/capabilities/oats-aweb/lib/captured-native.mjs +0 -91
  130. package/capabilities/oats-aweb/lib/grant-custody.mjs +0 -38
  131. package/capabilities/oats-aweb/lib/invocation-shape.mjs +0 -135
  132. package/capabilities/oats-aweb/lib/portable-binding.mjs +0 -146
  133. package/capabilities/oats-aweb/lib/session-readiness.mjs +0 -56
  134. package/capabilities/oats-aweb/lib/wake-receive.mjs +0 -56
  135. package/capabilities/oats-aweb/oats.json +0 -208
  136. package/capabilities/oats-aweb/skills/LICENSE +0 -21
  137. package/capabilities/oats-aweb/skills/VENDORED.md +0 -31
  138. package/capabilities/oats-aweb/skills/aweb-identity/SKILL.md +0 -201
  139. package/capabilities/oats-aweb/skills/aweb-messaging/SKILL.md +0 -161
  140. package/capabilities/oats-aweb/skills/aweb-messaging/references/messaging-scenarios.md +0 -61
  141. package/capabilities/oats-aweb/skills/aweb-team-membership/SKILL.md +0 -116
  142. package/capabilities/oats-aweb/skills/aweb-team-membership/references/team-membership-reference.md +0 -74
  143. package/capabilities/oats-aweb/skills/oats-aweb/SKILL.md +0 -216
  144. package/capabilities/oats-jira/bin/oats-jira.mjs +0 -40
  145. package/capabilities/oats-jira/injects/jira.md +0 -10
  146. package/capabilities/oats-jira/oats.json +0 -22
  147. package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +0 -179
  148. package/capabilities/oats-linear/bin/oats-linear-hook.mjs +0 -34
  149. package/capabilities/oats-linear/bin/oats-linear.mjs +0 -344
  150. package/capabilities/oats-linear/injects/linear.md +0 -8
  151. package/capabilities/oats-linear/oats.json +0 -24
  152. package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +0 -223
  153. package/capabilities/oats-okf/bin/oats-okf-binding.mjs +0 -14
  154. package/capabilities/oats-okf/bin/oats-okf.mjs +0 -209
  155. package/capabilities/oats-okf/injects/okf.md +0 -42
  156. package/capabilities/oats-okf/lib/binding-wire.mjs +0 -348
  157. package/capabilities/oats-okf/lib/captured-worker.mjs +0 -109
  158. package/capabilities/oats-okf/lib/config.mjs +0 -124
  159. package/capabilities/oats-okf/lib/consult.mjs +0 -518
  160. package/capabilities/oats-okf/lib/harvest-status.mjs +0 -88
  161. package/capabilities/oats-okf/lib/harvest-switch.mjs +0 -94
  162. package/capabilities/oats-okf/lib/inspection.mjs +0 -119
  163. package/capabilities/oats-okf/lib/invocation-context.mjs +0 -111
  164. package/capabilities/oats-okf/lib/invocation-shape.mjs +0 -135
  165. package/capabilities/oats-okf/lib/io.mjs +0 -118
  166. package/capabilities/oats-okf/lib/migration.mjs +0 -137
  167. package/capabilities/oats-okf/lib/okf-validate.mjs +0 -123
  168. package/capabilities/oats-okf/lib/portable-binding.mjs +0 -199
  169. package/capabilities/oats-okf/lib/source-contract.mjs +0 -46
  170. package/capabilities/oats-okf/lib/sources.mjs +0 -424
  171. package/capabilities/oats-okf/lib/stores.mjs +0 -473
  172. package/capabilities/oats-okf/lib/worker.mjs +0 -497
  173. package/capabilities/oats-okf/oats.json +0 -148
  174. package/capabilities/oats-okf/schemas/okf-base.schema.json +0 -46
  175. package/capabilities/oats-okf/schemas/okf-bindings.schema.json +0 -112
  176. package/capabilities/oats-okf/schemas/okf-portable-declaration.schema.json +0 -87
  177. package/capabilities/oats-okf/schemas/okf-portable-payload.schema.json +0 -113
  178. package/capabilities/oats-okf/schemas/okf-soul.schema.json +0 -37
  179. package/capabilities/oats-okf/skills/okf-consultation/SKILL.md +0 -144
  180. package/capabilities/oats-okf/skills/okf-consultation/references/consult.md +0 -86
  181. package/capabilities/oats-okf/skills/okf-instance-knowledge/SKILL.md +0 -104
  182. package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +0 -140
  183. package/capabilities/oats-okf-harvest/injects/harvester.md +0 -12
  184. package/capabilities/oats-okf-harvest/oats.json +0 -26
  185. package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +0 -168
  186. package/capabilities/oats-okf-harvest/skills/knowledge-theory/SKILL.md +0 -192
  187. package/capabilities/oats-okf-harvest/skills/okf-authoring/SKILL.md +0 -151
  188. package/capabilities/oats-okf-harvest/skills/okf-authoring/scripts/okf-validate.mjs +0 -123
  189. package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +0 -170
  190. package/capabilities/oats-okf-maintenance/injects/maintainer.md +0 -12
  191. package/capabilities/oats-okf-maintenance/lib/provenance.mjs +0 -50
  192. package/capabilities/oats-okf-maintenance/oats.json +0 -21
  193. package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +0 -159
  194. package/capabilities/oats-okf-maintenance/skills/knowledge-theory/SKILL.md +0 -192
  195. package/capabilities/oats-okf-maintenance/skills/okf-authoring/SKILL.md +0 -151
  196. package/capabilities/oats-okf-maintenance/skills/okf-authoring/scripts/okf-validate.mjs +0 -123
  197. package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +0 -146
  198. package/capabilities/oats-review/injects/review.md +0 -69
  199. package/capabilities/oats-review/oats.json +0 -10
  200. package/capabilities/oats-review/skills/code-review/SKILL.md +0 -44
  201. package/capabilities/oats-review/skills/security-review/SKILL.md +0 -59
  202. package/docs/conventions.md +0 -90
  203. package/docs/design/2026-09-07-architecture-reassessment.md +0 -131
  204. package/docs/design/2026-09-07-desktop-souls-capabilities.md +0 -50
  205. package/docs/design/2026-09-07-mobile-agent-management-proposal.md +0 -228
  206. package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +0 -558
  207. package/docs/design/2026-09-13-knowledge-and-memory-direction.md +0 -744
  208. package/docs/design/2026-09-13-knowledge-implementation.md +0 -127
  209. package/docs/design/2026-09-13-knowledge-location-contract.md +0 -340
  210. package/docs/design/2026-09-14-artifact-retention-contract.md +0 -190
  211. package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +0 -708
  212. package/docs/design/2026-09-14-portable-souls-contract-amendments.md +0 -85
  213. package/docs/design/2026-09-14-portable-souls-explainer.md +0 -750
  214. package/docs/design/2026-09-15-captured-dispatch.md +0 -127
  215. package/docs/design/2026-09-15-captured-resolution-records.md +0 -143
  216. package/docs/design/2026-09-15-package-preparation.md +0 -100
  217. package/docs/design/2026-09-15-portable-data-contract.md +0 -121
  218. package/docs/design/2026-09-15-portable-declarations.md +0 -189
  219. package/docs/design/2026-09-15-portable-souls-handoff.md +0 -150
  220. package/docs/design/2026-09-15-portable-souls-implementation.md +0 -417
  221. package/docs/design/2026-09-15-selection-lock-and-approval.md +0 -122
  222. package/docs/design/2026-09-15-source-observation.md +0 -119
  223. package/docs/design/2026-09-16-captured-admission.md +0 -77
  224. package/docs/design/2026-09-16-captured-helper-dispatch.md +0 -105
  225. package/docs/design/2026-09-16-captured-launch-inputs.md +0 -42
  226. package/docs/design/2026-09-16-command-profile-preparation.md +0 -86
  227. package/docs/design/2026-09-16-fresh-install-first-rollout.md +0 -47
  228. package/docs/design/2026-09-16-fresh-operator-walkthrough.md +0 -282
  229. package/docs/design/2026-09-16-messaging-capability-contract.md +0 -59
  230. package/docs/design/2026-09-16-portable-migration-evidence.md +0 -158
  231. package/docs/design/2026-09-16-portable-onboarding.md +0 -179
  232. package/docs/design/2026-09-16-prepare-request-transport.md +0 -26
  233. package/docs/design/2026-09-16-provider-binding-codecs.md +0 -98
  234. package/docs/design/2026-09-16-provider-binding-wire.md +0 -274
  235. package/docs/design/2026-09-17-capability-helper-input-contract.md +0 -95
  236. package/docs/design/2026-09-17-captured-backend-parity.md +0 -53
  237. package/docs/design/2026-09-17-captured-native-start.md +0 -58
  238. package/docs/design/2026-09-17-portable-boundary-hookup.md +0 -19
  239. package/docs/design/2026-09-17-portable-boundary-resources.md +0 -52
  240. package/docs/design/2026-09-17-public-captured-start.md +0 -108
  241. package/docs/design/2026-09-17-public-prepare-request.md +0 -90
  242. package/docs/design/2026-09-18-captured-pi-host.md +0 -205
  243. package/docs/design/2026-09-18-first-cut-release-checklist.md +0 -131
  244. package/docs/design/2026-09-18-herdr-protocol-compatibility.md +0 -60
  245. package/docs/design/2026-09-20-redesign-program-board.md +0 -142
  246. package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +0 -289
  247. package/docs/design/2026-09-20-workspace-onboarding-public.md +0 -207
  248. package/docs/design/2026-09-22-desktop-parity-seams.md +0 -58
  249. package/docs/design/2026-09-23-simplified-workspace-model.md +0 -711
  250. package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +0 -65
  251. package/docs/design/2026-09-24-desktop-phase-f-boundary.md +0 -242
  252. package/docs/design/2026-09-24-phase-d-plan.md +0 -305
  253. package/docs/design/2026-09-25-teams-contract.md +0 -258
  254. package/docs/design/2026-09-26-desktop-design-brief-architecture.md +0 -241
  255. package/docs/design/desktop-ux-plan.md +0 -362
  256. package/docs/design/launch-configurations.md +0 -168
  257. package/docs/design/okf-mirror-provenance.md +0 -105
  258. package/docs/design/operations-contract.md +0 -141
  259. package/docs/oats-member.schema.json +0 -38
  260. package/skills/integration-authoring/SKILL.md +0 -84
  261. package/skills/oats-support/SKILL.md +0 -79
  262. package/skills/skill-craft/SKILL.md +0 -109
  263. package/skills/soul-craft/SKILL.md +0 -116
@@ -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,119 +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
- export function workingDocuments(source,status=loadStatus(source)) {
57
- const before=liveHome(source,status),observedAt=new Date().toISOString();
58
- const unavailable=state=>({liveMemory:{available:false,reason:state.reason,observedAt,...(state.error?{error:state.error}:{})},documents:[]});
59
- if(!before.available) return unavailable(before);
60
- const documents=[];
61
- const doc=(label,file)=>{
62
- const content=regular(file,DOCUMENT_BYTES);
63
- if(content) documents.push({label,kind:'markdown',path:file,...content});
64
- };
65
- let error;
66
- try {
67
- doc('Working state (STATE.md)',join(source.home,'STATE.md'));
68
- doc('Log (log.md)',join(source.home,'log.md'));
69
- const notes=safePath(join(source.home,'notes'));
70
- function walk(dir,prefix='') {
71
- let entries;try {entries=fs.readdirSync(dir,{withFileTypes:true});} catch(e) {if(e.code==='ENOENT') return;throw e;}
72
- for(const entry of entries.sort((a,b)=>a.name<b.name?-1:a.name>b.name?1:0)) {
73
- const name=prefix+entry.name,file=join(dir,entry.name);
74
- // No symlink traversal, including directories or non-Markdown aliases.
75
- safePath(file);
76
- if(entry.isDirectory()) walk(file,name+'/');
77
- else if(entry.name.endsWith('.md')) doc(`Pending note: ${name}`,file);
78
- }
79
- }
80
- walk(notes);
81
- } catch(e) {error=e;}
82
- const after=liveHome(source,loadStatus(source));
83
- if(!after.available) return unavailable(after);
84
- if(before.dev!==after.dev || before.ino!==after.ino) return unavailable({reason:'home-changed'});
85
- if(error) fail(error.code==='E_PATH'?'E_PATH':'E_INSPECT_FAILED',error.message);
86
- return {liveMemory:{available:true,reason:'live',observedAt},documents};
87
- }
88
- export function capturedAuthority(source) {
89
- const fields=['registration','providerBinding','sourceIdentity','executionBinding','responsibleHuman'],present=fields.filter(key=>Object.hasOwn(source,key));
90
- const base={schemaVersion:1};
91
- if(!present.length) return {...base,registration:'legacy',capture:'unknown',migrationRequired:true,responsibleHuman:{status:'unknown'}};
92
- const binding=source.executionBinding,identity=source.sourceIdentity,complete=source.registration?.schemaVersion===1 && source.registration.kind==='captured'
93
- && source.providerBinding && typeof source.providerBinding==='object' && identity && typeof identity==='object' && !Array.isArray(identity)
94
- && binding?.schemaVersion===1 && typeof binding.deployment==='string' && binding.resolution?.schemaVersion===1 && typeof binding.resolution.id==='string'
95
- && Object.hasOwn(source,'responsibleHuman');
96
- const invalid={...base,registration:'invalid',capture:'invalid',migrationRequired:true,responsibleHuman:{status:'unknown'}};
97
- if(!complete) return invalid;
98
- try {if(Buffer.byteLength(JSON.stringify({identity,binding}))>64*1024) return invalid;qualifiedSoulIdentity(identity);} catch {return invalid;}
99
- return {...base,registration:'captured',capture:'recorded',migrationRequired:false,sourceIdentity:JSON.parse(JSON.stringify(identity)),executionBinding:JSON.parse(JSON.stringify(binding)),
100
- responsibleHuman:{status:source.responsibleHuman===null?'disabled':'specified'}};
101
- }
102
- /** A ./knowledge/ snapshot written by okf 2.x. okf 3.0.0 never reads it
103
- * (knowledge is consulted remotely) and never deletes it. */
104
- export function legacyLocalView(source,status=loadStatus(source)) {
105
- if(!liveHome(source,status).available) return null;
106
- const path=join(safePath(source.home),'knowledge');let stat;
107
- try {stat=fs.lstatSync(path);} catch(e) {if(missing(e)) return null;throw e;} // never follow it
108
- 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'};
109
- }
110
- export function inspect(source) {
111
- const status=loadStatus(source),working=workingDocuments(source,status),legacy=legacyLocalView(source,status);
112
- let health;try {health=oats(['schedule','list','--dir',source.context,'--json'],source.context).scheduler;} catch(e) {health={active:false,error:e.message};}
113
- const documents=[...working.documents,{label:'Durable processing receipts',kind:'text',path:join(dirname(source.file),'status.json'),text:JSON.stringify(status,null,2)}];
114
- return {
115
- 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)':''}`,
116
- source:source.file,owns:source.decl.owns,reads:source.decl.reads,bases:source.bindings.bases,authority:capturedAuthority(source),
117
- acceptedView:source.acceptedView,legacyLocalView:legacy,status,scheduler:health,liveMemory:working.liveMemory,documents
118
- };
119
- }
@@ -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
- }