@canonical/pragma-cli 0.38.0 → 0.42.0

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 (365) hide show
  1. package/README.md +3 -3
  2. package/dist/package.json +16 -16
  3. package/dist/pragma.conf.js +273 -176
  4. package/dist/pragma.conf.js.map +1 -1
  5. package/dist/src/capabilities/capabilities/capabilities.verb.js +2 -0
  6. package/dist/src/capabilities/capabilities/capabilities.verb.js.map +1 -1
  7. package/dist/src/capabilities/capabilities/catalog.js +47 -51
  8. package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
  9. package/dist/src/capabilities/capabilities/index.js +2 -0
  10. package/dist/src/capabilities/capabilities/index.js.map +1 -1
  11. package/dist/src/capabilities/capabilities/types.js +5 -5
  12. package/dist/src/capabilities/colophon/colophon.render.js +7 -4
  13. package/dist/src/capabilities/colophon/colophon.render.js.map +1 -1
  14. package/dist/src/capabilities/colophon/colophon.verb.js +1 -0
  15. package/dist/src/capabilities/colophon/colophon.verb.js.map +1 -1
  16. package/dist/src/capabilities/colophon/markdownTerminal.js.map +1 -1
  17. package/dist/src/capabilities/config/get.verb.js +2 -0
  18. package/dist/src/capabilities/config/get.verb.js.map +1 -1
  19. package/dist/src/capabilities/config/runField.js +2 -2
  20. package/dist/src/capabilities/config/runField.js.map +1 -1
  21. package/dist/src/capabilities/config/set.verb.js +2 -0
  22. package/dist/src/capabilities/config/set.verb.js.map +1 -1
  23. package/dist/src/capabilities/config/show.render.js.map +1 -1
  24. package/dist/src/capabilities/config/show.verb.js +1 -0
  25. package/dist/src/capabilities/config/show.verb.js.map +1 -1
  26. package/dist/src/capabilities/config/unset.verb.js +2 -0
  27. package/dist/src/capabilities/config/unset.verb.js.map +1 -1
  28. package/dist/src/capabilities/create/constants.js +8 -5
  29. package/dist/src/capabilities/create/constants.js.map +1 -1
  30. package/dist/src/capabilities/create/create.render.js.map +1 -1
  31. package/dist/src/capabilities/create/create.verb.js +36 -7
  32. package/dist/src/capabilities/create/create.verb.js.map +1 -1
  33. package/dist/src/capabilities/create/createSurface.generated.js +22 -0
  34. package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
  35. package/dist/src/capabilities/create/index.js +1 -1
  36. package/dist/src/capabilities/create/pickGenerator.js +1 -0
  37. package/dist/src/capabilities/create/pickGenerator.js.map +1 -1
  38. package/dist/src/capabilities/distribution.js +3 -1
  39. package/dist/src/capabilities/distribution.js.map +1 -1
  40. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +10 -2
  41. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -1
  42. package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
  43. package/dist/src/capabilities/doctor/doctor.verb.js +2 -0
  44. package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -1
  45. package/dist/src/capabilities/graph/connect.verb.js +2 -0
  46. package/dist/src/capabilities/graph/connect.verb.js.map +1 -1
  47. package/dist/src/capabilities/graph/inspect.render.js +7 -1
  48. package/dist/src/capabilities/graph/inspect.render.js.map +1 -1
  49. package/dist/src/capabilities/graph/inspect.verb.js +2 -0
  50. package/dist/src/capabilities/graph/inspect.verb.js.map +1 -1
  51. package/dist/src/capabilities/graph/query.render.js +37 -5
  52. package/dist/src/capabilities/graph/query.render.js.map +1 -1
  53. package/dist/src/capabilities/graph/query.verb.js +2 -0
  54. package/dist/src/capabilities/graph/query.verb.js.map +1 -1
  55. package/dist/src/capabilities/graph/queryText.js +28 -0
  56. package/dist/src/capabilities/graph/queryText.js.map +1 -1
  57. package/dist/src/capabilities/graph/runConnect.js +3 -3
  58. package/dist/src/capabilities/graph/runConnect.js.map +1 -1
  59. package/dist/src/capabilities/graph/runQuery.js +8 -6
  60. package/dist/src/capabilities/graph/runQuery.js.map +1 -1
  61. package/dist/src/capabilities/info/info.verb.js +2 -0
  62. package/dist/src/capabilities/info/info.verb.js.map +1 -1
  63. package/dist/src/capabilities/meta/mcp.verb.js +1 -0
  64. package/dist/src/capabilities/meta/mcp.verb.js.map +1 -1
  65. package/dist/src/capabilities/ontology/verbs.js +7 -10
  66. package/dist/src/capabilities/ontology/verbs.js.map +1 -1
  67. package/dist/src/capabilities/prompt/verbs.js +5 -4
  68. package/dist/src/capabilities/prompt/verbs.js.map +1 -1
  69. package/dist/src/capabilities/resources/provider.js +6 -3
  70. package/dist/src/capabilities/resources/provider.js.map +1 -1
  71. package/dist/src/capabilities/setup/operations/writability.js.map +1 -1
  72. package/dist/src/capabilities/setup/plan.render.js.map +1 -1
  73. package/dist/src/capabilities/setup/setup.verb.js +13 -5
  74. package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
  75. package/dist/src/capabilities/shared/calls.js +16 -0
  76. package/dist/src/capabilities/shared/calls.js.map +1 -0
  77. package/dist/src/capabilities/shared/packageManager.js.map +1 -1
  78. package/dist/src/capabilities/skill/discover.js.map +1 -1
  79. package/dist/src/capabilities/skill/render.js +7 -5
  80. package/dist/src/capabilities/skill/render.js.map +1 -1
  81. package/dist/src/capabilities/skill/verbs.js +5 -4
  82. package/dist/src/capabilities/skill/verbs.js.map +1 -1
  83. package/dist/src/capabilities/sources/collectStatus.js +6 -1
  84. package/dist/src/capabilities/sources/collectStatus.js.map +1 -1
  85. package/dist/src/capabilities/sources/index.js +4 -3
  86. package/dist/src/capabilities/sources/index.js.map +1 -1
  87. package/dist/src/capabilities/sources/reset.render.js +37 -0
  88. package/dist/src/capabilities/sources/reset.render.js.map +1 -0
  89. package/dist/src/capabilities/sources/reset.verb.js +51 -0
  90. package/dist/src/capabilities/sources/reset.verb.js.map +1 -0
  91. package/dist/src/capabilities/sources/runReset.js +59 -0
  92. package/dist/src/capabilities/sources/runReset.js.map +1 -0
  93. package/dist/src/capabilities/sources/runUpdate.js +7 -6
  94. package/dist/src/capabilities/sources/runUpdate.js.map +1 -1
  95. package/dist/src/capabilities/sources/status.render.js +24 -8
  96. package/dist/src/capabilities/sources/status.render.js.map +1 -1
  97. package/dist/src/capabilities/sources/status.verb.js +1 -0
  98. package/dist/src/capabilities/sources/status.verb.js.map +1 -1
  99. package/dist/src/capabilities/sources/update.verb.js +2 -0
  100. package/dist/src/capabilities/sources/update.verb.js.map +1 -1
  101. package/dist/src/capabilities/upgrade/upgrade.verb.js +1 -0
  102. package/dist/src/capabilities/upgrade/upgrade.verb.js.map +1 -1
  103. package/dist/src/capabilities/version/version.verb.js +1 -0
  104. package/dist/src/capabilities/version/version.verb.js.map +1 -1
  105. package/dist/src/constants.js +4 -3
  106. package/dist/src/constants.js.map +1 -1
  107. package/dist/src/kernel/completion/complete.js.map +1 -1
  108. package/dist/src/kernel/completion/emitScripts.js.map +1 -1
  109. package/dist/src/kernel/completion/parse.js.map +1 -1
  110. package/dist/src/kernel/completion/rank.js.map +1 -1
  111. package/dist/src/kernel/completion/resolve.js.map +1 -1
  112. package/dist/src/kernel/config/readConfig.js.map +1 -1
  113. package/dist/src/kernel/error/PragmaError.js.map +1 -1
  114. package/dist/src/kernel/error/index.js +1 -1
  115. package/dist/src/kernel/error/index.js.map +1 -1
  116. package/dist/src/kernel/error/recovery.js +24 -42
  117. package/dist/src/kernel/error/recovery.js.map +1 -1
  118. package/dist/src/kernel/packs/collect.js +123 -9
  119. package/dist/src/kernel/packs/collect.js.map +1 -1
  120. package/dist/src/kernel/packs/compile.js +74 -15
  121. package/dist/src/kernel/packs/compile.js.map +1 -1
  122. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js +3 -3
  123. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js.map +1 -1
  124. package/dist/src/kernel/packs/iri.js +2 -4
  125. package/dist/src/kernel/packs/iri.js.map +1 -1
  126. package/dist/src/kernel/packs/renderPack.js +90 -50
  127. package/dist/src/kernel/packs/renderPack.js.map +1 -1
  128. package/dist/src/kernel/packs/resolveEntity.js +202 -90
  129. package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
  130. package/dist/src/kernel/packs/runBodies.js +89 -30
  131. package/dist/src/kernel/packs/runBodies.js.map +1 -1
  132. package/dist/src/kernel/packs/schema.js +66 -12
  133. package/dist/src/kernel/packs/schema.js.map +1 -1
  134. package/dist/src/kernel/packs/sparql/buildListQuery.js +80 -13
  135. package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -1
  136. package/dist/src/kernel/packs/sparql/buildLookupQuery.js +41 -13
  137. package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
  138. package/dist/src/kernel/packs/sparql/filterValues.js +32 -23
  139. package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -1
  140. package/dist/src/kernel/packs/sparql/runSelect.js +3 -5
  141. package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
  142. package/dist/src/kernel/packs/storyRules.js +31 -4
  143. package/dist/src/kernel/packs/storyRules.js.map +1 -1
  144. package/dist/src/kernel/packs/tierScope.js +3 -3
  145. package/dist/src/kernel/packs/tierScope.js.map +1 -1
  146. package/dist/src/kernel/packs/types.js +4 -0
  147. package/dist/src/kernel/packs/types.js.map +1 -1
  148. package/dist/src/kernel/project/cli/buildProgram.js +4 -0
  149. package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
  150. package/dist/src/kernel/project/cli/dispatch.js +32 -8
  151. package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
  152. package/dist/src/kernel/project/cli/globalFlags.js.map +1 -1
  153. package/dist/src/kernel/project/cli/suggestNames.js +44 -1
  154. package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
  155. package/dist/src/kernel/project/cli/verbHelp.js +17 -18
  156. package/dist/src/kernel/project/cli/verbHelp.js.map +1 -1
  157. package/dist/src/kernel/project/mcp/buildServer.js +11 -4
  158. package/dist/src/kernel/project/mcp/buildServer.js.map +1 -1
  159. package/dist/src/kernel/project/mcp/envelope.js +3 -2
  160. package/dist/src/kernel/project/mcp/envelope.js.map +1 -1
  161. package/dist/src/kernel/project/mcp/instructions.js +101 -31
  162. package/dist/src/kernel/project/mcp/instructions.js.map +1 -1
  163. package/dist/src/kernel/project/mcp/registerVerb.js +59 -23
  164. package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
  165. package/dist/src/kernel/render/envelope.js.map +1 -1
  166. package/dist/src/kernel/render/renderers.js +3 -3
  167. package/dist/src/kernel/render/renderers.js.map +1 -1
  168. package/dist/src/kernel/render/turtle.js +21 -4
  169. package/dist/src/kernel/render/turtle.js.map +1 -1
  170. package/dist/src/kernel/runtime/boot.js.map +1 -1
  171. package/dist/src/kernel/runtime/graphpack/build.js +54 -1
  172. package/dist/src/kernel/runtime/graphpack/build.js.map +1 -1
  173. package/dist/src/kernel/runtime/graphpack/manifest.js +14 -0
  174. package/dist/src/kernel/runtime/graphpack/manifest.js.map +1 -1
  175. package/dist/src/kernel/runtime/graphpack/read.js +3 -5
  176. package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
  177. package/dist/src/kernel/runtime/graphpack/schemas.js +1 -0
  178. package/dist/src/kernel/runtime/graphpack/schemas.js.map +1 -1
  179. package/dist/src/kernel/runtime/loadSession.js +8 -1
  180. package/dist/src/kernel/runtime/loadSession.js.map +1 -1
  181. package/dist/src/kernel/runtime/packVersion.js +75 -0
  182. package/dist/src/kernel/runtime/packVersion.js.map +1 -0
  183. package/dist/src/kernel/runtime/readEntity.js +43 -4
  184. package/dist/src/kernel/runtime/readEntity.js.map +1 -1
  185. package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
  186. package/dist/src/kernel/runtime/resolveSources.js +61 -9
  187. package/dist/src/kernel/runtime/resolveSources.js.map +1 -1
  188. package/dist/src/kernel/runtime/storeReadiness.js +3 -2
  189. package/dist/src/kernel/runtime/storeReadiness.js.map +1 -1
  190. package/dist/src/kernel/spec/call.js +167 -0
  191. package/dist/src/kernel/spec/call.js.map +1 -0
  192. package/dist/src/kernel/spec/emitReference.js +9 -13
  193. package/dist/src/kernel/spec/emitReference.js.map +1 -1
  194. package/dist/src/kernel/spec/emitSurface.js +8 -1
  195. package/dist/src/kernel/spec/emitSurface.js.map +1 -1
  196. package/dist/src/kernel/spec/guidance.js +63 -0
  197. package/dist/src/kernel/spec/guidance.js.map +1 -0
  198. package/dist/src/kernel/spec/wireType.js +36 -0
  199. package/dist/src/kernel/spec/wireType.js.map +1 -0
  200. package/package.json +16 -16
  201. package/pragma.conf.ts +294 -177
  202. package/src/capabilities/block.parity.test.ts +1 -1
  203. package/src/capabilities/callRule.test.ts +147 -0
  204. package/src/capabilities/capabilities/capabilities.test.ts +47 -19
  205. package/src/capabilities/capabilities/capabilities.verb.ts +2 -0
  206. package/src/capabilities/capabilities/catalog.ts +60 -55
  207. package/src/capabilities/capabilities/index.ts +2 -0
  208. package/src/capabilities/capabilities/types.ts +16 -19
  209. package/src/capabilities/colophon/colophon.render.ts +9 -5
  210. package/src/capabilities/colophon/colophon.verb.ts +1 -0
  211. package/src/capabilities/config/get.verb.ts +2 -0
  212. package/src/capabilities/config/runField.ts +3 -3
  213. package/src/capabilities/config/set.verb.ts +3 -0
  214. package/src/capabilities/config/show.verb.ts +2 -0
  215. package/src/capabilities/config/unset.verb.ts +2 -0
  216. package/src/capabilities/create/constants.ts +8 -5
  217. package/src/capabilities/create/create.mcp.test.ts +173 -1
  218. package/src/capabilities/create/create.test.ts +4 -2
  219. package/src/capabilities/create/create.verb.ts +45 -3
  220. package/src/capabilities/create/createGrammar.test.ts +41 -5
  221. package/src/capabilities/create/createSurface.generated.ts +22 -0
  222. package/src/capabilities/create/crossCli.subprocess.test.ts +53 -14
  223. package/src/capabilities/create/index.ts +1 -1
  224. package/src/capabilities/create/pickGenerator.ts +1 -0
  225. package/src/capabilities/create/shippedCreate.subprocess.test.ts +28 -7
  226. package/src/capabilities/distribution.test.ts +6 -3
  227. package/src/capabilities/distribution.ts +6 -0
  228. package/src/capabilities/doctor/checks/checkPackageRefs.ts +14 -2
  229. package/src/capabilities/doctor/doctor.test.ts +38 -1
  230. package/src/capabilities/doctor/doctor.verb.ts +2 -0
  231. package/src/capabilities/emptyRecovery.test.ts +5 -0
  232. package/src/capabilities/graph/connect.verb.ts +2 -0
  233. package/src/capabilities/graph/inspect.render.ts +9 -1
  234. package/src/capabilities/graph/inspect.test.ts +73 -0
  235. package/src/capabilities/graph/inspect.verb.ts +2 -0
  236. package/src/capabilities/graph/query.render.test.ts +35 -0
  237. package/src/capabilities/graph/query.render.ts +45 -7
  238. package/src/capabilities/graph/query.shipped.exec.test.ts +44 -0
  239. package/src/capabilities/graph/query.verb.ts +2 -0
  240. package/src/capabilities/graph/queryText.test.ts +55 -1
  241. package/src/capabilities/graph/queryText.ts +47 -0
  242. package/src/capabilities/graph/runConnect.ts +4 -5
  243. package/src/capabilities/graph/runQuery.ts +23 -9
  244. package/src/capabilities/info/info.verb.ts +2 -0
  245. package/src/capabilities/links.shipped.exec.test.ts +398 -0
  246. package/src/capabilities/listBudget.shipped.exec.test.ts +2 -2
  247. package/src/capabilities/listLookup.shipped.exec.test.ts +122 -13
  248. package/src/capabilities/listQuery.shipped.exec.test.ts +39 -16
  249. package/src/capabilities/lookupBudget.shipped.exec.test.ts +208 -0
  250. package/src/capabilities/meta/mcp.verb.ts +2 -0
  251. package/src/capabilities/ontology/ontology.test.ts +24 -0
  252. package/src/capabilities/ontology/verbs.ts +16 -12
  253. package/src/capabilities/prompt/verbs.ts +10 -4
  254. package/src/capabilities/resources/provider.ts +7 -3
  255. package/src/capabilities/resources/resources.test.ts +2 -1
  256. package/src/capabilities/setup/setup.verb.ts +16 -0
  257. package/src/capabilities/shared/calls.ts +20 -0
  258. package/src/capabilities/skill/render.ts +9 -6
  259. package/src/capabilities/skill/verbs.ts +8 -4
  260. package/src/capabilities/sources/collectStatus.ts +6 -1
  261. package/src/capabilities/sources/index.ts +4 -3
  262. package/src/capabilities/sources/reset.render.test.ts +33 -0
  263. package/src/capabilities/sources/reset.render.ts +43 -0
  264. package/src/capabilities/sources/reset.verb.ts +59 -0
  265. package/src/capabilities/sources/runReset.ts +65 -0
  266. package/src/capabilities/sources/runUpdate.ts +12 -11
  267. package/src/capabilities/sources/sources.test.ts +158 -3
  268. package/src/capabilities/sources/status.render.test.ts +63 -1
  269. package/src/capabilities/sources/status.render.ts +25 -8
  270. package/src/capabilities/sources/status.verb.ts +2 -0
  271. package/src/capabilities/sources/types.ts +26 -0
  272. package/src/capabilities/sources/update.verb.ts +3 -0
  273. package/src/capabilities/surface.test.ts +29 -4
  274. package/src/capabilities/token.parity.test.ts +3 -2
  275. package/src/capabilities/tokenGraph.shipped.exec.test.ts +14 -2
  276. package/src/capabilities/upgrade/upgrade.verb.ts +1 -0
  277. package/src/capabilities/variable.parity.test.ts +2 -1
  278. package/src/capabilities/version/version.verb.ts +1 -0
  279. package/src/capabilities/wireContract.test.ts +110 -0
  280. package/src/constants.ts +4 -3
  281. package/src/kernel/completion/shellDrive.test.ts +2 -2
  282. package/src/kernel/copy.test.ts +1 -1
  283. package/src/kernel/error/PragmaError.test.ts +7 -3
  284. package/src/kernel/error/index.ts +1 -1
  285. package/src/kernel/error/recovery.test.ts +52 -18
  286. package/src/kernel/error/recovery.ts +25 -46
  287. package/src/kernel/error/types.ts +1 -1
  288. package/src/kernel/packs/collect.test.ts +79 -0
  289. package/src/kernel/packs/collect.ts +151 -11
  290. package/src/kernel/packs/compile.test.ts +107 -10
  291. package/src/kernel/packs/compile.ts +100 -11
  292. package/src/kernel/packs/filteredEmpty.test.ts +22 -5
  293. package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +5 -6
  294. package/src/kernel/packs/graphqlSource.test.ts +42 -0
  295. package/src/kernel/packs/iri.ts +5 -4
  296. package/src/kernel/packs/listNotice.test.ts +1 -1
  297. package/src/kernel/packs/nounLink.test.ts +385 -0
  298. package/src/kernel/packs/renderPack.ts +137 -57
  299. package/src/kernel/packs/resolveEntity.test.ts +170 -1
  300. package/src/kernel/packs/resolveEntity.ts +281 -101
  301. package/src/kernel/packs/runBodies.ts +133 -31
  302. package/src/kernel/packs/schema.ts +77 -12
  303. package/src/kernel/packs/sparql/buildListQuery.test.ts +78 -1
  304. package/src/kernel/packs/sparql/buildListQuery.ts +106 -16
  305. package/src/kernel/packs/sparql/buildLookupQuery.ts +44 -13
  306. package/src/kernel/packs/sparql/filterValues.test.ts +36 -4
  307. package/src/kernel/packs/sparql/filterValues.ts +50 -31
  308. package/src/kernel/packs/sparql/runSelect.ts +4 -6
  309. package/src/kernel/packs/storyRules.ts +37 -4
  310. package/src/kernel/packs/tierScope.test.ts +120 -6
  311. package/src/kernel/packs/tierScope.ts +5 -7
  312. package/src/kernel/packs/toolDescription.test.ts +59 -74
  313. package/src/kernel/packs/types.ts +93 -11
  314. package/src/kernel/project/cli/buildProgram.ts +4 -0
  315. package/src/kernel/project/cli/dispatch.ts +34 -8
  316. package/src/kernel/project/cli/suggest.test.ts +77 -0
  317. package/src/kernel/project/cli/suggestNames.ts +47 -1
  318. package/src/kernel/project/cli/verbHelp.ts +16 -19
  319. package/src/kernel/project/mcp/buildServer.ts +15 -4
  320. package/src/kernel/project/mcp/envelope.ts +3 -2
  321. package/src/kernel/project/mcp/instructions.ts +123 -35
  322. package/src/kernel/project/mcp/prompts/prompts.test.ts +136 -20
  323. package/src/kernel/project/mcp/registerVerb.test.ts +28 -0
  324. package/src/kernel/project/mcp/registerVerb.ts +67 -29
  325. package/src/kernel/render/__snapshots__/renderers.test.ts.snap +1 -1
  326. package/src/kernel/render/renderers.test.ts +2 -2
  327. package/src/kernel/render/renderers.ts +3 -3
  328. package/src/kernel/render/turtle.ts +24 -4
  329. package/src/kernel/runtime/graphpack/build.ts +58 -1
  330. package/src/kernel/runtime/graphpack/graphpack.test.ts +20 -0
  331. package/src/kernel/runtime/graphpack/manifest.ts +15 -0
  332. package/src/kernel/runtime/graphpack/read.ts +4 -6
  333. package/src/kernel/runtime/graphpack/schemas.ts +1 -0
  334. package/src/kernel/runtime/graphpack/types.ts +13 -0
  335. package/src/kernel/runtime/loadSession.ts +8 -1
  336. package/src/kernel/runtime/packVersion.test.ts +61 -0
  337. package/src/kernel/runtime/packVersion.ts +84 -0
  338. package/src/kernel/runtime/readEntity.ts +61 -6
  339. package/src/kernel/runtime/resolveSources.test.ts +90 -7
  340. package/src/kernel/runtime/resolveSources.ts +91 -10
  341. package/src/kernel/runtime/storeReadiness.ts +4 -4
  342. package/src/kernel/runtime/types.ts +12 -0
  343. package/src/kernel/spec/call.test.ts +95 -0
  344. package/src/kernel/spec/call.ts +186 -0
  345. package/src/kernel/spec/emitReference.ts +9 -13
  346. package/src/kernel/spec/emitSurface.ts +9 -1
  347. package/src/kernel/spec/guidance.test.ts +49 -0
  348. package/src/kernel/spec/guidance.ts +68 -0
  349. package/src/kernel/spec/index.ts +4 -0
  350. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  351. package/src/kernel/spec/types.ts +64 -7
  352. package/src/kernel/spec/wireType.ts +53 -0
  353. package/src/testing/behavioral/README.md +1 -1
  354. package/src/testing/behavioral/errorMatrix.mcp.test.ts +2 -1
  355. package/src/testing/behavioral/journeys.firstInstall.test.ts +72 -2
  356. package/src/testing/behavioral/parity.test.ts +40 -0
  357. package/src/testing/eval/__snapshots__/eval.test.ts.snap +1 -1
  358. package/src/testing/eval/cases/assembly.ts +4 -3
  359. package/src/testing/eval/cases/stable.ts +2 -3
  360. package/src/testing/helpers/callSchema.ts +40 -0
  361. package/src/testing/helpers/parity.ts +35 -2
  362. package/src/testing/setupCallChecking.ts +30 -0
  363. package/dist/src/capabilities/capabilities/hints.js +0 -223
  364. package/dist/src/capabilities/capabilities/hints.js.map +0 -1
  365. package/src/capabilities/capabilities/hints.ts +0 -266
