functionalscript 0.45.0 → 0.46.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 (296) hide show
  1. package/README.md +5 -3
  2. package/fjs/asn.1/module.f.mjs +8 -1
  3. package/fjs/asn.1/proof.f.d.mts +10 -0
  4. package/fjs/asn.1/proof.f.mjs +16 -0
  5. package/fjs/basen/base128/module.f.mjs +22 -5
  6. package/fjs/bnf/data/module.f.d.mts +32 -6
  7. package/fjs/bnf/data/module.f.mjs +112 -7
  8. package/fjs/bnf/data/proof.f.d.mts +2 -0
  9. package/fjs/bnf/data/proof.f.mjs +74 -3
  10. package/fjs/bnf/data/types.d.ts +20 -2
  11. package/fjs/bnf/descent/module.f.d.mts +28 -14
  12. package/fjs/bnf/descent/module.f.mjs +122 -77
  13. package/fjs/bnf/descent/proof.f.d.mts +2 -0
  14. package/fjs/bnf/descent/proof.f.mjs +117 -31
  15. package/fjs/bnf/descent/types.d.ts +12 -14
  16. package/fjs/bnf/ll1/module.f.d.mts +64 -22
  17. package/fjs/bnf/ll1/module.f.mjs +214 -154
  18. package/fjs/bnf/ll1/proof.f.d.mts +15 -2
  19. package/fjs/bnf/ll1/proof.f.mjs +323 -149
  20. package/fjs/bnf/ll1/types.d.ts +16 -24
  21. package/fjs/bnf/matcher/module.f.d.mts +66 -0
  22. package/fjs/bnf/matcher/module.f.mjs +81 -0
  23. package/fjs/bnf/matcher/proof.f.d.mts +10 -0
  24. package/fjs/bnf/matcher/proof.f.mjs +79 -0
  25. package/fjs/bnf/matcher/types.d.ts +54 -0
  26. package/fjs/bnf/testlib.f.d.mts +31 -0
  27. package/fjs/bnf/testlib.f.mjs +80 -0
  28. package/fjs/cas/cli/module.f.d.mts +1 -1
  29. package/fjs/cas/cli/module.f.mjs +14 -20
  30. package/fjs/cas/cli/proof.f.d.mts +1 -3
  31. package/fjs/cas/cli/proof.f.mjs +44 -33
  32. package/fjs/cas/evo/module.f.d.mts +64 -18
  33. package/fjs/cas/evo/module.f.mjs +148 -70
  34. package/fjs/cas/evo/proof.f.d.mts +10 -1
  35. package/fjs/cas/evo/proof.f.mjs +305 -223
  36. package/fjs/cas/evo/types.d.ts +45 -9
  37. package/fjs/cas/module.f.d.mts +18 -32
  38. package/fjs/cas/module.f.mjs +129 -128
  39. package/fjs/cas/proof.f.d.mts +11 -8
  40. package/fjs/cas/proof.f.mjs +259 -147
  41. package/fjs/cas/types.d.ts +24 -10
  42. package/fjs/ci/config/module.f.d.mts +2 -2
  43. package/fjs/ci/config/module.f.mjs +2 -2
  44. package/fjs/ci/module.f.d.mts +5 -5
  45. package/fjs/ci/module.f.mjs +8 -7
  46. package/fjs/ci/nix/module.f.d.mts +7 -5
  47. package/fjs/ci/nix/module.f.mjs +13 -12
  48. package/fjs/ci/nix/proof.f.mjs +2 -2
  49. package/fjs/ci/proof.f.mjs +9 -5
  50. package/fjs/cli/module.f.d.mts +4 -6
  51. package/fjs/cli/module.f.mjs +4 -8
  52. package/fjs/cli/proof.f.mjs +17 -16
  53. package/fjs/cli/types.d.ts +2 -3
  54. package/fjs/common/monoid/types.d.ts +1 -1
  55. package/fjs/crypto/hmac/module.f.mjs +2 -2
  56. package/fjs/crypto/sha2/module.f.mjs +3 -1
  57. package/fjs/crypto/sha2/proof.f.d.mts +1 -0
  58. package/fjs/crypto/sha2/proof.f.mjs +24 -0
  59. package/fjs/crypto/sha2/types.d.ts +11 -0
  60. package/fjs/crypto/sign/module.f.mjs +2 -2
  61. package/fjs/dev/module.f.d.mts +13 -4
  62. package/fjs/dev/module.f.mjs +56 -27
  63. package/fjs/dev/update/module.f.d.mts +9 -4
  64. package/fjs/dev/update/module.f.mjs +14 -10
  65. package/fjs/dev/update/proof.f.d.mts +1 -3
  66. package/fjs/dev/update/proof.f.mjs +10 -5
  67. package/fjs/djs/module.f.d.mts +13 -5
  68. package/fjs/djs/module.f.mjs +31 -16
  69. package/fjs/djs/parser/module.f.d.mts +13 -3
  70. package/fjs/djs/parser/module.f.mjs +117 -16
  71. package/fjs/djs/parser/proof.f.d.mts +5 -1
  72. package/fjs/djs/parser/proof.f.mjs +274 -12
  73. package/fjs/djs/parser/types.d.ts +7 -1
  74. package/fjs/djs/proof.f.d.mts +18 -2
  75. package/fjs/djs/proof.f.mjs +187 -12
  76. package/fjs/djs/serializer/module.f.d.mts +25 -8
  77. package/fjs/djs/serializer/module.f.mjs +61 -15
  78. package/fjs/djs/serializer/proof.f.d.mts +5 -0
  79. package/fjs/djs/serializer/proof.f.mjs +23 -1
  80. package/fjs/djs/tokenizer/module.f.d.mts +17 -5
  81. package/fjs/djs/tokenizer/module.f.mjs +99 -60
  82. package/fjs/djs/tokenizer/proof.f.d.mts +1 -1
  83. package/fjs/djs/tokenizer/proof.f.mjs +74 -67
  84. package/fjs/djs/transpiler/module.f.d.mts +12 -7
  85. package/fjs/djs/transpiler/module.f.mjs +82 -56
  86. package/fjs/djs/transpiler/types.d.ts +7 -3
  87. package/fjs/djs/types.d.ts +7 -1
  88. package/fjs/effects/list/module.f.d.mts +14 -11
  89. package/fjs/effects/list/module.f.mjs +13 -11
  90. package/fjs/effects/list/types.d.ts +27 -7
  91. package/fjs/effects/memory/module.f.d.mts +2 -1
  92. package/fjs/effects/memory/module.f.mjs +3 -4
  93. package/fjs/effects/memory/proof.f.mjs +13 -7
  94. package/fjs/effects/memory/types.d.ts +4 -3
  95. package/fjs/effects/mock/module.f.d.mts +20 -4
  96. package/fjs/effects/mock/module.f.mjs +37 -5
  97. package/fjs/effects/mock/types.d.ts +10 -1
  98. package/fjs/effects/module.d.mts +4 -2
  99. package/fjs/effects/module.f.d.mts +466 -280
  100. package/fjs/effects/module.f.mjs +537 -299
  101. package/fjs/effects/module.mjs +2 -1
  102. package/fjs/effects/node/memory/module.d.mts +4 -2
  103. package/fjs/effects/node/memory/module.mjs +6 -3
  104. package/fjs/effects/node/memory/proof.mjs +10 -4
  105. package/fjs/effects/node/module.d.mts +4 -3
  106. package/fjs/effects/node/module.f.d.mts +168 -33
  107. package/fjs/effects/node/module.f.mjs +256 -52
  108. package/fjs/effects/node/module.mjs +57 -34
  109. package/fjs/effects/node/proof.f.d.mts +28 -2
  110. package/fjs/effects/node/proof.f.mjs +161 -42
  111. package/fjs/effects/node/types.d.ts +106 -18
  112. package/fjs/effects/node/virtual/module.f.d.mts +18 -4
  113. package/fjs/effects/node/virtual/module.f.mjs +110 -68
  114. package/fjs/effects/node/virtual/proof.f.d.mts +28 -2
  115. package/fjs/effects/node/virtual/proof.f.mjs +190 -9
  116. package/fjs/effects/proof.f.d.mts +69 -37
  117. package/fjs/effects/proof.f.mjs +410 -130
  118. package/fjs/effects/types.d.ts +161 -33
  119. package/fjs/emergent_testing/module.f.d.mts +19 -12
  120. package/fjs/emergent_testing/module.f.mjs +93 -33
  121. package/fjs/emergent_testing/proof.f.d.mts +21 -7
  122. package/fjs/emergent_testing/proof.f.mjs +166 -32
  123. package/fjs/emergent_testing/types.d.ts +22 -4
  124. package/fjs/fsm/module.f.d.mts +14 -4
  125. package/fjs/fsm/module.f.mjs +54 -37
  126. package/fjs/fsm/proof.f.d.mts +2 -0
  127. package/fjs/fsm/proof.f.mjs +83 -114
  128. package/fjs/js/keywords/module.f.d.mts +52 -0
  129. package/fjs/js/keywords/module.f.mjs +72 -0
  130. package/fjs/js/keywords/proof.f.d.mts +3 -0
  131. package/fjs/js/keywords/proof.f.mjs +13 -0
  132. package/fjs/js/tokenizer/module.f.d.mts +26 -6
  133. package/fjs/js/tokenizer/module.f.mjs +145 -159
  134. package/fjs/js/tokenizer/proof.f.d.mts +1 -0
  135. package/fjs/js/tokenizer/proof.f.mjs +54 -24
  136. package/fjs/js/tokenizer/types.d.ts +33 -24
  137. package/fjs/mcp/cas/module.f.d.mts +1 -6
  138. package/fjs/mcp/cas/module.f.mjs +55 -51
  139. package/fjs/mcp/cas/proof.f.d.mts +15 -0
  140. package/fjs/mcp/cas/proof.f.mjs +174 -0
  141. package/fjs/mcp/evo/module.f.d.mts +19 -10
  142. package/fjs/mcp/evo/module.f.mjs +48 -27
  143. package/fjs/mcp/evo/proof.f.d.mts +6 -1
  144. package/fjs/mcp/evo/proof.f.mjs +115 -31
  145. package/fjs/mcp/module.f.d.mts +4 -4
  146. package/fjs/mcp/module.f.mjs +6 -6
  147. package/fjs/mcp/proof.f.d.mts +5 -3
  148. package/fjs/mcp/proof.f.mjs +112 -46
  149. package/fjs/media/html/module.f.mjs +1 -1
  150. package/fjs/media/json/extended/module.f.d.mts +82 -0
  151. package/fjs/media/json/extended/module.f.mjs +153 -0
  152. package/fjs/media/json/extended/proof.f.d.mts +42 -0
  153. package/fjs/media/json/extended/proof.f.mjs +127 -0
  154. package/fjs/media/json/extended/types.d.ts +23 -0
  155. package/fjs/media/json/module.f.d.mts +8 -2
  156. package/fjs/media/json/module.f.mjs +43 -41
  157. package/fjs/media/json/number/module.f.d.mts +59 -0
  158. package/fjs/media/json/number/module.f.mjs +136 -0
  159. package/fjs/media/json/number/proof.f.d.mts +24 -0
  160. package/fjs/media/json/number/proof.f.mjs +86 -0
  161. package/fjs/media/json/number/types.d.ts +28 -0
  162. package/fjs/media/json/parser/module.f.d.mts +25 -13
  163. package/fjs/media/json/parser/module.f.mjs +114 -70
  164. package/fjs/media/json/parser/proof.f.d.mts +5 -0
  165. package/fjs/media/json/parser/proof.f.mjs +31 -1
  166. package/fjs/media/json/parser/types.d.ts +33 -14
  167. package/fjs/media/json/rtti/module.f.d.mts +1 -1
  168. package/fjs/media/json/rtti/module.f.mjs +1 -1
  169. package/fjs/media/json/rtti/proof.f.mjs +9 -9
  170. package/fjs/media/json/schema/module.f.mjs +3 -13
  171. package/fjs/media/json/schema/proof.f.d.mts +0 -1
  172. package/fjs/media/json/schema/proof.f.mjs +1 -2
  173. package/fjs/media/json/serializer/module.f.d.mts +32 -1
  174. package/fjs/media/json/serializer/module.f.mjs +64 -2
  175. package/fjs/media/json/tokenizer/module.f.mjs +7 -3
  176. package/fjs/media/json/tokenizer/proof.f.d.mts +6 -0
  177. package/fjs/media/json/tokenizer/proof.f.mjs +62 -21
  178. package/fjs/media/json/types.d.ts +36 -10
  179. package/fjs/media/lock/module.f.d.mts +100 -0
  180. package/fjs/media/lock/module.f.mjs +125 -0
  181. package/fjs/media/lock/proof.f.d.mts +33 -0
  182. package/fjs/media/lock/proof.f.mjs +196 -0
  183. package/fjs/media/lock/types.d.ts +15 -0
  184. package/fjs/media/module.f.d.mts +6 -5
  185. package/fjs/media/module.f.mjs +8 -7
  186. package/fjs/media/note/module.f.d.mts +121 -0
  187. package/fjs/media/note/module.f.mjs +131 -0
  188. package/fjs/media/note/proof.f.d.mts +29 -0
  189. package/fjs/media/note/proof.f.mjs +150 -0
  190. package/fjs/media/note/types.d.ts +10 -0
  191. package/fjs/media/proof.f.d.mts +4 -1
  192. package/fjs/media/proof.f.mjs +40 -21
  193. package/fjs/media/revision/module.f.d.mts +78 -7
  194. package/fjs/media/revision/module.f.mjs +119 -12
  195. package/fjs/media/revision/proof.f.d.mts +10 -0
  196. package/fjs/media/revision/proof.f.mjs +88 -0
  197. package/fjs/media/revision/types.d.ts +34 -5
  198. package/fjs/media/type/module.f.d.mts +33 -15
  199. package/fjs/media/type/module.f.mjs +35 -29
  200. package/fjs/media/type/proof.f.d.mts +2 -1
  201. package/fjs/media/type/proof.f.mjs +30 -10
  202. package/fjs/module.f.mjs +29 -8
  203. package/fjs/nanvm/proof.f.mjs +3 -3
  204. package/fjs/nanvm/rust/module.f.mjs +1 -1
  205. package/fjs/nanvm/update/module.f.d.mts +4 -4
  206. package/fjs/nanvm/update/module.f.mjs +8 -9
  207. package/fjs/nanvm/update/proof.f.mjs +4 -3
  208. package/fjs/proof.f.d.mts +3 -3
  209. package/fjs/proof.f.mjs +36 -10
  210. package/fjs/protocol/json_rpc/module.f.d.mts +2 -2
  211. package/fjs/protocol/json_rpc/module.f.mjs +3 -3
  212. package/fjs/protocol/json_rpc/proof.f.mjs +4 -4
  213. package/fjs/protocol/mcp/module.f.d.mts +10 -13
  214. package/fjs/protocol/mcp/module.f.mjs +74 -58
  215. package/fjs/protocol/mcp/proof.f.d.mts +13 -4
  216. package/fjs/protocol/mcp/proof.f.mjs +193 -61
  217. package/fjs/protocol/mcp/stdio/module.f.d.mts +15 -7
  218. package/fjs/protocol/mcp/stdio/module.f.mjs +38 -25
  219. package/fjs/protocol/mcp/stdio/proof.f.d.mts +4 -2
  220. package/fjs/protocol/mcp/stdio/proof.f.mjs +45 -12
  221. package/fjs/protocol/mcp/stdio/types.d.ts +1 -1
  222. package/fjs/protocol/mcp/types.d.ts +17 -6
  223. package/fjs/sul/id/module.f.d.mts +0 -1
  224. package/fjs/sul/id/module.f.mjs +2 -3
  225. package/fjs/sul/level/hash/module.f.mjs +2 -1
  226. package/fjs/sul/level/hash/proof.f.mjs +2 -2
  227. package/fjs/sul/module.f.mjs +18 -13
  228. package/fjs/text/code_point/module.f.d.mts +8 -0
  229. package/fjs/text/code_point/module.f.mjs +8 -1
  230. package/fjs/text/code_point/proof.f.d.mts +1 -0
  231. package/fjs/text/code_point/proof.f.mjs +11 -0
  232. package/fjs/text/sgr/module.f.d.mts +4 -6
  233. package/fjs/text/sgr/module.f.mjs +6 -7
  234. package/fjs/text/utf16/module.f.mjs +7 -2
  235. package/fjs/text/utf16/proof.f.mjs +3 -3
  236. package/fjs/text/utf8/module.f.mjs +4 -2
  237. package/fjs/types/array/module.f.mjs +14 -2
  238. package/fjs/types/bit_vec/module.f.d.mts +0 -2
  239. package/fjs/types/bit_vec/module.f.mjs +46 -32
  240. package/fjs/types/bit_vec/proof.f.mjs +2 -2
  241. package/fjs/types/btree/remove/module.f.mjs +1 -1
  242. package/fjs/types/btree/set/module.f.mjs +9 -11
  243. package/fjs/types/btree/set/proof.f.mjs +12 -0
  244. package/fjs/types/byte_set/module.f.d.mts +11 -4
  245. package/fjs/types/byte_set/module.f.mjs +14 -6
  246. package/fjs/types/byte_set/proof.f.mjs +7 -7
  247. package/fjs/types/function/compare/module.f.mjs +10 -3
  248. package/fjs/types/list/module.f.d.mts +1 -1
  249. package/fjs/types/list/module.f.mjs +1 -1
  250. package/fjs/types/nullable/module.f.d.mts +18 -4
  251. package/fjs/types/nullable/module.f.mjs +21 -4
  252. package/fjs/types/nullable/proof.f.d.mts +4 -0
  253. package/fjs/types/nullable/proof.f.mjs +15 -0
  254. package/fjs/types/object/module.f.d.mts +12 -2
  255. package/fjs/types/object/module.f.mjs +11 -1
  256. package/fjs/types/patricia_trie/module.f.mjs +26 -11
  257. package/fjs/types/result/module.f.d.mts +3 -3
  258. package/fjs/types/result/module.f.mjs +3 -3
  259. package/fjs/types/rtti/common/module.f.d.mts +25 -28
  260. package/fjs/types/rtti/common/module.f.mjs +36 -32
  261. package/fjs/types/rtti/common/proof.f.mjs +5 -5
  262. package/fjs/types/rtti/data/module.f.d.mts +14 -0
  263. package/fjs/types/rtti/data/module.f.mjs +25 -2
  264. package/fjs/types/rtti/data/proof.f.d.mts +1 -0
  265. package/fjs/types/rtti/data/proof.f.mjs +25 -2
  266. package/fjs/types/rtti/parse/module.f.d.mts +24 -14
  267. package/fjs/types/rtti/parse/module.f.mjs +37 -28
  268. package/fjs/types/rtti/parse/proof.f.d.mts +3 -2
  269. package/fjs/types/rtti/parse/proof.f.mjs +33 -14
  270. package/fjs/types/rtti/proof.f.mjs +3 -1
  271. package/fjs/types/rtti/ts/module.f.mjs +13 -17
  272. package/fjs/types/rtti/ts/types.d.ts +14 -1
  273. package/fjs/types/rtti/validate/module.f.d.mts +81 -19
  274. package/fjs/types/rtti/validate/module.f.mjs +107 -61
  275. package/fjs/types/rtti/validate/proof.f.d.mts +13 -10
  276. package/fjs/types/rtti/validate/proof.f.mjs +186 -197
  277. package/fjs/types/sorted_set/module.f.d.mts +18 -0
  278. package/fjs/types/sorted_set/module.f.mjs +22 -0
  279. package/fjs/types/sorted_set/proof.f.d.mts +1 -0
  280. package/fjs/types/sorted_set/proof.f.mjs +16 -1
  281. package/fjs/types/uint8array/module.f.d.mts +1 -1
  282. package/fjs/types/uint8array/module.f.mjs +1 -1
  283. package/fjs/website/module.f.d.mts +3 -3
  284. package/fjs/website/module.f.mjs +4 -7
  285. package/fjs/website/proof.f.mjs +2 -1
  286. package/package.json +2 -2
  287. package/fjs/dev/package_json/module.f.d.mts +0 -39
  288. package/fjs/dev/package_json/module.f.mjs +0 -40
  289. package/fjs/dev/package_json/proof.f.d.mts +0 -6
  290. package/fjs/dev/package_json/proof.f.mjs +0 -32
  291. package/fjs/effects/eff/module.f.d.mts +0 -20
  292. package/fjs/effects/eff/module.f.mjs +0 -70
  293. package/fjs/effects/eff/proof.f.d.mts +0 -15
  294. package/fjs/effects/eff/proof.f.mjs +0 -69
  295. package/fjs/effects/eff/types.d.ts +0 -71
  296. package/fjs/types/rtti/validate/types.d.ts +0 -6
