@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,333 @@
1
+ /**
2
+ * Invocation capabilities (packages/documentation/CAPABILITIES-2026-10.md).
3
+ *
4
+ * Each invocation operates with a capability: a clearance (the data that it can see), the
5
+ * effects that it can have, and a deadline. A nested call gets the same capability or a
6
+ * narrower one: `narrowCapability()` calculates it, so a request for more has no effect. Only
7
+ * the host makes a capability, from its authentication.
8
+ *
9
+ * A procedure declares its effects in `metadata.effects`. The admission (`capabilityRefusal()`)
10
+ * compares them with the permitted effects. It examines each call of a host, each nested call
11
+ * of a data-driven procedure (its caller chose the refs), and each nested call with a narrowed
12
+ * capability. A procedure of code that declares its effects answers for its own nested calls:
13
+ * `checkEffects()` examines those declarations against the call graph, as `metadata.calls`
14
+ * makes the reach of a procedure visible.
15
+ */
16
+
17
+ import type {
18
+ AnyProcedure,
19
+ Capability,
20
+ CapabilityRequest,
21
+ ClearanceOrder,
22
+ Effects,
23
+ PermittedEffects,
24
+ ProcedurePath,
25
+ } from "./types.js";
26
+ import { pathToKey } from "./types.js";
27
+ import { isDataDriven } from "./ref.js";
28
+ import { callMatches } from "./policy.js";
29
+
30
+ /** The standard resource names. An application adds its own names. */
31
+ export const STANDARD_RESOURCES = ["fs", "network", "process", "registry"] as const;
32
+
33
+ /**
34
+ * The metadata key of the remaining time of a remote call, in milliseconds. A server uses it
35
+ * only to make its deadline earlier.
36
+ */
37
+ export const TIMEOUT_METADATA_KEY = "procedure-timeout";
38
+
39
+ /** The capability without limits: the capability of a host that sets none. */
40
+ export const UNLIMITED_CAPABILITY: Capability = Object.freeze({});
41
+
42
+ // =============================================================================
43
+ // Clearance orders
44
+ // =============================================================================
45
+
46
+ /**
47
+ * This function gives the order of a ladder: each value is higher than the values before it.
48
+ * `ladder(["public", "shared", "private"])`.
49
+ */
50
+ export function ladder<C>(levels: readonly C[]): ClearanceOrder<C> {
51
+ const rank = (value: C): number => {
52
+ const index = levels.indexOf(value);
53
+ if (index === -1) throw new Error(`Not a clearance value of the ladder: ${String(value)}`);
54
+ return index;
55
+ };
56
+ return {
57
+ leq: (a, b) => rank(a) <= rank(b),
58
+ meet: (a, b) => (rank(a) <= rank(b) ? a : b),
59
+ };
60
+ }
61
+
62
+ // =============================================================================
63
+ // Effects
64
+ // =============================================================================
65
+
66
+ /** The declared effects of a procedure, or undefined when it declares none. */
67
+ export function effectsOf(procedure: { readonly metadata?: unknown }): Effects | undefined {
68
+ const effects = (procedure.metadata as { effects?: unknown } | undefined)?.effects;
69
+ return effects !== null && typeof effects === "object" ? (effects as Effects) : undefined;
70
+ }
71
+
72
+ /** True when the effects say that the procedure changes nothing (`writes: []`). */
73
+ export function isReadOnly(effects: Effects | undefined): boolean {
74
+ return effects?.writes !== undefined && effects.writes.length === 0;
75
+ }
76
+
77
+ /** True when the procedure can delete or overwrite data. Not known: true (as in MCP). */
78
+ export function isDestructive(effects: Effects | undefined): boolean {
79
+ if (isReadOnly(effects)) return false;
80
+ return effects?.destructive ?? true;
81
+ }
82
+
83
+ /** True when the procedure can reach systems outside the host. Not known: true (as in MCP). */
84
+ export function isOpenWorld(effects: Effects | undefined): boolean {
85
+ if (effects?.reads === undefined || effects.writes === undefined) return true;
86
+ return effects.reads.includes("network") || effects.writes.includes("network") || effects.writes.includes("process");
87
+ }
88
+
89
+ /** The MCP hints of a tool. */
90
+ export interface ToolHints {
91
+ readonly readOnlyHint?: boolean;
92
+ readonly destructiveHint?: boolean;
93
+ readonly idempotentHint?: boolean;
94
+ readonly openWorldHint?: boolean;
95
+ }
96
+
97
+ /**
98
+ * The MCP hints of a procedure, from its declared effects. A procedure without a declaration,
99
+ * or a data-driven one (its operands decide what it does), gets no hints: then the MCP defaults
100
+ * apply (not read-only, destructive, open world).
101
+ */
102
+ export function toolHints(procedure: AnyProcedure): ToolHints {
103
+ const effects = effectsOf(procedure);
104
+ if (!effects || isDataDriven(procedure)) return {};
105
+ if (isReadOnly(effects)) {
106
+ return { readOnlyHint: true, openWorldHint: isOpenWorld(effects) };
107
+ }
108
+ return {
109
+ readOnlyHint: false,
110
+ destructiveHint: isDestructive(effects),
111
+ idempotentHint: effects.idempotent ?? false,
112
+ openWorldHint: isOpenWorld(effects),
113
+ };
114
+ }
115
+
116
+ // =============================================================================
117
+ // Narrowing
118
+ // =============================================================================
119
+
120
+ type Permit = readonly string[] | "any" | undefined;
121
+
122
+ function intersect(current: Permit, requested: Permit): Permit {
123
+ if (current === undefined || current === "any") return requested ?? current;
124
+ if (requested === undefined || requested === "any") return current;
125
+ return Object.freeze(current.filter((name) => requested.includes(name)));
126
+ }
127
+
128
+ function narrowEffects(current: PermittedEffects | undefined, requested: PermittedEffects | undefined): PermittedEffects | undefined {
129
+ if (!requested) return current;
130
+ const reads = intersect(current?.reads, requested.reads);
131
+ const writes = intersect(current?.writes, requested.writes);
132
+ const destructive = current?.destructive === false || requested.destructive === false ? false : undefined;
133
+ const result: { reads?: Permit; writes?: Permit; destructive?: boolean } = {};
134
+ if (reads !== undefined) result.reads = reads;
135
+ if (writes !== undefined) result.writes = writes;
136
+ if (destructive !== undefined) result.destructive = destructive;
137
+ return Object.freeze(result) as PermittedEffects;
138
+ }
139
+
140
+ /**
141
+ * This function gives the capability for a request: the same as `current`, or narrower. The
142
+ * clearance is the meet of the two values (a ladder: the lower one). The permitted effects are
143
+ * the intersection. The deadline is the earlier one. Two different clearances need an order.
144
+ */
145
+ export function narrowCapability<C>(
146
+ current: Capability<C> | undefined,
147
+ request: CapabilityRequest<C> | undefined,
148
+ order?: ClearanceOrder<C>,
149
+ now: number = Date.now()
150
+ ): Capability<C> {
151
+ const base = current ?? (UNLIMITED_CAPABILITY as Capability<C>);
152
+ if (!request) return base;
153
+ const result: { clearance?: C; effects?: PermittedEffects; deadline?: number } = {};
154
+
155
+ if (request.clearance === undefined) {
156
+ if (base.clearance !== undefined) result.clearance = base.clearance;
157
+ } else if (base.clearance === undefined) {
158
+ result.clearance = request.clearance;
159
+ } else if (Object.is(base.clearance, request.clearance)) {
160
+ result.clearance = base.clearance;
161
+ } else {
162
+ if (!order) {
163
+ throw new Error("Two different clearances need a clearance order: give clearanceOrder to the host (hostPolicy)");
164
+ }
165
+ result.clearance = order.meet(base.clearance, request.clearance);
166
+ }
167
+
168
+ const effects = narrowEffects(base.effects, request.effects);
169
+ if (effects !== undefined) result.effects = effects;
170
+
171
+ const requestedDeadline = request.timeoutMs !== undefined ? now + request.timeoutMs : undefined;
172
+ const deadline =
173
+ base.deadline === undefined ? requestedDeadline : requestedDeadline === undefined ? base.deadline : Math.min(base.deadline, requestedDeadline);
174
+ if (deadline !== undefined) result.deadline = deadline;
175
+
176
+ return Object.freeze(result) as Capability<C>;
177
+ }
178
+
179
+ // =============================================================================
180
+ // Admission
181
+ // =============================================================================
182
+
183
+ /** The reason of a refusal. */
184
+ export type CapabilityRefusalReason = "effects-undeclared" | "effects-exceeded" | "clearance" | "deadline";
185
+
186
+ /** A refused invocation: the reason and a message for the caller. */
187
+ export interface CapabilityRefusal {
188
+ readonly reason: CapabilityRefusalReason;
189
+ readonly message: string;
190
+ }
191
+
192
+ const VERB = { reads: "reads", writes: "changes" } as const;
193
+
194
+ /**
195
+ * This function gives the refusal of an invocation under a capability, or undefined when the
196
+ * capability permits it. It compares the declared effects with the permitted effects, and the
197
+ * time with the deadline. The clearance does not refuse an invocation: it limits the data.
198
+ */
199
+ export function capabilityRefusal(
200
+ procedure: AnyProcedure,
201
+ capability: Capability<unknown> | undefined,
202
+ now: number = Date.now()
203
+ ): CapabilityRefusal | undefined {
204
+ if (!capability) return undefined;
205
+ const key = pathToKey(procedure.path);
206
+ if (capability.deadline !== undefined && now >= capability.deadline) {
207
+ return { reason: "deadline", message: `The deadline of the call passed before ${key} started` };
208
+ }
209
+ const permitted = capability.effects;
210
+ if (!permitted) return undefined;
211
+ const effects = effectsOf(procedure);
212
+ for (const kind of ["reads", "writes"] as const) {
213
+ const allowed = permitted[kind];
214
+ if (allowed === undefined || allowed === "any") continue;
215
+ const declared = effects?.[kind];
216
+ const limit = allowed.length > 0 ? `only ${allowed.join(", ")}` : "nothing";
217
+ if (declared === undefined) {
218
+ return {
219
+ reason: "effects-undeclared",
220
+ message: `${key} does not declare what it ${VERB[kind]}, and this call ${VERB[kind]} ${limit}`,
221
+ };
222
+ }
223
+ const extra = declared.filter((name) => !allowed.includes(name));
224
+ if (extra.length > 0) {
225
+ return {
226
+ reason: "effects-exceeded",
227
+ message: `${key} ${VERB[kind]} ${extra.join(", ")}, and this call ${VERB[kind]} ${limit}`,
228
+ };
229
+ }
230
+ }
231
+ if (permitted.destructive === false && isDestructive(effects)) {
232
+ return {
233
+ reason: "effects-exceeded",
234
+ message: `${key} can delete or overwrite data, and this call cannot start such a procedure`,
235
+ };
236
+ }
237
+ return undefined;
238
+ }
239
+
240
+ /**
241
+ * True when the admission examines a nested call: the caller is data-driven (its caller chose
242
+ * the refs), the caller declares no effects, or the capability of the nested call is narrower
243
+ * than the capability of its caller. A procedure of code with declared effects answers for its
244
+ * own nested calls (`checkEffects()` examines them).
245
+ */
246
+ export function examinesNestedCall(caller: AnyProcedure, callerCapability: Capability<unknown>, capability: Capability<unknown>): boolean {
247
+ return isDataDriven(caller) || effectsOf(caller) === undefined || capability !== callerCapability;
248
+ }
249
+
250
+ /** The result of `explainInvocation()`. */
251
+ export interface InvocationExplanation {
252
+ readonly permitted: boolean;
253
+ readonly reason?: CapabilityRefusalReason | "not-found";
254
+ readonly message: string;
255
+ }
256
+
257
+ /**
258
+ * This function tells, without a call, if a host call of a path is permitted under a
259
+ * capability, and why. An application can show it to a user.
260
+ */
261
+ export function explainInvocation(
262
+ path: ProcedurePath,
263
+ capability: Capability<unknown> | undefined,
264
+ registry: { get(path: ProcedurePath): AnyProcedure | undefined },
265
+ now: number = Date.now()
266
+ ): InvocationExplanation {
267
+ const procedure = registry.get(path);
268
+ if (!procedure) return { permitted: false, reason: "not-found", message: `No procedure at ${pathToKey(path)}` };
269
+ const refusal = capabilityRefusal(procedure, capability, now);
270
+ if (refusal) return { permitted: false, reason: refusal.reason, message: refusal.message };
271
+ return { permitted: true, message: `${pathToKey(path)} is permitted` };
272
+ }
273
+
274
+ // =============================================================================
275
+ // The static check
276
+ // =============================================================================
277
+
278
+ /** A declaration of effects that does not include the effects of a procedure that it calls. */
279
+ export interface EffectsViolation {
280
+ readonly path: ProcedurePath;
281
+ readonly callee: string;
282
+ readonly message: string;
283
+ }
284
+
285
+ /**
286
+ * This function examines each procedure of code that declares its effects and its calls
287
+ * (`metadata.calls`). Its declaration must include the reads and the writes of each procedure
288
+ * that it calls: the resources are structural, so a caller cannot touch less than its callees.
289
+ *
290
+ * Two things are the caller's own claim, and the check does not compare them. A callee that
291
+ * writes `process` starts any program, and its caller declares what that program does. The
292
+ * flags `destructive` and `idempotent` depend on the input that the caller gives: `lib.new`
293
+ * creates new files with `fs.write`, which can overwrite. The effects snapshot of the MCP tools
294
+ * shows these claims in a review.
295
+ */
296
+ export function checkEffects(procedures: readonly AnyProcedure[]): EffectsViolation[] {
297
+ const byKey = new Map(procedures.map((procedure) => [pathToKey(procedure.path), procedure]));
298
+ const violations: EffectsViolation[] = [];
299
+ for (const procedure of procedures) {
300
+ const own = effectsOf(procedure);
301
+ const calls = (procedure.metadata as { calls?: unknown } | undefined)?.calls;
302
+ if (!own || !Array.isArray(calls) || isDataDriven(procedure)) continue;
303
+ const key = pathToKey(procedure.path);
304
+ const callees = new Set<string>();
305
+ for (const entry of calls as string[]) {
306
+ for (const candidate of byKey.keys()) if (callMatches(entry, candidate)) callees.add(candidate);
307
+ }
308
+ for (const calleeKey of [...callees].sort()) {
309
+ const callee = byKey.get(calleeKey)!;
310
+ const theirs = effectsOf(callee);
311
+ const report = (message: string): void => {
312
+ violations.push({ path: procedure.path, callee: calleeKey, message: `${key} calls ${calleeKey}: ${message}` });
313
+ };
314
+ if (!theirs) {
315
+ report("the callee declares no effects");
316
+ continue;
317
+ }
318
+ for (const kind of ["reads", "writes"] as const) {
319
+ const mine = own[kind];
320
+ const needed = (theirs[kind] ?? []).filter((name) => name !== "process");
321
+ if (theirs[kind] === undefined) {
322
+ report(`the callee does not declare what it ${VERB[kind]}`);
323
+ } else if (mine === undefined) {
324
+ continue;
325
+ } else {
326
+ const missing = needed.filter((name) => !mine.includes(name));
327
+ if (missing.length > 0) report(`the declaration does not include ${missing.join(", ")} in ${kind}`);
328
+ }
329
+ }
330
+ }
331
+ }
332
+ return violations;
333
+ }
@@ -61,9 +61,9 @@ function exportTargets(value: unknown): string[] {
61
61
  /**
62
62
  * The specifier that imports the procedures file of a package. With an `exports` map, a bare
63
63
  * `pkg/dist/register.js` import fails unless the map exports that path: the function looks for
64
- * the subpath (also a `./*` pattern) whose target is the file. When the map has none, it
65
- * imports the package root, which registers the procedures of the packages of this repository.
66
- * (Before, the generated import ignored the map, and failed: deep dive CORE-17.)
64
+ * the subpath (also a `./*` pattern) whose target is the file: for the packages of this
65
+ * repository, `./register`. When the map has none, it imports the package root. (Before, the
66
+ * generated import ignored the map, and failed: deep dive CORE-17.)
67
67
  *
68
68
  * @param name - Package name
69
69
  * @param manifest - The package.json of the package
@@ -10,6 +10,7 @@
10
10
  * - init: Initialize a new library with procedure support
11
11
  */
12
12
 
13
+ import { readFileSync } from "node:fs";
13
14
  import { parseArgs } from "node:util";
14
15
  import { discover } from "./discover.js";
15
16
  import { init } from "./init.js";
@@ -151,13 +152,14 @@ How it works:
151
152
 
152
153
  4. App runs "client-registry discover" to generate imports from the registry
153
154
 
154
- For more information, visit: https://github.com/your-org/client
155
+ For more information, visit: https://mark1russell7.github.io/client/
155
156
  `);
156
157
  }
157
158
 
158
159
  function printVersion(): void {
159
- // Read version from package.json at runtime
160
- console.log("client v1.0.0");
160
+ // This file is dist/procedures/cli/index.js: the package.json is three folders up
161
+ const manifest = JSON.parse(readFileSync(new URL("../../../package.json", import.meta.url), "utf8")) as { version: string };
162
+ console.log(`client-registry v${manifest.version}`);
161
163
  }
162
164
 
163
165
  // =============================================================================
@@ -235,7 +235,7 @@ npm install ${name}
235
235
 
236
236
  ## How It Works
237
237
 
238
- This library automatically registers its procedures when installed:
238
+ This library announces its procedures when installed:
239
239
 
240
240
  1. The \`postinstall\` script runs \`client-registry announce\`
241
241
  2. This writes to \`.client-registry.json\` at your project root
@@ -84,6 +84,8 @@ function getStorage<T>(ctx: ProcedureContext): ReturnType<NonNullable<ProcedureC
84
84
  */
85
85
  export function createCollectionProcedures(collectionName: string): AnyProcedure[] {
86
86
  const basePath = ["collections", collectionName];
87
+ // Each collection is a resource of its own (CAPABILITIES-2026-10): a capability can permit one
88
+ const resource = `collection:${collectionName}`;
87
89
 
88
90
  return [
89
91
  // Get
@@ -92,6 +94,7 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
92
94
  input: getInputSchema,
93
95
  output: getOutputSchema,
94
96
  metadata: {
97
+ effects: { reads: [resource], writes: [] },
95
98
  description: `Get item from ${collectionName} by ID`,
96
99
  tags: ["collections", collectionName, "read"],
97
100
  },
@@ -107,6 +110,7 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
107
110
  input: setInputSchema,
108
111
  output: setOutputSchema,
109
112
  metadata: {
113
+ effects: { reads: [resource], writes: [resource], idempotent: true },
110
114
  description: `Set item in ${collectionName}`,
111
115
  tags: ["collections", collectionName, "write"],
112
116
  },
@@ -122,6 +126,7 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
122
126
  input: deleteInputSchema,
123
127
  output: deleteOutputSchema,
124
128
  metadata: {
129
+ effects: { reads: [resource], writes: [resource], idempotent: true },
125
130
  description: `Delete item from ${collectionName}`,
126
131
  tags: ["collections", collectionName, "write"],
127
132
  },
@@ -137,6 +142,7 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
137
142
  input: hasInputSchema,
138
143
  output: hasOutputSchema,
139
144
  metadata: {
145
+ effects: { reads: [resource], writes: [] },
140
146
  description: `Check if item exists in ${collectionName}`,
141
147
  tags: ["collections", collectionName, "read"],
142
148
  },
@@ -152,6 +158,7 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
152
158
  input: getAllInputSchema,
153
159
  output: getAllOutputSchema,
154
160
  metadata: {
161
+ effects: { reads: [resource], writes: [] },
155
162
  description: `Get all items from ${collectionName}`,
156
163
  tags: ["collections", collectionName, "read"],
157
164
  },
@@ -167,6 +174,7 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
167
174
  input: sizeInputSchema,
168
175
  output: sizeOutputSchema,
169
176
  metadata: {
177
+ effects: { reads: [resource], writes: [] },
170
178
  description: `Get number of items in ${collectionName}`,
171
179
  tags: ["collections", collectionName, "read"],
172
180
  },
@@ -182,6 +190,7 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
182
190
  input: clearInputSchema,
183
191
  output: clearOutputSchema,
184
192
  metadata: {
193
+ effects: { reads: [resource], writes: [resource], idempotent: true },
185
194
  description: `Clear all items from ${collectionName}`,
186
195
  tags: ["collections", collectionName, "write"],
187
196
  },
@@ -197,6 +206,7 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
197
206
  input: getBatchInputSchema,
198
207
  output: getBatchOutputSchema,
199
208
  metadata: {
209
+ effects: { reads: [resource], writes: [] },
200
210
  description: `Get multiple items from ${collectionName}`,
201
211
  tags: ["collections", collectionName, "read", "batch"],
202
212
  },
@@ -214,6 +224,7 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
214
224
  input: setBatchInputSchema,
215
225
  output: setBatchOutputSchema,
216
226
  metadata: {
227
+ effects: { reads: [resource], writes: [resource], idempotent: true },
217
228
  description: `Set multiple items in ${collectionName}`,
218
229
  tags: ["collections", collectionName, "write", "batch"],
219
230
  },
@@ -233,6 +244,7 @@ export function createCollectionProcedures(collectionName: string): AnyProcedure
233
244
  input: deleteBatchInputSchema,
234
245
  output: deleteBatchOutputSchema,
235
246
  metadata: {
247
+ effects: { reads: [resource], writes: [resource], idempotent: true },
236
248
  description: `Delete multiple items from ${collectionName}`,
237
249
  tags: ["collections", collectionName, "write", "batch"],
238
250
  },
@@ -9,6 +9,7 @@ import { z } from "zod";
9
9
  import { anyOutput, arrayOutput, booleanOutput, numberOutput } from "./outputs.js";
10
10
  import { defineProcedure, namespace } from "../define.js";
11
11
  import type { AnyProcedure, Procedure } from "../types.js";
12
+ import { PURE_EFFECTS, withEffects } from "../effects.js";
12
13
 
13
14
  // =============================================================================
14
15
  // First Procedure
@@ -978,7 +979,8 @@ const arrayProceduresRaw: AnyProcedure[] = [
978
979
  /**
979
980
  * All array procedures namespaced under "client".
980
981
  */
981
- export const arrayProcedures: AnyProcedure[] = namespace(["client"], arrayProceduresRaw);
982
+ // Each one calculates its output from its input: no reads, no changes (CAPABILITIES-2026-10)
983
+ export const arrayProcedures: AnyProcedure[] = namespace(["client"], withEffects(PURE_EFFECTS, arrayProceduresRaw));
982
984
 
983
985
  /**
984
986
  * Array procedures module for registration.
@@ -9,6 +9,7 @@ import { z } from "zod";
9
9
  import { booleanOutput } from "./outputs.js";
10
10
  import { defineProcedure, namespace } from "../define.js";
11
11
  import type { AnyProcedure, Procedure } from "../types.js";
12
+ import { PURE_EFFECTS, withEffects } from "../effects.js";
12
13
 
13
14
  // =============================================================================
14
15
  // Eq Procedure (Strict Equality)
@@ -241,7 +242,8 @@ const comparisonProceduresRaw: AnyProcedure[] = [
241
242
  /**
242
243
  * All comparison procedures namespaced under "client".
243
244
  */
244
- export const comparisonProcedures: AnyProcedure[] = namespace(["client"], comparisonProceduresRaw);
245
+ // Each one calculates its output from its input: no reads, no changes (CAPABILITIES-2026-10)
246
+ export const comparisonProcedures: AnyProcedure[] = namespace(["client"], withEffects(PURE_EFFECTS, comparisonProceduresRaw));
245
247
 
246
248
  /**
247
249
  * Comparison procedures module for registration.
@@ -45,6 +45,7 @@
45
45
  * ```
46
46
  */
47
47
 
48
+ import { PURE_EFFECTS, withEffects } from "../effects.js";
48
49
  import { z } from "zod";
49
50
  import { anyOutput, booleanOutput } from "./outputs.js";
50
51
  import { defineProcedure, namespace } from "../define.js";
@@ -786,7 +787,9 @@ const tryCatchProcedure: TryCatchProcedure = defineProcedure({
786
787
  /**
787
788
  * All core language procedures namespaced under "client".
788
789
  */
789
- export const coreProcedures: AnyProcedure[] = namespace(["client"], [
790
+ // Control flow and the operators: no effects of their own. The admission examines each nested
791
+ // call of a data-driven procedure (CAPABILITIES-2026-10).
792
+ export const coreProcedures: AnyProcedure[] = namespace(["client"], withEffects(PURE_EFFECTS, [
790
793
  chainProcedure as AnyProcedure,
791
794
  parallelProcedure as AnyProcedure,
792
795
  conditionalProcedure as AnyProcedure,
@@ -805,7 +808,7 @@ export const coreProcedures: AnyProcedure[] = namespace(["client"], [
805
808
  constantProcedure as AnyProcedure,
806
809
  throwProcedure as AnyProcedure,
807
810
  tryCatchProcedure as AnyProcedure,
808
- ]);
811
+ ]));
809
812
 
810
813
  /**
811
814
  * Core procedures module for registration.
@@ -9,6 +9,7 @@ import { z } from "zod";
9
9
  import { numberOutput } from "./outputs.js";
10
10
  import { defineProcedure, namespace } from "../define.js";
11
11
  import type { AnyProcedure, Procedure } from "../types.js";
12
+ import { PURE_EFFECTS, withEffects } from "../effects.js";
12
13
 
13
14
  // =============================================================================
14
15
  // Add Procedure
@@ -440,7 +441,8 @@ const mathProceduresRaw: AnyProcedure[] = [
440
441
  /**
441
442
  * All math procedures namespaced under "client".
442
443
  */
443
- export const mathProcedures: AnyProcedure[] = namespace(["client"], mathProceduresRaw);
444
+ // Each one calculates its output from its input: no reads, no changes (CAPABILITIES-2026-10)
445
+ export const mathProcedures: AnyProcedure[] = namespace(["client"], withEffects(PURE_EFFECTS, mathProceduresRaw));
444
446
 
445
447
  /**
446
448
  * Math procedures module for registration.
@@ -23,6 +23,7 @@ import {
23
23
  } from "../ref.js";
24
24
  import { callOperand } from "./operands.js";
25
25
  import { PROCEDURE_REGISTRY } from "../registry.js";
26
+ import { PURE_EFFECTS, REGISTRY_READ_EFFECTS, withEffects } from "../effects.js";
26
27
 
27
28
  // =============================================================================
28
29
  // Import Procedure
@@ -352,12 +353,16 @@ const lookupProcedure: LookupProcedure = defineProcedure({
352
353
  * All meta procedures (before namespacing).
353
354
  */
354
355
  const metaProceduresRaw: AnyProcedure[] = [
355
- importProcedure as AnyProcedure,
356
- exportProcedure as AnyProcedure,
357
- evalProcedure as AnyProcedure,
358
- parseJsonProcedure as AnyProcedure,
359
- stringifyJsonProcedure as AnyProcedure,
360
- lookupProcedure as AnyProcedure,
356
+ // These parse, serialize or run programs: no effects of their own (CAPABILITIES-2026-10)
357
+ ...withEffects(PURE_EFFECTS, [
358
+ importProcedure as AnyProcedure,
359
+ exportProcedure as AnyProcedure,
360
+ evalProcedure as AnyProcedure,
361
+ parseJsonProcedure as AnyProcedure,
362
+ stringifyJsonProcedure as AnyProcedure,
363
+ ]),
364
+ // It reads the registry
365
+ ...withEffects(REGISTRY_READ_EFFECTS, [lookupProcedure as AnyProcedure]),
361
366
  ];
362
367
 
363
368
  /**
@@ -9,6 +9,7 @@ import { z } from "zod";
9
9
  import { anyOutput, arrayOutput, booleanOutput, recordOutput } from "./outputs.js";
10
10
  import { defineProcedure, namespace } from "../define.js";
11
11
  import type { AnyProcedure, Procedure } from "../types.js";
12
+ import { PURE_EFFECTS, withEffects } from "../effects.js";
12
13
 
13
14
  // =============================================================================
14
15
  // Helper: Get nested value by path
@@ -402,7 +403,8 @@ const objectProceduresRaw: AnyProcedure[] = [
402
403
  /**
403
404
  * All object procedures namespaced under "client".
404
405
  */
405
- export const objectProcedures: AnyProcedure[] = namespace(["client"], objectProceduresRaw);
406
+ // Each one calculates its output from its input: no reads, no changes (CAPABILITIES-2026-10)
407
+ export const objectProcedures: AnyProcedure[] = namespace(["client"], withEffects(PURE_EFFECTS, objectProceduresRaw));
406
408
 
407
409
  /**
408
410
  * Object procedures module for registration.
@@ -9,6 +9,7 @@ import { z } from "zod";
9
9
  import { booleanOutput, numberOutput, stringOutput } from "./outputs.js";
10
10
  import { defineProcedure, namespace } from "../define.js";
11
11
  import type { AnyProcedure, Procedure } from "../types.js";
12
+ import { PURE_EFFECTS, withEffects } from "../effects.js";
12
13
 
13
14
  // =============================================================================
14
15
  // Concat Procedure
@@ -430,7 +431,8 @@ const stringProceduresRaw: AnyProcedure[] = [
430
431
  /**
431
432
  * All string procedures namespaced under "client".
432
433
  */
433
- export const stringProcedures: AnyProcedure[] = namespace(["client"], stringProceduresRaw);
434
+ // Each one calculates its output from its input: no reads, no changes (CAPABILITIES-2026-10)
435
+ export const stringProcedures: AnyProcedure[] = namespace(["client"], withEffects(PURE_EFFECTS, stringProceduresRaw));
434
436
 
435
437
  /**
436
438
  * String procedures module for registration.
@@ -9,6 +9,7 @@ import { z } from "zod";
9
9
  import { anyOutput, booleanOutput, stringOutput } from "./outputs.js";
10
10
  import { defineProcedure, namespace } from "../define.js";
11
11
  import type { AnyProcedure, Procedure } from "../types.js";
12
+ import { PURE_EFFECTS, withEffects } from "../effects.js";
12
13
 
13
14
  // =============================================================================
14
15
  // Typeof Procedure
@@ -319,7 +320,8 @@ const typeProceduresRaw: AnyProcedure[] = [
319
320
  /**
320
321
  * All type procedures namespaced under "client".
321
322
  */
322
- export const typeProcedures: AnyProcedure[] = namespace(["client"], typeProceduresRaw);
323
+ // Each one calculates its output from its input: no reads, no changes (CAPABILITIES-2026-10)
324
+ export const typeProcedures: AnyProcedure[] = namespace(["client"], withEffects(PURE_EFFECTS, typeProceduresRaw));
323
325
 
324
326
  /**
325
327
  * Type procedures module for registration.
@@ -47,6 +47,7 @@ import {
47
47
  rawInputRule,
48
48
  RUNS_REFS_TAG,
49
49
  } from "./ref.js";
50
+ import { REGISTRY_READ_EFFECTS, withEffects } from "./effects.js";
50
51
 
51
52
  // Input schemas of procedure.define and procedure.delete (roadmap 2.1)
52
53
  const procedurePathField = z.array(z.string().min(1)).min(1);
@@ -524,8 +525,8 @@ export const deleteProcedureProcedure: Procedure<
524
525
  * All meta-procedures for procedure management.
525
526
  */
526
527
  export const metaProcedures: AnyProcedure[] = [
527
- defineProcedureProcedure as AnyProcedure,
528
- getProcedureProcedure as AnyProcedure,
529
- listProceduresProcedure as AnyProcedure,
530
- deleteProcedureProcedure as AnyProcedure,
528
+ // They read the registry. define and delete change it (CAPABILITIES-2026-10).
529
+ ...withEffects({ reads: ["registry"], writes: ["registry"] }, [defineProcedureProcedure as AnyProcedure]),
530
+ ...withEffects(REGISTRY_READ_EFFECTS, [getProcedureProcedure as AnyProcedure, listProceduresProcedure as AnyProcedure]),
531
+ ...withEffects({ reads: ["registry"], writes: ["registry"] }, [deleteProcedureProcedure as AnyProcedure]),
531
532
  ];