@canonical/pragma-cli 0.36.0 → 0.38.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 (312) hide show
  1. package/README.md +3 -2
  2. package/bundled-skills/adoption-a1-styles/SKILL.md +24 -0
  3. package/bundled-skills/adoption-a2-components/SKILL.md +22 -6
  4. package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +1397 -327
  5. package/bundled-skills/anatomy-author/SKILL.md +840 -137
  6. package/bundled-skills/design-auditor/SKILL.md +133 -9
  7. package/bundled-skills/implement-component/SKILL.md +192 -23
  8. package/bundled-skills/specify-component/SKILL.md +124 -12
  9. package/bundled-skills/specify-pattern/SKILL.md +47 -8
  10. package/dist/package.json +23 -20
  11. package/dist/pragma.conf.js +963 -40
  12. package/dist/pragma.conf.js.map +1 -1
  13. package/dist/src/capabilities/capabilities/catalog.js +14 -5
  14. package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
  15. package/dist/src/capabilities/capabilities/hints.js +33 -5
  16. package/dist/src/capabilities/capabilities/hints.js.map +1 -1
  17. package/dist/src/capabilities/config/show.render.js +30 -2
  18. package/dist/src/capabilities/config/show.render.js.map +1 -1
  19. package/dist/src/capabilities/create/create.verb.js +2 -1
  20. package/dist/src/capabilities/create/create.verb.js.map +1 -1
  21. package/dist/src/capabilities/create/createSurface.generated.js +23 -1
  22. package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
  23. package/dist/src/capabilities/doctor/checks/checkNodeVersion.js +11 -6
  24. package/dist/src/capabilities/doctor/checks/checkNodeVersion.js.map +1 -1
  25. package/dist/src/capabilities/doctor/checks/harnessInventory.js +1 -1
  26. package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -1
  27. package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js +38 -0
  28. package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js.map +1 -0
  29. package/dist/src/capabilities/doctor/checks/targetHealth.js +104 -14
  30. package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
  31. package/dist/src/capabilities/graph/connect.render.js +101 -0
  32. package/dist/src/capabilities/graph/connect.render.js.map +1 -0
  33. package/dist/src/capabilities/graph/connect.types.js +21 -0
  34. package/dist/src/capabilities/graph/connect.types.js.map +1 -0
  35. package/dist/src/capabilities/graph/connect.verb.js +147 -0
  36. package/dist/src/capabilities/graph/connect.verb.js.map +1 -0
  37. package/dist/src/capabilities/graph/index.js +5 -3
  38. package/dist/src/capabilities/graph/index.js.map +1 -1
  39. package/dist/src/capabilities/graph/queryText.js +206 -0
  40. package/dist/src/capabilities/graph/queryText.js.map +1 -0
  41. package/dist/src/capabilities/graph/relationIndex.js +302 -0
  42. package/dist/src/capabilities/graph/relationIndex.js.map +1 -0
  43. package/dist/src/capabilities/graph/runConnect.js +456 -0
  44. package/dist/src/capabilities/graph/runConnect.js.map +1 -0
  45. package/dist/src/capabilities/graph/runQuery.js +21 -4
  46. package/dist/src/capabilities/graph/runQuery.js.map +1 -1
  47. package/dist/src/capabilities/setup/buildPlan.js +6 -2
  48. package/dist/src/capabilities/setup/buildPlan.js.map +1 -1
  49. package/dist/src/capabilities/setup/operations/index.js +7 -2
  50. package/dist/src/capabilities/setup/operations/index.js.map +1 -1
  51. package/dist/src/capabilities/setup/operations/setupCompletions.js +6 -2
  52. package/dist/src/capabilities/setup/operations/setupCompletions.js.map +1 -1
  53. package/dist/src/capabilities/setup/operations/setupConfig.js +4 -2
  54. package/dist/src/capabilities/setup/operations/setupConfig.js.map +1 -1
  55. package/dist/src/capabilities/setup/operations/setupGenerator.js +72 -14
  56. package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
  57. package/dist/src/capabilities/setup/operations/setupLsp.js +372 -62
  58. package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
  59. package/dist/src/capabilities/setup/operations/setupMcp.js +150 -5
  60. package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
  61. package/dist/src/capabilities/setup/operations/setupSkills.js +4 -2
  62. package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -1
  63. package/dist/src/capabilities/setup/operations/writability.js +114 -0
  64. package/dist/src/capabilities/setup/operations/writability.js.map +1 -0
  65. package/dist/src/capabilities/setup/plan.js.map +1 -1
  66. package/dist/src/capabilities/setup/setup.verb.js +80 -31
  67. package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
  68. package/dist/src/capabilities/setup/targets.js +124 -42
  69. package/dist/src/capabilities/setup/targets.js.map +1 -1
  70. package/dist/src/capabilities/shared/index.js +1 -1
  71. package/dist/src/capabilities/shared/index.js.map +1 -1
  72. package/dist/src/constants.js +7 -1
  73. package/dist/src/constants.js.map +1 -1
  74. package/dist/src/kernel/error/renderError.js +3 -2
  75. package/dist/src/kernel/error/renderError.js.map +1 -1
  76. package/dist/src/kernel/error/validOptions.js +41 -0
  77. package/dist/src/kernel/error/validOptions.js.map +1 -0
  78. package/dist/src/kernel/packs/compile.js +90 -6
  79. package/dist/src/kernel/packs/compile.js.map +1 -1
  80. package/dist/src/kernel/packs/cursor.js +135 -0
  81. package/dist/src/kernel/packs/cursor.js.map +1 -0
  82. package/dist/src/kernel/packs/graphql/buildLookupDocument.js +9 -2
  83. package/dist/src/kernel/packs/graphql/buildLookupDocument.js.map +1 -1
  84. package/dist/src/kernel/packs/paging.js +108 -0
  85. package/dist/src/kernel/packs/paging.js.map +1 -0
  86. package/dist/src/kernel/packs/renderPack.js +154 -5
  87. package/dist/src/kernel/packs/renderPack.js.map +1 -1
  88. package/dist/src/kernel/packs/resolveEntity.js +118 -19
  89. package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
  90. package/dist/src/kernel/packs/runBodies.js +156 -18
  91. package/dist/src/kernel/packs/runBodies.js.map +1 -1
  92. package/dist/src/kernel/packs/schema.js +160 -12
  93. package/dist/src/kernel/packs/schema.js.map +1 -1
  94. package/dist/src/kernel/packs/sparql/authorQuery.js +227 -0
  95. package/dist/src/kernel/packs/sparql/authorQuery.js.map +1 -0
  96. package/dist/src/kernel/packs/sparql/buildListQuery.js +251 -0
  97. package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -0
  98. package/dist/src/kernel/packs/sparql/buildLookupQuery.js +125 -22
  99. package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
  100. package/dist/src/kernel/packs/sparql/filterValues.js +145 -0
  101. package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -0
  102. package/dist/src/kernel/packs/sparql/index.js +24 -14
  103. package/dist/src/kernel/packs/sparql/index.js.map +1 -1
  104. package/dist/src/kernel/packs/sparql/runSelect.js +10 -4
  105. package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
  106. package/dist/src/kernel/packs/sparql/searchTerm.js +25 -0
  107. package/dist/src/kernel/packs/sparql/searchTerm.js.map +1 -0
  108. package/dist/src/kernel/packs/storyRules.js +151 -0
  109. package/dist/src/kernel/packs/storyRules.js.map +1 -0
  110. package/dist/src/kernel/packs/tierScope.js +310 -0
  111. package/dist/src/kernel/packs/tierScope.js.map +1 -0
  112. package/dist/src/kernel/packs/types.js +80 -0
  113. package/dist/src/kernel/packs/types.js.map +1 -1
  114. package/dist/src/kernel/project/cli/buildProgram.js +10 -1
  115. package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
  116. package/dist/src/kernel/project/cli/dispatch.js +104 -3
  117. package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
  118. package/dist/src/kernel/project/cli/rootHelp.js +8 -5
  119. package/dist/src/kernel/project/cli/rootHelp.js.map +1 -1
  120. package/dist/src/kernel/project/cli/suggestNames.js +31 -7
  121. package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
  122. package/dist/src/kernel/project/mcp/registerVerb.js +5 -1
  123. package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
  124. package/dist/src/kernel/render/renderers.js +34 -2
  125. package/dist/src/kernel/render/renderers.js.map +1 -1
  126. package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js +2 -3
  127. package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js.map +1 -1
  128. package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js +1 -1
  129. package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js.map +1 -1
  130. package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js +4 -0
  131. package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js.map +1 -0
  132. package/dist/src/kernel/runtime/graphpack/embedded.js +13 -6
  133. package/dist/src/kernel/runtime/graphpack/embedded.js.map +1 -1
  134. package/dist/src/kernel/runtime/graphpack/read.js +17 -10
  135. package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
  136. package/dist/src/kernel/runtime/loadSession.js +3 -1
  137. package/dist/src/kernel/runtime/loadSession.js.map +1 -1
  138. package/dist/src/kernel/runtime/probe.js +1 -1
  139. package/dist/src/kernel/runtime/probe.js.map +1 -1
  140. package/dist/src/kernel/runtime/readEntity.js +19 -4
  141. package/dist/src/kernel/runtime/readEntity.js.map +1 -1
  142. package/dist/src/kernel/runtime/refs/parseRef.js +17 -1
  143. package/dist/src/kernel/runtime/refs/parseRef.js.map +1 -1
  144. package/dist/src/kernel/runtime/refs/resolve.js +38 -5
  145. package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
  146. package/dist/src/kernel/spec/emitReference.js +1 -1
  147. package/dist/src/kernel/spec/emitReference.js.map +1 -1
  148. package/package.json +23 -20
  149. package/pragma.conf.ts +983 -40
  150. package/src/capabilities/block.parity.test.ts +207 -27
  151. package/src/capabilities/block.shipped.exec.test.ts +156 -14
  152. package/src/capabilities/block.tierRank.exec.test.ts +46 -9
  153. package/src/capabilities/capabilities/catalog.ts +14 -5
  154. package/src/capabilities/capabilities/hints.ts +40 -5
  155. package/src/capabilities/config/collectConfigShow.test.ts +1 -0
  156. package/src/capabilities/config/set.test.ts +24 -25
  157. package/src/capabilities/config/show.render.test.ts +9 -4
  158. package/src/capabilities/config/show.render.ts +30 -2
  159. package/src/capabilities/create/byteEquality.test.ts +28 -1
  160. package/src/capabilities/create/create.test.ts +1 -0
  161. package/src/capabilities/create/create.verb.ts +2 -1
  162. package/src/capabilities/create/createGrammar.test.ts +4 -1
  163. package/src/capabilities/create/createSurface.generated.ts +23 -1
  164. package/src/capabilities/create/crossCli.subprocess.test.ts +10 -3
  165. package/src/capabilities/distribution.parity.test.ts +12 -11
  166. package/src/capabilities/distribution.test.ts +77 -7
  167. package/src/capabilities/doctor/checks/checkNodeVersion.test.ts +49 -0
  168. package/src/capabilities/doctor/checks/checkNodeVersion.ts +11 -7
  169. package/src/capabilities/doctor/checks/harnessInventory.test.ts +13 -6
  170. package/src/capabilities/doctor/checks/harnessInventory.ts +8 -4
  171. package/src/capabilities/doctor/checks/isSupportedNodeVersion.test.ts +111 -0
  172. package/src/capabilities/doctor/checks/isSupportedNodeVersion.ts +34 -0
  173. package/src/capabilities/doctor/checks/mcpCommand.test.ts +7 -1
  174. package/src/capabilities/doctor/checks/targetHealth.ts +114 -13
  175. package/src/capabilities/doctor/doctor.test.ts +327 -36
  176. package/src/capabilities/emptyRecovery.test.ts +84 -28
  177. package/src/capabilities/graph/connect.render.ts +141 -0
  178. package/src/capabilities/graph/connect.shipped.exec.test.ts +370 -0
  179. package/src/capabilities/graph/connect.test.ts +361 -0
  180. package/src/capabilities/graph/connect.types.ts +136 -0
  181. package/src/capabilities/graph/connect.verb.ts +165 -0
  182. package/src/capabilities/graph/index.ts +5 -3
  183. package/src/capabilities/graph/query.shipped.exec.test.ts +66 -0
  184. package/src/capabilities/graph/query.test.ts +17 -0
  185. package/src/capabilities/graph/queryText.test.ts +121 -0
  186. package/src/capabilities/graph/queryText.ts +240 -0
  187. package/src/capabilities/graph/relationIndex.ts +372 -0
  188. package/src/capabilities/graph/runConnect.ts +577 -0
  189. package/src/capabilities/graph/runQuery.ts +21 -6
  190. package/src/capabilities/lazy.test.ts +32 -2
  191. package/src/capabilities/listBudget.shipped.exec.test.ts +239 -0
  192. package/src/capabilities/listLookup.shipped.exec.test.ts +387 -0
  193. package/src/capabilities/listQuery.shipped.exec.test.ts +400 -0
  194. package/src/capabilities/resources/resources.test.ts +38 -2
  195. package/src/capabilities/setup/buildPlan.ts +7 -1
  196. package/src/capabilities/setup/operations/index.ts +14 -0
  197. package/src/capabilities/setup/operations/setupCompletions.ts +9 -2
  198. package/src/capabilities/setup/operations/setupConfig.ts +7 -2
  199. package/src/capabilities/setup/operations/setupGenerator.ts +93 -15
  200. package/src/capabilities/setup/operations/setupLsp.ts +544 -85
  201. package/src/capabilities/setup/operations/setupMcp.ts +212 -4
  202. package/src/capabilities/setup/operations/setupSkills.ts +7 -2
  203. package/src/capabilities/setup/operations/writability.test.ts +151 -0
  204. package/src/capabilities/setup/operations/writability.ts +135 -0
  205. package/src/capabilities/setup/plan.ts +11 -0
  206. package/src/capabilities/setup/setup.test.ts +992 -16
  207. package/src/capabilities/setup/setup.verb.ts +84 -36
  208. package/src/capabilities/setup/targets.ts +152 -48
  209. package/src/capabilities/setup/types.ts +41 -5
  210. package/src/capabilities/shared/index.ts +1 -0
  211. package/src/capabilities/sources/sources.test.ts +55 -0
  212. package/src/capabilities/surface.test.ts +59 -11
  213. package/src/capabilities/tier.parity.test.ts +2 -2
  214. package/src/capabilities/token.parity.test.ts +784 -0
  215. package/src/capabilities/tokenGraph.cli.test.ts +279 -0
  216. package/src/capabilities/tokenGraph.shipped.exec.test.ts +653 -0
  217. package/src/capabilities/variable.parity.test.ts +448 -0
  218. package/src/constants.ts +8 -0
  219. package/src/kernel/completion/shellDrive.test.ts +32 -30
  220. package/src/kernel/config/defaults.test.ts +21 -2
  221. package/src/kernel/config/readConfig.test.ts +1 -1
  222. package/src/kernel/error/renderError.ts +3 -2
  223. package/src/kernel/error/validOptions.test.ts +57 -0
  224. package/src/kernel/error/validOptions.ts +45 -0
  225. package/src/kernel/packs/collect.test.ts +2 -0
  226. package/src/kernel/packs/compile.test.ts +188 -15
  227. package/src/kernel/packs/compile.ts +109 -14
  228. package/src/kernel/packs/cursor.ts +135 -0
  229. package/src/kernel/packs/filteredEmpty.test.ts +213 -0
  230. package/src/kernel/packs/graphql/buildLookupDocument.ts +16 -9
  231. package/src/kernel/packs/graphqlSource.test.ts +152 -0
  232. package/src/kernel/packs/listNotice.test.ts +156 -0
  233. package/src/kernel/packs/paging.ts +110 -0
  234. package/src/kernel/packs/renderPack.ts +186 -13
  235. package/src/kernel/packs/resolveEntity.test.ts +62 -3
  236. package/src/kernel/packs/resolveEntity.ts +186 -26
  237. package/src/kernel/packs/runBodies.ts +196 -26
  238. package/src/kernel/packs/schema.ts +180 -13
  239. package/src/kernel/packs/sparql/authorQuery.test.ts +179 -0
  240. package/src/kernel/packs/sparql/authorQuery.ts +257 -0
  241. package/src/kernel/packs/sparql/buildListQuery.test.ts +344 -0
  242. package/src/kernel/packs/sparql/buildListQuery.ts +344 -0
  243. package/src/kernel/packs/sparql/buildLookupQuery.test.ts +138 -0
  244. package/src/kernel/packs/sparql/buildLookupQuery.ts +140 -20
  245. package/src/kernel/packs/sparql/filterValues.test.ts +86 -0
  246. package/src/kernel/packs/sparql/filterValues.ts +182 -0
  247. package/src/kernel/packs/sparql/index.ts +30 -14
  248. package/src/kernel/packs/sparql/runSelect.test.ts +63 -0
  249. package/src/kernel/packs/sparql/runSelect.ts +42 -2
  250. package/src/kernel/packs/sparql/searchTerm.ts +29 -0
  251. package/src/kernel/packs/storyRules.ts +180 -0
  252. package/src/kernel/packs/tierScope.test.ts +487 -0
  253. package/src/kernel/packs/tierScope.ts +409 -0
  254. package/src/kernel/packs/toolDescription.test.ts +13 -10
  255. package/src/kernel/packs/types.ts +311 -9
  256. package/src/kernel/project/cli/buildProgram.ts +17 -1
  257. package/src/kernel/project/cli/dispatch.test.ts +87 -0
  258. package/src/kernel/project/cli/dispatch.ts +114 -4
  259. package/src/kernel/project/cli/rootHelp.ts +8 -5
  260. package/src/kernel/project/cli/suggest.test.ts +35 -0
  261. package/src/kernel/project/cli/suggestNames.ts +34 -7
  262. package/src/kernel/project/mcp/registerVerb.ts +5 -1
  263. package/src/kernel/render/contracts.ts +2 -0
  264. package/src/kernel/render/prefixes.test.ts +2 -0
  265. package/src/kernel/render/renderers.test.ts +43 -0
  266. package/src/kernel/render/renderers.ts +34 -1
  267. package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +2 -3
  268. package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -1
  269. package/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.ts +3 -0
  270. package/src/kernel/runtime/graphpack/embedded.ts +14 -6
  271. package/src/kernel/runtime/graphpack/graphpack.test.ts +31 -5
  272. package/src/kernel/runtime/graphpack/read.ts +17 -13
  273. package/src/kernel/runtime/loadSession.ts +3 -1
  274. package/src/kernel/runtime/probe.ts +1 -1
  275. package/src/kernel/runtime/readEntity.ts +19 -4
  276. package/src/kernel/runtime/refs/parseRef.test.ts +49 -1
  277. package/src/kernel/runtime/refs/parseRef.ts +25 -1
  278. package/src/kernel/runtime/refs/resolve.ts +39 -5
  279. package/src/kernel/runtime/types.ts +30 -0
  280. package/src/kernel/spec/emitReference.ts +1 -1
  281. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  282. package/src/kernel/spec/types.ts +17 -0
  283. package/src/testing/PARITY_GAPS.ts +4 -4
  284. package/src/testing/behavioral/agentSession.mcp.test.ts +43 -39
  285. package/src/testing/behavioral/completion.test.ts +7 -6
  286. package/src/testing/behavioral/disclosure.test.ts +39 -37
  287. package/src/testing/behavioral/errorMatrix.mcp.test.ts +82 -64
  288. package/src/testing/behavioral/errorMatrix.storeless.test.ts +11 -10
  289. package/src/testing/behavioral/journeys.cli.test.ts +71 -32
  290. package/src/testing/behavioral/journeys.defaultPack.test.ts +6 -4
  291. package/src/testing/behavioral/journeys.firstInstall.test.ts +70 -5
  292. package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +4 -3
  293. package/src/testing/behavioral/journeys.packageStories.test.ts +2 -1
  294. package/src/testing/behavioral/mcpSurface.test.ts +28 -25
  295. package/src/testing/behavioral/parity.test.ts +25 -23
  296. package/src/testing/eval/__snapshots__/eval.test.ts.snap +20 -0
  297. package/src/testing/eval/cases/effectful.ts +7 -1
  298. package/src/testing/eval/cases/readNouns.ts +100 -11
  299. package/src/testing/fixtures/blockGraph.ts +89 -5
  300. package/src/testing/fixtures/connectGraph.ts +159 -0
  301. package/src/testing/fixtures/graph/canonical.ts +128 -32
  302. package/src/testing/helpers/fsProbe.ts +88 -0
  303. package/src/testing/helpers/runCli.test.ts +44 -0
  304. package/src/testing/helpers/runCli.ts +27 -3
  305. package/src/testing/perf/budgets.test.ts +3 -1
  306. package/src/testing/perf/budgets.ts +138 -54
  307. package/dist/src/kernel/packs/sparql/applyFilters.js +0 -167
  308. package/dist/src/kernel/packs/sparql/applyFilters.js.map +0 -1
  309. package/dist/src/kernel/packs/sparql/applySearch.js +0 -25
  310. package/dist/src/kernel/packs/sparql/applySearch.js.map +0 -1
  311. package/src/kernel/packs/sparql/applyFilters.ts +0 -203
  312. package/src/kernel/packs/sparql/applySearch.ts +0 -33