@@ -1,17 +1,35 @@
1
1
  /**
2
2
  * @import { Evo } from '../../cas/evo/types.ts'
3
3
  * @import { ToolEntry, ToolsCallResult } from '../../protocol/mcp/types.ts'
4
- * @import { Operation } from '../../effects/types.ts'
4
+ * @import { NotImplemented, Operation } from '../../effects/types.ts'
5
+ * @import { Result } from '../../types/result/types.ts'
6
+ * @import { State } from '../../effects/node/virtual/types.ts'
5
7
  */
6
8
 
7
9
  import { assert, assertEq } from '../../asserts/module.f.mjs'
8
10
  import { fileCas } from '../../cas/module.f.mjs'
9
11
  import { sha256 } from '../../crypto/sha2/module.f.mjs'
10
12
  import { emptyState, virtual } from '../../effects/node/virtual/module.f.mjs'
13
+ import { runPure } from '../../effects/module.f.mjs'
14
+ import { pureError } from '../../effects/module.f.mjs'
15
+ import { unwrap as unwrapResult } from '../../types/result/module.f.mjs'
11
16
  import { vec8 } from '../../types/bit_vec/module.f.mjs'
12
17
  import { vecToCBase32 } from '../../basen/cbase32/module.f.mjs'
13
18
  import { initEvo, evo } from '../../cas/evo/module.f.mjs'
