@canonical/pragma-cli 0.38.0 → 0.40.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 (330) hide show
  1. package/README.md +1 -1
  2. package/dist/package.json +14 -14
  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/config/get.verb.js +2 -0
  17. package/dist/src/capabilities/config/get.verb.js.map +1 -1
  18. package/dist/src/capabilities/config/runField.js +2 -2
  19. package/dist/src/capabilities/config/runField.js.map +1 -1
  20. package/dist/src/capabilities/config/set.verb.js +2 -0
  21. package/dist/src/capabilities/config/set.verb.js.map +1 -1
  22. package/dist/src/capabilities/config/show.verb.js +1 -0
  23. package/dist/src/capabilities/config/show.verb.js.map +1 -1
  24. package/dist/src/capabilities/config/unset.verb.js +2 -0
  25. package/dist/src/capabilities/config/unset.verb.js.map +1 -1
  26. package/dist/src/capabilities/create/constants.js +2 -2
  27. package/dist/src/capabilities/create/create.verb.js +14 -4
  28. package/dist/src/capabilities/create/create.verb.js.map +1 -1
  29. package/dist/src/capabilities/distribution.js +3 -1
  30. package/dist/src/capabilities/distribution.js.map +1 -1
  31. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +10 -2
  32. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -1
  33. package/dist/src/capabilities/doctor/doctor.verb.js +2 -0
  34. package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -1
  35. package/dist/src/capabilities/graph/connect.verb.js +2 -0
  36. package/dist/src/capabilities/graph/connect.verb.js.map +1 -1
  37. package/dist/src/capabilities/graph/inspect.render.js +7 -1
  38. package/dist/src/capabilities/graph/inspect.render.js.map +1 -1
  39. package/dist/src/capabilities/graph/inspect.verb.js +2 -0
  40. package/dist/src/capabilities/graph/inspect.verb.js.map +1 -1
  41. package/dist/src/capabilities/graph/query.render.js +37 -5
  42. package/dist/src/capabilities/graph/query.render.js.map +1 -1
  43. package/dist/src/capabilities/graph/query.verb.js +2 -0
  44. package/dist/src/capabilities/graph/query.verb.js.map +1 -1
  45. package/dist/src/capabilities/graph/queryText.js +28 -0
  46. package/dist/src/capabilities/graph/queryText.js.map +1 -1
  47. package/dist/src/capabilities/graph/runConnect.js +3 -3
  48. package/dist/src/capabilities/graph/runConnect.js.map +1 -1
  49. package/dist/src/capabilities/graph/runQuery.js +8 -6
  50. package/dist/src/capabilities/graph/runQuery.js.map +1 -1
  51. package/dist/src/capabilities/info/info.verb.js +2 -0
  52. package/dist/src/capabilities/info/info.verb.js.map +1 -1
  53. package/dist/src/capabilities/meta/mcp.verb.js +1 -0
  54. package/dist/src/capabilities/meta/mcp.verb.js.map +1 -1
  55. package/dist/src/capabilities/ontology/verbs.js +7 -10
  56. package/dist/src/capabilities/ontology/verbs.js.map +1 -1
  57. package/dist/src/capabilities/prompt/verbs.js +5 -4
  58. package/dist/src/capabilities/prompt/verbs.js.map +1 -1
  59. package/dist/src/capabilities/resources/provider.js +6 -3
  60. package/dist/src/capabilities/resources/provider.js.map +1 -1
  61. package/dist/src/capabilities/setup/setup.verb.js +13 -5
  62. package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
  63. package/dist/src/capabilities/shared/calls.js +16 -0
  64. package/dist/src/capabilities/shared/calls.js.map +1 -0
  65. package/dist/src/capabilities/skill/render.js +7 -5
  66. package/dist/src/capabilities/skill/render.js.map +1 -1
  67. package/dist/src/capabilities/skill/verbs.js +5 -4
  68. package/dist/src/capabilities/skill/verbs.js.map +1 -1
  69. package/dist/src/capabilities/sources/collectStatus.js +6 -1
  70. package/dist/src/capabilities/sources/collectStatus.js.map +1 -1
  71. package/dist/src/capabilities/sources/index.js +4 -3
  72. package/dist/src/capabilities/sources/index.js.map +1 -1
  73. package/dist/src/capabilities/sources/reset.render.js +37 -0
  74. package/dist/src/capabilities/sources/reset.render.js.map +1 -0
  75. package/dist/src/capabilities/sources/reset.verb.js +51 -0
  76. package/dist/src/capabilities/sources/reset.verb.js.map +1 -0
  77. package/dist/src/capabilities/sources/runReset.js +59 -0
  78. package/dist/src/capabilities/sources/runReset.js.map +1 -0
  79. package/dist/src/capabilities/sources/runUpdate.js +7 -6
  80. package/dist/src/capabilities/sources/runUpdate.js.map +1 -1
  81. package/dist/src/capabilities/sources/status.render.js +24 -8
  82. package/dist/src/capabilities/sources/status.render.js.map +1 -1
  83. package/dist/src/capabilities/sources/status.verb.js +1 -0
  84. package/dist/src/capabilities/sources/status.verb.js.map +1 -1
  85. package/dist/src/capabilities/sources/update.verb.js +2 -0
  86. package/dist/src/capabilities/sources/update.verb.js.map +1 -1
  87. package/dist/src/capabilities/upgrade/upgrade.verb.js +1 -0
  88. package/dist/src/capabilities/upgrade/upgrade.verb.js.map +1 -1
  89. package/dist/src/capabilities/version/version.verb.js +1 -0
  90. package/dist/src/capabilities/version/version.verb.js.map +1 -1
  91. package/dist/src/constants.js +4 -3
  92. package/dist/src/constants.js.map +1 -1
  93. package/dist/src/kernel/error/index.js +1 -1
  94. package/dist/src/kernel/error/index.js.map +1 -1
  95. package/dist/src/kernel/error/recovery.js +24 -42
  96. package/dist/src/kernel/error/recovery.js.map +1 -1
  97. package/dist/src/kernel/packs/collect.js +123 -9
  98. package/dist/src/kernel/packs/collect.js.map +1 -1
  99. package/dist/src/kernel/packs/compile.js +74 -15
  100. package/dist/src/kernel/packs/compile.js.map +1 -1
  101. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js +3 -3
  102. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js.map +1 -1
  103. package/dist/src/kernel/packs/iri.js +2 -4
  104. package/dist/src/kernel/packs/iri.js.map +1 -1
  105. package/dist/src/kernel/packs/renderPack.js +90 -50
  106. package/dist/src/kernel/packs/renderPack.js.map +1 -1
  107. package/dist/src/kernel/packs/resolveEntity.js +202 -90
  108. package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
  109. package/dist/src/kernel/packs/runBodies.js +89 -30
  110. package/dist/src/kernel/packs/runBodies.js.map +1 -1
  111. package/dist/src/kernel/packs/schema.js +66 -12
  112. package/dist/src/kernel/packs/schema.js.map +1 -1
  113. package/dist/src/kernel/packs/sparql/buildListQuery.js +80 -13
  114. package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -1
  115. package/dist/src/kernel/packs/sparql/buildLookupQuery.js +41 -13
  116. package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
  117. package/dist/src/kernel/packs/sparql/filterValues.js +32 -23
  118. package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -1
  119. package/dist/src/kernel/packs/sparql/runSelect.js +3 -5
  120. package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
  121. package/dist/src/kernel/packs/storyRules.js +31 -4
  122. package/dist/src/kernel/packs/storyRules.js.map +1 -1
  123. package/dist/src/kernel/packs/tierScope.js +3 -3
  124. package/dist/src/kernel/packs/tierScope.js.map +1 -1
  125. package/dist/src/kernel/packs/types.js +4 -0
  126. package/dist/src/kernel/packs/types.js.map +1 -1
  127. package/dist/src/kernel/project/cli/buildProgram.js +4 -0
  128. package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
  129. package/dist/src/kernel/project/cli/dispatch.js +32 -8
  130. package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
  131. package/dist/src/kernel/project/cli/suggestNames.js +44 -1
  132. package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
  133. package/dist/src/kernel/project/cli/verbHelp.js +17 -18
  134. package/dist/src/kernel/project/cli/verbHelp.js.map +1 -1
  135. package/dist/src/kernel/project/mcp/buildServer.js +11 -4
  136. package/dist/src/kernel/project/mcp/buildServer.js.map +1 -1
  137. package/dist/src/kernel/project/mcp/envelope.js +3 -2
  138. package/dist/src/kernel/project/mcp/envelope.js.map +1 -1
  139. package/dist/src/kernel/project/mcp/instructions.js +101 -31
  140. package/dist/src/kernel/project/mcp/instructions.js.map +1 -1
  141. package/dist/src/kernel/project/mcp/registerVerb.js +59 -23
  142. package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
  143. package/dist/src/kernel/render/renderers.js +3 -3
  144. package/dist/src/kernel/render/turtle.js +21 -4
  145. package/dist/src/kernel/render/turtle.js.map +1 -1
  146. package/dist/src/kernel/runtime/graphpack/build.js +54 -1
  147. package/dist/src/kernel/runtime/graphpack/build.js.map +1 -1
  148. package/dist/src/kernel/runtime/graphpack/manifest.js +14 -0
  149. package/dist/src/kernel/runtime/graphpack/manifest.js.map +1 -1
  150. package/dist/src/kernel/runtime/graphpack/read.js +3 -5
  151. package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
  152. package/dist/src/kernel/runtime/graphpack/schemas.js +1 -0
  153. package/dist/src/kernel/runtime/graphpack/schemas.js.map +1 -1
  154. package/dist/src/kernel/runtime/loadSession.js +8 -1
  155. package/dist/src/kernel/runtime/loadSession.js.map +1 -1
  156. package/dist/src/kernel/runtime/packVersion.js +75 -0
  157. package/dist/src/kernel/runtime/packVersion.js.map +1 -0
  158. package/dist/src/kernel/runtime/readEntity.js +43 -4
  159. package/dist/src/kernel/runtime/readEntity.js.map +1 -1
  160. package/dist/src/kernel/runtime/resolveSources.js +61 -9
  161. package/dist/src/kernel/runtime/resolveSources.js.map +1 -1
  162. package/dist/src/kernel/runtime/storeReadiness.js +3 -2
  163. package/dist/src/kernel/runtime/storeReadiness.js.map +1 -1
  164. package/dist/src/kernel/spec/call.js +167 -0
  165. package/dist/src/kernel/spec/call.js.map +1 -0
  166. package/dist/src/kernel/spec/emitReference.js +9 -13
  167. package/dist/src/kernel/spec/emitReference.js.map +1 -1
  168. package/dist/src/kernel/spec/emitSurface.js +8 -1
  169. package/dist/src/kernel/spec/emitSurface.js.map +1 -1
  170. package/dist/src/kernel/spec/guidance.js +63 -0
  171. package/dist/src/kernel/spec/guidance.js.map +1 -0
  172. package/dist/src/kernel/spec/wireType.js +36 -0
  173. package/dist/src/kernel/spec/wireType.js.map +1 -0
  174. package/package.json +14 -14
  175. package/pragma.conf.ts +294 -177
  176. package/src/capabilities/block.parity.test.ts +1 -1
  177. package/src/capabilities/callRule.test.ts +147 -0
  178. package/src/capabilities/capabilities/capabilities.test.ts +47 -19
  179. package/src/capabilities/capabilities/capabilities.verb.ts +2 -0
  180. package/src/capabilities/capabilities/catalog.ts +60 -55
  181. package/src/capabilities/capabilities/index.ts +2 -0
  182. package/src/capabilities/capabilities/types.ts +16 -19
  183. package/src/capabilities/colophon/colophon.render.ts +9 -5
  184. package/src/capabilities/colophon/colophon.verb.ts +1 -0
  185. package/src/capabilities/config/get.verb.ts +2 -0
  186. package/src/capabilities/config/runField.ts +3 -3
  187. package/src/capabilities/config/set.verb.ts +3 -0
  188. package/src/capabilities/config/show.verb.ts +2 -0
  189. package/src/capabilities/config/unset.verb.ts +2 -0
  190. package/src/capabilities/create/constants.ts +2 -2
  191. package/src/capabilities/create/create.verb.ts +16 -0
  192. package/src/capabilities/create/createGrammar.test.ts +1 -1
  193. package/src/capabilities/distribution.test.ts +6 -3
  194. package/src/capabilities/distribution.ts +6 -0
  195. package/src/capabilities/doctor/checks/checkPackageRefs.ts +14 -2
  196. package/src/capabilities/doctor/doctor.test.ts +38 -1
  197. package/src/capabilities/doctor/doctor.verb.ts +2 -0
  198. package/src/capabilities/emptyRecovery.test.ts +5 -0
  199. package/src/capabilities/graph/connect.verb.ts +2 -0
  200. package/src/capabilities/graph/inspect.render.ts +9 -1
  201. package/src/capabilities/graph/inspect.test.ts +73 -0
  202. package/src/capabilities/graph/inspect.verb.ts +2 -0
  203. package/src/capabilities/graph/query.render.test.ts +35 -0
  204. package/src/capabilities/graph/query.render.ts +45 -7
  205. package/src/capabilities/graph/query.shipped.exec.test.ts +44 -0
  206. package/src/capabilities/graph/query.verb.ts +2 -0
  207. package/src/capabilities/graph/queryText.test.ts +55 -1
  208. package/src/capabilities/graph/queryText.ts +47 -0
  209. package/src/capabilities/graph/runConnect.ts +4 -5
  210. package/src/capabilities/graph/runQuery.ts +23 -9
  211. package/src/capabilities/info/info.verb.ts +2 -0
  212. package/src/capabilities/links.shipped.exec.test.ts +398 -0
  213. package/src/capabilities/listBudget.shipped.exec.test.ts +2 -2
  214. package/src/capabilities/listLookup.shipped.exec.test.ts +122 -13
  215. package/src/capabilities/listQuery.shipped.exec.test.ts +39 -16
  216. package/src/capabilities/lookupBudget.shipped.exec.test.ts +208 -0
  217. package/src/capabilities/meta/mcp.verb.ts +2 -0
  218. package/src/capabilities/ontology/ontology.test.ts +24 -0
  219. package/src/capabilities/ontology/verbs.ts +16 -12
  220. package/src/capabilities/prompt/verbs.ts +10 -4
  221. package/src/capabilities/resources/provider.ts +7 -3
  222. package/src/capabilities/resources/resources.test.ts +2 -1
  223. package/src/capabilities/setup/setup.verb.ts +16 -0
  224. package/src/capabilities/shared/calls.ts +20 -0
  225. package/src/capabilities/skill/render.ts +9 -6
  226. package/src/capabilities/skill/verbs.ts +8 -4
  227. package/src/capabilities/sources/collectStatus.ts +6 -1
  228. package/src/capabilities/sources/index.ts +4 -3
  229. package/src/capabilities/sources/reset.render.test.ts +33 -0
  230. package/src/capabilities/sources/reset.render.ts +43 -0
  231. package/src/capabilities/sources/reset.verb.ts +59 -0
  232. package/src/capabilities/sources/runReset.ts +65 -0
  233. package/src/capabilities/sources/runUpdate.ts +12 -11
  234. package/src/capabilities/sources/sources.test.ts +158 -3
  235. package/src/capabilities/sources/status.render.test.ts +63 -1
  236. package/src/capabilities/sources/status.render.ts +25 -8
  237. package/src/capabilities/sources/status.verb.ts +2 -0
  238. package/src/capabilities/sources/types.ts +26 -0
  239. package/src/capabilities/sources/update.verb.ts +3 -0
  240. package/src/capabilities/surface.test.ts +2 -2
  241. package/src/capabilities/token.parity.test.ts +3 -2
  242. package/src/capabilities/tokenGraph.shipped.exec.test.ts +14 -2
  243. package/src/capabilities/upgrade/upgrade.verb.ts +1 -0
  244. package/src/capabilities/variable.parity.test.ts +2 -1
  245. package/src/capabilities/version/version.verb.ts +1 -0
  246. package/src/capabilities/wireContract.test.ts +110 -0
  247. package/src/constants.ts +4 -3
  248. package/src/kernel/copy.test.ts +1 -1
  249. package/src/kernel/error/PragmaError.test.ts +7 -3
  250. package/src/kernel/error/index.ts +1 -1
  251. package/src/kernel/error/recovery.test.ts +52 -18
  252. package/src/kernel/error/recovery.ts +25 -46
  253. package/src/kernel/error/types.ts +1 -1
  254. package/src/kernel/packs/collect.test.ts +79 -0
  255. package/src/kernel/packs/collect.ts +151 -11
  256. package/src/kernel/packs/compile.test.ts +107 -10
  257. package/src/kernel/packs/compile.ts +100 -11
  258. package/src/kernel/packs/filteredEmpty.test.ts +22 -5
  259. package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +5 -6
  260. package/src/kernel/packs/graphqlSource.test.ts +42 -0
  261. package/src/kernel/packs/iri.ts +5 -4
  262. package/src/kernel/packs/listNotice.test.ts +1 -1
  263. package/src/kernel/packs/nounLink.test.ts +385 -0
  264. package/src/kernel/packs/renderPack.ts +137 -57
  265. package/src/kernel/packs/resolveEntity.test.ts +170 -1
  266. package/src/kernel/packs/resolveEntity.ts +281 -101
  267. package/src/kernel/packs/runBodies.ts +133 -31
  268. package/src/kernel/packs/schema.ts +77 -12
  269. package/src/kernel/packs/sparql/buildListQuery.test.ts +78 -1
  270. package/src/kernel/packs/sparql/buildListQuery.ts +106 -16
  271. package/src/kernel/packs/sparql/buildLookupQuery.ts +44 -13
  272. package/src/kernel/packs/sparql/filterValues.test.ts +36 -4
  273. package/src/kernel/packs/sparql/filterValues.ts +50 -31
  274. package/src/kernel/packs/sparql/runSelect.ts +4 -6
  275. package/src/kernel/packs/storyRules.ts +37 -4
  276. package/src/kernel/packs/tierScope.test.ts +120 -6
  277. package/src/kernel/packs/tierScope.ts +5 -7
  278. package/src/kernel/packs/toolDescription.test.ts +59 -74
  279. package/src/kernel/packs/types.ts +93 -11
  280. package/src/kernel/project/cli/buildProgram.ts +4 -0
  281. package/src/kernel/project/cli/dispatch.ts +34 -8
  282. package/src/kernel/project/cli/suggest.test.ts +77 -0
  283. package/src/kernel/project/cli/suggestNames.ts +47 -1
  284. package/src/kernel/project/cli/verbHelp.ts +16 -19
  285. package/src/kernel/project/mcp/buildServer.ts +15 -4
  286. package/src/kernel/project/mcp/envelope.ts +3 -2
  287. package/src/kernel/project/mcp/instructions.ts +123 -35
  288. package/src/kernel/project/mcp/prompts/prompts.test.ts +136 -20
  289. package/src/kernel/project/mcp/registerVerb.test.ts +28 -0
  290. package/src/kernel/project/mcp/registerVerb.ts +67 -29
  291. package/src/kernel/render/__snapshots__/renderers.test.ts.snap +1 -1
  292. package/src/kernel/render/renderers.test.ts +2 -2
  293. package/src/kernel/render/renderers.ts +3 -3
  294. package/src/kernel/render/turtle.ts +24 -4
  295. package/src/kernel/runtime/graphpack/build.ts +58 -1
  296. package/src/kernel/runtime/graphpack/graphpack.test.ts +20 -0
  297. package/src/kernel/runtime/graphpack/manifest.ts +15 -0
  298. package/src/kernel/runtime/graphpack/read.ts +4 -6
  299. package/src/kernel/runtime/graphpack/schemas.ts +1 -0
  300. package/src/kernel/runtime/graphpack/types.ts +13 -0
  301. package/src/kernel/runtime/loadSession.ts +8 -1
  302. package/src/kernel/runtime/packVersion.test.ts +61 -0
  303. package/src/kernel/runtime/packVersion.ts +84 -0
  304. package/src/kernel/runtime/readEntity.ts +61 -6
  305. package/src/kernel/runtime/resolveSources.test.ts +90 -7
  306. package/src/kernel/runtime/resolveSources.ts +91 -10
  307. package/src/kernel/runtime/storeReadiness.ts +4 -4
  308. package/src/kernel/runtime/types.ts +12 -0
  309. package/src/kernel/spec/call.test.ts +95 -0
  310. package/src/kernel/spec/call.ts +186 -0
  311. package/src/kernel/spec/emitReference.ts +9 -13
  312. package/src/kernel/spec/emitSurface.ts +9 -1
  313. package/src/kernel/spec/guidance.test.ts +49 -0
  314. package/src/kernel/spec/guidance.ts +68 -0
  315. package/src/kernel/spec/index.ts +4 -0
  316. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  317. package/src/kernel/spec/types.ts +64 -7
  318. package/src/kernel/spec/wireType.ts +53 -0
  319. package/src/testing/behavioral/errorMatrix.mcp.test.ts +2 -1
  320. package/src/testing/behavioral/journeys.firstInstall.test.ts +72 -2
  321. package/src/testing/behavioral/parity.test.ts +40 -0
  322. package/src/testing/eval/__snapshots__/eval.test.ts.snap +1 -1
  323. package/src/testing/eval/cases/assembly.ts +4 -3
  324. package/src/testing/eval/cases/stable.ts +2 -3
  325. package/src/testing/helpers/callSchema.ts +40 -0
  326. package/src/testing/helpers/parity.ts +35 -2
  327. package/src/testing/setupCallChecking.ts +30 -0
  328. package/dist/src/capabilities/capabilities/hints.js +0 -223
  329. package/dist/src/capabilities/capabilities/hints.js.map +0 -1
  330. package/src/capabilities/capabilities/hints.ts +0 -266