@@ -17,9 +17,12 @@
17
17
  * defect class is not standard-specific, since every pair shares this
18
18
  * machinery. Three guarantees per noun:
19
19
  *
20
- * - every name `list` publishes resolves through `lookup` (executed through
21
- * the real verb bodies, so glob/IRI argument-shape dispatch is exercised
22
- * with the very strings an agent would paste);
20
+ * - every IDENTIFIER `list` publishes resolves through `lookup`, in every form
21
+ * a row is printed in — the name, the prefixed IRI the plain and condensed
22
+ * formats print, and the absolute IRI JSON carries (executed through the
23
+ * real verb bodies, so glob/IRI argument-shape dispatch is exercised with
24
+ * the very strings an agent would paste). One case per form, so a failure
25
+ * names the spelling that broke;
23
26
  * - every published name is in the addressable population (`listEntityNames`
24
27
  * — the ONE pool that feeds miss-suggestions, glob expansion AND `sample`'s
25
28
  * draw), so a suggestion can never offer a name the resolve cannot answer;
@@ -44,6 +47,7 @@ import {
44
47
  type PackPage,
45
48
  } from "../kernel/packs/types.js";
46
49
  import { verbKey } from "../kernel/packs/uniqueness.js";
50
+ import { compactUri, DEFAULT_PREFIX_MAP } from "../kernel/render/index.js";
47
51
  import { bootRuntime } from "../kernel/runtime/boot.js";