14
- import { evoToolRegistry } from './module.f.mjs'
19
+ import { evoAddArgs, evoToolRegistry } from './module.f.mjs'
20
+ import { toJsonSchema } from '../../media/json/schema/module.f.mjs'
21
+ import { at } from '../../types/object/module.f.mjs'
22
+
23
+ /**
24
+ * Unwraps what a run answered, keeping the state beside it.
25
+ *
26
+ * These cases drive the tools against a virtual filesystem that always
27
+ * cooperates, so a channel failure here is a broken fixture rather than a case
28
+ * under test — and the unwrap is where that assumption is stated.
29
+ *
30
+ * @type {<T, E>(r: readonly [State, Result<T, E>]) => readonly [State, T]}
31
+ */
32
+ const unwrapRun = ([state, r]) => [state, unwrapResult(r)]
15
33
  import { parse as parseJson } from '../../media/json/module.f.mjs'
16
34
  import { array, string as rttiString } from '../../types/rtti/module.f.mjs'
17
35
  import { parse as rttiParse } from '../../types/rtti/parse/module.f.mjs'
@@ -30,17 +48,43 @@ const parseSubjects = rttiParse(array(rttiString))
30
48
  const findEntry = (registry, name) => {
31
49
  const entry = registry.find(e => e.name === name)
32
50
  assert(entry !== undefined, ['missing tool entry', name])
33
- return /** @type {ToolEntry<O>} */ (entry)
51
+ return entry
34
52
  }