@@ -55,48 +55,52 @@ afterAll(async () => {
55
55
  });
56
56
 
57
57
  describe("agent session — list, pick, lookup (B1)", () => {
58
- it.each(
59
- browsableNouns,
60
- )("%s: list -> pick first -> lookup resolves it", async (noun) => {
61
- const list = await mcp.callTool(`${noun}_list`);
62
- expect(list.ok).toBe(true);
63
- const rows = list.data as { name: string }[];
64
- expect(rows.length).toBeGreaterThan(0);
65
- const first = rows[0] as { name: string };
58
+ it.each(browsableNouns)(
59
+ "%s: list -> pick first -> lookup resolves it",
60
+ async (noun) => {
61
+ const list = await mcp.callTool(`${noun}_list`);
62
+ expect(list.ok).toBe(true);
63
+ const rows = list.data as { name: string }[];
64
+ expect(rows.length).toBeGreaterThan(0);
65
+ const first = rows[0] as { name: string };
66
66
 
67
- const lookup = await mcp.callTool(`${noun}_lookup`, { name: [first.name] });
68
- expect(lookup.ok).toBe(true);
69
- const data = lookup.data as {
70
- results: { name: string }[];
71
- errors: unknown[];
72
- };
73
- expect(data.errors).toEqual([]);
74
- expect(data.results[0]?.name).toBe(first.name);
75
- });
67
+ const lookup = await mcp.callTool(`${noun}_lookup`, {
68
+ name: [first.name],
69
+ });
70
+ expect(lookup.ok).toBe(true);
71
+ const data = lookup.data as {
72
+ results: { name: string }[];
73
+ errors: unknown[];
74
+ };
75
+ expect(data.errors).toEqual([]);
76
+ expect(data.results[0]?.name).toBe(first.name);
77
+ },
78
+ );
76
79
 
77
- it.each(
78
- browsableNouns,
79
- )("%s: a BATCH lookup with one hit reports the miss without failing the call", async (noun) => {
80
- // `makeLookupRun` (kernel/packs/runBodies.ts) throws on a TOTAL miss (see
81
- // the errorMatrix.mcp.test.ts B3 suite) — a partial batch is the "miss
82
- // reported, call not failed" shape (an ADAPTATION of the plan's B1/B3
83
- // wording, which described a single-name miss; verified against the
84
- // live behavior).
85
- const list = await mcp.callTool(`${noun}_list`);
86
- const rows = list.data as { name: string }[];
87
- const known = (rows[0] as { name: string }).name;
80
+ it.each(browsableNouns)(
81
+ "%s: a BATCH lookup with one hit reports the miss without failing the call",
82
+ async (noun) => {
83
+ // `makeLookupRun` (kernel/packs/runBodies.ts) throws on a TOTAL miss (see
84
+ // the errorMatrix.mcp.test.ts B3 suite) — a partial batch is the "miss
85
+ // reported, call not failed" shape (an ADAPTATION of the plan's B1/B3
86
+ // wording, which described a single-name miss; verified against the
87
+ // live behavior).
88
+ const list = await mcp.callTool(`${noun}_list`);
89
+ const rows = list.data as { name: string }[];
90
+ const known = (rows[0] as { name: string }).name;
88
91
 
89
- const result = await mcp.callTool(`${noun}_lookup`, {
90
- name: [known, "zzz-definitely-not-a-real-entity"],
91
- });
92
- expect(result.ok).toBe(true);
93
- const data = result.data as {
94
- results: { name: string }[];
95
- errors: { code: string }[];
96
- };
97
- expect(data.results[0]?.name).toBe(known);
98
- expect(data.errors[0]?.code).toBe("ENTITY_NOT_FOUND");
99
- });
92
+ const result = await mcp.callTool(`${noun}_lookup`, {
93
+ name: [known, "zzz-definitely-not-a-real-entity"],
94
+ });
95
+ expect(result.ok).toBe(true);
96
+ const data = result.data as {
97
+ results: { name: string }[];
98
+ errors: { code: string }[];
99
+ };
100
+ expect(data.results[0]?.name).toBe(known);
101
+ expect(data.errors[0]?.code).toBe("ENTITY_NOT_FOUND");
102
+ },
103
+ );
100
104
  });