@@ -85,7 +85,7 @@
85
85
  */
86
86
 
87
87
  import { PragmaError } from "../../error/index.js";
88
- import { RESERVED_VARIABLE_PREFIX } from "../types.js";
88
+ import { type PackRow, RESERVED_VARIABLE_PREFIX } from "../types.js";
89
89
  import { readAuthorQuery } from "./authorQuery.js";
90
90
  import { escapeSparqlString, formatTerm } from "./escape.js";
91
91
 
@@ -93,13 +93,20 @@ import { escapeSparqlString, formatTerm } from "./escape.js";
93
93
  export interface ListPredicate {
94
94
  /** SELECT variable the filter constrains (without `?`). */
95
95
  readonly variable: string;
96
- /** Whole-cell comparison, or membership of a whitespace-separated set. */
97
- readonly match: "exact" | "set";
96
+ /**
97
+ * Whole-cell comparison, membership of a whitespace-separated set, or — for
98
+ * `"iri"` — identity with one of the entity IRIs in {@link terms}.
99
+ */
100
+ readonly match: "exact" | "set" | "iri";
98
101
  /**
99
102
  * The admitted values, in the graph's own display spelling. Several are a
100
103
  * union (the row matches any of them); several FILTERS are a conjunction.
104
+ * For `"iri"` they are absolute IRIs the store itself returned, and none
105
+ * at all matches no row.
101
106
  */
102
107
  readonly terms: readonly string[];
108
+ /** `"iri"` only: the path from the variable to the entity {@link terms} name. */
109
+ readonly via?: string;
103
110
  }
