@procedures/client 0.1.0 → 0.2.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 (334) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +5 -5
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/adapters/http/server/transport.d.ts.map +1 -1
  5. package/dist/adapters/http/server/transport.js +1 -0
  6. package/dist/adapters/http/server/transport.js.map +1 -1
  7. package/dist/browser.d.ts +6 -3
  8. package/dist/browser.d.ts.map +1 -1
  9. package/dist/browser.js +4 -1
  10. package/dist/browser.js.map +1 -1
  11. package/dist/client/client.d.ts +9 -1
  12. package/dist/client/client.d.ts.map +1 -1
  13. package/dist/client/client.js +27 -6
  14. package/dist/client/client.js.map +1 -1
  15. package/dist/client/types.d.ts +9 -0
  16. package/dist/client/types.d.ts.map +1 -1
  17. package/dist/client/types.js.map +1 -1
  18. package/dist/procedures/capability.d.ts +111 -0
  19. package/dist/procedures/capability.d.ts.map +1 -0
  20. package/dist/procedures/capability.js +269 -0
  21. package/dist/procedures/capability.js.map +1 -0
  22. package/dist/procedures/cli/discover.d.ts +3 -3
  23. package/dist/procedures/cli/discover.js +3 -3
  24. package/dist/procedures/cli/index.js +5 -3
  25. package/dist/procedures/cli/index.js.map +1 -1
  26. package/dist/procedures/cli/init.js +1 -1
  27. package/dist/procedures/collection/procedures.d.ts.map +1 -1
  28. package/dist/procedures/collection/procedures.js +12 -0
  29. package/dist/procedures/collection/procedures.js.map +1 -1
  30. package/dist/procedures/core/array.d.ts.map +1 -1
  31. package/dist/procedures/core/array.js +3 -1
  32. package/dist/procedures/core/array.js.map +1 -1
  33. package/dist/procedures/core/comparison.d.ts.map +1 -1
  34. package/dist/procedures/core/comparison.js +3 -1
  35. package/dist/procedures/core/comparison.js.map +1 -1
  36. package/dist/procedures/core/index.d.ts.map +1 -1
  37. package/dist/procedures/core/index.js +5 -2
  38. package/dist/procedures/core/index.js.map +1 -1
  39. package/dist/procedures/core/math.d.ts.map +1 -1
  40. package/dist/procedures/core/math.js +3 -1
  41. package/dist/procedures/core/math.js.map +1 -1
  42. package/dist/procedures/core/meta.d.ts.map +1 -1
  43. package/dist/procedures/core/meta.js +11 -6
  44. package/dist/procedures/core/meta.js.map +1 -1
  45. package/dist/procedures/core/object.d.ts.map +1 -1
  46. package/dist/procedures/core/object.js +3 -1
  47. package/dist/procedures/core/object.js.map +1 -1
  48. package/dist/procedures/core/string.d.ts.map +1 -1
  49. package/dist/procedures/core/string.js +3 -1
  50. package/dist/procedures/core/string.js.map +1 -1
  51. package/dist/procedures/core/type.d.ts.map +1 -1
  52. package/dist/procedures/core/type.js +3 -1
  53. package/dist/procedures/core/type.js.map +1 -1
  54. package/dist/procedures/define-procedure.d.ts.map +1 -1
  55. package/dist/procedures/define-procedure.js +5 -4
  56. package/dist/procedures/define-procedure.js.map +1 -1
  57. package/dist/procedures/effects.d.ts +15 -0
  58. package/dist/procedures/effects.d.ts.map +1 -0
  59. package/dist/procedures/effects.js +18 -0
  60. package/dist/procedures/effects.js.map +1 -0
  61. package/dist/procedures/index.d.ts +5 -2
  62. package/dist/procedures/index.d.ts.map +1 -1
  63. package/dist/procedures/index.js +8 -23
  64. package/dist/procedures/index.js.map +1 -1
  65. package/dist/procedures/interceptors.d.ts.map +1 -1
  66. package/dist/procedures/interceptors.js +2 -1
  67. package/dist/procedures/interceptors.js.map +1 -1
  68. package/dist/procedures/invoke.d.ts +36 -3
  69. package/dist/procedures/invoke.d.ts.map +1 -1
  70. package/dist/procedures/invoke.js +78 -12
  71. package/dist/procedures/invoke.js.map +1 -1
  72. package/dist/procedures/module.d.ts +13 -8
  73. package/dist/procedures/module.d.ts.map +1 -1
  74. package/dist/procedures/module.js +19 -9
  75. package/dist/procedures/module.js.map +1 -1
  76. package/dist/procedures/types.d.ts +100 -2
  77. package/dist/procedures/types.d.ts.map +1 -1
  78. package/dist/procedures/types.js.map +1 -1
  79. package/dist/register.d.ts +8 -0
  80. package/dist/register.d.ts.map +1 -0
  81. package/dist/register.js +9 -0
  82. package/dist/register.js.map +1 -0
  83. package/dist/server/host-policy.d.ts +24 -2
  84. package/dist/server/host-policy.d.ts.map +1 -1
  85. package/dist/server/host-policy.js +22 -0
  86. package/dist/server/host-policy.js.map +1 -1
  87. package/dist/server/procedure-server.d.ts +16 -2
  88. package/dist/server/procedure-server.d.ts.map +1 -1
  89. package/dist/server/procedure-server.js +26 -1
  90. package/dist/server/procedure-server.js.map +1 -1
  91. package/package.json +6 -4
  92. package/src/adapters/http/server/transport.ts +1 -0
  93. package/src/browser.ts +29 -0
  94. package/src/client/client.ts +33 -6
  95. package/src/client/types.ts +11 -0
  96. package/src/procedures/capability.ts +333 -0
  97. package/src/procedures/cli/discover.ts +3 -3
  98. package/src/procedures/cli/index.ts +5 -3
  99. package/src/procedures/cli/init.ts +1 -1
  100. package/src/procedures/collection/procedures.ts +12 -0
  101. package/src/procedures/core/array.ts +3 -1
  102. package/src/procedures/core/comparison.ts +3 -1
  103. package/src/procedures/core/index.ts +5 -2
  104. package/src/procedures/core/math.ts +3 -1
  105. package/src/procedures/core/meta.ts +11 -6
  106. package/src/procedures/core/object.ts +3 -1
  107. package/src/procedures/core/string.ts +3 -1
  108. package/src/procedures/core/type.ts +3 -1
  109. package/src/procedures/define-procedure.ts +5 -4
  110. package/src/procedures/effects.ts +24 -0
  111. package/src/procedures/index.ts +33 -22
  112. package/src/procedures/interceptors.ts +2 -1
  113. package/src/procedures/invoke.ts +112 -13
  114. package/src/procedures/module.ts +19 -9
  115. package/src/procedures/types.ts +113 -2
  116. package/src/register.ts +10 -0
  117. package/src/server/host-policy.ts +44 -2
  118. package/src/server/procedure-server.ts +44 -2
  119. package/dist/client/middleware/batching.d.ts +0 -102
  120. package/dist/client/middleware/batching.d.ts.map +0 -1
  121. package/dist/client/middleware/batching.js +0 -245
  122. package/dist/client/middleware/batching.js.map +0 -1
  123. package/dist/collections/async/async-queue.d.ts +0 -144
  124. package/dist/collections/async/async-queue.d.ts.map +0 -1
  125. package/dist/collections/async/async-queue.js +0 -347
  126. package/dist/collections/async/async-queue.js.map +0 -1
  127. package/dist/collections/async/channels.d.ts +0 -188
  128. package/dist/collections/async/channels.d.ts.map +0 -1
  129. package/dist/collections/async/channels.js +0 -312
  130. package/dist/collections/async/channels.js.map +0 -1
  131. package/dist/collections/behaviors/bounded.d.ts +0 -79
  132. package/dist/collections/behaviors/bounded.d.ts.map +0 -1
  133. package/dist/collections/behaviors/bounded.js +0 -384
  134. package/dist/collections/behaviors/bounded.js.map +0 -1
  135. package/dist/collections/behaviors/evented.d.ts +0 -154
  136. package/dist/collections/behaviors/evented.d.ts.map +0 -1
  137. package/dist/collections/behaviors/evented.js +0 -299
  138. package/dist/collections/behaviors/evented.js.map +0 -1
  139. package/dist/collections/behaviors/lru.d.ts +0 -84
  140. package/dist/collections/behaviors/lru.d.ts.map +0 -1
  141. package/dist/collections/behaviors/lru.js +0 -330
  142. package/dist/collections/behaviors/lru.js.map +0 -1
  143. package/dist/collections/behaviors/readonly.d.ts +0 -73
  144. package/dist/collections/behaviors/readonly.d.ts.map +0 -1
  145. package/dist/collections/behaviors/readonly.js +0 -158
  146. package/dist/collections/behaviors/readonly.js.map +0 -1
  147. package/dist/collections/behaviors/safe.d.ts +0 -163
  148. package/dist/collections/behaviors/safe.d.ts.map +0 -1
  149. package/dist/collections/behaviors/safe.js +0 -172
  150. package/dist/collections/behaviors/safe.js.map +0 -1
  151. package/dist/collections/behaviors/synchronized.d.ts +0 -88
  152. package/dist/collections/behaviors/synchronized.d.ts.map +0 -1
  153. package/dist/collections/behaviors/synchronized.js +0 -294
  154. package/dist/collections/behaviors/synchronized.js.map +0 -1
  155. package/dist/collections/behaviors/ttl.d.ts +0 -118
  156. package/dist/collections/behaviors/ttl.d.ts.map +0 -1
  157. package/dist/collections/behaviors/ttl.js +0 -338
  158. package/dist/collections/behaviors/ttl.js.map +0 -1
  159. package/dist/collections/core/effects.d.ts +0 -176
  160. package/dist/collections/core/effects.d.ts.map +0 -1
  161. package/dist/collections/core/effects.js +0 -180
  162. package/dist/collections/core/effects.js.map +0 -1
  163. package/dist/collections/core/events.d.ts +0 -192
  164. package/dist/collections/core/events.d.ts.map +0 -1
  165. package/dist/collections/core/events.js +0 -291
  166. package/dist/collections/core/events.js.map +0 -1
  167. package/dist/collections/core/middleware.d.ts +0 -231
  168. package/dist/collections/core/middleware.d.ts.map +0 -1
  169. package/dist/collections/core/middleware.js +0 -161
  170. package/dist/collections/core/middleware.js.map +0 -1
  171. package/dist/collections/core/policies.d.ts +0 -220
  172. package/dist/collections/core/policies.d.ts.map +0 -1
  173. package/dist/collections/core/policies.js +0 -48
  174. package/dist/collections/core/policies.js.map +0 -1
  175. package/dist/collections/core/traits.d.ts +0 -142
  176. package/dist/collections/core/traits.d.ts.map +0 -1
  177. package/dist/collections/core/traits.js +0 -8
  178. package/dist/collections/core/traits.js.map +0 -1
  179. package/dist/collections/examples.d.ts +0 -13
  180. package/dist/collections/examples.d.ts.map +0 -1
  181. package/dist/collections/examples.js +0 -302
  182. package/dist/collections/examples.js.map +0 -1
  183. package/dist/collections/fx/collectors.d.ts +0 -190
  184. package/dist/collections/fx/collectors.d.ts.map +0 -1
  185. package/dist/collections/fx/collectors.js +0 -369
  186. package/dist/collections/fx/collectors.js.map +0 -1
  187. package/dist/collections/fx/iter.d.ts +0 -166
  188. package/dist/collections/fx/iter.d.ts.map +0 -1
  189. package/dist/collections/fx/iter.js +0 -421
  190. package/dist/collections/fx/iter.js.map +0 -1
  191. package/dist/collections/impl/array-deque.d.ts +0 -107
  192. package/dist/collections/impl/array-deque.d.ts.map +0 -1
  193. package/dist/collections/impl/array-deque.js +0 -381
  194. package/dist/collections/impl/array-deque.js.map +0 -1
  195. package/dist/collections/impl/array-list.d.ts +0 -101
  196. package/dist/collections/impl/array-list.d.ts.map +0 -1
  197. package/dist/collections/impl/array-list.js +0 -387
  198. package/dist/collections/impl/array-list.js.map +0 -1
  199. package/dist/collections/impl/hash-map.d.ts +0 -141
  200. package/dist/collections/impl/hash-map.d.ts.map +0 -1
  201. package/dist/collections/impl/hash-map.js +0 -450
  202. package/dist/collections/impl/hash-map.js.map +0 -1
  203. package/dist/collections/impl/hash-set.d.ts +0 -125
  204. package/dist/collections/impl/hash-set.d.ts.map +0 -1
  205. package/dist/collections/impl/hash-set.js +0 -264
  206. package/dist/collections/impl/hash-set.js.map +0 -1
  207. package/dist/collections/impl/linked-hash-map.d.ts +0 -141
  208. package/dist/collections/impl/linked-hash-map.d.ts.map +0 -1
  209. package/dist/collections/impl/linked-hash-map.js +0 -472
  210. package/dist/collections/impl/linked-hash-map.js.map +0 -1
  211. package/dist/collections/impl/linked-list.d.ts +0 -109
  212. package/dist/collections/impl/linked-list.d.ts.map +0 -1
  213. package/dist/collections/impl/linked-list.js +0 -491
  214. package/dist/collections/impl/linked-list.js.map +0 -1
  215. package/dist/collections/impl/priority-queue.d.ts +0 -134
  216. package/dist/collections/impl/priority-queue.d.ts.map +0 -1
  217. package/dist/collections/impl/priority-queue.js +0 -336
  218. package/dist/collections/impl/priority-queue.js.map +0 -1
  219. package/dist/collections/impl/tree-map.d.ts +0 -146
  220. package/dist/collections/impl/tree-map.d.ts.map +0 -1
  221. package/dist/collections/impl/tree-map.js +0 -781
  222. package/dist/collections/impl/tree-map.js.map +0 -1
  223. package/dist/collections/impl/tree-set.d.ts +0 -112
  224. package/dist/collections/impl/tree-set.d.ts.map +0 -1
  225. package/dist/collections/impl/tree-set.js +0 -374
  226. package/dist/collections/impl/tree-set.js.map +0 -1
  227. package/dist/collections/index.d.ts +0 -105
  228. package/dist/collections/index.d.ts.map +0 -1
  229. package/dist/collections/index.js +0 -135
  230. package/dist/collections/index.js.map +0 -1
  231. package/dist/collections/interfaces/collection.d.ts +0 -119
  232. package/dist/collections/interfaces/collection.d.ts.map +0 -1
  233. package/dist/collections/interfaces/collection.js +0 -8
  234. package/dist/collections/interfaces/collection.js.map +0 -1
  235. package/dist/collections/interfaces/list.d.ts +0 -247
  236. package/dist/collections/interfaces/list.d.ts.map +0 -1
  237. package/dist/collections/interfaces/list.js +0 -8
  238. package/dist/collections/interfaces/list.js.map +0 -1
  239. package/dist/collections/interfaces/map.d.ts +0 -344
  240. package/dist/collections/interfaces/map.d.ts.map +0 -1
  241. package/dist/collections/interfaces/map.js +0 -8
  242. package/dist/collections/interfaces/map.js.map +0 -1
  243. package/dist/collections/interfaces/queue.d.ts +0 -304
  244. package/dist/collections/interfaces/queue.d.ts.map +0 -1
  245. package/dist/collections/interfaces/queue.js +0 -8
  246. package/dist/collections/interfaces/queue.js.map +0 -1
  247. package/dist/collections/interfaces/set.d.ts +0 -137
  248. package/dist/collections/interfaces/set.d.ts.map +0 -1
  249. package/dist/collections/interfaces/set.js +0 -8
  250. package/dist/collections/interfaces/set.js.map +0 -1
  251. package/dist/collections/storage/api.d.ts +0 -109
  252. package/dist/collections/storage/api.d.ts.map +0 -1
  253. package/dist/collections/storage/api.js +0 -182
  254. package/dist/collections/storage/api.js.map +0 -1
  255. package/dist/collections/storage/hybrid.d.ts +0 -164
  256. package/dist/collections/storage/hybrid.d.ts.map +0 -1
  257. package/dist/collections/storage/hybrid.js +0 -416
  258. package/dist/collections/storage/hybrid.js.map +0 -1
  259. package/dist/collections/storage/index.d.ts +0 -16
  260. package/dist/collections/storage/index.d.ts.map +0 -1
  261. package/dist/collections/storage/index.js +0 -13
  262. package/dist/collections/storage/index.js.map +0 -1
  263. package/dist/collections/storage/interface.d.ts +0 -141
  264. package/dist/collections/storage/interface.d.ts.map +0 -1
  265. package/dist/collections/storage/interface.js +0 -23
  266. package/dist/collections/storage/interface.js.map +0 -1
  267. package/dist/collections/storage/memory.d.ts +0 -68
  268. package/dist/collections/storage/memory.d.ts.map +0 -1
  269. package/dist/collections/storage/memory.js +0 -154
  270. package/dist/collections/storage/memory.js.map +0 -1
  271. package/dist/collections/utils/defaults.d.ts +0 -135
  272. package/dist/collections/utils/defaults.d.ts.map +0 -1
  273. package/dist/collections/utils/defaults.js +0 -322
  274. package/dist/collections/utils/defaults.js.map +0 -1
  275. package/dist/collections/utils/factories.d.ts +0 -144
  276. package/dist/collections/utils/factories.d.ts.map +0 -1
  277. package/dist/collections/utils/factories.js +0 -293
  278. package/dist/collections/utils/factories.js.map +0 -1
  279. package/dist/collections/utils/helpers.d.ts +0 -137
  280. package/dist/collections/utils/helpers.d.ts.map +0 -1
  281. package/dist/collections/utils/helpers.js +0 -221
  282. package/dist/collections/utils/helpers.js.map +0 -1
  283. package/dist/collections.d.ts +0 -12
  284. package/dist/collections.d.ts.map +0 -1
  285. package/dist/collections.js +0 -10
  286. package/dist/collections.js.map +0 -1
  287. package/dist/components/define.d.ts +0 -134
  288. package/dist/components/define.d.ts.map +0 -1
  289. package/dist/components/define.js +0 -486
  290. package/dist/components/define.js.map +0 -1
  291. package/dist/components/index.d.ts +0 -39
  292. package/dist/components/index.d.ts.map +0 -1
  293. package/dist/components/index.js +0 -38
  294. package/dist/components/index.js.map +0 -1
  295. package/dist/components/types.d.ts +0 -264
  296. package/dist/components/types.d.ts.map +0 -1
  297. package/dist/components/types.js +0 -46
  298. package/dist/components/types.js.map +0 -1
  299. package/dist/procedures/storage/adapter.d.ts +0 -148
  300. package/dist/procedures/storage/adapter.d.ts.map +0 -1
  301. package/dist/procedures/storage/adapter.js +0 -214
  302. package/dist/procedures/storage/adapter.js.map +0 -1
  303. package/dist/procedures/storage/api.d.ts +0 -125
  304. package/dist/procedures/storage/api.d.ts.map +0 -1
  305. package/dist/procedures/storage/api.js +0 -220
  306. package/dist/procedures/storage/api.js.map +0 -1
  307. package/dist/procedures/storage/factory.d.ts +0 -134
  308. package/dist/procedures/storage/factory.d.ts.map +0 -1
  309. package/dist/procedures/storage/factory.js +0 -166
  310. package/dist/procedures/storage/factory.js.map +0 -1
  311. package/dist/procedures/storage/hybrid.d.ts +0 -193
  312. package/dist/procedures/storage/hybrid.d.ts.map +0 -1
  313. package/dist/procedures/storage/hybrid.js +0 -519
  314. package/dist/procedures/storage/hybrid.js.map +0 -1
  315. package/dist/procedures/storage/index.d.ts +0 -17
  316. package/dist/procedures/storage/index.d.ts.map +0 -1
  317. package/dist/procedures/storage/index.js +0 -17
  318. package/dist/procedures/storage/index.js.map +0 -1
  319. package/dist/procedures/storage/procedures.d.ts +0 -39
  320. package/dist/procedures/storage/procedures.d.ts.map +0 -1
  321. package/dist/procedures/storage/procedures.js +0 -261
  322. package/dist/procedures/storage/procedures.js.map +0 -1
  323. package/dist/procedures/storage/serialization.d.ts +0 -123
  324. package/dist/procedures/storage/serialization.d.ts.map +0 -1
  325. package/dist/procedures/storage/serialization.js +0 -216
  326. package/dist/procedures/storage/serialization.js.map +0 -1
  327. package/dist/procedures/storage/synced-registry.d.ts +0 -246
  328. package/dist/procedures/storage/synced-registry.d.ts.map +0 -1
  329. package/dist/procedures/storage/synced-registry.js +0 -575
  330. package/dist/procedures/storage/synced-registry.js.map +0 -1
  331. package/dist/procedures/storage/types.d.ts +0 -130
  332. package/dist/procedures/storage/types.d.ts.map +0 -1
  333. package/dist/procedures/storage/types.js +0 -8
  334. package/dist/procedures/storage/types.js.map +0 -1
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Effect declarations for groups of procedures (CAPABILITIES-2026-10). This file imports only
3
+ * types, so each file of procedures can use it without an import cycle.
4
+ */
5
+
6
+ import type { AnyProcedure, Effects } from "./types.js";
7
+
8
+ /** The effects of a procedure that reads and changes nothing: its output comes from its input. */
9
+ export const PURE_EFFECTS: Effects = Object.freeze({ reads: Object.freeze([]), writes: Object.freeze([]) });
10
+
11
+ /** The effects of a procedure that reads the procedure registry and changes nothing. */
12
+ export const REGISTRY_READ_EFFECTS: Effects = Object.freeze({ reads: Object.freeze(["registry"]), writes: Object.freeze([]) });
13
+
14
+ /**
15
+ * This function gives copies of the procedures with the effects in their metadata. A procedure
16
+ * that declares its own effects keeps them.
17
+ */
18
+ export function withEffects<T extends AnyProcedure>(effects: Effects, procedures: readonly T[]): T[] {
19
+ return procedures.map((procedure) =>
20
+ (procedure.metadata as { effects?: unknown } | undefined)?.effects !== undefined
21
+ ? procedure
22
+ : ({ ...procedure, metadata: { ...procedure.metadata, effects } } as T)
23
+ );
24
+ }
@@ -24,11 +24,39 @@ export type {
24
24
  ProcedureModule,
25
25
  RegistrationOptions,
26
26
  ProcedureRegistryLike,
27
+ Effects,
28
+ PermittedEffects,
29
+ Register,
30
+ ClearanceOf,
31
+ ResourcesOf,
32
+ Capability,
33
+ CapabilityRequest,
34
+ ClearanceOrder,
35
+ NestedCallOptions,
27
36
  } from "./types.js";