101
105
 
102
106
  describe("agent session — the categories journey, if the live surface declares one (B1)", () => {
@@ -65,12 +65,13 @@ describe("verb-level candidates: the SET matches the live surface", () => {
65
65
  expect(multiVerbNouns.length).toBeGreaterThan(0);
66
66
  });
67
67
 
68
- it.each(
69
- multiVerbNouns,
70
- )("%s: candidates == its live verb set", async (noun, verbs) => {
71
- const candidates = await runComplete([noun, ""], capabilities);
72
- expect([...candidates].sort()).toEqual([...verbs].sort());
73
- });
68
+ it.each(multiVerbNouns)(
69
+ "%s: candidates == its live verb set",
70
+ async (noun, verbs) => {
71
+ const candidates = await runComplete([noun, ""], capabilities);
72
+ expect([...candidates].sort()).toEqual([...verbs].sort());
73
+ },
74
+ );
74
75
  });
75
76
 
76
77
  describe("flags are kebab-cased at emission (the live, testable guarantee)", () => {
@@ -69,47 +69,49 @@ describe("disclosure lookups declare the aligned canonical level set (B4)", () =
69
69
  // the full `[summary, standard, detailed]`, so a config `detail=standard` named
70
70
  // a level block never advertised. Every disclosure lookup now declares the same
71
71
  // canonical ladder; per-noun DEFAULTS may still differ (domain-tuned).
72
- it.each(
73
- disclosureLookupVerbs,
74
- )("$noun: levels === the canonical ladder", (v) => {
75
- expect(v.spec.disclosure?.levels).toEqual([...DETAIL_LEVELS]);
76
- });
72
+ it.each(disclosureLookupVerbs)(
73
+ "$noun: levels === the canonical ladder",
74
+ (v) => {
75
+ expect(v.spec.disclosure?.levels).toEqual([...DETAIL_LEVELS]);
76
+ },
77
+ );
77
78
  });
78
79
 
79
80
  describe("detail:summary vs detail:detailed — key-set growth (B6)", () => {
80
- it.each(
81
- disclosureLookupVerbs,
82
- )("%s_lookup: detailed carries every summary key plus at least one more", async (v) => {
83
- const noun = v.noun;
84
- const list = await mcp.callTool(`${noun}_list`);
85
- const rows = list.data as { name: string }[];
86
- const name = rows[0]?.name;
87
- expect(name).toBeDefined();
88
- if (!name) return;
81
+ it.each(disclosureLookupVerbs)(
82
+ "%s_lookup: detailed carries every summary key plus at least one more",
83
+ async (v) => {
84
+ const noun = v.noun;
85
+ const list = await mcp.callTool(`${noun}_list`);
86
+ const rows = list.data as { name: string }[];
87
+ const name = rows[0]?.name;
88
+ expect(name).toBeDefined();
89
+ if (!name) return;
89
90
 
90
- const summary = await mcp.callTool(`${noun}_lookup`, {
91
- name: [name],
92
- detail: "summary",
93
- });
94
- const detailed = await mcp.callTool(`${noun}_lookup`, {
95
- name: [name],
96
- detail: "detailed",
97
- });
98
- expect(summary.ok).toBe(true);
99
- expect(detailed.ok).toBe(true);
91
+ const summary = await mcp.callTool(`${noun}_lookup`, {
92
+ name: [name],
93
+ detail: "summary",
94
+ });
95
+ const detailed = await mcp.callTool(`${noun}_lookup`, {
96
+ name: [name],
97
+ detail: "detailed",
98
+ });
99
+ expect(summary.ok).toBe(true);
100
+ expect(detailed.ok).toBe(true);
100
101
 
101
- const summaryEntity = (
102
- summary.data as { results: Record<string, unknown>[] }
103
- ).results[0] as Record<string, unknown>;
104
- const detailedEntity = (
105
- detailed.data as { results: Record<string, unknown>[] }
106
- ).results[0] as Record<string, unknown>;
102
+ const summaryEntity = (
103
+ summary.data as { results: Record<string, unknown>[] }
104
+ ).results[0] as Record<string, unknown>;
105
+ const detailedEntity = (
106
+ detailed.data as { results: Record<string, unknown>[] }
107
+ ).results[0] as Record<string, unknown>;
107
108
 
108
- const summaryKeys = new Set(Object.keys(summaryEntity));
109
- const detailedKeys = new Set(Object.keys(detailedEntity));
110
- for (const key of summaryKeys) {
111
- expect(detailedKeys.has(key)).toBe(true);
112
- }
113
- expect(detailedKeys.size).toBeGreaterThan(summaryKeys.size);
114
- });
109
+ const summaryKeys = new Set(Object.keys(summaryEntity));
110
+ const detailedKeys = new Set(Object.keys(detailedEntity));
111
+ for (const key of summaryKeys) {
112
+ expect(detailedKeys.has(key)).toBe(true);
113
+ }
114
+ expect(detailedKeys.size).toBeGreaterThan(summaryKeys.size);
115
+ },
116
+ );
115
117
  });
@@ -25,6 +25,7 @@
25
25
 
26
26
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
27
27
  import { capabilities } from "../../capabilities/index.js";
28
+ import { RESERVED_STORY_PARAMS } from "../../kernel/packs/types.js";
28
29
  import {
29
30
  ALL_VISIBLE_CONFIG,
30
31
  CANONICAL_TTL,
@@ -37,16 +38,25 @@ import { projectMcp } from "../helpers/projectMcp.js";
37
38
  import { listVerbs, lookupVerbs } from "./liveReadSurface.js";
38
39
 
39
40
  /**
40
- * List verbs with a STRING or ENUM (narrowable, non-boolean) flag — e.g.
41
- * `standard_list --category`. Boolean flags are excluded: they reject a string
42
- * value at the schema layer rather than narrowing to an empty result. (`block
43
- * list --all-tiers` used to be the example; it retired with the hand-written
44
- * filtering in L-OPEN-9.)
41
+ * List verbs with a STRING or ENUM (narrowable, non-boolean) flag the STORY
42
+ * itself declares — e.g. `standard_list --category`. Boolean flags are
43
+ * excluded: they reject a string value at the schema layer rather than
44
+ * narrowing to an empty result. (`block list --all-tiers` used to be the
45
+ * example; it retired with the hand-written filtering in L-OPEN-9.)
46
+ *
47
+ * The kernel's own params are excluded by name, from the grammar's own list.
48
+ * Without that, every list verb qualified the day the kernel gave them all an
49
+ * `--after` cursor, and four unfiltered nouns were swept for a narrowing
50
+ * behaviour they do not have — the assertions failed on a param that is not a
51
+ * filter at all.
45
52
  */
46
53
  const filteredListVerbs = listVerbs
47
54
  .map((v) => {
48
55
  const flag = v.spec.params.find(
49
- (p) => !p.positional && (p.kind === "string" || p.kind === "enum"),
56
+ (p) =>
57
+ !p.positional &&
58
+ (p.kind === "string" || p.kind === "enum") &&
59
+ !RESERVED_STORY_PARAMS.includes(p.name),
50
60
  );
51
61
  return flag
52
62
  ? { noun: v.noun, tool: v.tool as string, param: flag.name }
@@ -73,25 +83,27 @@ afterAll(async () => {
73
83
  });
74
84
 
75
85
  describe("lookup miss — total miss fails the call (B3, adapted)", () => {
76
- it.each(
77
- lookupVerbs.map((v) => v.noun),
78
- )("%s_lookup: a single unknown name fails with ENTITY_NOT_FOUND", async (noun) => {
79
- const result = await mcp.callTool(`${noun}_lookup`, {
80
- name: ["zzz-definitely-not-a-real-entity"],
81
- });
82
- expect(result.ok).toBe(false);
83
- expect((result.error as { code: string }).code).toBe("ENTITY_NOT_FOUND");
84
- });
86
+ it.each(lookupVerbs.map((v) => v.noun))(
87
+ "%s_lookup: a single unknown name fails with ENTITY_NOT_FOUND",
88
+ async (noun) => {
89
+ const result = await mcp.callTool(`${noun}_lookup`, {
90
+ name: ["zzz-definitely-not-a-real-entity"],
91
+ });
92
+ expect(result.ok).toBe(false);
93
+ expect((result.error as { code: string }).code).toBe("ENTITY_NOT_FOUND");
94
+ },
95
+ );
85
96
 
86
- it.each(
87
- lookupVerbs.map((v) => v.noun),
88
- )("%s_lookup: an all-unknown batch ALSO fails (not a partial report)", async (noun) => {
89
- const result = await mcp.callTool(`${noun}_lookup`, {
90
- name: ["zzz-nope-one", "zzz-nope-two"],
91
- });
92
- expect(result.ok).toBe(false);
93
- expect((result.error as { code: string }).code).toBe("ENTITY_NOT_FOUND");
94
- });
97
+ it.each(lookupVerbs.map((v) => v.noun))(
98
+ "%s_lookup: an all-unknown batch ALSO fails (not a partial report)",
99
+ async (noun) => {
100
+ const result = await mcp.callTool(`${noun}_lookup`, {
101
+ name: ["zzz-nope-one", "zzz-nope-two"],
102
+ });
103
+ expect(result.ok).toBe(false);
104
+ expect((result.error as { code: string }).code).toBe("ENTITY_NOT_FOUND");
105
+ },
106
+ );
95
107
  });
96
108
 
97
109
  /**
@@ -116,45 +128,43 @@ describe("list — a filter value the graph does not carry is INVALID_INPUT (B3,
116
128
  expect(filteredListVerbs.length).toBeGreaterThan(0);
117
129
  });
118
130
 
119
- it.each(
120
- filteredListVerbs,
121
- )("$tool: an unobserved filter value is rejected, naming the observed ones", async ({
122
- tool,
123
- param,
124
- }) => {
125
- const result = await mcp.callTool(tool, {
126
- [param]: "zzz-definitely-not-a-real-value",
127
- });
128
- expect(result.ok).toBe(false);
129
- const error = result.error as { code: string; validOptions?: string[] };
130
- expect(error.code).toBe("INVALID_INPUT");
131
- expect(error.validOptions?.length).toBeGreaterThan(0);
132
- expect(error.validOptions).not.toContain("zzz-definitely-not-a-real-value");
133
- });
131
+ it.each(filteredListVerbs)(
132
+ "$tool: an unobserved filter value is rejected, naming the observed ones",
133
+ async ({ tool, param }) => {
134
+ const result = await mcp.callTool(tool, {
135
+ [param]: "zzz-definitely-not-a-real-value",
136
+ });
137
+ expect(result.ok).toBe(false);
138
+ const error = result.error as { code: string; validOptions?: string[] };
139
+ expect(error.code).toBe("INVALID_INPUT");
140
+ expect(error.validOptions?.length).toBeGreaterThan(0);
141
+ expect(error.validOptions).not.toContain(
142
+ "zzz-definitely-not-a-real-value",
143
+ );
144
+ },
145
+ );
134
146
 
135
- it.each(
136
- filteredListVerbs,
137
- )("$tool: every value the graph DOES carry narrows to a calm list", async ({
138
- tool,
139
- param,
140
- }) => {
141
- const rejected = await mcp.callTool(tool, { [param]: "zzz-nope" });
142
- const admissible = (rejected.error as { validOptions: string[] })
143
- .validOptions;
144
- expect(admissible.length).toBeGreaterThan(0);
145
- // EVERY admissible value is accepted, and at least one is populated. Not
146
- // "the first one returns rows": the vocabulary is the graph's, so it can
147
- // legitimately contain a value no row carries (a category with zero
148
- // standards) — that value is a calm empty list, not a rejection, and the
149
- // sweep would otherwise read a real answer as a failure.
150
- let populated = 0;
151
- for (const value of admissible) {
152
- const result = await mcp.callTool(tool, { [param]: value });
153
- expect(result.ok).toBe(true);
154
- if ((result.data as unknown[]).length > 0) populated += 1;
155
- }
156
- expect(populated).toBeGreaterThan(0);
157
- });
147
+ it.each(filteredListVerbs)(
148
+ "$tool: every value the graph DOES carry narrows to a calm list",
149
+ async ({ tool, param }) => {
150
+ const rejected = await mcp.callTool(tool, { [param]: "zzz-nope" });
151
+ const admissible = (rejected.error as { validOptions: string[] })
152
+ .validOptions;
153
+ expect(admissible.length).toBeGreaterThan(0);
154
+ // EVERY admissible value is accepted, and at least one is populated. Not
155
+ // "the first one returns rows": the vocabulary is the graph's, so it can
156
+ // legitimately contain a value no row carries (a category with zero
157
+ // standards) — that value is a calm empty list, not a rejection, and the
158
+ // sweep would otherwise read a real answer as a failure.
159
+ let populated = 0;
160
+ for (const value of admissible) {
161
+ const result = await mcp.callTool(tool, { [param]: value });
162
+ expect(result.ok).toBe(true);
163
+ if ((result.data as unknown[]).length > 0) populated += 1;
164
+ }
165
+ expect(populated).toBeGreaterThan(0);
166
+ },
167
+ );
158
168
 
159
169
  it("standard_list: a declared category with NO standards is a calm empty list", async () => {
160
170
  // The case that separates "the graph is the vocabulary" from "the returned
@@ -202,14 +212,22 @@ describe("empty ≠ silence on the machine surfaces", () => {
202
212
  // an agent received `{"ok":true,"data":[],"meta":{}}` and nothing else. The
203
213
  // search path is the honest way to reach an empty list without a bad
204
214
  // argument.
215
+ //
216
+ // The guidance leads with the question that was actually answered — the
217
+ // SEARCH that matched nothing, in the caller's own spelling — and the
218
+ // story's `emptyRecovery` follows it, as it does on an unfiltered empty
219
+ // answer. Leading with the recovery instead was the defect: `sources
220
+ // update` told a reader with a populated store to rebuild it.
205
221
  const result = await mcp.callTool("standard_list", {
206
222
  search: "zzz-nothing-matches-this",
207
223
  });
208
224
  expect(result.ok).toBe(true);
209
225
  expect(result.data).toEqual([]);
210
226
  const notice = (result.meta as { notice?: string }).notice ?? "";
211
- expect(notice).toContain("No standard entries found.");
212
- expect(notice).toContain("pragma standard categories");
227
+ expect(notice).toContain(
228
+ "No standard matches `--search zzz-nothing-matches-this`.",
229
+ );
230
+ expect(notice).toContain("Category slugs come from the graph");
213
231
  });
214
232
  });
215
233
 
@@ -96,16 +96,17 @@ describe("error/recovery matrix — render shapes (A8, storeless)", () => {
96
96
  }
97
97
  });
98
98
 
99
- it.each(MATRIX)("json renders the failure envelope for $label", ({
100
- error,
101
- }) => {
102
- const envelope = JSON.parse(renderErrorJson(error)) as {
103
- ok: boolean;
104
- error: { code: string };
105
- };
106
- expect(envelope.ok).toBe(false);
107
- expect(envelope.error.code).toBe(error.code);
108
- });
99
+ it.each(MATRIX)(
100
+ "json renders the failure envelope for $label",
101
+ ({ error }) => {
102
+ const envelope = JSON.parse(renderErrorJson(error)) as {
103
+ ok: boolean;
104
+ error: { code: string };
105
+ };
106
+ expect(envelope.ok).toBe(false);
107
+ expect(envelope.error.code).toBe(error.code);
108
+ },
109
+ );
109
110
  });
110
111
 
111
112
  describe("error/recovery matrix — grounded in a real spawn (A8, e2e)", () => {
@@ -8,22 +8,29 @@
8
8
  * against the fixture, rather than spawning. It still exercises the true CLI
9
9
  * seam (typed params -> dispatch -> render), just without forking a process.
10
10
  *
11
- * CONTRACT CHANGE (L-OPEN-9): this journey used to enumerate four different
12
- * answers — one per tier config, one per channel, one for `--all-tiers`. That
13
- * hand-written filtering is gone; `block list` is the block story's compiled,
14
- * UNFILTERED list. So the journey pins the honest NEW behaviour, which is a
15
- * STRONGER claim than the old one: the SAME graph under FOUR different configs
16
- * gives ONE answer, containing every block in the store. `config.tier` and
17
- * `config.channel` no longer reach the query at all, and the owner-signed
18
- * consequence — experimental/alpha blocks are visible to everyone — is asserted
19
- * here rather than left to prose.
11
+ * CONTRACT CHANGE, TWICE, and this journey is where both are legible. It used
12
+ * to enumerate four answers — one per tier config, one per channel, one for
13
+ * `--all-tiers` — from hand-written filtering. That went, and the journey then
14
+ * pinned ONE answer under every config, because neither `config.tier` nor
15
+ * `config.channel` reached the query at all.
20
16
  *
21
- * CONTENT ADAPTATION (R2 — verified against the live `block lookup`, not
22
- * assumed): the plan's wording describes an unscoped `block lookup` returning
23
- * multiple tier-disambiguated matches — that mechanism doesn't exist in v2
24
- * (`block lookup` resolves by name GLOBALLY with no tier awareness at all;
25
- * PARITY_GAPS `block-lookup-not-tier-scoped`). Since L-OPEN-9 neither block
26
- * verb is tier-aware, so both halves of the noun now agree.
17
+ * The TIER SCOPE brings back exactly one of those two, in declared form: the
18
+ * `block` story declares the tier hierarchy, so `config.tier` and a per-call
19
+ * `--tier` decide which tiers the list answers from — unset is the top-level
20
+ * tiers, a tier is its own chain, `all` is the unfiltered list this journey used
21
+ * to pin. `config.channel` still reaches nothing, and the owner-signed
22
+ * consequence of that — experimental/alpha blocks are visible to everyone — is
23
+ * still asserted here rather than left to prose.
24
+ *
25
+ * So there are several answers again, and that is the improvement: each one
26
+ * states the scope it used, and one argument widens it.
27
+ *
28
+ * BOTH HALVES OF THE NOUN AGREE, as they have to: `block lookup` reads the same
29
+ * declared hierarchy, so the tier that decides which blocks this list shows is
30
+ * the tier that decides which Button a bare name means. The lookup's own half
31
+ * of that — it PREFERS the scope and falls back rather than refusing — is
32
+ * asserted in `kernel/packs/tierScope.test.ts` and, on the shipped pack's real
33
+ * tiers, in `capabilities/block.tierRank.exec.test.ts`.
27
34
  */
28
35
 
29
36
  import { afterAll, describe, expect, it } from "vitest";
@@ -70,6 +77,16 @@ const EVERY_BLOCK = [
70
77
  "Modal",
71
78
  ];
72
79
 
80
+ /**
81
+ * What the DEFAULT tier scope answers with: the top-level tiers (`global` and
82
+ * `apps`), so `LXD Panel` — two levels down, in `apps/lxd` — is out.
83
+ *
84
+ * `Button Icon` is IN, and that is the half worth stating twice: it carries no
85
+ * tier, and an entity the scope cannot PLACE is not an entity the scope hides.
86
+ * The hand-written verb's required tier join is what used to hide it.
87
+ */
88
+ const TOP_LEVEL_BLOCKS = ["Beta Widget", "Button", "Button Icon", "Modal"];
89
+
73
90
  const fixtures: FixtureGraph[] = [];
74
91
  afterAll(async () => {
75
92
  await Promise.all(fixtures.map((f) => f.dispose()));
@@ -84,10 +101,13 @@ async function bootWith(
84
101
  return fixture;
85
102
  }
86
103
 
87
- async function blockListNames(fixture: FixtureGraph): Promise<string[]> {
104
+ async function blockListNames(
105
+ fixture: FixtureGraph,
106
+ params: Record<string, unknown> = {},
107
+ ): Promise<string[]> {
88
108
  const out = await executeVerb(
89
109
  listVerb,
90
- {},
110
+ params,
91
111
  NO_MUTATION,
92
112
  bootRuntime(JSON_FLAGS, fixture.cwd),
93
113
  );
@@ -95,27 +115,43 @@ async function blockListNames(fixture: FixtureGraph): Promise<string[]> {
95
115
  return rows.map((r) => r.name).sort();
96
116
  }
97
117
 
98
- describe("block list — the declared, unfiltered list (B9, in-process CLI dispatch)", () => {
99
- it("with no tier configured, lists every block in the store", async () => {
118
+ describe("block list — the declared, tier-scoped list (B9, in-process CLI dispatch)", () => {
119
+ it("with no tier configured, answers from the top-level tiers", async () => {
100
120
  expect(await blockListNames(await bootWith(CANONICAL_CONFIG))).toEqual(
101
- EVERY_BLOCK,
121
+ TOP_LEVEL_BLOCKS,
102
122
  );
103
123
  });
104
124
 
105
- it("a configured tier does NOT narrow the list — tier scopes nothing", async () => {
106
- // The old verb answered ["Button", "Modal"] here: `global` excluded the
107
- // apps/lxd block, and the required `ds:tier` join hid the untiered
108
- // subcomponent. Nothing filters now, so the tier config is inert.
125
+ it("a configured tier narrows the list to its own chain", async () => {
126
+ // `global` is the base and has nothing above it, so its chain is itself.
127
+ // The apps/lxd block is out — and the UNTIERED subcomponent is not.
109
128
  const fixture = await bootWith({ tier: "global", channel: "normal" });
110
- expect(await blockListNames(fixture)).toEqual(EVERY_BLOCK);
129
+ expect(await blockListNames(fixture)).toEqual(TOP_LEVEL_BLOCKS);
130
+ });
131
+
132
+ it("the deepest tier in the chain sees MORE than the shallowest", async () => {
133
+ // Tier-chain inheritance was the point of the hand-written query, and it is
134
+ // the point again: `apps/lxd` -> [global, apps, apps/lxd], so the deepest
135
+ // tier answers with its own block AND everything it inherits. That is why
136
+ // the scope is the chain and not the single tier.
137
+ const names = await blockListNames(await bootWith(FILTERED_CONFIG));
138
+ expect(names).toEqual(EVERY_BLOCK);
139
+ expect(names).toContain("LXD Panel");
140
+ });
141
+
142
+ it("every tier on request is the unfiltered list this journey used to pin", async () => {
143
+ // The escape, and the proof that the scope removes nothing from the store:
144
+ // one argument, and every tier answers again.
145
+ const fixture = await bootWith(CANONICAL_CONFIG);
146
+ expect(await blockListNames(fixture, { tier: "all" })).toEqual(EVERY_BLOCK);
111
147
  });
112
148
 
113
- it("the deepest tier in the chain gets the same answer as the shallowest", async () => {
114
- // Tier-chain inheritance was the point of the hand-written query
115
- // (`apps/lxd` -> [global, apps, apps/lxd]). With no filtering there is no
116
- // chain to walk, so `apps/lxd` and `global` are indistinguishable.
117
- expect(await blockListNames(await bootWith(FILTERED_CONFIG))).toEqual(
118
- EVERY_BLOCK,
149
+ it("a per-call tier replaces the configured one", async () => {
150
+ // Precedence, at the seam a reader meets it: the machine is set to the deep
151
+ // tier, the call asks for the base, and the call wins.
152
+ const fixture = await bootWith(FILTERED_CONFIG);
153
+ expect(await blockListNames(fixture, { tier: "global" })).toEqual(
154
+ TOP_LEVEL_BLOCKS,
119
155
  );
120
156
  });
121
157
  });
@@ -131,8 +167,11 @@ describe("block list — channel visibility is gone too (B9)", () => {
131
167
  });
132
168
 
133
169
  it("`prerelease` — once the only config that saw everything — is now unremarkable", async () => {
170
+ // Both configs set no tier, so both answer from the top-level tiers: the
171
+ // channel changes nothing, which is the claim. What narrows these rows is
172
+ // the tier scope, and it narrows them identically.
134
173
  expect(await blockListNames(await bootWith(ALL_VISIBLE_CONFIG))).toEqual(
135
- EVERY_BLOCK,
174
+ TOP_LEVEL_BLOCKS,
136
175
  );
137
176
  });
138
177
  });
@@ -168,10 +168,12 @@ describe("default-pack journey — sources update, build, boot (E1)", () => {
168
168
  expect(outcome.exitCode).toBe(0);
169
169
  expect(readActivePack(cwd)).toMatch(/^[0-9a-f]{64}$/);
170
170
  // A SEPARATE, fresh runtime boots the just-built pack from the pointer and
171
- // answers a read over it. Since L-OPEN-9 the list is unfiltered, so the
172
- // cold-boot read returns the pack's WHOLE block set — the beta-gated Beta
173
- // Badge and the untiered Orphan Widget included. That makes this the
174
- // strictest form of the assert (an exact set, not a tolerant subset).
171
+ // answers a read over it. The cold-boot read returns the pack's WHOLE
172
+ // block set — the beta-gated Beta Badge (no channel filters) and the
173
+ // untiered Orphan Widget (no scope hides an entity it cannot place)
174
+ // included, and every tier this fixture declares is top-level, so the
175
+ // tier scope admits the rest. That makes this the strictest form of the
176
+ // assert (an exact set, not a tolerant subset).
175
177
  expect(await blockListNames(cwd)).toEqual([
176
178
  "Beta Badge",
177
179
  "Button",