104
111
 
105
112
  /** A declared search, with the term a caller actually supplied. */
@@ -152,6 +159,8 @@ export interface ListQueryInput {
152
159
  readonly search?: ListSearch;
153
160
  /** The tier scope this read answers under, absent when it answers from all. */
154
161
  readonly scope?: ListTierScope;
162
+ /** Projected variables the page leaves out of its rows. */
163
+ readonly omit?: readonly string[];
155
164
  /** The page to return. */
156
165
  readonly window: ListWindow;
157
166
  /** The story's label, for a configuration diagnosis. */
@@ -171,7 +180,79 @@ export interface ListQueryInput {
171
180
  * the wrong part of their own file.
172
181
  */
173
182
  export function buildListQuery(input: ListQueryInput): string {
174
- const { query, predicates, search, scope, window, label } = input;
183
+ const { prologue, projection, body, clauses } = readWrapped(input);
184
+ const shown = projection?.filter((name) => !input.omit?.includes(name));
185
+ return [
186
+ ...(prologue === "" ? [] : [prologue]),
187
+ `SELECT ${shown ? shown.map((variable) => `?${variable}`).join(" ") : "*"}`,
188
+ "WHERE {",
189
+ " {",
190
+ body,
191
+ " }",
192
+ ...clauses.map((clause) => ` ${clause}`),
193
+ "}",
194
+ modifier(input.window),
195
+ ].join("\n");
196
+ }
197
+
198
+ /**
199
+ * Build the SELECT counting a scoped list's whole filtered answer per tier —
200
+ * the page's wrap without the page and without the scope clause — and the
201
+ * reader of its rows.
202
+ *
203
+ * @param input - The author query, the supplied filters/search, and the scope
204
+ * whose `entity` and `via` say how a row reaches its tier.
205
+ * @returns The query text, and `read`: its rows as tier IRI → row count, with
206
+ * `""` for rows whose entity is in no tier. An entity in two tiers counts
207
+ * under each.
208
+ * @throws PragmaError CONFIG_ERROR for the reasons {@link buildListQuery} does.
209
+ */
210
+ export function buildTierCountQuery(
211
+ input: Omit<ListQueryInput, "window"> & { readonly scope: ListTierScope },
212
+ ): { text: string; read: (rows: readonly PackRow[]) => Map<string, number> } {
213
+ const { scope } = input;
214
+ const tier = `${RESERVED_VARIABLE_PREFIX}TierOf`;
215
+ const count = `${RESERVED_VARIABLE_PREFIX}TierRows`;
216
+ const { prologue, body, clauses } = readWrapped({
217
+ ...input,
218
+ scope: { ...scope, tiers: [] },
219
+ reserved: true,
220
+ });
221
+ const text = [
222
+ ...(prologue === "" ? [] : [prologue]),
223
+ `SELECT ?${tier} (COUNT(*) AS ?${count})`,
224
+ "WHERE {",
225
+ " {",
226
+ body,
227
+ " }",
228
+ ...clauses.map((clause) => ` ${clause}`),
229
+ ` OPTIONAL { ?${scope.entity} ${formatTerm(scope.via)} ?${tier} }`,
230
+ "}",
231
+ `GROUP BY ?${tier}`,
232
+ ].join("\n");
233
+ return {
234
+ text,
235
+ read: (rows) =>
236
+ new Map(rows.map((row) => [row[tier] ?? "", Number(row[count])])),
237
+ };
238
+ }
239
+
240
+ /**
241
+ * Split the author query and compile the clauses that go around it — the part
242
+ * the page and the tier count share.
243
+ *
244
+ * @param input - As {@link buildListQuery}; `reserved` says the caller binds a
245
+ * generated variable of its own even when no clause does.
246
+ */
247
+ function readWrapped(
248
+ input: Omit<ListQueryInput, "window"> & { readonly reserved?: boolean },
249
+ ): {
250
+ prologue: string;
251
+ projection: readonly string[] | undefined;
252
+ body: string;
253
+ clauses: string[];
254
+ } {
255
+ const { query, predicates, search, scope, label } = input;
175
256
  const clauses = [
176
257
  ...predicates.map((predicate, index) =>
177
258
  filterClause(predicate, `${RESERVED_VARIABLE_PREFIX}Filter${index}`),
@@ -183,7 +264,10 @@ export function buildListQuery(input: ListQueryInput): string {
183
264
  ? [scopeClause(scope, `${RESERVED_VARIABLE_PREFIX}Tier`)]
184
265
  : []),
185
266
  ];
186
- if (clauses.length > 0 && query.includes(RESERVED_VARIABLE_PREFIX)) {
267
+ if (
268
+ (clauses.length > 0 || input.reserved === true) &&
269
+ query.includes(RESERVED_VARIABLE_PREFIX)
270
+ ) {
187
271
  throw PragmaError.configError(
188
272
  `Story query in ${label} uses the reserved variable prefix "?${RESERVED_VARIABLE_PREFIX}", ` +
189
273
  "which the generated filter clauses bind a caller's values to. Rename it.",
@@ -211,17 +295,7 @@ export function buildListQuery(input: ListQueryInput): string {
211
295
  "same order, which `SELECT *` cannot promise.",
212
296
  );
213
297
  }
214
- return [
215
- ...(prologue === "" ? [] : [prologue]),
216
- `SELECT ${projection ? projection.map((variable) => `?${variable}`).join(" ") : "*"}`,
217
- "WHERE {",
218
- " {",
219
- body,
220
- " }",
221
- ...clauses.map((clause) => ` ${clause}`),
222
- "}",
223
- modifier(window),
224
- ].join("\n");
298
+ return { prologue, projection, body, clauses };
225
299
  }
226
300
 
227
301
  /** The page as SPARQL solution modifiers (`OFFSET 0` omitted as the no-op it is). */
@@ -239,6 +313,7 @@ function modifier(window: ListWindow): string {
239
313
  * `set` cell carrying two of them would be returned twice.
240
314
  */
241
315
  function filterClause(predicate: ListPredicate, bound: string): string {
316
+ if (predicate.match === "iri") return entityClause(predicate, bound);
242
317
  const values = predicate.terms
243
318
  .map((term) => `"${escapeSparqlString(term)}"`)
244
319
  .join(" ");
@@ -250,6 +325,21 @@ function filterClause(predicate: ListPredicate, bound: string): string {
250
325
  return `FILTER EXISTS { VALUES ?${bound} { ${values} } FILTER(${comparison}) }`;
251
326
  }
252
327
 
328
+ /**
329
+ * A row's entity is one of the named ones, or reaches one along `via`.
330
+ *
331
+ * The IRIs are the store's own, returned by the named noun's resolve — a
332
+ * caller's text never reaches this clause.
333
+ */
334
+ function entityClause(predicate: ListPredicate, bound: string): string {
335
+ const values = predicate.terms.map((iri) => `<${iri}>`).join(" ");
336
+ const entity = `?${predicate.variable}`;
337
+ const test = predicate.via
338
+ ? `${entity} ${formatTerm(predicate.via)} ?${bound}`
339
+ : `FILTER(${entity} = ?${bound})`;
340
+ return `FILTER EXISTS { VALUES ?${bound} { ${values} } ${test} }`;
341
+ }
342
+
253
343
  /**
254
344
  * Membership of a whitespace-separated set, spelled as a padded substring.
255
345
  *
@@ -540,17 +540,19 @@ export function buildIriResolveQuery(lookup: PackLookup, iri: string): string {
540
540
  .join("\n");
541
541
  }
542
542
 
543
- /** Build the SELECT listing every entity IRI a lookup can address (IRI globs). */
544
- export function buildLookupIrisQuery(lookup: PackLookup): string {
545
- const constraint = buildTypeConstraint(lookup).trimEnd();
543
+ /**
544
+ * Build the SELECT listing every entity IRI a lookup can address, with the name
545
+ * it carries when it carries one and, for a scoped lookup, its tier.
546
+ */
547
+ export function buildLookupIrisQuery(lookup: PackLookup, via?: string): string {
548
+ const tier = scopeTierProjection(via);
546
549
  return [
547
- "SELECT DISTINCT ?uri WHERE {",
548
- // A pack that constrains by class is asking about its class; one that does
549
- // not has only the `by` triple to bound the scan, so it keeps that bound
550
- // (and, like the name population, addresses only entities that carry one).
551
- constraint !== "" ? constraint : ` ?uri ${formatTerm(lookup.by)} ?name .`,
550
+ `SELECT DISTINCT ?uri ?name${tier.variable} WHERE {`,
551
+ buildTypeConstraint(lookup).trimEnd(),
552
+ // OPTIONAL under a class, required without one — the only bound there is.
553
+ iriNameBinding(lookup),
554
+ tier.optional,
552
555
  "}",
553
- "ORDER BY STR(?uri)",
554
556
  ]
555
557
  .filter((line) => line !== "")
556
558
  .join("\n");
@@ -568,28 +570,51 @@ export function buildLookupIrisQuery(lookup: PackLookup): string {
568
570
  * `lookup` is read for ONE thing — the entity's own identity property, which a
569
571
  * `blankWhenSelf` field compares its value against. A lookup declaring no such
570
572
  * field never binds it.
573
+ *
574
+ * A `many` field is aggregated to one space-separated cell, and the rows are
575
+ * then grouped by child — so it adds values to a row and never rows. A grouped
576
+ * expand's rows come back in one order on every run.
571
577
  */
572
578
  export function buildExpandQuery(
573
579
  expand: PackExpand,
574
580
  entityUri: string,
575
581
  lookup?: Pick<PackLookup, "by">,
576
582
  ): string {
577
- const vars = expand.select.map((field) => `?${field.name}`).join(" ");
583
+ const fields = expand.select.filter((field) => "property" in field);
584
+ const single = fields.filter((field) => !field.many);
585
+ const vars = fields
586
+ .map((field) =>
587
+ field.many
588
+ ? `(GROUP_CONCAT(DISTINCT ?${eachOf(field)}; SEPARATOR=" ") AS ?${field.name})`
589
+ : `?${field.name}`,
590
+ )
591
+ .join(" ");
592
+ const grouped = single.length < fields.length;
593
+ const group = grouped
594
+ ? `GROUP BY ?child${single.map((field) => ` ?${field.name}`).join("")}`
595
+ : "";
578
596
  const optionals = expand.select
579
597
  .map((field) =>
580
598
  "property" in field ? expandFieldClause(field, entityUri, lookup) : "",
581
599
  )
582
600
  .filter((line) => line !== "")
583
601
  .join("\n");
602
+ // Grouping discards the store's scan order, so a grouped expand orders by
603
+ // every single-valued field: the declared names first, the rest as tiebreaks.
604
+ const declared = expand.orderBy ?? [];
605
+ const keys = grouped
606
+ ? [...new Set([...declared, ...single.map((field) => field.name)])]
607
+ : declared;
584
608
  const order =
585
- expand.orderBy && expand.orderBy.length > 0
586
- ? `ORDER BY ${expand.orderBy.map((name) => `?${name}`).join(" ")}`
609
+ keys.length > 0
610
+ ? `ORDER BY ${keys.map((name) => `?${name}`).join(" ")}`
587
611
  : "";
588
612
  return [
589
613
  `SELECT ${vars} WHERE {`,
590
614
  ` <${entityUri}> ${formatTerm(expand.relation)} ?child .`,
591
615
  optionals,
592
616
  "}",
617
+ group,
593
618
  order,
594
619
  ]
595
620
  .filter((line) => line !== "")
@@ -611,7 +636,8 @@ function expandFieldClause(
611
636
  entityUri: string,
612
637
  lookup?: Pick<PackLookup, "by">,
613
638
  ): string {
614
- const read = `?child ${formatTerm(field.property)} ?${field.name} .`;
639
+ const variable = field.many ? eachOf(field) : field.name;
640
+ const read = `?child ${formatTerm(field.property)} ?${variable} .`;
615
641
  if (!field.blankWhenSelf || !lookup) return ` OPTIONAL { ${read} }`;
616
642
  const self = `?${RESERVED_VARIABLE_PREFIX}Self`;
617
643
  return [
@@ -623,6 +649,11 @@ function expandFieldClause(
623
649
  ].join("\n");
624
650
  }
625
651
 
652
+ /** The variable one value of a `many` field is bound to before aggregation. */
653
+ function eachOf(field: PackExpandField): string {
654
+ return `${RESERVED_VARIABLE_PREFIX}Each_${field.name}`;
655
+ }
656
+
626
657
  /** Build the SELECT listing all entity names — lookup-miss suggestions. */
627
658
  export function buildLookupNamesQuery(lookup: PackLookup): string {
628
659
  return [
@@ -30,7 +30,7 @@ const SET_FILTER: PackFilter = {
30
30
  /** The error a filter raises for a value it does not admit. */
31
31
  function rejection(
32
32
  filter: PackFilter,
33
- value: string,
33
+ value: string | readonly string[],
34
34
  vocabulary?: readonly string[],
35
35
  ): PragmaError {
36
36
  try {
@@ -58,8 +58,8 @@ describe("a refused filter value names its vocabulary once", () => {
58
58
  expect(occurrences(plain, "color.text.0,")).toBe(1);
59
59
  expect(plain).toContain("Valid options (745), first 40:");
60
60
  // The recovery says what to do, and names the flag — without the list.
61
- expect(plain).toContain("Pick one of the 745 values --symbol accepts.");
62
- expect(renderErrorLlm(error)).toContain("Pick one of the 745 values");
61
+ expect(plain).toContain("Every value must be one of the 745 accepted.");
62
+ expect(renderErrorLlm(error)).toContain("must be one of the 745");
63
63
  expect(occurrences(renderErrorLlm(error), "color.text.0,")).toBe(1);
64
64
  });
65
65
 
@@ -75,7 +75,7 @@ describe("a refused filter value names its vocabulary once", () => {
75
75
 
76
76
  expect(occurrences(plain, "dimension")).toBe(1);
77
77
  expect(plain).toContain("Valid options: color, dimension, duration");
78
- expect(plain).toContain("Pick one of the 3 values --type accepts.");
78
+ expect(plain).toContain("Every value must be one of the 3 accepted.");
79
79
  });
80
80
 
81
81
  it("a short vocabulary stays cheap to read", () => {
@@ -84,3 +84,35 @@ describe("a refused filter value names its vocabulary once", () => {
84
84
  expect(plain.length).toBeLessThan(200);
85
85
  });
86
86
  });
87
+
88
+ describe("a filter handed several values", () => {
89
+ it("admits them as one union predicate", () => {
90
+ const predicates = resolveFilterPredicates(
91
+ [SET_FILTER],
92
+ { type: ["Color", "duration"] },
93
+ undefined,
94
+ "test:story",
95
+ );
96
+
97
+ expect(predicates).toEqual([
98
+ { variable: "type", match: "exact", terms: ["color", "duration"] },
99
+ ]);
100
+ });
101
+
102
+ it("names EVERY refused value, not just the first", () => {
103
+ const error = rejection(
104
+ FREE_FILTER,
105
+ ["color.text.1", "nope", "color.text.2", "neither"],
106
+ VOCABULARY,
107
+ );
108
+
109
+ expect(error.message).toBe('Invalid symbol "nope", "neither".');
110
+ });
111
+
112
+ it("names every refused value of a declared set", () => {
113
+ const error = rejection(SET_FILTER, ["color", "nope", "neither"]);
114
+
115
+ expect(error.message).toBe('Invalid type "nope", "neither".');
116
+ expect(error.validOptions).toEqual(["color", "dimension", "duration"]);
117
+ });
118
+ });
@@ -59,17 +59,18 @@ export function resolveFilterPredicates(
59
59
  for (const filter of filters ?? []) {
60
60
  const provided = params[filter.param];
61
61
  if (provided === undefined) continue;
62
- // A repeated CLI flag accumulates into an array; MCP args stay scalar.
63
- // Several values for ONE filter are a union (a row matches any of them);
62
+ // A repeated CLI flag accumulates into an array, and the MCP schema hands
63
+ // over the same array (one bare value coerced into it). Several values for ONE filter are a union (a row matches any of them);
64
64
  // several filters still combine conjunctively.
65
65
  const occurrences = Array.isArray(provided) ? provided : [provided];
66
66
  if (occurrences.length === 0) continue;
67
67
  const values = filter.values;
68
- const terms = occurrences.map((occurrence) =>
68
+ const terms =
69
69
  values === undefined
70
- ? requireStringValue(occurrence, filter)
71
- : canonicalizeFilterValue(occurrence, filter, values),
72
- );
70
+ ? occurrences.map((occurrence) =>
71
+ requireStringValue(occurrence, filter),
72
+ )
73
+ : canonicalizeFilterValues(occurrences, filter, values);
73
74
  if (values === undefined) {
74
75
  rejectUnknownValue(
75
76
  requireVocabulary(filter, vocabularies, label),
@@ -135,18 +136,38 @@ function rejectUnknownValue(
135
136
  if (!admissible.has(key)) admissible.set(key, value.normalize("NFC"));
136
137
  }
137
138
  if (admissible.size === 0) return;
138
- const index = terms.findIndex((term) => !admissible.has(term.toLowerCase()));
139
- if (index === -1) return;
140
- const validOptions = [...admissible.values()].sort();
139
+ const refused = occurrences.filter(
140
+ (_, index) => !admissible.has((terms.at(index) ?? "").toLowerCase()),
141
+ );
142
+ if (refused.length === 0) return;
141
143
  // The VALUES ride `validOptions`, which every renderer already prints (and
142
144
  // truncates, and counts). Repeating them in the recovery printed the same
143
145
  // 40-name, 1.4KB list twice on one error — see `error/validOptions.ts`.
144
- throw PragmaError.invalidInput(filter.param, String(occurrences[index]), {
145
- validOptions,
146
- recovery: {
147
- message: `Pick one of the ${validOptions.length} values --${filter.param} accepts.`,
146
+ throw refuseValues(filter, refused, [...admissible.values()].sort());
147
+ }
148
+
149
+ /**
150
+ * The refusal for values a filter does not admit — EVERY one of them.
151
+ *
152
+ * A filter takes several values, so a caller who sent twenty and mistyped three
153
+ * is told about all three at once: naming only the first would cost a round
154
+ * trip per typo, which is the cost taking several values exists to remove.
155
+ */
156
+ function refuseValues(
157
+ filter: PackFilter,
158
+ refused: readonly unknown[],
159
+ validOptions: string[],
160
+ ): PragmaError {
161
+ return PragmaError.invalidInput(
162
+ filter.param,
163
+ refused.map(String).join('", "'),
164
+ {
165
+ validOptions,
166
+ recovery: {
167
+ message: `Every value must be one of the ${validOptions.length} accepted.`,
168
+ },
148
169
  },
149
- });
170
+ );
150
171
  }
151
172
 
152
173
  /** @throws PragmaError INVALID_INPUT when a value-free filter value is not a string. */
@@ -159,24 +180,22 @@ function requireStringValue(provided: unknown, filter: PackFilter): string {
159
180
  return provided.trim().normalize("NFC");
160
181
  }
161
182
 
162
- /** @throws PragmaError INVALID_INPUT when the value is not in the declared set. */
163
- function canonicalizeFilterValue(
164
- provided: unknown,
183
+ /** @throws PragmaError INVALID_INPUT when a value is not in the declared set. */
184
+ function canonicalizeFilterValues(
185
+ provided: readonly unknown[],
165
186
  filter: PackFilter,
166
187
  values: readonly string[],
167
- ): string {
168
- if (typeof provided === "string") {
169
- const normalized = provided.trim().normalize("NFC");
170
- const match = values.find(
171
- (value) =>
172
- value.normalize("NFC").toLowerCase() === normalized.toLowerCase(),
173
- );
174
- if (match !== undefined) return match.normalize("NFC");
175
- }
176
- throw PragmaError.invalidInput(filter.param, String(provided), {
177
- validOptions: [...values],
178
- recovery: {
179
- message: `Pick one of the ${values.length} values --${filter.param} accepts.`,
180
- },
188
+ ): string[] {
189
+ const matches = provided.map((occurrence) => {
190
+ if (typeof occurrence !== "string") return undefined;
191
+ const normalized = occurrence.trim().normalize("NFC").toLowerCase();
192
+ return values
193
+ .find((value) => value.normalize("NFC").toLowerCase() === normalized)
194
+ ?.normalize("NFC");
181
195
  });
196
+ const refused = provided.filter(
197
+ (_, index) => matches.at(index) === undefined,
198
+ );
199
+ if (refused.length > 0) throw refuseValues(filter, refused, [...values]);
200
+ return matches.filter((match): match is string => match !== undefined);
182
201
  }
@@ -10,8 +10,9 @@
10
10
  * (`graph query`) goes through the facade directly and stays INVALID_INPUT.
11
11
  */
12
12
 
13
- import { cliRecovery, PragmaError } from "../../error/index.js";
13
+ import { callRecovery, PragmaError } from "../../error/index.js";
14
14
  import type { PragmaRuntime } from "../../runtime/index.js";
15
+ import { BUILD_STORE_CALL } from "../../spec/call.js";
15
16
  import type { PackRow, StorySource } from "../types.js";
16
17
 
17
18
  /**
@@ -63,12 +64,9 @@ function unboundPrefixError(source: StorySource): PragmaError {
63
64
  return PragmaError.storeUnavailable(
64
65
  "The local store was not built from a pack that defines every term this read uses.",
65
66
  {
66
- recovery: cliRecovery(
67
- "sources update",
67
+ recovery: callRecovery(
68
+ BUILD_STORE_CALL,
68
69
  "Build the local store from the configured packs.",
69
- // An agent recovers by calling the tool, then retrying (PR9 C1 cold-
70
- // store retry makes the post-update retry succeed).
71
- { tool: "sources_update" },
72
70
  ),
73
71
  },
74
72
  );
@@ -32,9 +32,9 @@ import {
32
32
  TIER_PARAM,
33
33
  } from "./types.js";
34
34
 
35
- /** The message a filter declaring neither `values` nor a `vocabulary` gets. */
35
+ /** The message a filter declaring nothing to admit a value against gets. */
36
36
  const FILTER_VOCABULARY_MESSAGE =
37
- 'a filter must declare "values" or a "vocabulary" query — a value-free filter with neither has nothing to check a caller\'s value against';
37
+ 'a filter must declare "values", a "vocabulary" query or a "noun" — a value-free filter with none has nothing to check a caller\'s value against';
38
38
 
39
39
  /** One reason a declaration cannot be compiled, and where in it to look. */
40
40
  export interface StoryIssue {
@@ -61,15 +61,28 @@ export function listShapeIssues(
61
61
  ): StoryIssue[] {
62
62
  const issues: StoryIssue[] = [];
63
63
  for (const [index, filter] of (shape.filters ?? []).entries()) {
64
- if (filter.values === undefined && filter.vocabulary === undefined) {
64
+ if (
65
+ filter.values === undefined &&
66
+ filter.vocabulary === undefined &&
67
+ filter.noun === undefined
68
+ ) {
65
69
  issues.push({
66
70
  path: [...path, "filters", index],
67
71
  message: FILTER_VOCABULARY_MESSAGE,
68
72
  });
69
73
  }
74
+ if (filter.noun !== undefined && filter.entity === undefined) {
75
+ issues.push({
76
+ path: [...path, "filters", index],
77
+ message:
78
+ 'a filter naming a "noun" must declare the "entity" variable its IRIs constrain',
79
+ });
80
+ }
70
81
  }
71
82
  const constrained = [
72
- ...(shape.filters ?? []).map((filter) => filter.variable),
83
+ ...(shape.filters ?? []).flatMap((filter) =>
84
+ filter.entity ? [filter.variable, filter.entity] : [filter.variable],
85
+ ),
73
86
  ...(shape.search?.variables ?? []),
74
87
  ];
75
88
  const read = readAuthorQuery(shape.query);
@@ -107,6 +120,26 @@ export function listShapeIssues(
107
120
  return issues;
108
121
  }
109
122
 
123
+ /**
124
+ * Every noun a story's cells and filters name, once each.
125
+ *
126
+ * @param definition - The story to read.
127
+ * @returns The named nouns, in declaration order.
128
+ */
129
+ export function nounsNamed(definition: PackDefinition): string[] {
130
+ const lookup = definition.lookup;
131
+ const named = [
132
+ ...[
133
+ ...(definition.list ? [definition.list] : []),
134
+ ...(definition.verbs ?? []),
135
+ ].flatMap((shape) => [...shape.columns, ...(shape.filters ?? [])]),
136
+ ...(lookup?.fields ?? []),
137
+ ...(lookup?.sections ?? []),
138
+ ...(lookup?.expand ?? []).flatMap((expand) => expand.select),
139
+ ].flatMap((cell) => ("noun" in cell && cell.noun ? [cell.noun] : []));
140
+ return [...new Set(named)];
141
+ }
142
+
110
143
  /**
111
144
  * Every compilability rule a whole definition breaks, `list` and extra verbs
112
145
  * alike.