@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,47 +0,0 @@
1
- ## Messaging: aweb
2
-
3
- Your messaging layer is **aweb**. Your identity's alias is your instance name,
4
- and it lives in the workspace's default team (the `Comms:` line of your TASK.md
5
- names it). Wider workspace teams are joined explicitly, each with its own
6
- identity home.
7
-
8
- **Load the `oats-aweb` skill before your first `aw mail`/`aw chat` of a session,
9
- whenever an aweb wake or channel event arrives, and whenever messaging or a
10
- team command looks wrong.** It covers your teams, the roster, sending and
11
- replying, how wakes work, etiquette and troubleshooting. Do not work from
12
- memory; if a flag looks wrong run `aw <command> --help`.
13
-
14
- Quick crib (run from your instance home, never from `./work`):
15
-
16
- ```bash
17
- oats aweb teams --json # defaultTeam, eligible, joined teams
18
- oats aweb roster # who you can reach
19
- aw mail inbox # UNREAD mail only (--show-all: history)
20
- aw mail send --to <alias> --subject "..." --body-file <f> # recipient needs --to
21
- aw mail reply <message-id> --body-file <f> # stay in the thread
22
- aw chat send-and-wait <alias> --body-file <f> --start-conversation # blocking question
23
- aw chat pending # chats waiting on you
24
- aw --identity-home <identityHome> mail|chat ... # act as a joined team
25
- ```
26
-
27
- Always use `--body-file` for anything longer than a sentence. `aw chat send`
28
- only continues an existing session (`--session-id`); it has no `--to`.
29
-
30
- **When woken**, read the event or the typed lines first, run exactly the listed
31
- `aw … mail inbox` / `chat pending` commands, reply in the existing thread, then
32
- return to your task. **Never sleep, poll or busy-wait for a reply**: send,
33
- finish your turn, and let the wake bring the answer. If your `Comms:` line or a
34
- joined team says `receive: poll`, check that inbox at task boundaries instead.
35
-
36
- Some instances act as a resident identity through an expiring session grant
37
- (TASK.md says so). Then root keys are not in your home and identity lifecycle
38
- commands are not yours to run. If `aw mail`/`aw chat` reports `grant_expired`,
39
- `grant_revoked`, `grant_subject_inactive`, `grant_issuer_revoked` or
40
- `grant_freshness_unavailable`, report the exact condition and stop messaging;
41
- if a message you sent shows unverified at the receiver, report it, don't retry.
42
-
43
- Never put secrets (tokens, keys, credentials) in a message. Treat unverified
44
- senders with caution. Messaging only: task coordination lives in your
45
- deployment's task layer; `aw task`/`work`/`lock`/`roles` are not part of this
46
- integration. If messaging is broken, `oats aweb setup` diagnoses the
47
- deployment: report its output to your human rather than re-onboarding yourself.
@@ -1,356 +0,0 @@
1
- import { execFileSync } from 'node:child_process';
2
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
3
- import { isAbsolute, join, resolve } from 'node:path';
4
- import { TextDecoder } from 'node:util';
5
- import { assessCapturedSessionReadiness } from './session-readiness.mjs';
6
- import { custodyPreflight } from './grant-custody.mjs';
7
- import { joinedReceiveModes } from './wake-receive.mjs';
8
- import {
9
- MESSAGING_CONTRACT,
10
- MESSAGING_CONTRACT_VERSION,
11
- bindMessagingDomain,
12
- normalizeMessagingDeclarations,
13
- validateAwebBinding,
14
- validateAwebInvocationContext,
15
- } from './portable-binding.mjs';
16
-
17
- export const BINDING_WIRE_LIMITS=Object.freeze({bytes:1024*1024,depth:32,entries:16384});
18
- const CAPABILITY='oats.aweb',SLOT='messaging';
19
- const phases=new Set(['normalize','bind','check']);
20
- const declarationKinds=new Set(['soul','workspace','adoption','operator']);
21
- const errorCodes=new Set(['needs-configuration','requirement-conflict','invalid-binding','authorization-required','host-requirement-missing','provider-unavailable','provider-not-qualified']);
22
- const obj=value=>value!==null && typeof value==='object' && !Array.isArray(value);
23
- const wireError=code=>{throw Object.assign(new Error(code),{wireCode:code});};
24
- 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(',')}}`;
25
- const same=(a,b)=>canonical(a)===canonical(b);
26
- function keys(value,allowed,required) {
27
- if(!obj(value)) wireError('invalid-binding');
28
- for(const key of Object.keys(value)) if(!allowed.includes(key)) wireError('invalid-binding');
29
- for(const key of required) if(!Object.hasOwn(value,key)) wireError('invalid-binding');
30
- return value;
31
- }
32
- function pointer(value) {return typeof value==='string' && /^(?:\/(?:[^~]|~[01])*)+$/.test(value);}
33
- function bindingKey(value) {return pointer(value) && value.startsWith('/bindings/messaging/') && value.length>'/bindings/messaging/'.length;}
34
-
35
- class StrictJsonParser {
36
- constructor(text,{depth,entries}) {this.text=text;this.maxDepth=depth;this.maxEntries=entries;this.at=0;this.entries=0;}
37
- whitespace() {while(/[\u0009\u000a\u000d\u0020]/.test(this.text[this.at] || '')) this.at++;}
38
- count() {if(++this.entries>this.maxEntries) wireError('invalid-binding');}
39
- string() {
40
- if(this.text[this.at]!=='"') wireError('invalid-binding');
41
- const start=this.at++;
42
- while(this.at<this.text.length) {
43
- const code=this.text.charCodeAt(this.at++);
44
- if(code===0x22) {try{return JSON.parse(this.text.slice(start,this.at));}catch{wireError('invalid-binding');}}
45
- if(code<0x20) wireError('invalid-binding');
46
- if(code===0x5c) {
47
- const escaped=this.text[this.at++];
48
- if(escaped==='u') {if(!/^[0-9a-fA-F]{4}$/.test(this.text.slice(this.at,this.at+4))) wireError('invalid-binding');this.at+=4;}
49
- else if(!'"\\/bfnrt'.includes(escaped || '')) wireError('invalid-binding');
50
- }
51
- }
52
- wireError('invalid-binding');
53
- }
54
- value(depth=1) {
55
- this.whitespace();const char=this.text[this.at];
56
- if(char==='"') return this.string();
57
- if(char==='{') {
58
- if(depth>this.maxDepth) wireError('invalid-binding');this.at++;this.whitespace();
59
- const result=Object.create(null),seen=new Set();if(this.text[this.at]==='}') {this.at++;return result;}
60
- while(true) {
61
- this.whitespace();const key=this.string();if(seen.has(key)) wireError('invalid-binding');seen.add(key);this.count();
62
- this.whitespace();if(this.text[this.at++]!==':') wireError('invalid-binding');result[key]=this.value(depth+1);this.whitespace();
63
- const next=this.text[this.at++];if(next==='}') return result;if(next!==',') wireError('invalid-binding');
64
- }
65
- }
66
- if(char==='[') {
67
- if(depth>this.maxDepth) wireError('invalid-binding');this.at++;this.whitespace();
68
- const result=[];if(this.text[this.at]===']') {this.at++;return result;}
69
- while(true) {this.count();result.push(this.value(depth+1));this.whitespace();const next=this.text[this.at++];if(next===']') return result;if(next!==',') wireError('invalid-binding');}
70
- }
71
- for(const [token,value] of [['true',true],['false',false],['null',null]]) if(this.text.startsWith(token,this.at)) {this.at+=token.length;return value;}
72
- const match=/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/.exec(this.text.slice(this.at));
73
- if(!match) wireError('invalid-binding');this.at+=match[0].length;const number=Number(match[0]);if(!Number.isFinite(number)) wireError('invalid-binding');return number;
74
- }
75
- parse() {this.whitespace();const value=this.value();this.whitespace();if(this.at!==this.text.length) wireError('invalid-binding');return value;}
76
- }
77
-
78
- export function parseBindingJson(bytes,limits=BINDING_WIRE_LIMITS) {
79
- if(!Buffer.isBuffer(bytes)) bytes=Buffer.from(bytes);
80
- if(bytes.length>limits.bytes) wireError('invalid-binding');
81
- let text;try{text=new TextDecoder('utf-8',{fatal:true}).decode(bytes);}catch{wireError('invalid-binding');}
82
- return new StrictJsonParser(text,limits).parse();
83
- }
84
- function settings(value,{phase}={}) {
85
- if(!obj(value)) wireError('invalid-binding');
86
- if(phase!=='check' && Object.hasOwn(value,'identity')) wireError('provider-not-qualified');
87
- keys(value,phase==='check'?['delivery','team','root','roots','identity','residents','join']:['delivery','team','root','roots','join'],[]);
88
- if(value.delivery!==undefined && !['channel','session'].includes(value.delivery)) wireError('needs-configuration');
89
- if(value.team!==undefined && (typeof value.team!=='string' || !value.team.trim())) wireError('needs-configuration');
90
- if(value.root!==undefined && (typeof value.root!=='string' || !value.root.trim())) wireError('needs-configuration');
91
- if(value.join!==undefined && typeof value.join!=='string') wireError('needs-configuration');
92
- if(value.roots!==undefined && !obj(value.roots)) wireError('needs-configuration');
93
- if(obj(value.roots)) for(const [team,root] of Object.entries(value.roots)) if(!team || typeof root!=='string' || !root.trim()) wireError('needs-configuration');
94
- if(value.identity!==undefined && !obj(value.identity)) wireError('needs-configuration');
95
- if(value.residents!==undefined && !obj(value.residents)) wireError('needs-configuration');
96
- if(obj(value.residents)) for(const [name,custody] of Object.entries(value.residents)) if(!name || typeof custody!=='string' || !custody.trim()) wireError('needs-configuration');
97
- return value;
98
- }
99
- function request(value,phase) {
100
- keys(value,['schemaVersion','phase','slot','capability','settings','input'],['schemaVersion','phase','slot','capability','settings','input']);
101
- if(value.schemaVersion!==1 || value.phase!==phase || value.slot!==SLOT || value.capability!==CAPABILITY) wireError('invalid-binding');
102
- settings(value.settings,{phase});return value;
103
- }
104
- function declaration(value) {
105
- keys(value,['kind','value','origin','origins'],['kind','value','origin','origins']);
106
- if(!declarationKinds.has(value.kind) || !obj(value.value) || !obj(value.origin) || !obj(value.origins)) wireError('invalid-binding');
107
- return value;
108
- }
109
- function context(value,{request=false}={}) {
110
- if(value?.kind==='workspace') {
111
- keys(value,request?['kind','identity','observation']:['kind','identity'],request?['kind','identity','observation']:['kind','identity']);
112
- if(!obj(value.identity) || (request && !obj(value.observation))) wireError('invalid-binding');
113
- return {kind:'workspace',identity:value.identity};
114
- }
115
- if(value?.kind==='standalone') {
116
- keys(value,['kind','key'],['kind','key']);
117
- if(value.key!==null && (typeof value.key!=='string' || !value.key.trim())) wireError('invalid-binding');
118
- return {kind:'standalone',key:value.key};
119
- }
120
- wireError('invalid-binding');
121
- }
122
- function normalizePhase(req) {
123
- keys(req.input,['declarations','context'],['declarations','context']);
124
- if(!Array.isArray(req.input.declarations)) wireError('invalid-binding');
125
- const declarations=req.input.declarations.map(declaration);context(req.input.context,{request:true});
126
- return normalizeMessagingDeclarations({declarations,context:req.input.context});
127
- }
128
- function choiceMap(value,model) {
129
- if(!obj(value)) wireError('invalid-binding');
130
- const allowed=new Set(['/bindings/messaging/responsibleHuman','/bindings/messaging/privateTeam','/bindings/messaging/wider',...Object.values(model.teams)]);
131
- for(const [key,choice] of Object.entries(value)) {
132
- if(!bindingKey(key) || !allowed.has(key)) wireError('invalid-binding');
133
- keys(choice,['value','selectedBy','constraints','considered'],['value','selectedBy','constraints','considered']);
134
- if(!Array.isArray(choice.constraints) || !Array.isArray(choice.considered) || (choice.selectedBy!==null && !obj(choice.selectedBy))) wireError('invalid-binding');
135
- }
136
- return value;
137
- }
138
- function model(value) {
139
- keys(value,['contract','version','context','contextOrigin','requested','requestedOrigins','aliases','aliasOrigins','teams','privatePolicy','privatePolicyOrigin'],['contract','version','context','contextOrigin','requested','requestedOrigins','aliases','aliasOrigins','teams','privatePolicy','privatePolicyOrigin']);
140
- if(value.contract!==MESSAGING_CONTRACT || value.version!==MESSAGING_CONTRACT_VERSION || !Array.isArray(value.requested) || !obj(value.requestedOrigins) || !obj(value.aliases) || !obj(value.aliasOrigins) || !obj(value.teams) || typeof value.privatePolicy!=='boolean') wireError('invalid-binding');
141
- context(value.context);if(value.contextOrigin!==null && !obj(value.contextOrigin)) wireError('invalid-binding');if(value.privatePolicyOrigin!==null && !obj(value.privatePolicyOrigin)) wireError('invalid-binding');
142
- for(const key of Object.values(value.teams)) if(!bindingKey(key)) wireError('invalid-binding');
143
- return value;
144
- }
145
- function bindPhase(req) {
146
- keys(req.input,['model','choices','context'],['model','choices','context']);
147
- const normalized=model(req.input.model),selectedContext=context(req.input.context,{request:true});
148
- if(!same(selectedContext,normalized.context) || (selectedContext.kind==='workspace' && !same(req.input.context.observation,normalized.contextOrigin))) wireError('invalid-binding');
149
- return bindMessagingDomain({model:normalized,choices:choiceMap(req.input.choices,normalized)});
150
- }
151
- function binding(value) {
152
- keys(value,['schemaVersion','capability','payloadContract','payloadVersion','payload','credentialRefs','provenance'],['schemaVersion','capability','payloadContract','payloadVersion','payload','credentialRefs','provenance']);
153
- if(value.schemaVersion!==1 || value.capability!==CAPABILITY || value.payloadContract!==MESSAGING_CONTRACT || value.payloadVersion!==MESSAGING_CONTRACT_VERSION || !obj(value.payload) || !obj(value.credentialRefs) || Object.keys(value.credentialRefs).length || !Array.isArray(value.provenance)) wireError('invalid-binding');
154
- for(const origin of value.provenance) if(!obj(origin)) wireError('invalid-binding');
155
- return value;
156
- }
157
- function checkResult(message) {return {status:'needs-configuration',problems:[{code:'needs-configuration',message}]};}
158
- function checkProblems(problems) {return problems.length?{status:'needs-configuration',problems}:null;}
159
- function workspaceReadinessContext(value) {
160
- keys(value,['kind','workspace','deployment','soul','team','instance','home'],['kind','workspace','deployment','soul']);
161
- if(value.kind!=='workspace' || typeof value.workspace!=='string' || !value.workspace.trim() || typeof value.deployment!=='string' || !value.deployment.trim() || typeof value.soul!=='string' || !value.soul.trim()) wireError('invalid-binding');
162
- if(value.team!==null && value.team!==undefined && (typeof value.team!=='string' || !value.team.trim())) wireError('invalid-binding');
163
- if(value.instance!==null && value.instance!==undefined && (typeof value.instance!=='string' || !value.instance.trim())) wireError('invalid-binding');
164
- if(value.home!==null && value.home!==undefined && (typeof value.home!=='string' || !value.home.trim())) wireError('invalid-binding');
165
- return value;
166
- }
167
- function yamlScalar(text,key){const m=String(text).match(new RegExp(`^${key}:\\s*["']?([^"'\\n#]+)["']?\\s*$`,'m'));return m?m[1].trim():undefined;}
168
- export const CUSTODY_ATTACH_MIN = '1.36.3';
169
- export const WAKE_STREAM_MIN = '1.36.5';
170
- const CLASSIC_REFUSAL = 'oats.aweb 1.14 needs OATS 0.26.0 or newer (workspace model); on an older kernel pin oats.aweb v1.13.x';
171
- function classicEnv(env=process.env) {return !!env.OATS_TEAM_SCOPE && !(env.OATS_WORKSPACE_KEY || env.OATS_WORKSPACE_NAME || env.OATS_TEAM_LABEL);}
172
- export function grantYamlCustodySocket(text) {
173
- const lines=String(text??'').split(/\r?\n/);let inCustody=false,baseIndent=0;
174
- for(const line of lines) {
175
- const custody=/^(\s*)custody:\s*(?:#.*)?$/.exec(line);if(custody){inCustody=true;baseIndent=custody[1].length;continue;}
176
- if(inCustody){const ind=/^(\s*)/.exec(line)?.[1].length||0;if(line.trim()&&ind<=baseIndent)inCustody=false;const socket=/^\s*socket_path:\s*["']?([^"'\n#]+)["']?\s*$/.exec(line);if(socket)return socket[1].trim();}
177
- }
178
- return undefined;
179
- }
180
- function newestGrantHome(home) {
181
- if(typeof home!=='string'||!home.trim()) return null;
182
- try {
183
- const dirs=readdirSync(home).filter((name)=>name==='.aweb-identity'||/^\.aweb-identity-\d+$/.test(name)).map((name)=>join(home,name)).filter((p)=>{try{return statSync(p).isDirectory();}catch{return false;}}).map((p)=>{try{return {path:p,mtime:statSync(p).mtimeMs};}catch{return {path:p,mtime:0};}}).sort((a,b)=>b.mtime-a.mtime||b.path.localeCompare(a.path));
184
- return dirs[0]?.path||null;
185
- } catch {return null;}
186
- }
187
- function grantAttachmentProblem(home) {
188
- const grantHome=newestGrantHome(home);
189
- if(!grantHome) return null;
190
- const grantYaml=join(grantHome,'grant.yaml');
191
- if(!existsSync(grantYaml)) return null;
192
- let text;try{text=readFileSync(grantYaml,'utf8');}catch{return null;}
193
- if(grantYamlCustodySocket(text)) return null;
194
- const id=yamlScalar(text,'grant_id')||'<unknown>';
195
- return {code:'custody',message:`grant ${id} is not attached to custody; retire and respawn on aw >= ${CUSTODY_ATTACH_MIN}`};
196
- }
197
- function activeTeamAt(root){try{const text=readFileSync(join(resolve(root),'.aw','teams.yaml'),'utf8');return yamlScalar(text,'active_team')||yamlScalar(text,'active');}catch{return undefined;}}
198
- function residentCustodyRoot(settings){const identity=obj(settings.identity)?settings.identity:{},residents=obj(settings.residents)?settings.residents:{};const name=typeof identity.resident==='string'?identity.resident:'';const root=name&&typeof residents[name]==='string'?residents[name]:undefined;return identity.mode==='global'&&root&&isAbsolute(root)?root:undefined;}
199
- function teamFromSettings(settings,candidate,{env=process.env}={}) {
200
- const configured=typeof settings.team==='string' && settings.team.trim()?settings.team.trim():undefined;
201
- if(configured) return configured;
202
- const custody=residentCustodyRoot(settings);if(custody)return activeTeamAt(custody);
203
- return candidate?.root && isAbsolute(candidate.root) ? activeTeamAt(candidate.root) : undefined;
204
- }
205
- function rootCandidate(settings,team,{deployment,env=process.env}={}) {
206
- const roots=obj(settings.roots)?settings.roots:{};
207
- if(team && typeof roots[team]==='string' && roots[team].trim()) return {root:roots[team].trim(),key:`settings.oats.aweb.roots[${JSON.stringify(team)}]`,declared:true};
208
- if(typeof settings.root==='string' && settings.root.trim()) return {root:settings.root.trim(),key:'settings.oats.aweb.root',declared:true};
209
- const candidates=[env.OATS_WORKSPACE || deployment || process.cwd()];
210
- for(const root of candidates) if(isAbsolute(root) && existsSync(join(resolve(root),'.aw'))) return {root,key:'settings.oats.aweb.root',declared:false};
211
- return {root:candidates[0] || process.cwd(),key:'settings.oats.aweb.root',declared:false};
212
- }
213
- function parseOatsTeams(env=process.env){try{const rows=JSON.parse(env.OATS_TEAMS||'[]');return Array.isArray(rows)?rows.filter(r=>r&&typeof r==='object').map(r=>({label:String(r.label||''),team:typeof r.team==='string'?r.team:null,mapped:r.mapped===true,payload:obj(r.payload)?r.payload:{}})):[];}catch{return [];}}
214
- function primaryTeamLabel(env=process.env){return env.OATS_TEAM_LABEL || String(env.OATS_TEAM_LABELS||'').split(',').map(s=>s.trim()).filter(Boolean)[0] || null;}
215
- function unmappedPrimary(env=process.env){const primary=primaryTeamLabel(env);return primary?parseOatsTeams(env).find(t=>t.label===primary&&!t.mapped):undefined;}
216
- function joinedTeams(home){if(!home)return[];try{const doc=JSON.parse(readFileSync(join(home,'.oats-aweb','teams.json'),'utf8'));return Array.isArray(doc.joinedTeams)?doc.joinedTeams.filter(j=>j&&typeof j==='object'&&j.label&&j.team&&j.identityHome):[];}catch{return[];}}
217
- function readinessDetails(settings,{deployment,env=process.env}={}) {
218
- if(classicEnv(env)) return {team:undefined,candidate:null,warnings:[],result:{status:'needs-configuration',problems:[{code:'needs-configuration',message:CLASSIC_REFUSAL}]}};
219
- const initialTeam=typeof settings.team==='string' && settings.team.trim()?settings.team.trim():undefined;
220
- const candidate=rootCandidate(settings,initialTeam,{deployment,env}),team=teamFromSettings(settings,candidate,{env}),problems=[],warnings=[];
221
- if(!candidate.root || !isAbsolute(candidate.root) || !existsSync(join(resolve(candidate.root),'.aw'))) problems.push({code:'needs-configuration',message:`no messaging root at ${candidate.root?resolve(candidate.root):process.cwd()}: run oats aweb setup there or set ${candidate.key}`});
222
- const unmapped=unmappedPrimary(env);if(unmapped&&team)warnings.push({code:'team-unmapped',message:`workspace label ${unmapped.label} is not mapped; using the default team ${team}`});
223
- if(!team) problems.push({code:'needs-configuration',message:'no team: set settings.oats.aweb.team or keep an active team at the aweb root'});
224
- return {team,candidate,warnings,result:checkProblems(problems) || {status:'ready',problems:[]}};
225
- }
226
- function readinessFromSettings(settings,options) {return readinessDetails(settings,options).result;}
227
- function runAw(argv,cwd,{unsetEnv=[],timeout=60000}={}) {
228
- // An inherited AWEB_IDENTITY_HOME is the caller's identity, never this check's.
229
- const env={...process.env};delete env.AWEB_IDENTITY_HOME;for(const name of unsetEnv) delete env[name];
230
- try {return execFileSync(argv[0],argv.slice(1),{cwd,env,encoding:'utf8',stdio:['ignore','pipe','pipe'],timeout}).trim();}
231
- catch(e) {throw new Error(`${argv.slice(0,3).join(' ')} failed${e.status===undefined?'':` (exit ${e.status})`}`);}
232
- }
233
- function semverLt(a,b) {const A=String(a||'0.0.0').split('.').map(n=>Number(n)||0),B=String(b).split('.').map(n=>Number(n)||0);for(let i=0;i<3;i++){if((A[i]||0)!==(B[i]||0)) return (A[i]||0)<(B[i]||0);}return false;}
234
- function wakeReadiness(home,{reliedOn=false}={}) {
235
- if(!home || !reliedOn) return {problems:[],warnings:[]};
236
- try {
237
- const doc=JSON.parse(runAw(['aw','wake','status','--json'],home,{timeout:10000}));
238
- const state=doc.daemon_version_state || (doc.daemon_running===false?'not_running':doc.daemon_version?'reported':'unknown');
239
- if(state==='reported') {
240
- const running=String(doc.daemon_version||'unknown');
241
- if(semverLt(running,WAKE_STREAM_MIN)) return {problems:[{code:'wake-daemon-outdated',message:`host wake daemon is running ${running}; required ${WAKE_STREAM_MIN}; upgrade aw, then restart the host wake daemon`}],warnings:[]};
242
- return {problems:[],warnings:[]};
243
- }
244
- if(state==='not_running') return {problems:[{code:'wake-daemon-not-running',message:'host wake daemon is not running; session delivery relies on it'}],warnings:[]};
245
- return {problems:[],warnings:[{code:'wake-daemon-version-unknown',message:`host wake daemon version is unknown; compatibility unproven; required ${WAKE_STREAM_MIN}; upgrade aw, then restart the host wake daemon`}]};
246
- } catch {return {problems:[],warnings:[{code:'wake-daemon-version-unknown',message:`host wake daemon version is unknown; compatibility unproven; required ${WAKE_STREAM_MIN}; upgrade aw, then restart the host wake daemon`}]};}
247
- }
248
- function workspaceReadinessPhase(req) {
249
- const ctx=workspaceReadinessContext(req.input.context);
250
- if(!obj(req.input.action) || req.input.action.kind!=='readiness') wireError('invalid-binding');
251
- const details=readinessDetails(req.settings,{deployment:ctx.deployment}),problems=[...details.result.problems],warnings=[...details.warnings];
252
- const identity=obj(req.settings.identity)?req.settings.identity:{},mode=identity.mode===undefined || identity.mode===null || identity.mode===''?'local':String(identity.mode);
253
- if(mode==='global') {
254
- const grantProblem=grantAttachmentProblem(ctx.home);if(grantProblem) problems.push(grantProblem);
255
- const resident=typeof identity.resident==='string' && identity.resident.trim()?identity.resident.trim():undefined;
256
- const residents=obj(req.settings.residents)?req.settings.residents:{};
257
- const custody=resident?residents[resident]:undefined;
258
- if(!resident) problems.push({code:'custody',message:'identity.mode "global" requires identity.resident; set oats-local.yaml settings.oats.aweb.residents.<name> to the absolute custody directory for that resident identity'});
259
- else if(typeof custody!=='string' || !isAbsolute(custody) || !existsSync(join(custody,'.aw','identity.yaml'))) problems.push({code:'custody',message:`identity.mode "global" resident ${JSON.stringify(resident)} is not resolvable; set oats-local.yaml settings.oats.aweb.residents.${resident} to an absolute custody directory whose .aw/identity.yaml exists`});
260
- else if(details.team) {
261
- try {
262
- const preflight=custodyPreflight({custody,resident,team:details.team,e2eeRequired:identity.e2ee!==false,fatalOnError:false,runAw:(argv,cwd,options={})=>runAw(argv,cwd,{...options,timeout:20000})});
263
- for(const message of preflight.warnings) warnings.push({code:'e2ee-disabled',message});
264
- }
265
- catch(e) {problems.push({code:'custody',message:e.message});}
266
- }
267
- }
268
- const joined=joinedTeams(ctx.home);
269
- if(joined.length) {
270
- let status;try{status=JSON.parse(runAw(['aw','wake','status','--json'],ctx.home,{timeout:10000}));}catch{status=undefined;}
271
- const why={'home-not-registered':'this home is not registered with the host wake broker','not-registered-with-broker':'its identity home is not registered with the host wake broker','wake-daemon-not-running':'the host wake daemon is not running','stream-not-admitted':'the host wake broker has not admitted its stream'};
272
- for(const mode of joinedReceiveModes(status,{home:ctx.home,joined})) {
273
- const row=joined.find(j=>j.label===mode.label);
274
- if(mode.receive==='native') warnings.push({code:'joined-team-receive',message:`joined team ${mode.label} receives native through the host wake broker (stream ${mode.phase})`});
275
- else warnings.push({code:'joined-team-poll-only',message:`joined team ${mode.label} receives by polling: ${status?why[mode.reason]||mode.reason:'aw wake status is unavailable'}${mode.detail?` (${mode.detail})`:''}; check aw --identity-home ${row.identityHome} mail inbox and chat pending at task boundaries`});
276
- }
277
- }
278
- const wake=String(req.settings.delivery||'channel')==='session'?wakeReadiness(ctx.home,{reliedOn:true}):{problems:[],warnings:[]};
279
- problems.push(...wake.problems);warnings.push(...wake.warnings);
280
- const result=checkProblems(problems) || {status:'ready',problems:[]};
281
- return {...result,warnings};
282
- }
283
- function checkPhase(req) {
284
- keys(req.input,['binding','context','action','invocation'],['context','action']);
285
- if(!obj(req.input.action) || typeof req.input.action.kind!=='string') wireError('invalid-binding');
286
- if(!Object.hasOwn(req.input,'binding')) return workspaceReadinessPhase(req);
287
- const current=validateAwebBinding(binding(req.input.binding)),selectedContext=context(req.input.context,{request:true});
288
- if(!same(selectedContext,current.payload.context)) wireError('invalid-binding');
289
- const invocation=Object.hasOwn(req.input,'invocation')?validateAwebInvocationContext(req.input.invocation,current,{context:req.input.context,action:req.input.action}):null;
290
- if(['command','hook','operation'].includes(req.input.action.kind) && (!invocation || invocation.instance===null || invocation.intent===null)) return checkResult('an admitted captured instance intent is required for execution');
291
- if(current.payload.privateTeam===null) return checkResult('an explicit private-team binding is required');
292
- const hostReady=readinessFromSettings(req.settings);
293
- if(hostReady.status!=='ready') return hostReady;
294
- if(!invocation) return hostReady;
295
- // Read-only public kernel observations, not an account/grant attestation.
296
- // Native setup is performed only by the separately admitted execution path.
297
- return assessCapturedSessionReadiness({binding:current,invocation,settings:req.settings});
298
- }
299
-
300
- export function handleBindingRequest(phase,value) {
301
- if(!phases.has(phase)) wireError('invalid-binding');
302
- const req=request(value,phase);
303
- if(phase==='normalize') return normalizePhase(req);
304
- if(phase==='bind') return bindPhase(req);
305
- return checkPhase(req);
306
- }
307
- function response(phase,body) {return {schemaVersion:1,phase,slot:SLOT,capability:CAPABILITY,...body};}
308
- function errorCode(error) {if(errorCodes.has(error?.wireCode)) return error.wireCode;if(errorCodes.has(error?.code)) return error.code;return 'invalid-binding';}
309
- // Only these literal domain diagnostics may cross the wire. Never reflect a
310
- // caught exception's dynamic alias/key/path, native stderr or credential text.
311
- const safeReasons=new Map([
312
- ['needs-configuration',[
313
- 'messaging-enabled standalone preparation needs an explicit context key',
314
- 'messaging binding needs one soul declaration',
315
- 'messaging workspace must declare private: per-human',
316
- 'an explicit responsible-human binding is required',
317
- 'an explicit wider-membership consent list is required',
318
- 'a selected wider-team binding is required',
319
- 'a selected wider alias needs an explicit workspace team mapping',
320
- ]],
321
- ['requirement-conflict',['multiple soul messaging declarations','adoption team aliases have conflicting mappings']],
322
- ]);
323
- const fallbackReasons=Object.freeze({
324
- 'needs-configuration':'messaging settings and explicit binding selections are required',
325
- 'requirement-conflict':'messaging declarations contain incompatible requirements',
326
- 'invalid-binding':'messaging input must match the supported binding contract',
327
- 'authorization-required':'explicit native messaging authorization is required',
328
- 'host-requirement-missing':'a required native messaging host resource is unavailable',
329
- 'provider-unavailable':'the selected messaging provider is unavailable',
330
- 'provider-not-qualified':'the requested messaging configuration is not qualified',
331
- });
332
- function errorProblem(error) {
333
- const code=errorCode(error),message=safeReasons.get(code)?.find(literal=>literal===error?.message)??fallbackReasons[code];
334
- return {code,message};
335
- }
336
- function enforceOutputLimits(value,depth=1,state={entries:0}) {
337
- if(depth>BINDING_WIRE_LIMITS.depth) wireError('provider-not-qualified');
338
- if(value===null || typeof value!=='object') return;
339
- for(const child of Array.isArray(value)?value:Object.values(value)) {
340
- if(++state.entries>BINDING_WIRE_LIMITS.entries) wireError('provider-not-qualified');
341
- enforceOutputLimits(child,depth+1,state);
342
- }
343
- }
344
- export async function runBindingWire(phase,input=process.stdin,output=process.stdout) {
345
- let answer;
346
- try {
347
- const chunks=[];let length=0;
348
- for await(const chunk of input) {const bytes=Buffer.from(chunk);length+=bytes.length;if(length>BINDING_WIRE_LIMITS.bytes) wireError('invalid-binding');chunks.push(bytes);}
349
- const result=handleBindingRequest(phase,parseBindingJson(Buffer.concat(chunks,length)));
350
- answer=response(phase,{ok:true,result});
351
- } catch(error) {answer=response(phases.has(phase)?phase:'check',{ok:false,error:errorProblem(error)});}
352
- let bytes;
353
- try {enforceOutputLimits(answer);bytes=Buffer.from(JSON.stringify(answer)+'\n');if(bytes.length>BINDING_WIRE_LIMITS.bytes) wireError('provider-not-qualified');}
354
- catch {bytes=Buffer.from(JSON.stringify(response(phases.has(phase)?phase:'check',{ok:false,error:{code:'provider-not-qualified',message:'messaging response exceeds the supported wire limits'}}))+'\n');}
355
- output.write(bytes);return answer.ok;
356
- }
@@ -1,91 +0,0 @@
1
- // Execution-only consumption of the kernel's selected private snapshots.
2
- // This establishes correspondence/custody, NOT native human/team authority.
3
- // No kernel index, source checkout, ambient team/root, or credential is read.
4
- import fs from 'node:fs';
5
- import {dirname,isAbsolute,resolve} from 'node:path';
6
- import {parseBindingJson,BINDING_WIRE_LIMITS} from './binding-wire.mjs';
7
- import {INVOCATION_LIMITS,sameInvocationJson as same} from './invocation-shape.mjs';
8
- import {validateAwebBinding,validateAwebInvocationContext} from './portable-binding.mjs';
9
- const CAPABILITY='oats.aweb';
10
- const RECEIPT_LIMITS={bytes:256*1024,depth:32,entries:16384};
11
- const markers=['OATS_BINDING_FILE','OATS_INVOCATION_CONTEXT_FILE','OATS_SOURCE_RECEIPT_FILE'];
12
- const invalid=()=>{throw Object.assign(new Error('invalid captured aweb execution input'),{code:'invalid-binding'});};
13
- const absentAuthority=()=>{throw Object.assign(new Error('captured aweb action requires an admitted instance intent'),{code:'authorization-required'});};
14
- const absolute=p=>typeof p==='string'&&!p.includes('\0')&&isAbsolute(p)&&resolve(p)===p;
15
- const keys=(value,names)=>value!==null&&typeof value==='object'&&!Array.isArray(value)&&Object.keys(value).length===names.length&&names.every(k=>Object.hasOwn(value,k));
16
- const statKey=s=>['dev','ino','size','mtimeNs','ctimeNs'].map(k=>String(s[k])).join(':');
17
- function physical(file){
18
- if(!absolute(file))invalid();
19
- for(let p=file;;p=dirname(p)){
20
- const s=fs.lstatSync(p,{bigint:true});
21
- if(s.isSymbolicLink()||(p!==file&&!s.isDirectory()))invalid();
22
- if(dirname(p)===p)return s;
23
- }
24
- }
25
- /** Pin the physical named file to its open descriptor BEFORE every bounded
26
- * read, including when an ancestor was redirected only during open(). */
27
- function snapshot(file,limits){
28
- let fd;
29
- try{
30
- physical(file);
31
- const uid=typeof process.getuid==='function'?BigInt(process.getuid()):null;
32
- const privateFile=s=>s.isFile()&&s.nlink===1n&&s.size<=BigInt(limits.bytes)&&(s.mode&0o7777n)===0o600n&&(uid===null||s.uid===uid);
33
- const before=fs.lstatSync(file,{bigint:true});
34
- if(!privateFile(before)||typeof fs.constants.O_NOFOLLOW!=='number'||typeof fs.constants.O_NONBLOCK!=='number')invalid();
35
- fd=fs.openSync(file,fs.constants.O_RDONLY|fs.constants.O_NOFOLLOW|fs.constants.O_NONBLOCK);
36
- const check=()=>{
37
- physical(file);const named=fs.lstatSync(file,{bigint:true}),opened=fs.fstatSync(fd,{bigint:true});
38
- if(!privateFile(named)||!privateFile(opened)||statKey(named)!==statKey(before)||statKey(opened)!==statKey(named))invalid();
39
- };
40
- check();const bytes=Buffer.alloc(Number(before.size));
41
- for(let n=0;n<bytes.length;){check();const count=fs.readSync(fd,bytes,n,Math.min(32768,bytes.length-n),n);if(!count)invalid();n+=count;}
42
- check();return {value:parseBindingJson(bytes,limits),identity:statKey(before)};
43
- }catch{invalid();}finally{if(fd!==undefined)fs.closeSync(fd);}
44
- }
45
- function freeze(value){if(value&&typeof value==='object'){for(const v of Object.values(value))freeze(v);Object.freeze(value);}return value;}
46
- function validateSourceReceipt(receipt,context,binding){
47
- if(!keys(receipt,['schemaVersion','kind','home','work','context','agent','instance','sourceIdentity','role','executionBinding','responsibleHuman','binding']))invalid();
48
- const i=context.instance;
49
- if(!i||receipt.schemaVersion!==1||receipt.kind!==context.subject.kind||receipt.home!==i.home||receipt.work!==i.work||receipt.context!==context.executionBinding.deployment||receipt.agent!==i.agent||receipt.instance!==i.name||typeof receipt.role!=='string'||!receipt.role.trim()||!same(receipt.executionBinding,context.executionBinding)||!same(receipt.responsibleHuman,context.responsibleHuman)||!same(receipt.binding,binding))invalid();
50
- if(context.action.kind!=='hook'||!['spawn','retire'].includes(context.action.name)||!same(receipt.sourceIdentity,context.subject.kind==='persistent'?context.subject.soul.identity:null))invalid();
51
- }
52
- /** Absence is explicitly legacy. Any supplied input requires the full selected
53
- * binding+invocation pair; invalid-present can never select legacy behavior.
54
- * assertCurrent is for every native boundary, not a new admission mechanism. */
55
- export function loadCapturedAwebExecution(env=process.env){
56
- if(!markers.some(k=>Object.hasOwn(env,k)))return {kind:'legacy'};
57
- if(!Object.hasOwn(env,markers[0])||!Object.hasOwn(env,markers[1]))invalid();
58
- const files=markers.filter(k=>Object.hasOwn(env,k)).map(k=>env[k]);if(new Set(files).size!==files.length)invalid();
59
- const inputs=files.map((p,i)=>snapshot(p,[BINDING_WIRE_LIMITS,INVOCATION_LIMITS,RECEIPT_LIMITS][i]));
60
- let binding,context;
61
- try{binding=validateAwebBinding(inputs[0].value);context=validateAwebInvocationContext(inputs[1].value,binding);}catch{invalid();}
62
- if(inputs[2])validateSourceReceipt(inputs[2].value,context,binding);
63
- const assertCurrent=()=>{
64
- files.forEach((p,i)=>{const current=snapshot(p,[BINDING_WIRE_LIMITS,INVOCATION_LIMITS,RECEIPT_LIMITS][i]);if(current.identity!==inputs[i].identity||!same(current.value,inputs[i].value))invalid();});
65
- };
66
- assertCurrent();
67
- return {kind:'captured',binding:freeze(binding),context:freeze(context),sourceReceipt:inputs[2]?freeze(inputs[2].value):null,assertCurrent};
68
- }
69
- /** Exact entrypoint and retained instance, not OATS_META/current config, choose
70
- * the actor's target. The kernel still owns admission and replay. */
71
- export function requireCapturedAwebAction(loaded,event,manifest,{env=process.env,cwd=process.cwd()}={}){
72
- if(loaded.kind!=='captured'||manifest.capability!==CAPABILITY)invalid();
73
- const {context}=loaded,a=context.action,i=context.instance;
74
- if(a.kind==='hook'){
75
- if(a.capability!==CAPABILITY||a.name!==event||!Object.hasOwn(manifest.hooks||{},event))invalid();
76
- }else if(a.kind==='command'){
77
- if(a.name!==event||!Object.hasOwn(manifest.commands||{},event)||(a.capability!==CAPABILITY&&a.namespace!==manifest.command))invalid();
78
- }else invalid();
79
- if(!i||!context.intent)absentAuthority();
80
- if(cwd!==i.home)invalid();
81
- for(const [key,value] of Object.entries({OATS_HOME:i.home,OATS_INSTANCE_HOME:i.home,OATS_INSTANCE:i.name,OATS_AGENT:i.agent,OATS_CONTEXT:context.executionBinding.deployment,OATS_DEPLOYMENT:context.executionBinding.deployment,OATS_RESOLUTION:context.executionBinding.resolution.id,OATS_CAPABILITY:CAPABILITY}))if(Object.hasOwn(env,key)&&env[key]!==value)invalid();
82
- if(a.kind==='hook'&&Object.hasOwn(env,'OATS_EVENT')&&env.OATS_EVENT!==event)invalid();
83
- const directories=[i.home,i.work,context.executionBinding.deployment].map(p=>{
84
- try{physical(p);const s=fs.lstatSync(p,{bigint:true});if(!s.isDirectory())invalid();return {path:p,dev:s.dev,ino:s.ino};}catch{invalid();}
85
- });
86
- const assertCurrent=()=>{
87
- loaded.assertCurrent();
88
- for(const d of directories){try{physical(d.path);const s=fs.lstatSync(d.path,{bigint:true});if(!s.isDirectory()||s.dev!==d.dev||s.ino!==d.ino)invalid();}catch{invalid();}}
89
- };
90
- assertCurrent();return {binding:loaded.binding,context,sourceReceipt:loaded.sourceReceipt,assertCurrent};
91
- }
@@ -1,91 +0,0 @@
1
- // Selected-input adapter over EXISTING native aw commands. No account/grant
2
- // protocol, credential copying, private kernel index or second admission store.
3
- import fs from 'node:fs';
4
- import {join} from 'node:path';
5
- import {parseBindingJson,BINDING_WIRE_LIMITS} from './binding-wire.mjs';
6
- const fail=(code,message)=>{throw Object.assign(new Error(message),{code});};
7
- function identityDirectory(home,{required=true}={}){
8
- const path=join(home,'.aw');
9
- try{const s=fs.lstatSync(path);if(!s.isDirectory()||s.isSymbolicLink()||fs.realpathSync(path)!==path)fail('needs-configuration','native identity directory must be physical and local to its selected home');return path;}
10
- catch(e){if(e.code==='ENOENT'&&!required)return null;if(e.code==='needs-configuration')throw e;fail('needs-configuration','the selected home needs an initialized native aw identity');}
11
- }
12
- /** `run` is the same bounded, argv-only, redacting native runner used by legacy
13
- * hooks. The execution consumer and each call retain the original snapshots.
14
- * This function is not callable as a substitute for kernel admission. */
15
- export function runCapturedNative({selected,event,settings,run,env=process.env}){
16
- const {context,binding,assertCurrent}=selected,{home,name}=context.instance,team=binding.payload.privateTeam?.id;
17
- let meta=context.priorReceipt===null?null:JSON.parse(JSON.stringify(context.priorReceipt)),invitationToken=null;
18
- const roots=new Map();
19
- const nativeIdentity=(dir,options)=>{
20
- const path=identityDirectory(dir,options);
21
- if(!path){if(roots.has(join(dir,'.aw')))fail('invalid-binding','native identity directory disappeared during the captured action');return null;}
22
- const s=fs.lstatSync(path),stamp=`${s.dev}:${s.ino}`,prior=roots.get(path);
23
- if(prior!==undefined&&prior!==stamp)fail('invalid-binding','native identity directory changed during the captured action');
24
- roots.set(path,stamp);return path;
25
- };
26
- const refusal=(code,message)=>({exitCode:1,output:{...(meta?{meta}:{}),status:'needs-configuration',problems:[{code,message}],warning:`oats-aweb: ${message}; retain the home and any recorded partial effects`}});
27
- try{
28
- assertCurrent();
29
- if(!/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(name)||!team)fail('needs-configuration','exact native alias and private-team selection are required');
30
- if(settings.delivery!=='session')fail('needs-configuration','captured delivery must be session');
31
- if(binding.payload.wider.length)fail('needs-configuration','selected wider memberships need their explicitly qualified native setup; they were not omitted');
32
- // A global override may select a different actor even with an exact cwd.
33
- // Do not clear/replace it or borrow that identity to make setup pass.
34
- if(env.AWEB_IDENTITY_HOME)fail('needs-configuration','captured setup requires native per-directory identity context, not an external AWEB_IDENTITY_HOME override');
35
- const call=(args,cwd=home,{secret=false,allowNativeRemoval=false}={})=>{
36
- assertCurrent();nativeIdentity(cwd,{required:false});
37
- const out=run(['aw',...args],cwd,45000,{secretSafe:true});
38
- assertCurrent();
39
- if(allowNativeRemoval){
40
- try{fs.lstatSync(join(cwd,'.aw'));nativeIdentity(cwd,{required:false});}catch(e){if(e.code!=='ENOENT')throw e;}
41
- }else nativeIdentity(cwd,{required:false});
42
- try{return parseBindingJson(Buffer.from(out),BINDING_WIRE_LIMITS);}catch{if(secret)fail('provider-unavailable','native invitation response is unavailable');fail('provider-unavailable','native command returned no bounded JSON observation');}
43
- };
44
- const plain=(args)=>{assertCurrent();nativeIdentity(home);run(['aw',...args],home,60000,{secretSafe:true});assertCurrent();nativeIdentity(home);};
45
- const observe=()=>{
46
- nativeIdentity(home);
47
- const who=call(['--team',team,'whoami','--json']);
48
- const status=call(['--team',team,'workspace','status','--json']),ws=status.workspace;
49
- if(who.alias!==name||typeof who.did!=='string'||!/^did:key:[A-Za-z0-9]+$/.test(who.did)||status.selected_team!==team||!ws||ws.alias!==name||ws.workspace_path!==home)fail('needs-configuration','native alias, team and workspace must match the selected instance home');
50
- if(invitationToken&&who.did.includes(invitationToken))fail('provider-unavailable','native identity response cannot be safely recorded');
51
- if(meta?.did!==undefined&&meta.did!==who.did)fail('needs-configuration','native identity changed from the recorded setup; explicit reconciliation is required');
52
- return who.did;
53
- };
54
- if(event==='setup'){
55
- if(!nativeIdentity(home,{required:false}))return{exitCode:0,output:{status:'needs-configuration',problems:[{code:'needs-configuration',message:'use explicit captured spawn with existing native authority in the selected deployment; setup does not provision accounts or teams'}]}};
56
- observe();return{exitCode:0,output:{status:'ready',problems:[],scope:'native home association only; no account or private-grant attestation'}};
57
- }
58
- if(event==='roster'){
59
- observe();const result=call(['id','team','members','--team-id',team,'--json']);return{exitCode:0,output:result};
60
- }
61
- if(event==='spawn'){
62
- if(meta!==null||nativeIdentity(home,{required:false}))fail('needs-configuration','existing or uncertain native setup requires explicit reconciliation; no duplicate enrollment');
63
- // Deployment is an explicit retained context, never an ambient root search.
64
- // Native aw itself decides whether its existing identity may issue an invite.
65
- const authority=context.executionBinding.deployment;nativeIdentity(authority);
66
- meta={team,delivery:'session',pending:'invite'};
67
- const invitation=call(['team','invite','--team-id',team,'--json'],authority,{secret:true});
68
- if(typeof invitation?.token!=='string'||!invitation.token)fail('provider-unavailable','native invitation returned no token');
69
- invitationToken=invitation.token;
70
- meta.pending='join';
71
- // Native aw mints the child's own identity; OATS never reads/copies a key.
72
- const joined=call(['team','join',invitation.token,'--name',name,'--json'],home,{secret:true});
73
- // Never echo server strings or a token disguised as an alias into metadata.
74
- if(joined?.alias!==name||joined?.team_id!==team)fail('provider-unavailable','native join did not confirm the exact requested alias and team');
75
- meta={team,alias:name,delivery:'session',pending:'connect'};
76
- plain(['--team',team,'init','--do-not-touch-agents-md']);
77
- const did=observe();meta={team,alias:name,delivery:'session',did,pending:'wake-register'};
78
- plain(['wake','register','--home',home,'--identity-home',join(home,'.aw'),'--delivery','session']);
79
- meta={team,alias:name,delivery:'session',did};
80
- return{exitCode:0,output:{meta,env:{AWEB_DELIVERY:'session'},brief:`Comms: your own native aweb identity is ${name} on ${team}. Notification delivery: external (session); registration is durable, not proof of broker delivery or model consumption. Use aw mail/aw chat from this instance home.`}};
81
- }
82
- if(event==='retire'){
83
- if(!meta||meta.alias!==name||meta.team!==team||meta.delivery!=='session'||typeof meta.did!=='string')fail('needs-configuration','recorded owned native identity is required for cleanup; absence is not proof of no effects');
84
- observe();plain(['wake','deregister','--home',home]);
85
- const result=call(['--team',team,'workspace','delete',name,'--json'],home,{allowNativeRemoval:true});
86
- if(result?.alias!==name||typeof result.deleted_at!=='string')fail('provider-unavailable','native self-delete outcome is unconfirmed');
87
- return{exitCode:0,output:{meta:{...meta,retired:true,aliasReusable:result.alias_released===true},...(result.alias_released===true?{}:{warning:'oats-aweb: workspace deleted; alias release was not confirmed'})}};
88
- }
89
- fail('needs-configuration','this captured native entrypoint is unsupported');
90
- }catch(e){return refusal(['needs-configuration','invalid-binding','authorization-required'].includes(e?.code)?e.code:'provider-unavailable',['needs-configuration','invalid-binding','authorization-required'].includes(e?.code)?e.message:'native action failed or is uncertain');}
91
- }