28
37
 
29
38
  // Type utilities
30
39
  export { pathToKey, keyToPath } from "./types.js";
31
40
 
41
+ // Capabilities (CAPABILITIES-2026-10)
42
+ export {
43
+ STANDARD_RESOURCES,
44
+ UNLIMITED_CAPABILITY,
45
+ ladder,
46
+ effectsOf,
47
+ isReadOnly,
48
+ isDestructive,
49
+ isOpenWorld,
50
+ toolHints,
51
+ narrowCapability,
52
+ capabilityRefusal,
53
+ examinesNestedCall,
54
+ explainInvocation,
55
+ checkEffects,
56
+ } from "./capability.js";
57
+ export { PURE_EFFECTS, REGISTRY_READ_EFFECTS, withEffects } from "./effects.js";
58
+ export type { ToolHints, CapabilityRefusal, CapabilityRefusalReason, InvocationExplanation, EffectsViolation } from "./capability.js";
59
+
32
60
  // Registry
33
61
  export { ProcedureRegistry, RegistryError, PROCEDURE_REGISTRY, assertValidPath } from "./registry.js";
34
62
 
@@ -61,6 +89,7 @@ export {
61
89
  moduleClosure,
62
90
  registerModules,
63
91
  createRegistry,
92
+ registerClientProcedures,
64
93
  } from "./module.js";