48
52
  import type { PragmaRuntime } from "../kernel/runtime/types.js";
49
53
  import type { CapabilityModule, VerbSpec } from "../kernel/spec/types.js";
@@ -96,7 +100,19 @@ function verbOf(
96
100
  }
97
101
 
98
102
  /**
99
- * Run `<noun> list` and return every name it publishes, verbatim, deduped.
103
+ * The forms a list row's identifier is printed in, by the format that prints
104
+ * it. Whatever a reader copies out of a list has to be an address.
105
+ */
106
+ const FORMS = ["name", "prefixed IRI", "absolute IRI"] as const;
107
+ type Form = (typeof FORMS)[number];
108
+
109
+ /**
110
+ * Run `<noun> list` and return every identifier it publishes, per form,
111
+ * verbatim, deduped.
112
+ *
113
+ * The prefixed IRI is compacted against the map the distribution's stories are
114
+ * compiled with (`distribution.ts`), so it is the string the plain and
115
+ * condensed renderers print — not a second opinion about how to shorten it.
100
116
  *
101
117
  * WALKS THE PAGES, and it has to. This file's promise is the round trip over the
102
118
  * WHOLE corpus rather than a sample, and that used to come free: every declared
@@ -111,13 +127,15 @@ function verbOf(
111
127
  * a large corpus, and an unbounded `while` over a broken cursor would hang the
112
128
  * suite instead of failing it.
113
129
  */