35
53
 
36
54
  /** @type {(result: ToolsCallResult) => string} */
37
55
  const textOf = result => {
38
56
  const [item] = result.content
39
57
  assert(item.type === 'text', ['expected a text content item', item])
40
- return /** @type {{ text: string }} */ (item).text
58
+ return item.text
41
59
  }
42
60
 
43
61
  export const proof = {
62
+ // Every Evo tool answers a channel failure — a cache slot the runner
63
+ // cannot reach — as an ordinary `isError` result. The client asked a
64
+ // question the server could not answer, which is a tool-level failure,
65
+ // not a reason to take the server down.
66
+ channelFailureIsToolError: () => {
67
+ /** @type {NotImplemented} */
68
+ const boom = ['notImplemented', 'memRead']
69
+ /** @type {Evo<never>} */
70
+ const failing = {
71
+ list: () => pureError(boom),
72
+ head: () => pureError(boom),
73
+ add: () => pureError(boom),
74
+ revision: () => { throw 'unused' },
75
+ }
76
+ const registry = evoToolRegistry(failing)
77
+ for (const [name, args] of /** @type {const} */ ([
78
+ ['evo_list', {}],
79
+ ['evo_head', { subject: 'x' }],
80
+ ['evo_add', { parents: [] }],
81
+ ])) {
82
+ const [r] = runPure(findEntry(registry, name).handle(args))
83
+ const result = r === undefined ? undefined : r[1]
84
+ assert(result !== undefined && result.isError === true, ['expected isError', name, result])
85
+ assert(textOf(result).includes('memRead'), ['expected the command name in the message', name])
86
+ }
87
+ },
44
88
  toolNamesMatchTheDesign: () => {
45
89
  /** @type {Evo<never>} */
46
90
  const e = {
@@ -53,10 +97,10 @@ export const proof = {
53
97
  },
54
98
  evoListReflectsTheCache: () => {
55
99
  const c = fileCas(sha256)(home)
56
- const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
100
+ const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
57
101
  const e = evo(c)(cacheKey)
58
102
  const entry = findEntry(evoToolRegistry(e), 'evo_list')
59
- const [, result] = virtual(state0)(entry.handle({}))
103
+ const [, [, result]] = virtual(state0)(entry.handle({}))
60
104
  assert(!result.isError)
61
105
  assertEq(textOf(result), '[]')
62
106
  },
@@ -67,14 +111,12 @@ export const proof = {
67
111
  // encoding preserves both exactly.
68
112
  evoListEncodesArbitrarySubjectsAsJson: () => {
69
113
  const c = fileCas(sha256)(home)
70
- const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
114
+ const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
71
115
  const e = evo(c)(cacheKey)
72
- const [state1, addA] = virtual(state0)(e.add({ parents: [], subject: 'line one\nline two', snapshot: vecToCBase32(vec8(0x2an)) }))
73
- assert(addA[0] === 'ok', ['expected add ok', addA])
74
- const [state2, addB] = virtual(state1)(e.add({ parents: [], subject: '', snapshot: vecToCBase32(vec8(0x2bn)) }))
75
- assert(addB[0] === 'ok', ['expected add ok', addB])
116
+ const [state1] = unwrapRun(virtual(state0)(e.add({ parents: [], subject: 'line one\nline two', snapshot: vecToCBase32(vec8(0x2an)) })))
117
+ const [state2] = unwrapRun(virtual(state1)(e.add({ parents: [], subject: '', snapshot: vecToCBase32(vec8(0x2bn)) })))
76
118
  const entry = findEntry(evoToolRegistry(e), 'evo_list')
77
- const [, result] = virtual(state2)(entry.handle({}))
119
+ const [, [, result]] = virtual(state2)(entry.handle({}))
78
120
  assert(!result.isError)
79
121
  const subjects = unwrap(parseSubjects(unwrap(parseJson(textOf(result)))))
80
122
  assertEq(subjects.length, 2)
@@ -85,33 +127,32 @@ export const proof = {
85
127
  // omitted lists the active subjects, `true` the archived ones.
86
128
  evoListForwardsTheArchivedFilter: () => {
87
129
  const c = fileCas(sha256)(home)
88
- const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
130
+ const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
89
131
  const e = evo(c)(cacheKey)
90
- const [state1, add] = virtual(state0)(e.add({ parents: [], subject: 'gone', snapshot: vecToCBase32(vec8(0x2cn)), archived: true }))
91
- assert(add[0] === 'ok', ['expected add ok', add])
132
+ const [state1] = unwrapRun(virtual(state0)(e.add({ parents: [], subject: 'gone', snapshot: vecToCBase32(vec8(0x2cn)), archived: true })))
92
133
  const entry = findEntry(evoToolRegistry(e), 'evo_list')
93
- const [state2, active] = virtual(state1)(entry.handle({}))
134
+ const [state2, [, active]] = virtual(state1)(entry.handle({}))
94
135
  assert(!active.isError)
95
136
  assertEq(textOf(active), '[]')
96
- const [, archived] = virtual(state2)(entry.handle({ archived: true }))
137
+ const [, [, archived]] = virtual(state2)(entry.handle({ archived: true }))
97
138
  assert(!archived.isError)
98
139
  assertEq(textOf(archived), '["gone"]')
99
140
  },
100
141
  evoHeadReflectsTheCache: () => {
101
142
  const c = fileCas(sha256)(home)
102
- const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
143
+ const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
103
144
  const e = evo(c)(cacheKey)
104
145
  const entry = findEntry(evoToolRegistry(e), 'evo_head')
105
- const [, result] = virtual(state0)(entry.handle({ subject: 'nope' }))
146
+ const [, [, result]] = virtual(state0)(entry.handle({ subject: 'nope' }))
106
147
  assert(!result.isError)
107
148
  assertEq(textOf(result), '')
108
149
  },
109
150
  evoHeadMissingSubjectIsInvalidArguments: () => {
110
151
  const c = fileCas(sha256)(home)
111
- const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
152
+ const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
112
153
  const e = evo(c)(cacheKey)
113
154
  const entry = findEntry(evoToolRegistry(e), 'evo_head')
114
- const [, result] = virtual(state0)(entry.handle({}))
155
+ const [, [, result]] = virtual(state0)(entry.handle({}))
115
156
  assertEq(result.isError, true)
116
157
  },
117
158
  // Covers evo_revision's success branch: the stored revision comes back as
@@ -119,13 +160,12 @@ export const proof = {
119
160
  // resolved `snapshot` included.
120
161
  evoRevisionReturnsRevisionJson: () => {
121
162
  const c = fileCas(sha256)(home)
122
- const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
163
+ const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
123
164
  const e = evo(c)(cacheKey)
124
165
  const subject = vecToCBase32(vec8(0x3n))
125
- const [state1, added] = virtual(state0)(e.add({ parents: [], subject }))
126
- assert(added[0] === 'ok', ['expected add ok', added])
166
+ const [state1, added] = unwrapRun(virtual(state0)(e.add({ parents: [], subject })))
127
167
  const entry = findEntry(evoToolRegistry(e), 'evo_revision')
128
- const [, result] = virtual(state1)(entry.handle({ hash: added[1] }))
168
+ const [, [, result]] = virtual(state1)(entry.handle({ hash: added }))
129
169
  assert(!result.isError)
130
170
  assertEq(textOf(result), `{"subject":"${subject}","parents":[],"snapshot":"${subject}","generation":0}`)
131
171
  },
@@ -133,10 +173,10 @@ export const proof = {
133
173
  // store has nothing under) is surfaced as isError with the message.
134
174
  evoRevisionDomainErrorIsError: () => {
135
175
  const c = fileCas(sha256)(home)
136
- const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
176
+ const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
137
177
  const e = evo(c)(cacheKey)
138
178
  const entry = findEntry(evoToolRegistry(e), 'evo_revision')
139
- const [, result] = virtual(state0)(entry.handle({ hash: vecToCBase32(vec8(0x4n)) }))
179
+ const [, [, result]] = virtual(state0)(entry.handle({ hash: vecToCBase32(vec8(0x4n)) }))
140
180
  assertEq(result.isError, true)
141
181
  assert(textOf(result).includes('revision not found'))
142
182
  },
@@ -144,22 +184,66 @@ export const proof = {
144
184
  // hash comes back as plain, non-error text.
145
185
  evoAddSuccessReturnsHash: () => {
146
186
  const c = fileCas(sha256)(home)
147
- const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
187
+ const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
148
188
  const e = evo(c)(cacheKey)
149
189
  const entry = findEntry(evoToolRegistry(e), 'evo_add')
150
190
  const args = { parents: [], subject: 'doc', snapshot: vecToCBase32(vec8(0x1n)) }
151
- const [, result] = virtual(state0)(entry.handle(args))
191
+ const [, [, result]] = virtual(state0)(entry.handle(args))
152
192
  assert(!result.isError)
153
193
  assert(textOf(result).length > 0)
154
194
  },
195
+ // `evo_add` accepts a nested lock map and `evo_revision` gives it back —
196
+ // the two tools speak one recursive schema, so a revision read out can be
197
+ // added again as-is however deep its lock nests.
198
+ evoAddAndRevisionCarryNestedLocks: () => {
199
+ const c = fileCas(sha256)(home)
200
+ const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
201
+ const e = evo(c)(cacheKey)
202
+ const registry = evoToolRegistry(e)
203
+ const snapshot = vecToCBase32(vec8(0x1n))
204
+ const d1 = vecToCBase32(vec8(0x2n))
205
+ const d2 = vecToCBase32(vec8(0x3n))
206
+ const args = {
207
+ parents: [], subject: 'doc', snapshot,
208
+ lock: { B: { D: d1 }, C: { D: d2 } },
209
+ }
210
+ const [state1, [, added]] = virtual(state0)(findEntry(registry, 'evo_add').handle(args))
211
+ assert(!added.isError)
212
+ const [, [, read]] = virtual(state1)(findEntry(registry, 'evo_revision').handle({ hash: textOf(added) }))
213
+ assert(!read.isError)
214
+ assertEq(
215
+ textOf(read),
216
+ `{"subject":"doc","parents":[],"snapshot":"${snapshot}","generation":0,"lock":{"B":{"D":"${d1}"},"C":{"D":"${d2}"}}}`)
217
+ },
218
+ // The advertised `inputSchema` publishes both halves of the format's
219
+ // `lock` field: either a shared-lock reference (a string) or an inline
220
+ // map, and the map's recursion the way JSON Schema expresses one — a named
221
+ // `$defs` rule reached through a local `$ref`, rather than a flat-only
222
+ // shape or an infinitely inlined one.
223
+ evoAddInputSchemaPublishesTheLockRecursion: () => {
224
+ const schema = toJsonSchema(evoAddArgs)
225
+ const lock = at('lock')(schema.properties ?? {})
226
+ assert(lock !== null, ['expected a lock property', schema])
227
+ const ref = '#/$defs/lockValue'
228
+ const [sharedRef, inlineMap] = lock.anyOf ?? []
229
+ assertEq(sharedRef?.type, 'string')
230
+ assertEq(inlineMap?.type, 'object')
231
+ assertEq(at('$ref')(inlineMap?.additionalProperties ?? {}), ref)
232
+ const rule = at('lockValue')(schema.$defs ?? {})
233
+ assert(rule !== null, ['expected a lockValue definition', schema])
234
+ const [directHash, nestedMap] = rule.anyOf ?? []
235
+ assertEq(directHash?.type, 'string')
236
+ assertEq(nestedMap?.type, 'object')
237
+ assertEq(at('$ref')(nestedMap?.additionalProperties ?? {}), ref)
238
+ },
155
239
  // Covers evo_add's error branch: a domain-level failure (Evo.add's
156
240
  // Result) is surfaced as isError with the failure message as text.
157
241
  evoAddDomainErrorIsError: () => {
158
242
  const c = fileCas(sha256)(home)
159
- const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
243
+ const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
160
244
  const e = evo(c)(cacheKey)
161
245
  const entry = findEntry(evoToolRegistry(e), 'evo_add')
162
- const [, result] = virtual(state0)(entry.handle({ parents: [] }))
246
+ const [, [, result]] = virtual(state0)(entry.handle({ parents: [] }))
163
247
  assertEq(result.isError, true)
164
248
  assert(textOf(result).includes('subject is required'))
165
249
  },
@@ -28,7 +28,7 @@
28
28
  *
29
29
  * @import { Effect } from '../effects/types.ts'
30
30
  * @import { MemOp } from '../effects/memory/types.ts'
31
- * @import { Read, Write } from '../effects/node/types.ts'
31
+ * @import { IoChannel, Read, Write } from '../effects/node/types.ts'
32
32
  * @import { McpConfig, McpHandlers } from '../protocol/mcp/types.ts'
33
33
  * @import { FileCasOperation } from '../cas/types.ts'
34
34
  * @import { Cache } from '../cas/evo/types.ts'
@@ -36,7 +36,7 @@
36
36
  */
37
37
  import type { Effect } from '../effects/types.ts';
38
38
  import type { MemOp } from '../effects/memory/types.ts';
39
- import type { Read, Write } from '../effects/node/types.ts';
39
+ import type { IoChannel, Read, Write } from '../effects/node/types.ts';
40
40
  import type { McpConfig, McpHandlers } from '../protocol/mcp/types.ts';
41
41
  import type { FileCasOperation } from '../cas/types.ts';
42
42
  import type { Cache } from '../cas/evo/types.ts';
@@ -59,9 +59,9 @@ export declare const casConfig: McpConfig;
59
59
  * build the Evo subject/head cache (`initEvo`), allocates the session-state
60
60
  * slot, builds the `mcpStep` for the merged tool registry, and drives the
61
61
  * read → parse → dispatch → write loop until stdin EOF.
62
- * @type {(home: string) => Effect<Read | Write | MemOp | FileCasOperation, void>}
62
+ * @type {(home: string) => Effect<Read | Write | MemOp | FileCasOperation, void, IoChannel>}
63
63
  */
64
- export declare const casMcpServer: (home: string) => Effect<Read | Write | MemOp | FileCasOperation, void>;
64
+ export declare const casMcpServer: (home: string) => Effect<Read | Write | MemOp | FileCasOperation, void, IoChannel>;
65
65
  export declare const proof: {
66
66
  casMcpServer: () => void;
67
67
  };
@@ -28,14 +28,14 @@
28
28
  *
29
29
  * @import { Effect } from '../effects/types.ts'
30
30
  * @import { MemOp } from '../effects/memory/types.ts'
31
- * @import { Read, Write } from '../effects/node/types.ts'
31
+ * @import { IoChannel, Read, Write } from '../effects/node/types.ts'
32
32
  * @import { McpConfig, McpHandlers } from '../protocol/mcp/types.ts'
33
33
  * @import { FileCasOperation } from '../cas/types.ts'
34
34
  * @import { Cache } from '../cas/evo/types.ts'
35
35
  * @import { Key } from '../effects/memory/types.ts'
36
36
  */
37
37
 
38
- import { step } from '../effects/module.f.mjs'
38
+ import { step as ioStep } from '../effects/module.f.mjs'
39
39
  import { create } from '../effects/memory/module.f.mjs'
40
40
  import { stdioTransport } from '../protocol/mcp/stdio/module.f.mjs'
41
41
  import {
@@ -78,11 +78,11 @@ export const casConfig = {
78
78
  * build the Evo subject/head cache (`initEvo`), allocates the session-state
79
79
  * slot, builds the `mcpStep` for the merged tool registry, and drives the
80
80
  * read → parse → dispatch → write loop until stdin EOF.
81
- * @type {(home: string) => Effect<Read | Write | MemOp | FileCasOperation, void>}
81
+ * @type {(home: string) => Effect<Read | Write | MemOp | FileCasOperation, void, IoChannel>}
82
82
  */
83
- export const casMcpServer = home => step(
83
+ export const casMcpServer = home => ioStep(
84
84
  initEvo(fileCas(sha256)(home)),
85
- cacheKey => step(
85
+ cacheKey => ioStep(
86
86
  create(uninitializedState),
87
87
  sessionKey =>
88
88
  stdioTransport(mcpStep(casConfig)(casMcpHandlers(home)(cacheKey))(sessionKey)),
@@ -93,6 +93,6 @@ export const casMcpServer = home => step(
93
93
 
94
94
  export const proof = {
95
95
  // casMcpServer is never called in integration tests because it drives a
96
- // real stdio server; call it here to cover its Effect-building body.
96
+ // real stdio server; call it here to cover its effect-building body.
97
97
  casMcpServer: () => { casMcpServer('/') },
98
98
  }
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * @import { Unknown } from '../media/json/types.ts'
3
- * @import { Effect, Operation } from '../effects/types.ts'
3
+ * @import { Operation } from '../effects/types.ts'
4
4
  * @import { Response } from '../protocol/json_rpc/types.ts'
5
+ * @import { Handle } from '../protocol/mcp/types.ts'
5
6
  * @import { Vec } from '../types/bit_vec/types.ts'
6
7
  * @import { FileCasOperation } from '../cas/types.ts'
7
8
  * @import { List } from '../effects/list/types.ts'
8
- * @import { McpSessionState, ToolsCallResult } from '../protocol/mcp/types.ts'
9
- * @import { IoResult, Mkdir, Now, RandomInt, ReadBytes, Rename, } from '../effects/node/types.ts'
9
+ * @import { Effect } from '../effects/types.ts'
10
+ * @import { ContentItem, ToolsCallResult } from '../protocol/mcp/types.ts'
11
+ * @import { IoChannel, Mkdir, Now, RandomInt, ReadBytes, Rename } from '../effects/node/types.ts'
10
12
  * @import { Dir } from '../effects/node/virtual/types.ts'
11
13
  */
12
14
  export declare const proof: {
@@ -1,17 +1,19 @@
1
1
  /**
2
2
  * @import { Unknown } from '../media/json/types.ts'
3
- * @import { Effect, Operation } from '../effects/types.ts'
3
+ * @import { Operation } from '../effects/types.ts'
4
4
  * @import { Response } from '../protocol/json_rpc/types.ts'
5
+ * @import { Handle } from '../protocol/mcp/types.ts'
5
6
  * @import { Vec } from '../types/bit_vec/types.ts'
6
7
  * @import { FileCasOperation } from '../cas/types.ts'
7
8
  * @import { List } from '../effects/list/types.ts'
8
- * @import { McpSessionState, ToolsCallResult } from '../protocol/mcp/types.ts'
9
- * @import { IoResult, Mkdir, Now, RandomInt, ReadBytes, Rename, } from '../effects/node/types.ts'
9
+ * @import { Effect } from '../effects/types.ts'
10
+ * @import { ContentItem, ToolsCallResult } from '../protocol/mcp/types.ts'
11
+ * @import { IoChannel, Mkdir, Now, RandomInt, ReadBytes, Rename } from '../effects/node/types.ts'
10
12
  * @import { Dir } from '../effects/node/virtual/types.ts'
11
13
  */
12
14
 
13
15
  import { assert, assertEq } from '../asserts/module.f.mjs'
14
- import { pure, step } from '../effects/module.f.mjs'
16
+ import { pureOk, step } from '../effects/module.f.mjs'
15
17
  import { create } from '../effects/memory/module.f.mjs'
16
18
  import { parse as parseJson } from '../media/json/module.f.mjs'
17
19
  import { number as rttiNumber, option, string as rttiString } from '../types/rtti/module.f.mjs'
@@ -29,7 +31,7 @@ import {
29
31
  } from '../protocol/mcp/module.f.mjs'
30
32
  import { emptyState, virtual } from '../effects/node/virtual/module.f.mjs'
31
33
  import { casConfig, casMcpHandlers } from './module.f.mjs'
32
- import { ok as resultOk, unwrap } from '../types/result/module.f.mjs'
34
+ import { ok as resultOk, unwrap, unwrap as unwrapResult } from '../types/result/module.f.mjs'
33
35
  import { stdioTransport } from '../protocol/mcp/stdio/module.f.mjs'
34
36
  import { fromVec } from '../types/uint8array/module.f.mjs'
35
37
  import { initEvo } from '../cas/evo/module.f.mjs'
@@ -55,14 +57,16 @@ const parseCasGetResult = rttiParse(casGetResult)
55
57
  // Feeds each message to `handler` in order, collecting every response.
56
58
  /**
57
59
  * @template {Operation} O
58
- * @param {(v: Unknown) => Effect<O, Response | null>} handler
59
- * @returns {(msgs: readonly unknown[]) => Effect<O, readonly unknown[]>}
60
+ * @param {Handle<O>} handler
61
+ * @returns {(msgs: readonly unknown[]) => Effect<O, readonly unknown[], never>}
60
62
  */
61
63
  const feed = handler => msgs => {
62
- /** @type {(i: number, acc: readonly unknown[]) => Effect<O, readonly unknown[]>} */
64
+ /** @type {(i: number, acc: readonly unknown[]) => Effect<O, readonly unknown[], never>} */
63
65
  const go = (i, acc) => (
64
66
  i === msgs.length
65
- ? pure(acc)
67
+ ? pureOk(acc)
68
+ // The channel stays `never`: a `Handle` has absorbed its own
69
+ // failures into the response, so every message contributes one.
66
70
  : step(
67
71
  handler(/** @type {Unknown} */ (msgs[i])),
68
72
  r => go(i + 1, [...acc, r]))
@@ -81,11 +85,10 @@ const runSessionVirtual =
81
85
  initEvo(fileCas(sha256)(home)),
82
86
  cacheKey => step(
83
87
  create(uninitializedState),
84
- sessionKey => {
85
- const step = mcpStep(casConfig)(casMcpHandlers(home)(cacheKey))(sessionKey)
86
- return feed(step)(msgs)
87
- }))
88
- return virtual({ ...emptyState, root })(effect)[1]
88
+ sessionKey => feed(mcpStep(casConfig)(casMcpHandlers(home)(cacheKey))(sessionKey))(msgs)))
89
+ // A proof has nobody to report a channel failure to, so it panics: the
90
+ // session either ran or the test is meaningless.
91
+ return unwrapResult(virtual({ ...emptyState, root })(effect)[1])
89
92
  }
90
93
 
91
94
  // Seeds a blob directly into the virtual store via `c.write`, bypassing the
@@ -98,8 +101,8 @@ const runSessionVirtual =
98
101
  const seedBlob = (root, home = '/home/user') => chunks => {
99
102
  const c = fileCas(sha256)(home)
100
103
  const stream = chunks.reduceRight(
101
- (/** @type {List<never, IoResult<Vec>>} */ tail, chunk) => nonEmpty(resultOk(chunk), tail),
102
- /** @type {List<never, IoResult<Vec>>} */ (elEmpty()))
104
+ (/** @type {List<never, Vec, IoChannel>} */ tail, chunk) => nonEmpty(chunk, tail),
105
+ /** @satisfies {List<never, Vec, IoChannel>} */ (elEmpty()))
103
106
  const [state, result] = virtual({ ...emptyState, root })(c.write(stream))
104
107
  assert(result[0] === 'ok', result)
105
108
  return [state.root, vecToCBase32(result[1])]
@@ -143,7 +146,7 @@ const runStdio =
143
146
  const effect = step(
144
147
  initEvo(fileCas(sha256)(home)),
145
148
  cacheKey => step(
146
- create(/** @type {McpSessionState} */ (uninitializedState)),
149
+ create(uninitializedState),
147
150
  sessionKey =>
148
151
  stdioTransport(mcpStep(casConfig)(casMcpHandlers(home)(cacheKey))(sessionKey))
149
152
  )
@@ -154,15 +157,80 @@ const runStdio =
154
157
  return stdout.split('\n').filter(line => line.length > 0).slice(1).map(line => unwrap(parseJson(line)))
155
158
  }
156
159
 
160
+ /**
161
+ * The `result` of a `tools/call` response.
162
+ *
163
+ * This is the one place a response crosses from `unknown` to a typed
164
+ * {@link ToolsCallResult}. The structural essentials are checked here so the
165
+ * accessors below — and every call site — do not have to assume them; only the
166
+ * final step from a checked `content` array to `ToolsCallResult` is taken on
167
+ * trust, because rtti's `parse` reads `Unknown`, and getting there from
168
+ * `unknown` is the same problem one level down.
169
+ */
157
170
  /** @type {(resp: unknown) => ToolsCallResult} */
158
- const resultOf = resp =>
159
- /** @type {{ readonly result: ToolsCallResult }} */ (resp).result
171
+ const resultOf = resp => {
172
+ assert(typeof resp === 'object' && resp !== null && 'result' in resp, resp)
173
+ const { result } = resp
174
+ assert(typeof result === 'object' && result !== null && 'content' in result, result)
175
+ const { content } = result
176
+ assert(content instanceof Array, content)
177
+ return /** @type {ToolsCallResult} */ (result)
178
+ }
160
179
 
161
- /** @type {(resp: unknown) => unknown} */
162
- const item0 = resp => resultOf(resp).content[0]
180
+ /** @type {(resp: unknown) => ContentItem} */
181
+ const item0 = resp => {
182
+ const [item] = resultOf(resp).content
183
+ assert(item !== undefined, resp)
184
+ return item
185
+ }
163
186
 
164
187
  /** @type {(resp: unknown) => string} */
165
- const textOf = resp => (/** @type {{ readonly text: string }} */ (item0(resp))).text
188
+ const textOf = resp => {
189
+ const item = item0(resp)
190
+ assert(item.type === 'text', item)
191
+ return item.text
192
+ }
193
+
194
+ // The accessors below read a response that is `unknown` at this boundary. They
195
+ // check their way in rather than casting, so a malformed response fails at the
196
+ // read with the offending value attached.
197
+
198
+ /** Whether `resp` is an object carrying `key`. */
199
+ /** @type {(resp: unknown, key: 'error' | 'result') => boolean} */
200
+ const has = (resp, key) => typeof resp === 'object' && resp !== null && key in resp
201
+
202
+ /** @type {(resp: unknown) => number} */
203
+ const errorCode = resp => {
204
+ assert(typeof resp === 'object' && resp !== null && 'error' in resp, resp)
205
+ const { error } = resp
206
+ assert(typeof error === 'object' && error !== null && 'code' in error, error)
207
+ const { code } = error
208
+ assert(typeof code === 'number', code)
209
+ return code
210
+ }
211
+
212
+ /** @type {(resp: unknown) => unknown} */
213
+ const idOf = resp => {
214
+ assert(typeof resp === 'object' && resp !== null && 'id' in resp, resp)
215
+ return resp.id
216
+ }
217
+
218
+ /** The `name` and `inputSchema.type` of each tool in a `tools/list` result. */
219
+ /** @type {(resp: unknown) => readonly { readonly name: string, readonly schemaType: unknown }[]} */
220
+ const toolsOf = resp => {
221
+ assert(typeof resp === 'object' && resp !== null && 'result' in resp, resp)
222
+ const { result } = resp
223
+ assert(typeof result === 'object' && result !== null && 'tools' in result, result)
224
+ const { tools } = result
225
+ assert(tools instanceof Array, tools)
226
+ return tools.map(t => {
227
+ assert(typeof t === 'object' && t !== null && 'name' in t && 'inputSchema' in t, t)
228
+ const { name, inputSchema } = t
229
+ assert(typeof name === 'string', name)
230
+ assert(typeof inputSchema === 'object' && inputSchema !== null && 'type' in inputSchema, inputSchema)
231
+ return { name, schemaType: inputSchema.type }
232
+ })
233
+ }
166
234
 
167
235
  /** The `text` payload of a `cas_get` response, parsed and checked against {@link casGetResult}. */
168
236
  const casGetResultOf = (/** @type {unknown} */ resp) =>
@@ -176,12 +244,12 @@ const textSample = 'hello, world!'
176
244
  const revisionSample = `{"dialect":"${revisionDialect}","subject":"8","parents":[],"snapshot":"8","generation":0}`
177
245
 
178
246
  // A base64-encoded binary payload for binary add→get round-trips.
179
- const binarySample = /** @type {string} */ (base64Encode(vec8(0x2An)))
247
+ const binarySample = base64Encode(vec8(0x2An))
180
248
 
181
249
  // A base64 blob whose leading bytes are the PNG magic-byte signature, so
182
250
  // `cas_get` detects its type and returns base64 with mimeType image/png.
183
- const pngSample = /** @type {string} */ (base64Encode(
184
- u8ListToVec(msb)([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x01])))
251
+ const pngSample = base64Encode(
252
+ u8ListToVec(msb)([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x01]))
185
253
 
186
254
  // Returns the RFC 4648 base64 encoding of `n` zero bytes, computed directly
187
255
  // without bigint arithmetic — independent of `base64.encode` so these tests
@@ -209,10 +277,10 @@ const largeMultiChunkBlobMeta =
209
277
  (/** @type {Vec} */ chunk0, /** @type {Vec} */ chunk1, /** @type {string} */ expectedType,
210
278
  /** @type {string} */ expectedMime) => () => {
211
279
  const [root, hash] = seedBlob({})([chunk0, chunk1])
212
- const [metaResp] = /** @type {readonly unknown[]} */ (runSessionVirtual(root)([
280
+ const [metaResp] = runSessionVirtual(root)([
213
281
  init, initialized,
214
282
  call(2, 'cas_get', { hash }),
215
- ]).slice(2))
283
+ ]).slice(2)
216
284
  assert(!resultOf(metaResp).isError)
217
285
  const meta = casGetResultOf(metaResp)
218
286
  assertEq(meta.type, expectedType)
@@ -265,10 +333,10 @@ export const proof = {
265
333
  // buffered inline. The metadata-only path (above) still returns its size/type.
266
334
  getContentLargeBlobTooLargeError: () => {
267
335
  const [root, hash] = seedBlob({})([asciiChunk, asciiChunk])
268
- const [getResp] = /** @type {readonly unknown[]} */ (runSessionVirtual(root)([
336
+ const [getResp] = runSessionVirtual(root)([
269
337
  init, initialized,
270
338
  call(2, 'cas_get', { hash, content: true }),
271
- ]).slice(2))
339
+ ]).slice(2)
272
340
  assertEq(resultOf(getResp).isError, true)
273
341
  const text = textOf(getResp)
274
342
  assert(text.includes('too large'))
@@ -302,9 +370,8 @@ export const proof = {
302
370
  const [getResp] = runStdio(root)([
303
371
  call(2, 'cas_get', { hash, content: true }),
304
372
  ])
305
- const err = /** @type {{ readonly error?: { readonly code: number }, readonly id: unknown }} */ (getResp)
306
- assertEq(err.error?.code, -32603)
307
- assertEq(err.id, 2)
373
+ assertEq(errorCode(getResp), -32603)
374
+ assertEq(idOf(getResp), 2)
308
375
  },
309
376
 
310
377
  // The paired boundary case: a blob whose base64 inflation leaves enough
@@ -337,19 +404,18 @@ export const proof = {
337
404
  const [getResp] = runStdio(root)([
338
405
  call(2, 'cas_get', { hash, content: true }),
339
406
  ])
340
- const err = /** @type {{ readonly error?: { readonly code: number }, readonly id: unknown }} */ (getResp)
341
- assertEq(err.error?.code, -32603)
342
- assertEq(err.id, 2)
407
+ assertEq(errorCode(getResp), -32603)
408
+ assertEq(idOf(getResp), 2)
343
409
  },
344
410
 
345
411
  toolsListAdvertisesSevenTools: () => {
346
412
  const [resp] = runSessionVirtual({})([init, initialized, list(2)]).slice(2)
347
- const tools = (/** @type {{ result: { tools: readonly { name: string }[] } }} */ (resp)).result.tools
413
+ const tools = toolsOf(resp)
348
414
  assertEq(tools.length, 7)
349
415
  assertEq(tools.map(t => t.name).join(','), 'cas_add,cas_get,cas_list,evo_list,evo_head,evo_revision,evo_add')
350
- const add = (/** @type {{ result: { tools: readonly { inputSchema: { type?: string } }[] } }} */ (resp))
351
- .result.tools[0]
352
- assertEq(add.inputSchema.type, 'object')
416
+ const [add] = tools
417
+ assert(add !== undefined, tools)
418
+ assertEq(add.schemaType, 'object')
353
419
  },
354
420
 
355
421
  // The same server also exposes Evo (fjs/cas/evo): add a revision, then
@@ -476,7 +542,7 @@ export const proof = {
476
542
  call(2, 'cas_add', { content: textSample }),
477
543
  call(3, 'cas_get', { hash, content: true }),
478
544
  )
479
- assertEq(item0(getResp) === null ? null : (/** @type {{ type: string }} */ (item0(getResp))).type, 'text')
545
+ assertEq(item0(getResp).type, 'text')
480
546
  const result = casGetResultOf(getResp)
481
547
  assertEq(result.type, 'text')
482
548
  assertEq(result.mimeType, 'text/plain')
@@ -491,7 +557,7 @@ export const proof = {
491
557
  call(3, 'cas_get', { hash, content: true }),
492
558
  )
493
559
  assert(!resultOf(getResp).isError)
494
- assertEq((/** @type {{ type: string }} */ (item0(getResp))).type, 'text')
560
+ assertEq(item0(getResp).type, 'text')
495
561
  const result = casGetResultOf(getResp)
496
562
  assertEq(result.type, 'base64')
497
563
  assertEq(result.mimeType, 'image/png')
@@ -620,8 +686,8 @@ export const proof = {
620
686
 
621
687
  toolErrorIsNotJsonRpcError: () => {
622
688
  const [resp] = session(call(2, 'cas_add', { content: 'not valid!', type: 'base64' }))
623
- assert(!('error' in /** @type {object} */ (resp)))
624
- assert('result' in /** @type {object} */ (resp))
689
+ assert(!has(resp, 'error'), resp)
690
+ assert(has(resp, 'result'), resp)
625
691
  },
626
692
 
627
693
  // cas_get without content:true returns only metadata.
@@ -657,7 +723,7 @@ export const proof = {
657
723
 
658
724
  getMetaOctetStreamForUnknownBinary: () => {
659
725
  const binaryContent = u8ListToVec(msb)([0xFF, 0xFE, 0x00, 0x01])
660
- const binaryB64 = /** @type {string} */ (base64Encode(binaryContent))
726
+ const binaryB64 = base64Encode(binaryContent)
661
727
  const [addResp] = session(call(2, 'cas_add', { content: binaryB64, type: 'base64' }))
662
728
  const hash = textOf(addResp)
663
729
  const [, metaResp] = session(
@@ -675,7 +741,7 @@ export const proof = {
675
741
  // base64/octet-stream, not text/plain.
676
742
  getMetaOctetStreamForNulBlob: () => {
677
743
  const nulContent = u8ListToVec(msb)([0x00, 0x00, 0x00])
678
- const nulB64 = /** @type {string} */ (base64Encode(nulContent))
744
+ const nulB64 = base64Encode(nulContent)
679
745
  const [addResp] = session(call(2, 'cas_add', { content: nulB64, type: 'base64' }))
680
746
  const hash = textOf(addResp)
681
747
  const [, metaResp] = session(
@@ -702,7 +768,7 @@ export const proof = {
702
768
  // cas_get with content:true on octet-stream (no magic bytes, not UTF-8) returns inline base64.
703
769
  getOctetStreamWithContentIncludesBase64: () => {
704
770
  const binaryContent = u8ListToVec(msb)([0xFF, 0xFE, 0x00, 0x01])
705
- const binaryB64 = /** @type {string} */ (base64Encode(binaryContent))
771
+ const binaryB64 = base64Encode(binaryContent)
706
772
  const [addResp] = session(call(2, 'cas_add', { content: binaryB64, type: 'base64' }))
707
773
  const hash = textOf(addResp)
708
774
  const [, getResp] = session(