65
94
  export type { ModuleInput } from "./module.js";
66
95
 
@@ -202,27 +231,9 @@ export type {
202
231
  // procedures moved to @procedures/store (roadmap 3.2): the program store.
203
232
 
204
233
  // =============================================================================
205
- // Auto-register core procedures
234
+ // Registration
206
235
  // =============================================================================
207
236
 
208
- import { coreProcedures as _coreProcedures } from "./core/index.js";
209
- import { metaProcedures as _metaProcedures } from "./define-procedure.js";
210
- import { PROCEDURE_REGISTRY } from "./registry.js";
211
-
212
- // Register core procedures when this module is imported
213
- // This ensures client.* procedures are available without explicit registration
214
- try {
215
- for (const proc of _coreProcedures) {
216
- if (!PROCEDURE_REGISTRY.has(proc.path)) {
217
- PROCEDURE_REGISTRY.register(proc);
218
- }
219
- }
220
- // Also register meta-procedures
221
- for (const proc of _metaProcedures) {
222
- if (!PROCEDURE_REGISTRY.has(proc.path)) {
223
- PROCEDURE_REGISTRY.register(proc);
224
- }
225
- }
226
- } catch {
227
- // Ignore errors during auto-registration (e.g., if already registered)
228
- }
237
+ // Importing this package registers nothing (ARCHITECTURE-PROPOSALS-2026-10, P9). The
238
+ // "./register" entry registers the procedures of `clientModule` in the global registry, and
239
+ // `createRegistry()` adds them to each new registry.
@@ -98,7 +98,8 @@ export function timeoutInterceptor(options: TimeoutInterceptorOptions): Intercep
98
98
 
99
99
  let output: ProcedureOutput<unknown>;
100
100
  try {
101
- output = await Promise.race([next({ signal }), deadline]);
101
+ // The deadline goes into the capability: the nested calls and the remote calls see it
102
+ output = await Promise.race([next({ signal, capability: { timeoutMs: ms } }), deadline]);
102
103
  } catch (error) {
103
104
  stop();
104
105
  throw error;
@@ -13,14 +13,24 @@
13
13
  * Interceptors run around each invocation, the nested calls too (roadmap 1.2): policy, tracing,
14
14
  * timeouts and metrics for every host. `interceptors.ts` has a timeout, a tracing and a metrics
15
15
  * interceptor.
16
+ *
17
+ * Each invocation operates with a capability (CAPABILITIES-2026-10, `capability.ts`). A nested
18
+ * call gets the same capability or a narrower one. The admission refuses a procedure whose
19
+ * declared effects the capability does not permit (`FORBIDDEN`). The host can give resources
20
+ * for the capability (`ctx.resources`) and a filter of the output that leaves the host.
16
21
  */
17
22
 
18
23
  import type {
19
24
  AnyProcedure,
25
+ Capability,
26
+ CapabilityRequest,
27
+ ClearanceOrder,
28
+ NestedCallOptions,
20
29
  ProcedureClient,
21
30
  ProcedureContext,
22
31
  ProcedurePath,
23
32
  RepositoryProvider,
33
+ ResourcesOf,
24
34
  } from "./types.js";
25
35
  import { pathToKey } from "./types.js";
26
36
  import { validateSchema } from "./schema.js";
@@ -28,6 +38,8 @@ import type { ProcedureRegistry } from "./registry.js";
28
38
  import { PROCEDURE_REGISTRY } from "./registry.js";
29
39
  import { nestedCallRefusal } from "./policy.js";
30
40
  import { scopeOf, withScope, type HydrationEvent } from "./ref.js";
41
+ import { UNLIMITED_CAPABILITY, capabilityRefusal, examinesNestedCall, narrowCapability, type CapabilityRefusalReason } from "./capability.js";
42
+ import { timeoutInterceptor } from "./interceptors.js";
31
43
  import type { EventBus } from "../events/types.js";
32
44
  import { getGlobalEventBus, withEventBusSignal } from "../events/bus.js";
33
45
 
@@ -45,7 +57,9 @@ export type InvocationErrorCode =
45
57
  | "OUTPUT_VALIDATION_ERROR"
46
58
  | "NO_OUTPUT"
47
59
  | "ABORTED"
48
- | "TIMEOUT";
60
+ | "TIMEOUT"
61
+ /** The capability of the call does not permit it (CAPABILITIES-2026-10). `reason` tells why. */
62
+ | "FORBIDDEN";
49
63
 
50
64
  /** An error of the invocation itself. An error that the handler throws passes through unchanged. */
51
65
  export class InvocationError extends Error {
@@ -53,11 +67,14 @@ export class InvocationError extends Error {
53
67
  readonly code: InvocationErrorCode;
54
68
  readonly path: ProcedurePath;
55
69
  readonly retryable: boolean = false;
70
+ /** The reason of a `FORBIDDEN` error: `effects-undeclared`, `effects-exceeded` or `clearance`. */
71
+ readonly reason: CapabilityRefusalReason | undefined;
56
72
 
57
- constructor(code: InvocationErrorCode, message: string, path: ProcedurePath) {
73
+ constructor(code: InvocationErrorCode, message: string, path: ProcedurePath, reason?: CapabilityRefusalReason) {
58
74
  super(message);
59
75
  this.code = code;
60
76
  this.path = path;
77
+ this.reason = reason;
61
78
  }
62
79
  }
63
80
 
@@ -108,8 +125,28 @@ export interface InvokeOptions {
108
125
  interceptors?: readonly Interceptor[] | undefined;
109
126
  /** The invocation whose handler makes this call. The context client sets it for a nested call. */
110
127
  parent?: Invocation | undefined;
128
+ /**
129
+ * The capability of the call: its clearance, permitted effects and deadline. The host makes it
130
+ * from its authentication. The nested calls get it, or a narrower one. Absent: no limits.
131
+ */
132
+ capability?: Capability | undefined;
133
+ /** The order of the clearance values. Narrowing two different clearances needs it. */
134
+ clearanceOrder?: ClearanceOrder | undefined;
135
+ /** The resources of an invocation for its capability (`ctx.resources`). */
136
+ resources?: ((invocation: Invocation) => ResourcesOf) | undefined;
137
+ /**
138
+ * The filter of the output that leaves the host (the call of the host, not its nested calls).
139
+ * It drops a refused stream item. A refused value gives `FORBIDDEN` (reason `clearance`).
140
+ */
141
+ admitItem?: AdmitItem | undefined;
111
142
  }
112
143
 
144
+ /**
145
+ * The output filter of a host. It can look up data (the clearance of a record), so it can give a
146
+ * promise.
147
+ */
148
+ export type AdmitItem = (item: unknown, invocation: Invocation) => boolean | Promise<boolean>;
149
+
113
150
  // =============================================================================
114
151
  // Interceptors
115
152
  // =============================================================================
@@ -130,12 +167,16 @@ export interface Invocation {
130
167
  readonly parent: Invocation | undefined;
131
168
  /** 0 for a call of a host, 1 for its nested calls, and so on. */
132
169
  readonly depth: number;
170
+ /** The capability of the invocation (frozen). */
171
+ readonly capability: Capability;
133
172
  }
134
173
 
135
174
  /** The changes that an interceptor gives to the next step: the handler and the nested calls get them. */
136
175
  export interface InvocationChanges {
137
176
  signal?: AbortSignal | undefined;
138
177
  metadata?: Record<string, unknown> | undefined;
178
+ /** A request for a narrower capability. The framework calculates the result: it is never wider. */
179
+ capability?: CapabilityRequest | undefined;
139
180
  }
140
181
 
141
182
  /** The rest of the chain: the next interceptors, then the validation and the handler. */
@@ -256,8 +297,15 @@ export async function invokeProcedure<TOutput = unknown>(
256
297
  signal: options.signal,
257
298
  parent: options.parent,
258
299
  depth: options.parent ? options.parent.depth + 1 : 0,
300
+ capability: options.capability ?? UNLIMITED_CAPABILITY,
259
301
  };
260
- const interceptors = options.interceptors ?? [];
302
+ // A deadline that the parent does not have (from a host, a request or a narrowed nested call)
303
+ // ends the invocation at that time, as the timeout interceptor does
304
+ const deadline = invocation.capability.deadline;
305
+ const ownDeadline = deadline !== undefined && options.parent?.capability.deadline !== deadline;
306
+ const interceptors = ownDeadline
307
+ ? [timeoutInterceptor({ ms: Math.max(0, deadline - Date.now()), appliesTo: () => true }), ...(options.interceptors ?? [])]
308
+ : (options.interceptors ?? []);
261
309
  if (interceptors.length === 0) return runInvocation<TOutput>(invocation, options);
262
310
 
263
311
  const dispatch = (index: number, current: Invocation, currentOptions: InvokeOptions): Promise<ProcedureOutput<unknown>> => {
@@ -269,8 +317,16 @@ export async function invokeProcedure<TOutput = unknown>(
269
317
  ...current,
270
318
  signal: changes.signal ?? current.signal,
271
319
  metadata: changes.metadata ?? current.metadata,
320
+ capability: changes.capability
321
+ ? narrowCapability(current.capability, changes.capability, currentOptions.clearanceOrder)
322
+ : current.capability,
272
323
  };
273
- return dispatch(index + 1, changed, { ...currentOptions, signal: changed.signal, metadata: changed.metadata });
324
+ return dispatch(index + 1, changed, {
325
+ ...currentOptions,
326
+ signal: changed.signal,
327
+ metadata: changed.metadata,
328
+ capability: changed.capability,
329
+ });
274
330
  });
275
331
  };
276
332
  return dispatch(0, invocation, options) as Promise<ProcedureOutput<TOutput>>;
@@ -282,6 +338,14 @@ async function runInvocation<TOutput>(invocation: Invocation, options: InvokeOpt
282
338
  const handler = procedure.handler!;
283
339
  if (options.signal?.aborted) throw abortError(path);
284
340
 
341
+ // The admission: before the input validation, so a refused call learns nothing from its input
342
+ const parent = invocation.parent;
343
+ if (!parent || examinesNestedCall(parent.procedure, parent.capability, invocation.capability)) {
344
+ const refusal = capabilityRefusal(procedure, invocation.capability);
345
+ if (refusal?.reason === "deadline") throw new InvocationError("TIMEOUT", refusal.message, path);
346
+ if (refusal) throw new InvocationError("FORBIDDEN", refusal.message, path, refusal.reason);
347
+ }
348
+
285
349
  let data: unknown = input;
286
350
  if (!options.inputValidated) {
287
351
  // A synchronous schema gives its result at once, so the handler starts before an await
@@ -301,11 +365,32 @@ async function runInvocation<TOutput>(invocation: Invocation, options: InvokeOpt
301
365
  }
302
366
 
303
367
  // The handler starts here, before the first await (the site's trace depends on it)
304
- const result: unknown = await handler(data, createProcedureContext(procedure, options, invocation));
368
+ const result: unknown = await handler(data, createProcedureContext(procedure, { ...options, capability: invocation.capability }, invocation));
369
+ // The output filter examines only what leaves the host
370
+ const admit = invocation.depth === 0 ? options.admitItem : undefined;
305
371
  if (isAsyncIterable(result)) {
306
- return { kind: "stream", items: validatedItems(procedure, result, options.signal) as AsyncGenerator<TOutput, void, undefined> };
372
+ const items = validatedItems(procedure, result, options.signal);
373
+ return {
374
+ kind: "stream",
375
+ items: (admit ? admittedItems(items, admit, invocation) : items) as AsyncGenerator<TOutput, void, undefined>,
376
+ };
377
+ }
378
+ const value = await validateOutput(procedure, result);
379
+ if (admit && !(await admit(value, invocation))) {
380
+ throw new InvocationError("FORBIDDEN", `The clearance of the call does not permit the output of ${pathToKey(path)}`, path, "clearance");
381
+ }
382
+ return { kind: "value", value: value as TOutput };
383
+ }
384
+
385
+ /** The items that the output filter of the host admits. */
386
+ async function* admittedItems(
387
+ items: AsyncGenerator<unknown, void, undefined>,
388
+ admit: AdmitItem,
389
+ invocation: Invocation,
390
+ ): AsyncGenerator<unknown, void, undefined> {
391
+ for await (const item of items) {
392
+ if (await admit(item, invocation)) yield item;
307
393
  }
308
- return { kind: "value", value: (await validateOutput(procedure, result)) as TOutput };
309
394
  }
310
395
 
311
396
  /** This function finds the procedure at a path in the registry of the options, then runs it. */
@@ -379,6 +464,8 @@ export function createProcedureContext(
379
464
  path: procedure.path,
380
465
  client,
381
466
  registry: options.registry ?? PROCEDURE_REGISTRY,
467
+ capability: options.capability ?? UNLIMITED_CAPABILITY,
468
+ resources: invocation && options.resources ? options.resources(invocation) : (EMPTY_RESOURCES as ResourcesOf),
382
469
  };
383
470
  if (options.signal) context.signal = options.signal;
384
471
  if (options.repository) context.repository = options.repository;
@@ -391,23 +478,35 @@ export function createProcedureContext(
391
478
  return context;
392
479
  }
393
480
 
481
+ /** The resources of an invocation when the host gives none. */
482
+ const EMPTY_RESOURCES = Object.freeze({});
483
+
394
484
  /** The client of a procedure's context: it runs the nested calls from the registry. */
395
485
  function registryClient(caller: AnyProcedure, options: InvokeOptions): ProcedureClient {
396
- const start = <TOutput>(path: ProcedurePath, input: unknown): Promise<ProcedureOutput<TOutput>> => {
486
+ const start = <TOutput>(path: ProcedurePath, input: unknown, callOptions?: NestedCallOptions): Promise<ProcedureOutput<TOutput>> => {
397
487
  // The call policy of the host, with the immediate caller (BUGS-2026-07 H18, deep dive MCP-3).
398
488
  // It runs before the lookup, so a refused path tells nothing about the procedure.
399
489
  const refusal = nestedCallRefusal(caller, path, options);
400
490
  if (refusal) {
401
491
  return Promise.reject(new InvocationError(refusal.code, refusal.message, path));
402
492
  }
403
- return invokePath<TOutput>(path, input, options);
493
+ // A nested call gets the capability of its caller, or a narrower one that it asks for
494
+ const narrow = callOptions?.narrow;
495
+ if (!narrow) return invokePath<TOutput>(path, input, options);
496
+ let capability: Capability;
497
+ try {
498
+ capability = narrowCapability(options.capability, narrow, options.clearanceOrder);
499
+ } catch (error) {
500
+ return Promise.reject(error);
501
+ }
502
+ return invokePath<TOutput>(path, input, { ...options, capability });
404
503
  };
405
504
  return {
406
- async call<TInput, TOutput>(path: ProcedurePath, input: TInput): Promise<TOutput> {
407
- return outputValue(await start<TOutput>(path, input), path);
505
+ async call<TInput, TOutput>(path: ProcedurePath, input: TInput, callOptions?: NestedCallOptions): Promise<TOutput> {
506
+ return outputValue(await start<TOutput>(path, input, callOptions), path);
408
507
  },
409
- stream<TInput, TOutput>(path: ProcedurePath, input: TInput): AsyncIterable<TOutput> {
410
- return streamOutput(() => start<TOutput>(path, input));
508
+ stream<TInput, TOutput>(path: ProcedurePath, input: TInput, callOptions?: NestedCallOptions): AsyncIterable<TOutput> {
509
+ return streamOutput(() => start<TOutput>(path, input, callOptions));
411
510
  },
412
511
  };
413
512
  }
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Procedure modules and explicit registries (architecture review, roadmap 3.1).
3
3
  *
4
- * Importing a package root registers its procedures in the global `PROCEDURE_REGISTRY`
5
- * (DECISIONS-2026-10, "Every package root registers its procedures"). That stays. A host that
6
- * wants its own registry uses the module of each package instead: the `./module` entry of a
7
- * package exports its procedures as data, and importing it registers nothing.
4
+ * Importing a package registers nothing (ARCHITECTURE-PROPOSALS-2026-10, P9). A host with its own
5
+ * registry gives it the module of each package: the `./module` entry exports the procedures as
6
+ * data. The global `PROCEDURE_REGISTRY` gets the procedures of a package from its `./register`
7
+ * entry (`import "@procedures/git/register"`).
8
8
  *
9
9
  * ```ts
10
10
  * import { createRegistry, Client, LocalTransport } from "@procedures/client";
@@ -18,16 +18,15 @@
18
18
  * rule of a server all use the registry of the invocation (`ctx.registry`).
19
19
  */
20
20
 
21
- import { ProcedureRegistry, RegistryError } from "./registry.js";
21
+ import { PROCEDURE_REGISTRY, ProcedureRegistry, RegistryError } from "./registry.js";
22
22
  import { coreProcedures } from "./core/index.js";
23
23
  import { metaProcedures as definitionProcedures } from "./define-procedure.js";
24
24
  import type { AnyProcedure, ProcedureModule, RegistrationOptions } from "./types.js";
25
25
 
26
26
  /**
27
- * The procedures that `@procedures/client` registers in the global registry when it is
28
- * imported: the control flow (`client.chain`, `client.map` and the others) and
29
- * `procedure.define`/`get`/`list`/`delete`. `createRegistry()` always registers this module, so an
30
- * explicit registry has what the global registry has.
27
+ * The procedures of `@procedures/client`: the control flow (`client.chain`, `client.map` and the
28
+ * others) and `procedure.define`/`get`/`list`/`delete`. `createRegistry()` always registers this
29
+ * module. `import "@procedures/client/register"` registers it in the global registry.
31
30
  */
32
31
  export const clientModule: ProcedureModule = {
33
32
  name: "@procedures/client",
@@ -105,3 +104,14 @@ export function registerModules(
105
104
  export function createRegistry(...modules: ModuleInput[]): ProcedureRegistry {
106
105
  return registerModules(new ProcedureRegistry(), [clientModule, ...modules]);
107
106
  }
107
+
108
+ /**
109
+ * This function registers the procedures of `clientModule` in a registry: the global registry by
110
+ * default. A procedure that the registry has already stays. The `./register` entry of the package
111
+ * starts this function.
112
+ */
113
+ export function registerClientProcedures(registry: ProcedureRegistry = PROCEDURE_REGISTRY): void {
114
+ for (const procedure of clientModule.procedures) {
115
+ if (!registry.has(procedure.path)) registry.register(procedure);
116
+ }
117
+ }
@@ -69,10 +69,101 @@ export interface ProcedureMetadata {
69
69
  */
70
70
  calls?: readonly string[];
71
71
 
72
+ /**
73
+ * The side effects of the procedure (CAPABILITIES-2026-10). A host with a restriction of
74
+ * effects refuses a procedure without this declaration.
75
+ */
76
+ effects?: Effects;
77
+
72
78
  /** Custom metadata fields */
73
79
  [key: string]: unknown;
74
80
  }
75
81
 
82
+ // =============================================================================
83
+ // Capabilities (CAPABILITIES-2026-10)
84
+ // =============================================================================
85
+
86
+ /**
87
+ * The side effects of a procedure. The resource names are strings that the application chooses.
88
+ * The standard names are `fs` (the files of the host), `network` (systems outside the host),
89
+ * `process` (it starts any program) and `registry` (the procedure registry).
90
+ */
91
+ export interface Effects {
92
+ /** The resources that the procedure reads. Absent: not known. */
93
+ readonly reads?: readonly string[];
94
+ /** The resources that the procedure changes. An empty list: it changes nothing. Absent: not known. */
95
+ readonly writes?: readonly string[];
96
+ /** True: a second call with the same input changes nothing more. The default is false. */
97
+ readonly idempotent?: boolean;
98
+ /**
99
+ * True: the procedure can delete or overwrite data that it did not make. The default is true for
100
+ * a procedure that writes, as in MCP. A procedure that writes only new data declares false.
101
+ */
102
+ readonly destructive?: boolean;
103
+ }
104
+
105
+ /** The effects that a capability permits. An absent field permits everything of its kind. */
106
+ export interface PermittedEffects {
107
+ /** The resources that a call can read: names, or `"any"`. */
108
+ readonly reads?: readonly string[] | "any";
109
+ /** The resources that a call can change: names, or `"any"`. An empty list: no change. */
110
+ readonly writes?: readonly string[] | "any";
111
+ /** False: a call cannot start a destructive procedure. */
112
+ readonly destructive?: boolean;
113
+ }
114
+
115
+ /**
116
+ * The types that an application gives to the framework, by declaration merging:
117
+ *
118
+ * ```ts
119
+ * declare module "@procedures/client" {
120
+ * interface Register {
121
+ * clearance: "public" | "shared" | "private";
122
+ * resources: { world: World };
123
+ * }
124
+ * }
125
+ * ```
126
+ */
127
+ export interface Register {}
128
+
129
+ /** The type of a clearance value: `Register["clearance"]`, or `unknown`. */
130
+ export type ClearanceOf = Register extends { clearance: infer C } ? C : unknown;
131
+
132
+ /** The type of `ctx.resources`: `Register["resources"]`, or a record of unknown values. */
133
+ export type ResourcesOf = Register extends { resources: infer R } ? R : Readonly<Record<string, unknown>>;
134
+
135
+ /**
136
+ * The permissions of one invocation. A nested call gets the same capability or a narrower one,
137
+ * and only the host makes a capability (from its authentication). The object is frozen.
138
+ */
139
+ export interface Capability<C = ClearanceOf> {
140
+ /** The data that the call can see. Absent: no limit. The application gives the order. */
141
+ readonly clearance?: C;
142
+ /** The effects that the call and its nested calls can have. Absent: all effects. */
143
+ readonly effects?: PermittedEffects;
144
+ /** The time (milliseconds since the epoch) at which the call and its nested calls stop. */
145
+ readonly deadline?: number;
146
+ }
147
+
148
+ /**
149
+ * A request for a narrower capability (a nested call, an interceptor). The framework calculates
150
+ * the result, so a request for more has no effect.
151
+ */
152
+ export interface CapabilityRequest<C = ClearanceOf> {
153
+ clearance?: C;
154
+ effects?: PermittedEffects;
155
+ /** The time from now, in milliseconds. The result is the earlier of the two deadlines. */
156
+ timeoutMs?: number;
157
+ }
158
+
159
+ /** The order of clearance values: a partial order with a meet (the highest common lower value). */
160
+ export interface ClearanceOrder<C = ClearanceOf> {
161
+ /** True when `a` is the same as or lower than `b`. */
162
+ leq(a: C, b: C): boolean;
163
+ /** The highest value that is the same as or lower than both. */
164
+ meet(a: C, b: C): C;
165
+ }
166
+
76
167
  // =============================================================================
77
168
  // Procedure Context
78
169
  // =============================================================================
@@ -172,6 +263,18 @@ export interface ProcedureContext {
172
263
  * `hydrateInput`, so a trace sees the events of its own program only.
173
264
  */
174
265
  onRef?: ((event: HydrationEvent) => void) | undefined;
266
+
267
+ /**
268
+ * The capability of this invocation (CAPABILITIES-2026-10): its clearance, permitted effects
269
+ * and deadline. The nested calls of `ctx.client` get it, or a narrower one. Frozen.
270
+ */
271
+ capability: Capability;
272
+
273
+ /**
274
+ * The resources that the host gives to this invocation, for its capability: a view of a
275
+ * database for a lower clearance, for example. Empty when the host gives none.
276
+ */
277
+ resources: ResourcesOf;
175
278
  }
176
279
 
177
280
  /**
@@ -195,7 +298,8 @@ export interface ProcedureClient {
195
298
  */
196
299
  call<TInput = unknown, TOutput = unknown>(
197
300
  path: ProcedurePath,
198
- input: TInput
301
+ input: TInput,
302
+ options?: NestedCallOptions
199
303
  ): Promise<TOutput>;
200
304
 
201
305
  /**
@@ -205,10 +309,17 @@ export interface ProcedureClient {
205
309
  */
206
310
  stream?<TInput = unknown, TOutput = unknown>(
207
311
  path: ProcedurePath,
208
- input: TInput
312
+ input: TInput,
313
+ options?: NestedCallOptions
209
314
  ): AsyncIterable<TOutput>;
210
315
  }
211
316
 
317
+ /** The options of one nested call. */
318
+ export interface NestedCallOptions {
319
+ /** A narrower capability for the nested call. A request for more has no effect. */
320
+ narrow?: CapabilityRequest | undefined;
321
+ }
322
+
212
323
  // =============================================================================
213
324
  // Procedure Handler
214
325
  // =============================================================================
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Importing this file registers the procedures of `@procedures/client` in the global registry:
3
+ * `import "@procedures/client/register"`. These are the control flow (`client.chain`, `client.map`
4
+ * and the others) and `procedure.define`/`get`/`list`/`delete`. The other entries of the package
5
+ * register nothing (ARCHITECTURE-PROPOSALS-2026-10, P9).
6
+ */
7
+
8
+ import { registerClientProcedures } from "./procedures/module.js";
9
+
10
+ registerClientProcedures();
@@ -12,10 +12,14 @@
12
12
  * procedures (a host for an outside caller sets it).
13
13
  * - `timeoutMs`: the time limit of each call from a caller (not of its nested calls).
14
14
  * - `token`, `browser`: the checks of the network transports (`client-server` applies them).
15
+ * - `capability`, `readOnly`, `clearanceOrder`, `resources`, `admitItem`: the capability of each
16
+ * call and what the host gives for it (CAPABILITIES-2026-10).
15
17
  */
16
18
 
17
- import type { ProcedurePath } from "../procedures/types.js";
18
- import type { Interceptor } from "../procedures/invoke.js";
19
+ import type { Capability, ClearanceOrder, ProcedurePath, ResourcesOf } from "../procedures/types.js";
20
+ import type { AdmitItem, Interceptor, Invocation } from "../procedures/invoke.js";
21
+ import { narrowCapability } from "../procedures/capability.js";
22
+ import type { ServerRequest } from "./types.js";
19
23
  import { timeoutInterceptor } from "../procedures/interceptors.js";
20
24
  import type { BrowserGuardOptions } from "./browser-guard.js";
21
25
  import type { ProcedureRegistry } from "../procedures/registry.js";
@@ -67,6 +71,20 @@ export interface HostPolicyOptions {
67
71
  token?: string | undefined;
68
72
  /** The browser checks of the network transports (`allowedOrigins`, `allowedHosts`, `allowGet`). */
69
73
  browser?: BrowserGuardOptions | undefined;
74
+ /**
75
+ * The capability of each call from a caller: one value, or a function of the request. The
76
+ * function reads only what the transport authenticated (the token, a session), not a claim of
77
+ * the caller. Without it: no limits.
78
+ */
79
+ capability?: Capability | ((request: ServerRequest<unknown>) => Capability | undefined) | undefined;
80
+ /** True: the callers start only the procedures that change nothing (`effects.writes` is empty). */
81
+ readOnly?: boolean | undefined;
82
+ /** The order of the clearance values. */
83
+ clearanceOrder?: ClearanceOrder | undefined;
84
+ /** The resources of each invocation for its capability (`ctx.resources`). */
85
+ resources?: ((invocation: Invocation) => ResourcesOf) | undefined;
86
+ /** The filter of the output that leaves the host. */
87
+ admitItem?: AdmitItem | undefined;
70
88
  }
71
89
 
72
90
  /** What a host lets its callers do. */
@@ -76,6 +94,22 @@ export interface HostPolicy {
76
94
  readonly interceptors: readonly Interceptor[];
77
95
  readonly token: string | undefined;
78
96
  readonly browser: BrowserGuardOptions;
97
+ /** The capability of a call from a caller, or undefined for no limits. */
98
+ readonly capabilityOf: ((request?: ServerRequest<unknown>) => Capability | undefined) | undefined;
99
+ readonly clearanceOrder: ClearanceOrder | undefined;
100
+ readonly resources: ((invocation: Invocation) => ResourcesOf) | undefined;
101
+ readonly admitItem: AdmitItem | undefined;
102
+ }
103
+
104
+ /** The capability function of a policy: the capability of the options, narrowed by `readOnly`. */
105
+ function capabilityOfOptions(options: HostPolicyOptions): HostPolicy["capabilityOf"] {
106
+ const { capability, readOnly } = options;
107
+ if (capability === undefined && !readOnly) return undefined;
108
+ const readOnlyRequest = readOnly ? { effects: { writes: [] as readonly string[] } } : undefined;
109
+ return (request) => {
110
+ const base = typeof capability === "function" ? (request ? capability(request) : undefined) : capability;
111
+ return readOnlyRequest ? narrowCapability(base, readOnlyRequest, options.clearanceOrder) : base;
112
+ };
79
113
  }
80
114
 
81
115
  function exposeOf(options: HostPolicyOptions): ((path: ProcedurePath) => boolean) | undefined {
@@ -106,6 +140,10 @@ export function hostPolicy(options: HostPolicyOptions = {}): HostPolicy {
106
140
  interceptors,
107
141
  token: options.token,
108
142
  browser: options.browser ?? {},
143
+ capabilityOf: capabilityOfOptions(options),
144
+ clearanceOrder: options.clearanceOrder,
145
+ resources: options.resources,
146
+ admitItem: options.admitItem,
109
147
  };
110
148
  }
111
149
 
@@ -117,5 +155,9 @@ export function procedureServerOptions(policy: HostPolicy, registry?: ProcedureR
117
155
  ...(policy.expose ? { expose: policy.expose } : {}),
118
156
  strictCalls: policy.strictCalls,
119
157
  ...(policy.interceptors.length > 0 ? { interceptors: policy.interceptors } : {}),
158
+ ...(policy.capabilityOf ? { capability: policy.capabilityOf } : {}),
159
+ ...(policy.clearanceOrder ? { clearanceOrder: policy.clearanceOrder } : {}),
160
+ ...(policy.resources ? { resources: policy.resources } : {}),
161
+ ...(policy.admitItem ? { admitItem: policy.admitItem } : {}),
120
162
  };
121
163
  }