114
- async function publishedNames(
130
+ async function publishedIdentifiers(
115
131
  rt: PragmaRuntime,
116
132
  module: CapabilityModule,
117
133
  noun: string,
118
- ): Promise<string[]> {
134
+ ): Promise<Record<Form, string[]>> {
119
135
  const verb = verbOf(module, noun, "list");
120
136
  const names = new Set<string>();
137
+ const prefixed = new Set<string>();
138
+ const absolute = new Set<string>();
121
139
  let after: string | undefined;
122
140
  let pages = 0;
123
141
  do {
@@ -128,6 +146,10 @@ async function publishedNames(
128
146
  for (const row of page.rows) {
129
147
  const name = row.name ?? "";
130
148
  if (name !== "") names.add(name);
149
+ const uri = row.uri ?? "";
150
+ if (uri === "") continue;
151
+ absolute.add(uri);
152
+ prefixed.add(compactUri(uri, DEFAULT_PREFIX_MAP));
131
153
  }
132
154
  after = page.nextAfter;
133
155
  pages += 1;
@@ -137,7 +159,20 @@ async function publishedNames(
137
159
  );
138
160
  }
139
161
  } while (after !== undefined);
140
- return [...names];
162
+ return {
163
+ name: [...names],
164
+ "prefixed IRI": [...prefixed],
165
+ "absolute IRI": [...absolute],
166
+ };
167
+ }
168
+
169
+ /** Every NAME `<noun> list` publishes — the form the pool guarantees read. */
170
+ async function publishedNames(
171
+ rt: PragmaRuntime,
172
+ module: CapabilityModule,
173
+ noun: string,
174
+ ): Promise<string[]> {
175
+ return (await publishedIdentifiers(rt, module, noun)).name;
141
176
  }
142
177
 
143
178
  /**
@@ -197,11 +232,11 @@ describe("every list-published name resolves through lookup, whole corpus (PROTE
197
232
  expect(NOUNS).toContain("block");
198
233
  });
199
234
 
200
- it.each(NOUNS.map((noun) => [noun] as const))(
201
- "%s: every published name round-trips, and its corpus is not silently empty",
202
- async (noun) => {
235
+ it.each(NOUNS.flatMap((noun) => FORMS.map((form) => [noun, form] as const)))(
236
+ "%s: every published %s round-trips, and its corpus is not silently empty",
237
+ async (noun, form) => {
203
238
  const module = storyModules.get(noun) as CapabilityModule;
204
- const names = await publishedNames(rt, module, noun);
239
+ const names = (await publishedIdentifiers(rt, module, noun))[form];
205
240
  if (EMPTY_CORPUS_TODAY.includes(noun)) {
206
241
  // Asserted empty, not skipped: this is what expires the allowlist.
207
242
  expect(
@@ -210,16 +245,18 @@ describe("every list-published name resolves through lookup, whole corpus (PROTE
210
245
  "delete it from EMPTY_CORPUS_TODAY so a later regression to zero rows cannot hide behind the entry",
211
246
  ).toEqual([]);
212
247
  } else {
248
+ // Held for every form: each shipped list projects its IRI column, and
249
+ // an IRI case that passed over zero IRIs would prove nothing.
213
250
  expect(
214
251
  names.length,
215
- `${noun} list published no rows at all from the shipped pack`,
252
+ `${noun} list published no ${form} at all from the shipped pack`,
216
253
  ).toBeGreaterThan(0);
217
254
  }
218
255
 
219
256
  const errors = await lookupErrors(rt, module, noun, names);
220
257
  expect(
221
258
  errors,
222
- `${noun} list published ${errors.length} name(s) its own lookup cannot resolve — ` +
259
+ `${noun} list published ${errors.length} ${form}(s) its own lookup cannot resolve — ` +
223
260
  "the two-step grammar the tool descriptions document is broken for every agent following it",
224
261
  ).toEqual([]);
225
262
  },
@@ -305,6 +342,78 @@ describe("every list-published name resolves through lookup, whole corpus (PROTE
305
342
  );
306
343
  });
307
344
 
345
+ describe("an IRI's local name is one step from an address (shipped pack)", () => {
346
+ /** A published block IRI of the `tier.component.name` shape, and its local name. */
347
+ async function sampleIri(): Promise<{ prefixed: string; local: string }> {
348
+ const module = storyModules.get("block") as CapabilityModule;
349
+ const { "prefixed IRI": prefixed } = await publishedIdentifiers(
350
+ rt,
351
+ module,
352
+ "block",
353
+ );
354
+ const chosen = prefixed.find((iri) => iri.includes(".component.")) ?? "";
355
+ expect(chosen).not.toBe("");
356
+ return { prefixed: chosen, local: chosen.replace(/^[^:]*:/, "") };
357
+ }
358
+
359
+ it("a wildcard over a local name reaches its entity", async () => {
360
+ const { prefixed, local } = await sampleIri();
361
+ const module = storyModules.get("block") as CapabilityModule;
362
+ const output = (await verbOf(module, "block", "lookup").run(
363
+ { name: [`*.${local.split(".").slice(1).join(".")}`] },
364
+ rt,
365
+ )) as { results: { uri: string }[]; errors: unknown[] };
366
+
367
+ expect(output.errors).toEqual([]);
368
+ expect(
369
+ output.results.map((entity) =>
370
+ compactUri(entity.uri, DEFAULT_PREFIX_MAP),
371
+ ),
372
+ ).toContain(prefixed);
373
+ }, 60_000);
374
+
375
+ it("a bare local name misses, and the miss leads with the prefixed form", async () => {
376
+ const { prefixed, local } = await sampleIri();
377
+ const module = storyModules.get("block") as CapabilityModule;
378
+ const reason = await Promise.resolve(
379
+ verbOf(module, "block", "lookup").run({ name: [local] }, rt),
380
+ ).catch((error: unknown) => error);
381
+
382
+ expect(reason).toMatchObject({ code: "ENTITY_NOT_FOUND" });
383
+ expect((reason as { suggestions: string[] }).suggestions.at(0)).toBe(
384
+ prefixed,
385
+ );
386
+ }, 60_000);
387
+ });
388
+
389
+ describe("every story example answers from the shipped pack", () => {
390
+ // An example is copied verbatim by the agents it is shown to, so a name the
391
+ // graph has since renamed is a miss handed out in advance.
392
+ const examples = [...storyModules.values()].flatMap((module) =>
393
+ module.verbs
394
+ .filter((verb) => verb.example !== undefined)
395
+ .map((verb) => [verbKey(verb.path), verb] as const),
396
+ );
397
+
398
+ it.each(examples)(
399
+ "%s",
400
+ async (_label, verb) => {
401
+ const answer = (await verb.run({ ...verb.example }, rt)) as {
402
+ rows?: unknown[];
403
+ results?: unknown[];
404
+ errors?: unknown[];
405
+ };
406
+ expect(answer.errors ?? []).toEqual([]);
407
+ const named = verb.example?.name;
408
+ if (Array.isArray(named)) {
409
+ expect(answer.results?.length).toBeGreaterThanOrEqual(named.length);
410
+ }
411
+ if (answer.rows) expect(answer.rows.length).toBeGreaterThan(0);
412
+ },
413
+ 60_000,
414
+ );
415
+ });
416
+
308
417
  describe("negative control: the pre-#1047 shape fails these assertions", () => {
309
418
  // The exact divergence that shipped: a list that COALESCEs an OPTIONAL
310
419
  // display name with an IRI-derived one, over a lookup that requires the
@@ -34,6 +34,7 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest";
34
34
  import { MAX_LISTED_OPTIONS } from "../kernel/error/validOptions.js";
35
35
  import { compileStoryModule } from "../kernel/packs/compile.js";
36
36
  import { MAX_LIST_WINDOW } from "../kernel/packs/paging.js";
37
+ import { listEntityNames } from "../kernel/packs/resolveEntity.js";
37
38
  import {
38
39
  distributionSource,
39
40
  type PackFilter,
@@ -47,7 +48,7 @@ import type { PragmaRuntime } from "../kernel/runtime/types.js";
47
48
  import { kebabCase } from "../kernel/spec/emitSurface.js";
48
49
  import type { VerbSpec } from "../kernel/spec/types.js";
49
50
  import { TEST_FLAGS } from "../testing/helpers/projectCli.js";
50
- import { declaredStories } from "./distribution.js";
51
+ import { declaredLookups, declaredStories } from "./distribution.js";
51
52
 
52
53
  const SOURCE = distributionSource("pragma.conf.ts");
53
54
 
@@ -65,7 +66,7 @@ interface Body {
65
66
  */
66
67
  const BODIES: readonly Body[] = [...declaredStories].flatMap(
67
68
  ([noun, story]) => {
68
- const module = compileStoryModule(story, SOURCE, {});
69
+ const module = compileStoryModule(story, SOURCE, {}, declaredLookups);
69
70
  const find = (verb: string): VerbSpec => {
70
71
  const found = module.verbs.find(
71
72
  (v) => verbKey(v.path) === `${noun} ${verb}`,
@@ -119,9 +120,14 @@ async function population(body: Body): Promise<readonly PackRow[]> {
119
120
  return (await page(body, { limit: MAX_LIST_WINDOW })).rows;
120
121
  }
121
122
 
122
- /** The values a value-free filter's declared vocabulary admits. */
123
+ /**
124
+ * The values a value-free filter admits: its declared vocabulary, or — for a
125
+ * filter naming a noun — every name that noun's lookup resolves.
126
+ */
123
127
  async function vocabulary(filter: PackFilter): Promise<readonly string[]> {
124
128
  if (filter.values) return filter.values;
129
+ const named = filter.noun ? declaredLookups(filter.noun) : undefined;
130
+ if (named) return listEntityNames(rt, named, SOURCE);
125
131
  const declared = filter.vocabulary;
126
132
  if (!declared) throw new Error(`filter --${filter.param} declares neither`);
127
133
  const result = await rt.query.sparql(declared.query);
@@ -275,9 +281,15 @@ describe("the compiled query agrees with the retired row predicate (PROTECTED)",
275
281
  [filter.param]: value,
276
282
  limit: MAX_LIST_WINDOW,
277
283
  });
278
- expect(JSON.stringify(answered.rows)).toBe(
279
- JSON.stringify(rowPredicate(whole, filter, value)),
280
- );
284
+ const expected = rowPredicate(whole, filter, value);
285
+ // A `via` filter constrains an entity the cell only PRINTS one name
286
+ // of, so a name the cell does not print may still answer; whatever
287
+ // the cell does print must answer exactly.
288
+ if (filter.via === undefined || expected.length > 0) {
289
+ expect(JSON.stringify(answered.rows)).toBe(
290
+ JSON.stringify(expected),
291
+ );
292
+ }
281
293
  }
282
294
  }
283
295
  },
@@ -351,13 +363,21 @@ describe("a refusal and a calm empty answer stay distinguishable (PROTECTED)", (
351
363
  "%s: a value the vocabulary does not admit is INVALID_INPUT, naming the ones it does",
352
364
  async (_label, body) => {
353
365
  for (const filter of body.shape.filters ?? []) {
354
- const admissible = await vocabulary(filter);
355
- await expect(
356
- page(body, { [filter.param]: "zzz-definitely-not-a-value" }),
357
- ).rejects.toMatchObject({
358
- code: "INVALID_INPUT",
359
- validOptions: [...admissible].sort(),
366
+ const refusal = page(body, {
367
+ [filter.param]: "zzz-definitely-not-a-value",
360
368
  });
369
+ // A noun has too many names to list: its miss says so by name.
370
+ await expect(refusal).rejects.toMatchObject(
371
+ filter.noun
372
+ ? {
373
+ code: "INVALID_INPUT",
374
+ message: `No ${filter.noun} is named "zzz-definitely-not-a-value".`,
375
+ }
376
+ : {
377
+ code: "INVALID_INPUT",
378
+ validOptions: [...(await vocabulary(filter))].sort(),
379
+ },
380
+ );
361
381
  }
362
382
  },
363
383
  60_000,
@@ -374,6 +394,8 @@ describe("a refusal and a calm empty answer stay distinguishable (PROTECTED)", (
374
394
  for (const body of NARROWABLE) {
375
395
  const whole = await population(body);
376
396
  for (const filter of body.shape.filters ?? []) {
397
+ // The cell is no oracle for emptiness under a `via` filter (above).
398
+ if (filter.via !== undefined) continue;
377
399
  for (const value of sweep(await vocabulary(filter))) {
378
400
  if (rowPredicate(whole, filter, value).length === 0) {
379
401
  unpopulated.push({ body, param: filter.param, value });
@@ -389,11 +411,12 @@ describe("a refusal and a calm empty answer stay distinguishable (PROTECTED)", (
389
411
  // And the calm sentence rides the same seam it always did — naming the
390
412
  // filter that narrowed to nothing, since a filtered empty answer is a
391
413
  // statement about the filter and not about the population.
414
+ // In the flag's own spelling: a story's `channelOf` param is typed
415
+ // `--channel-of`, and an answer that named the param would be naming
416
+ // something no caller can pass. A value with a space in it is quoted.
417
+ const shown = /\s/.test(value) ? `'${value}'` : value;
392
418
  expect(body.verb.output.formatters.notice?.(answered as never)).toContain(
393
- // In the flag's own spelling: a story's `channelOf` param is typed
394
- // `--channel-of`, and an answer that named the param would be naming
395
- // something no caller can pass.
396
- `matches \`--${kebabCase(param)} ${value}\`.`,
419
+ `matches \`--${kebabCase(param)} ${shown}\`.`,
397
420
  );
398
421
  }
399
422
  }, 120_000);
@@ -0,0 +1,208 @@
1
+ /**
2
+ * The response-size budget for a LOOKUP and for `graph inspect`, at every
3
+ * disclosure level, measured over the SHIPPED pack (PROTECTED).
4
+ *
5
+ * The list budget beside this file bounds what a browse costs. A read of one
6
+ * entity had no bound at all, and two recorded answers are why it has one now:
7
+ * `token_lookup { name: ["color.*"] }` answered with 473 entities and 460 KB,
8
+ * and `graph_inspect` of one block with 41 KB — neither with any way to be
9
+ * asked for less, since most lookups declared no `detail`.
10
+ *
11
+ * So three things are held here, each over the whole shipped corpus and each
12
+ * derived from the configuration rather than listed:
13
+ *
14
+ * - the LARGEST single entity a lookup answers with, per level — `summary` is
15
+ * the cheap level by measurement rather than by name;
16
+ * - the widest possible pattern (`*`), which the expansion cap has to hold to
17
+ * a bounded answer on every noun;
18
+ * - the largest `graph inspect` of a block, per level.
19
+ *
20
+ * WHERE IT IS ENFORCED, and why bytes of the re-serialised JSON: exactly as the
21
+ * list budget does it, for the reasons given there.
22
+ */
23
+
24
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
25
+ import { DETAIL_LEVELS, type DetailLevel } from "../constants.js";
26
+ import { MAX_LIST_WINDOW } from "../kernel/packs/paging.js";
27
+ import {
28
+ GLOB_EXPANSION_CAP,
29
+ type LookupOutput,
30
+ } from "../kernel/packs/resolveEntity.js";
31
+ import type { PackPage } from "../kernel/packs/types.js";
32
+ import { verbKey } from "../kernel/packs/uniqueness.js";
33
+ import { bootRuntime } from "../kernel/runtime/boot.js";
34
+ import type { PragmaRuntime } from "../kernel/runtime/types.js";
35
+ import type { CapabilityModule, VerbSpec } from "../kernel/spec/types.js";
36
+ import { TEST_FLAGS } from "../testing/helpers/projectCli.js";
37
+ import { declaredStories, storyModules } from "./distribution.js";
38
+ import { graphModule } from "./graph/index.js";
39
+
40
+ /**
41
+ * The ceiling on ONE looked-up entity, in bytes, per level.
42
+ *
43
+ * **Derived from measurement, 2026-09-17**, as the largest entity of any noun
44
+ * at that level: 2,425 bytes at `summary` (a code standard), 25,282 at
45
+ * `standard` (a concept, whose Markdown body arrives there) and 70,097 at
46
+ * `detailed` (a block). Each ceiling is the measurement × 1.5, rounded — the
47
+ * list budget's shape of derivation — so ordinary upstream growth in a pack
48
+ * does not turn this red on a bump, and a lookup whose `summary` quietly grows
49
+ * an expand does. Per noun, `summary` measured: token 548 (4,174 at
50
+ * `detailed`), variable 393 (4,854), tier 101 (3,847), modifier 103 (203),
51
+ * concept 763 (25,302), block 2,312 (70,097), standard 2,425 (6,622).
52
+ */
53
+ const ENTITY_BUDGET_BYTES: Readonly<Record<DetailLevel, number>> = {
54
+ summary: 3_700,
55
+ standard: 38_000,
56
+ detailed: 105_000,
57
+ };
58
+
59
+ /**
60
+ * The ceiling on the answer to the widest pattern a noun can be handed.
61
+ *
62
+ * It moves with {@link GLOB_EXPANSION_CAP} and with how fat a noun's entities
63
+ * are. Measured 2026-09-17: `block` at 22,592 bytes (`summary`) and 206,809
64
+ * (`detailed`), × 1.5, rounded. Full table in BUDGETS.md.
65
+ */
66
+ const GLOB_BUDGET_BYTES: Readonly<Record<"summary" | "detailed", number>> = {
67
+ summary: 34_000,
68
+ detailed: 310_000,
69
+ };
70
+
71
+ /**
72
+ * The ceiling on ONE `graph inspect` of a block, in bytes, per level.
73
+ *
74
+ * **Derived from measurement, 2026-09-17**, over every block the shipped pack
75
+ * lists: 5,785 bytes at `summary`, 20,551 at `standard`, 72,258 at `detailed`;
76
+ * × 1.5, rounded. `detailed` is the uncut neighbourhood by design, so its
77
+ * ceiling records a size rather than enforcing a cut; `summary` and `standard`
78
+ * are the levels the outbound cap holds (the global Button read 28,713 at
79
+ * `standard` before it, 18,173 after).
80
+ */
81
+ const INSPECT_BUDGET_BYTES: Readonly<Record<DetailLevel, number>> = {
82
+ summary: 8_700,
83
+ standard: 31_000,
84
+ detailed: 108_000,
85
+ };
86
+
87
+ /** Every noun with a lookup, derived from the config. */
88
+ const NOUNS: readonly string[] = [...declaredStories]
89
+ .filter(([, story]) => story.lookup !== undefined)
90
+ .map(([noun]) => noun);
91
+
92
+ let rt: PragmaRuntime;
93
+ beforeAll(async () => {
94
+ rt = bootRuntime(TEST_FLAGS);
95
+ await rt.store.get();
96
+ }, 60_000);
97
+ afterAll(async () => {
98
+ (await rt.store.get()).store.dispose();
99
+ });
100
+
101
+ /** The runtime one call at `detail` sees — what `--detail`/`detail` seed. */
102
+ const at = (detail: DetailLevel): PragmaRuntime => ({
103
+ ...rt,
104
+ globalFlags: { ...rt.globalFlags, detail },
105
+ });
106
+
107
+ /** Bytes of a value as either machine surface re-serialises it. */
108
+ const bytesOf = (value: unknown): number =>
109
+ Buffer.byteLength(JSON.stringify(value));
110
+
111
+ function verbOf(noun: string, verb: string): VerbSpec {
112
+ const module = storyModules.get(noun) as CapabilityModule;
113
+ const found = module.verbs.find((v) => verbKey(v.path) === `${noun} ${verb}`);
114
+ if (!found) throw new Error(`no compiled "${noun} ${verb}" verb`);
115
+ return found;
116
+ }
117
+
118
+ /**
119
+ * The argument that lifts a tiered noun's scope, and nothing for the rest: the
120
+ * budget is over the whole corpus, not over the default scope's part of it.
121
+ */
122
+ const everyTier = (noun: string): Record<string, unknown> =>
123
+ declaredStories.get(noun)?.tierScope ? { tier: "all" } : {};
124
+
125
+ /** Every entity a noun's lookup can be handed, by IRI (none when it has no list). */
126
+ async function population(noun: string): Promise<string[]> {
127
+ if (declaredStories.get(noun)?.list === undefined) return [];
128
+ const page = (await verbOf(noun, "list").run(
129
+ { limit: MAX_LIST_WINDOW, ...everyTier(noun) },
130
+ rt,
131
+ )) as PackPage;
132
+ return [...new Set(page.rows.map((row) => row.uri ?? ""))].filter(Boolean);
133
+ }
134
+
135
+ describe("lookup response budget, shipped pack (PROTECTED)", () => {
136
+ it("derives every lookup from the config", () => {
137
+ expect(NOUNS).toContain("token");
138
+ expect(NOUNS.length).toBeGreaterThanOrEqual(7);
139
+ });
140
+
141
+ it.each(
142
+ NOUNS.flatMap((noun) =>
143
+ DETAIL_LEVELS.map((level) => [noun, level] as const),
144
+ ),
145
+ )(
146
+ "%s lookup at %s: the largest entity is inside the budget",
147
+ async (noun, level) => {
148
+ const names = await population(noun);
149
+ expect(names.length).toBeGreaterThan(0);
150
+ const output = (await verbOf(noun, "lookup").run(
151
+ { name: names, ...everyTier(noun) },
152
+ at(level),
153
+ )) as LookupOutput;
154
+ const largest = Math.max(...output.results.map(bytesOf));
155
+ expect(output.errors).toEqual([]);
156
+ expect(largest).toBeLessThanOrEqual(ENTITY_BUDGET_BYTES[level]);
157
+ },
158
+ 120_000,
159
+ );
160
+
161
+ it.each(
162
+ NOUNS.flatMap((noun) =>
163
+ (["summary", "detailed"] as const).map((level) => [noun, level] as const),
164
+ ),
165
+ )(
166
+ "%s lookup '*' at %s: the widest pattern is cut, and inside the budget",
167
+ async (noun, level) => {
168
+ const verb = verbOf(noun, "lookup");
169
+ const output = (await verb.run(
170
+ { name: ["*"], ...everyTier(noun) },
171
+ at(level),
172
+ )) as LookupOutput;
173
+ const bytes = bytesOf(JSON.parse(verb.output.formatters.json(output)));
174
+ // Cut exactly when there was more than the cap to cut.
175
+ const entities = (await population(noun)).length;
176
+ expect(output.truncated === true).toBe(entities > GLOB_EXPANSION_CAP);
177
+ expect(output.results.length).toBe(
178
+ Math.min(entities, GLOB_EXPANSION_CAP),
179
+ );
180
+ expect(bytes).toBeLessThanOrEqual(GLOB_BUDGET_BYTES[level]);
181
+ },
182
+ 120_000,
183
+ );
184
+ });
185
+
186
+ describe("graph inspect response budget, shipped pack (PROTECTED)", () => {
187
+ const inspect = graphModule.verbs.find(
188
+ (v) => verbKey(v.path) === "graph inspect",
189
+ ) as VerbSpec;
190
+
191
+ it.each(DETAIL_LEVELS.map((level) => [level] as const))(
192
+ "at %s: the largest block neighbourhood is inside the budget",
193
+ async (level) => {
194
+ const blocks = await population("block");
195
+ expect(blocks.length).toBeGreaterThan(0);
196
+ let largest = 0;
197
+ for (const uri of blocks) {
198
+ const result = await inspect.run({ uri }, at(level));
199
+ largest = Math.max(
200
+ largest,
201
+ bytesOf(JSON.parse(inspect.output.formatters.json(result as never))),
202
+ );
203
+ }
204
+ expect(largest).toBeLessThanOrEqual(INSPECT_BUDGET_BYTES[level]);
205
+ },
206
+ 120_000,
207
+ );
208
+ });
@@ -16,6 +16,8 @@ import type { VerbSpec } from "../../kernel/spec/index.js";
16
16
  export const mcpVerb: VerbSpec<Record<string, unknown>, void> = {
17
17
  path: ["mcp", "serve"],
18
18
  summary: "Start the MCP server over stdio.",
19
+ useWhen:
20
+ "when an MCP client needs to launch this server; people rarely run it by hand",
19
21
  params: [],
20
22
  output: {
21
23
  formatters: {
@@ -126,6 +126,30 @@ describe("ontology lookup (primary by-name read)", () => {
126
126
  /prefix/i,
127
127
  );
128
128
  });
129
+
130
+ // Each refusal ends in the same next call, in both spellings.
131
+ const LIST_RECOVERY = {
132
+ cli: "pragma ontology list",
133
+ mcp: { tool: "ontology_list", params: {} },
134
+ };
135
+
136
+ it("an unbound namespace URI recovers to the namespace list", async () => {
137
+ await expect(
138
+ lookupVerb.run({ prefix: "https://nope.example/" }, rt),
139
+ ).rejects.toMatchObject({
140
+ code: "INVALID_INPUT",
141
+ recovery: LIST_RECOVERY,
142
+ });
143
+ });
144
+
145
+ it("a class the namespace does not define recovers to the namespace list", async () => {
146
+ await expect(
147
+ lookupVerb.run({ prefix: "ds", class: "NoSuchClass" }, rt),
148
+ ).rejects.toMatchObject({
149
+ code: "ENTITY_NOT_FOUND",
150
+ recovery: LIST_RECOVERY,
151
+ });
152
+ });
129
153
  });
130
154
 
131
155
  describe("ontology_lookup honours detail over MCP (B5)", () => {
@@ -22,7 +22,7 @@
22
22
  */
23
23
 
24
24
  import { BIN_NAME } from "../../constants.js";
25
- import { cliRecovery, PragmaError } from "../../kernel/error/index.js";
25
+ import { callRecovery, PragmaError } from "../../kernel/error/index.js";
26
26
  import { resolvePackDetail } from "../../kernel/packs/disclosure.js";
27
27
  import type { PragmaRuntime } from "../../kernel/runtime/index.js";
28
28
  import { asVerb } from "../../kernel/spec/asVerb.js";
@@ -51,12 +51,9 @@ function resolvePrefix(
51
51
  if (entry) return { prefix: entry[0], namespace: entry[1] };
52
52
  throw PragmaError.invalidInput("namespace", input, {
53
53
  validOptions: Object.values(prefixes),
54
- recovery: cliRecovery(
55
- "ontology list",
54
+ recovery: callRecovery(
55
+ { verb: "ontology list" },
56
56
  "List loaded ontology namespaces.",
57
- {
58
- tool: "ontology_list",
59
- },
60
57
  ),
61
58
  });
62
59
  }
@@ -64,9 +61,10 @@ function resolvePrefix(
64
61
  if (namespace === undefined) {
65
62
  throw PragmaError.invalidInput("prefix", input, {
66
63
  validOptions: Object.keys(prefixes),
67
- recovery: cliRecovery("ontology list", "List loaded ontologies.", {
68
- tool: "ontology_list",
69
- }),
64
+ recovery: callRecovery(
65
+ { verb: "ontology list" },
66
+ "List loaded ontologies.",
67
+ ),
70
68
  });
71
69
  }
72
70
  return { prefix: input, namespace };
@@ -75,6 +73,8 @@ function resolvePrefix(
75
73
  const listVerb: VerbSpec<Record<string, unknown>, OntologySummary[]> = {
76
74
  path: ["ontology", "list"],
77
75
  summary: "List loaded ontology namespaces with class and property counts.",
76
+ useWhen:
77
+ "when asked which vocabularies (namespaces and their prefixes) the data uses — the first step before writing a raw query",
78
78
  params: [],
79
79
  output: { formatters: ontologyListFormatters },
80
80
  examples: [{ cmd: `${BIN_NAME} ontology list` }],
@@ -192,9 +192,10 @@ async function runByName(
192
192
 
193
193
  if (classes.length === 0 && properties.length === 0) {
194
194
  throw PragmaError.notFound("ontology", String(params.prefix), {
195
- recovery: cliRecovery("ontology list", "List loaded ontologies.", {
196
- tool: "ontology_list",
197
- }),
195
+ recovery: callRecovery(
196
+ { verb: "ontology list" },
197
+ "List loaded ontologies.",
198
+ ),
198
199
  });
199
200
  }
200
201
 
@@ -215,6 +216,9 @@ export const ontologyListVerb = asVerb(listVerb);
215
216
  export const ontologyLookupVerb = asVerb({
216
217
  path: ["ontology", "lookup"],
217
218
  summary: "Look up a namespace's classes (hierarchy + counts) and properties.",
219
+ useWhen:
220
+ "before writing a raw query, to find the real class and property names under one prefix",
221
+ example: { prefix: "ds" },
218
222
  params: BY_NAME_PARAMS,
219
223
  output: { formatters: ontologyShowFormatters },
220
224
  examples: [