@canonical/pragma-cli 0.32.0 → 0.34.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 (295) hide show
  1. package/README.md +72 -0
  2. package/dist/pragma +0 -0
  3. package/package.json +19 -15
  4. package/pragma.conf.ts +664 -0
  5. package/src/bin.ts +15 -6
  6. package/src/capabilities/block.parity.test.ts +250 -0
  7. package/src/capabilities/capabilities/capabilities.render.ts +3 -2
  8. package/src/capabilities/capabilities/capabilities.test.ts +10 -1
  9. package/src/capabilities/capabilities/capabilities.verb.ts +32 -14
  10. package/src/capabilities/capabilities/catalog.ts +20 -8
  11. package/src/capabilities/capabilities/hints.ts +21 -36
  12. package/src/capabilities/capabilities/types.ts +2 -0
  13. package/src/capabilities/colophon/collectColophon.ts +34 -19
  14. package/src/capabilities/colophon/colophon.render.test.ts +31 -4
  15. package/src/capabilities/colophon/colophon.render.ts +13 -4
  16. package/src/capabilities/colophon/colophon.test.ts +3 -3
  17. package/src/capabilities/colophon/colophon.verb.ts +11 -4
  18. package/src/capabilities/colophon/markdownTerminal.ts +33 -14
  19. package/src/capabilities/colophon/types.ts +16 -7
  20. package/src/capabilities/config/collectConfigShow.test.ts +57 -0
  21. package/src/capabilities/config/collectConfigShow.ts +41 -4
  22. package/src/capabilities/config/fields.ts +10 -108
  23. package/src/capabilities/config/set.test.ts +6 -6
  24. package/src/capabilities/config/set.verb.ts +12 -8
  25. package/src/capabilities/config/show.render.test.ts +137 -0
  26. package/src/capabilities/config/show.render.ts +57 -16
  27. package/src/capabilities/config/show.verb.ts +9 -8
  28. package/src/capabilities/config/types.ts +8 -0
  29. package/src/capabilities/create/byteEquality.subprocess.test.ts +1 -1
  30. package/src/capabilities/create/byteEquality.test.ts +82 -101
  31. package/src/capabilities/create/compiledCreate.subprocess.test.ts +53 -16
  32. package/src/capabilities/create/constants.ts +75 -0
  33. package/src/capabilities/create/create.mcp.test.ts +2 -2
  34. package/src/capabilities/create/create.test.ts +192 -16
  35. package/src/capabilities/create/create.verb.ts +208 -79
  36. package/src/capabilities/create/index.ts +2 -10
  37. package/src/capabilities/create/pickGenerator.ts +34 -28
  38. package/src/capabilities/create/templates.embedded.generated.ts +4 -29
  39. package/src/capabilities/create/types.ts +10 -6
  40. package/src/capabilities/{standard/parity.test.ts → distribution.parity.test.ts} +24 -14
  41. package/src/capabilities/distribution.test.ts +120 -0
  42. package/src/capabilities/distribution.ts +59 -0
  43. package/src/capabilities/doctor/checks/checkConfigFile.ts +6 -5
  44. package/src/capabilities/doctor/checks/checkKeStore.ts +2 -2
  45. package/src/capabilities/doctor/checks/checkMcpCommands.ts +6 -0
  46. package/src/capabilities/doctor/checks/checkMcpConfigured.ts +15 -8
  47. package/src/capabilities/doctor/checks/checkPackageRefs.ts +79 -45
  48. package/src/capabilities/doctor/checks/checkPragmaVersion.ts +3 -2
  49. package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +61 -11
  50. package/src/capabilities/doctor/checks/checkShellCompletions.ts +63 -23
  51. package/src/capabilities/doctor/checks/checkSkillsSymlinked.ts +3 -2
  52. package/src/capabilities/doctor/checks/deriveBand.test.ts +42 -0
  53. package/src/capabilities/doctor/checks/deriveBand.ts +28 -0
  54. package/src/capabilities/doctor/doctor.render.test.ts +170 -0
  55. package/src/capabilities/doctor/doctor.render.ts +117 -34
  56. package/src/capabilities/doctor/doctor.test.ts +68 -9
  57. package/src/capabilities/doctor/doctor.verb.ts +6 -2
  58. package/src/capabilities/doctor/runChecks.ts +33 -9
  59. package/src/capabilities/doctor/types.ts +16 -1
  60. package/src/capabilities/emptyRecovery.test.ts +32 -15
  61. package/src/capabilities/graph/inspect.verb.ts +3 -2
  62. package/src/capabilities/graph/query.verb.ts +3 -2
  63. package/src/capabilities/graph/runQuery.ts +8 -5
  64. package/src/capabilities/index.ts +22 -18
  65. package/src/capabilities/info/collectInfo.ts +10 -6
  66. package/src/capabilities/info/info.render.ts +3 -2
  67. package/src/capabilities/info/info.verb.ts +3 -2
  68. package/src/capabilities/lazy.test.ts +93 -2
  69. package/src/capabilities/ontology/index.ts +10 -3
  70. package/src/capabilities/ontology/ontology.test.ts +157 -5
  71. package/src/capabilities/ontology/verbs.ts +203 -104
  72. package/src/capabilities/prompt/prompt.render.test.ts +94 -0
  73. package/src/capabilities/prompt/prompt.render.ts +23 -6
  74. package/src/capabilities/prompt/prompt.test.ts +68 -1
  75. package/src/capabilities/prompt/verbs.ts +24 -11
  76. package/src/capabilities/reference.test.ts +32 -0
  77. package/src/capabilities/resources/provider.ts +58 -25
  78. package/src/capabilities/resources/resources.test.ts +176 -21
  79. package/src/capabilities/scopeTypeSync.test.ts +37 -0
  80. package/src/capabilities/setup/index.ts +7 -1
  81. package/src/capabilities/setup/operations/setupCompletions.ts +49 -16
  82. package/src/capabilities/setup/operations/setupGenerator.ts +166 -63
  83. package/src/capabilities/setup/operations/setupLsp.ts +56 -12
  84. package/src/capabilities/setup/operations/setupMcp.ts +216 -39
  85. package/src/capabilities/setup/operations/setupSkills.ts +10 -4
  86. package/src/capabilities/setup/setup.render.test.ts +109 -0
  87. package/src/capabilities/setup/setup.render.ts +61 -14
  88. package/src/capabilities/setup/setup.test.ts +352 -1
  89. package/src/capabilities/setup/setup.verb.ts +68 -9
  90. package/src/capabilities/setup/shell.ts +29 -3
  91. package/src/capabilities/setup/types.ts +59 -2
  92. package/src/capabilities/shared/bands.ts +16 -0
  93. package/src/capabilities/skill/discover.ts +11 -5
  94. package/src/capabilities/skill/render.ts +4 -5
  95. package/src/capabilities/skill/verbs.ts +7 -7
  96. package/src/capabilities/sources/collectStatus.ts +69 -49
  97. package/src/capabilities/sources/index.ts +1 -1
  98. package/src/capabilities/sources/installSkills.ts +2 -2
  99. package/src/capabilities/sources/runUpdate.ts +146 -98
  100. package/src/capabilities/sources/sources.test.ts +264 -84
  101. package/src/capabilities/sources/status.render.test.ts +94 -0
  102. package/src/capabilities/sources/status.render.ts +55 -33
  103. package/src/capabilities/sources/status.verb.ts +15 -8
  104. package/src/capabilities/sources/types.ts +18 -25
  105. package/src/capabilities/sources/update.render.ts +9 -2
  106. package/src/capabilities/sources/update.verb.ts +12 -23
  107. package/src/capabilities/surface.test.ts +65 -17
  108. package/src/capabilities/tier.parity.test.ts +144 -0
  109. package/src/capabilities/upgrade/upgrade.verb.ts +7 -3
  110. package/src/constants.test.ts +9 -7
  111. package/src/constants.ts +49 -23
  112. package/src/identity.test.ts +333 -0
  113. package/src/index.ts +2 -2
  114. package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +18 -16
  115. package/src/kernel/completion/complete.test.ts +3 -5
  116. package/src/kernel/completion/emitScripts.test.ts +1 -1
  117. package/src/kernel/completion/emitScripts.ts +4 -2
  118. package/src/kernel/completion/entitySource.test.ts +185 -41
  119. package/src/kernel/completion/entitySource.ts +131 -113
  120. package/src/kernel/completion/model.test.ts +3 -3
  121. package/src/kernel/completion/model.ts +11 -1
  122. package/src/kernel/completion/parse.test.ts +2 -4
  123. package/src/kernel/completion/safety.test.ts +82 -36
  124. package/src/kernel/completion/shellDrive.test.ts +592 -0
  125. package/src/kernel/completion/templates/fish.ts +28 -6
  126. package/src/kernel/completion/types.ts +3 -3
  127. package/src/kernel/config/__snapshots__/firstRun.test.ts.snap +1 -1
  128. package/src/kernel/config/defaults.test.ts +85 -0
  129. package/src/kernel/config/defaults.ts +12 -23
  130. package/src/kernel/config/evaluateProjectConfig.ts +2 -3
  131. package/src/kernel/config/findProjectConfig.ts +4 -3
  132. package/src/kernel/config/firstRun.ts +12 -5
  133. package/src/kernel/config/globalConfig.ts +2 -2
  134. package/src/kernel/config/index.ts +4 -5
  135. package/src/kernel/config/paths.ts +17 -17
  136. package/src/kernel/config/readConfig.test.ts +242 -6
  137. package/src/kernel/config/readConfig.ts +8 -8
  138. package/src/kernel/config/schema.test.ts +50 -0
  139. package/src/kernel/config/schema.ts +97 -10
  140. package/src/kernel/config/types.ts +73 -19
  141. package/src/kernel/copy.test.ts +453 -0
  142. package/src/kernel/error/PragmaError.test.ts +5 -5
  143. package/src/kernel/error/constants.ts +3 -2
  144. package/src/kernel/error/index.ts +1 -1
  145. package/src/kernel/error/recovery.test.ts +22 -45
  146. package/src/kernel/error/recovery.ts +22 -27
  147. package/src/kernel/error/renderError.ts +1 -1
  148. package/src/kernel/error/types.ts +5 -4
  149. package/src/kernel/packs/collect.test.ts +195 -11
  150. package/src/kernel/packs/collect.ts +213 -58
  151. package/src/kernel/packs/compile.test.ts +163 -7
  152. package/src/kernel/packs/compile.ts +15 -12
  153. package/src/kernel/packs/disclosure.test.ts +5 -2
  154. package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +11 -10
  155. package/src/kernel/packs/graphqlSource.test.ts +6 -3
  156. package/src/kernel/packs/iri.ts +5 -7
  157. package/src/kernel/packs/renderPack.ts +5 -3
  158. package/src/kernel/packs/resolveEntity.ts +21 -17
  159. package/src/kernel/packs/runBodies.ts +15 -14
  160. package/src/kernel/packs/schema.ts +96 -10
  161. package/src/kernel/packs/sparql/runSelect.test.ts +104 -10
  162. package/src/kernel/packs/sparql/runSelect.ts +73 -34
  163. package/src/kernel/packs/toolDescription.test.ts +10 -2
  164. package/src/kernel/packs/types.ts +39 -4
  165. package/src/kernel/packs/uniqueness.ts +4 -4
  166. package/src/kernel/project/cli/buildProgram.test.ts +60 -11
  167. package/src/kernel/project/cli/buildProgram.ts +11 -0
  168. package/src/kernel/project/cli/dispatch.test.ts +67 -0
  169. package/src/kernel/project/cli/dispatch.ts +34 -12
  170. package/src/kernel/project/cli/globalFlags.test.ts +12 -4
  171. package/src/kernel/project/cli/globalFlags.ts +14 -14
  172. package/src/kernel/project/cli/helpFormat.ts +1 -1
  173. package/src/kernel/project/cli/rootHelp.ts +109 -107
  174. package/src/kernel/project/mcp/buildServer.ts +11 -6
  175. package/src/kernel/project/mcp/instructions.ts +12 -7
  176. package/src/kernel/project/mcp/mcpError.ts +52 -0
  177. package/src/kernel/project/mcp/parity.test.ts +39 -1
  178. package/src/kernel/project/mcp/prompts/prompts.test.ts +128 -5
  179. package/src/kernel/project/mcp/prompts/provider.ts +51 -7
  180. package/src/kernel/project/mcp/prompts/source.ts +68 -67
  181. package/src/kernel/project/mcp/registerVerb.ts +25 -6
  182. package/src/kernel/project/mcp/serve.ts +11 -3
  183. package/src/kernel/project/mcp/toolDescriptions.test.ts +6 -2
  184. package/src/kernel/render/disclosure.ts +4 -2
  185. package/src/kernel/render/formatters.ts +5 -4
  186. package/src/kernel/render/prefixes.test.ts +36 -0
  187. package/src/kernel/render/prefixes.ts +40 -13
  188. package/src/kernel/render/renderers.test.ts +35 -1
  189. package/src/kernel/render/renderers.ts +25 -7
  190. package/src/kernel/render/style.test.ts +31 -0
  191. package/src/kernel/render/style.ts +74 -0
  192. package/src/kernel/runtime/graphpack/build.ts +57 -3
  193. package/src/kernel/runtime/graphpack/buildIndex.test.ts +149 -0
  194. package/src/kernel/runtime/graphpack/buildIndex.ts +173 -50
  195. package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +5 -8
  196. package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +3 -4
  197. package/src/kernel/runtime/graphpack/embedded/pack.stories.generated.ts +3 -0
  198. package/src/kernel/runtime/graphpack/embedded.ts +37 -11
  199. package/src/kernel/runtime/graphpack/graphpack.test.ts +274 -6
  200. package/src/kernel/runtime/graphpack/hash.ts +3 -2
  201. package/src/kernel/runtime/graphpack/index.ts +1 -1
  202. package/src/kernel/runtime/graphpack/manifest.ts +114 -12
  203. package/src/kernel/runtime/graphpack/read.ts +32 -22
  204. package/src/kernel/runtime/graphpack/schemas.ts +74 -0
  205. package/src/kernel/runtime/graphpack/stories.ts +84 -0
  206. package/src/kernel/runtime/graphpack/types.ts +72 -45
  207. package/src/kernel/runtime/loadSession.ts +4 -12
  208. package/src/kernel/runtime/paths.ts +64 -15
  209. package/src/kernel/runtime/probe.test.ts +8 -4
  210. package/src/kernel/runtime/readEntity.ts +6 -5
  211. package/src/kernel/runtime/refs/gitOps.ts +1 -19
  212. package/src/kernel/runtime/refs/parseRef.ts +7 -7
  213. package/src/kernel/runtime/refs/resolve.ts +141 -65
  214. package/src/kernel/runtime/resolveSources.test.ts +63 -24
  215. package/src/kernel/runtime/resolveSources.ts +36 -25
  216. package/src/kernel/runtime/storeReadiness.ts +62 -0
  217. package/src/kernel/runtime/storeless.test.ts +1 -1
  218. package/src/kernel/runtime/types.ts +1 -1
  219. package/src/kernel/runtime/wasmEmbed.test.ts +25 -20
  220. package/src/kernel/spec/emitReference.test.ts +196 -0
  221. package/src/kernel/spec/emitReference.ts +624 -0
  222. package/src/kernel/spec/emitSurface.ts +13 -7
  223. package/src/kernel/spec/index.ts +2 -0
  224. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  225. package/src/kernel/spec/surfaceConformance.ts +8 -0
  226. package/src/kernel/spec/types.ts +29 -9
  227. package/src/kernel/spec/validate.ts +2 -1
  228. package/src/kernel/vocabulary.test.ts +164 -0
  229. package/src/kernel/vocabulary.ts +146 -0
  230. package/src/testing/PARITY_GAPS.ts +9 -3
  231. package/src/testing/behavioral/README.md +3 -3
  232. package/src/testing/behavioral/autoLlm.e2e.test.ts +12 -2
  233. package/src/testing/behavioral/completion.test.ts +21 -8
  234. package/src/testing/behavioral/disclosure.test.ts +12 -0
  235. package/src/testing/behavioral/docExamples.test.ts +283 -0
  236. package/src/testing/behavioral/errorMatrix.mcp.test.ts +4 -3
  237. package/src/testing/behavioral/errorMatrix.storeless.test.ts +3 -2
  238. package/src/testing/behavioral/exitCodes.e2e.test.ts +6 -5
  239. package/src/testing/behavioral/harness.foundation.test.ts +14 -5
  240. package/src/testing/behavioral/journeys.cli.test.ts +116 -73
  241. package/src/testing/behavioral/journeys.defaultPack.test.ts +477 -0
  242. package/src/testing/behavioral/journeys.firstInstall.test.ts +167 -0
  243. package/src/testing/behavioral/journeys.interactiveTty.e2e.test.ts +66 -0
  244. package/src/testing/behavioral/journeys.livePack.e2e.test.ts +129 -0
  245. package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +179 -0
  246. package/src/testing/behavioral/journeys.packageStories.test.ts +188 -0
  247. package/src/testing/behavioral/liveReadSurface.ts +4 -3
  248. package/src/testing/behavioral/mcpSurface.test.ts +10 -5
  249. package/src/testing/behavioral/staleCommands.test.ts +86 -0
  250. package/src/testing/eval/__snapshots__/eval.test.ts.snap +9 -9
  251. package/src/testing/eval/cases/assembly.ts +16 -9
  252. package/src/testing/eval/cases/effectful.ts +31 -16
  253. package/src/testing/eval/cases/readNouns.ts +23 -18
  254. package/src/testing/eval/cases/stable.ts +5 -1
  255. package/src/testing/eval/eval.test.ts +7 -1
  256. package/src/testing/fixtures/fixtureCapability.ts +201 -1
  257. package/src/testing/fixtures/graph/canonical.ts +6 -3
  258. package/src/testing/fixtures/graph/defaultPack.ts +179 -0
  259. package/src/testing/fixtures/graph/owlExport.ts +97 -0
  260. package/src/testing/helpers/extractSnippets.ts +29 -0
  261. package/src/testing/helpers/fixtureGraph.ts +33 -16
  262. package/src/testing/helpers/golden.ts +1 -1
  263. package/src/testing/helpers/packRuntime.ts +2 -3
  264. package/src/testing/helpers/projectCli.ts +2 -1
  265. package/src/testing/helpers/projectMcp.ts +6 -0
  266. package/src/testing/perf/budgets.test.ts +4 -0
  267. package/src/testing/perf/budgets.ts +22 -7
  268. package/src/testing/perf/globalSetup.ts +121 -6
  269. package/src/types.ts +2 -2
  270. package/src/capabilities/block/blockList.verb.ts +0 -150
  271. package/src/capabilities/block/index.ts +0 -25
  272. package/src/capabilities/block/pack.ts +0 -155
  273. package/src/capabilities/block/parity.test.ts +0 -180
  274. package/src/capabilities/block/tierChain.ts +0 -78
  275. package/src/capabilities/colophon/pragmaColophon.ts +0 -66
  276. package/src/capabilities/config/field.test.ts +0 -188
  277. package/src/capabilities/modifier/index.ts +0 -15
  278. package/src/capabilities/modifier/pack.ts +0 -64
  279. package/src/capabilities/standard/index.ts +0 -18
  280. package/src/capabilities/standard/pack.ts +0 -128
  281. package/src/capabilities/tier/index.ts +0 -19
  282. package/src/capabilities/tier/lookup.render.ts +0 -34
  283. package/src/capabilities/tier/lookup.test.ts +0 -68
  284. package/src/capabilities/tier/lookup.verb.ts +0 -44
  285. package/src/capabilities/tier/pack.ts +0 -28
  286. package/src/capabilities/tier/runLookup.ts +0 -59
  287. package/src/capabilities/token/addConfig.render.ts +0 -27
  288. package/src/capabilities/token/addConfig.test.ts +0 -62
  289. package/src/capabilities/token/addConfig.verb.ts +0 -40
  290. package/src/capabilities/token/index.ts +0 -19
  291. package/src/capabilities/token/pack.ts +0 -58
  292. package/src/capabilities/token/runAddConfig.ts +0 -95
  293. package/src/kernel/completion/bashDrive.test.ts +0 -73
  294. package/src/kernel/runtime/graphpack/embedded/sample.ttl +0 -39
  295. package/src/kernel/runtime/lock.ts +0 -90
@@ -0,0 +1,66 @@
1
+ /**
2
+ * E4 (AV-231, Backlog E) — the interactive TTY journey. SCAFFOLD.
3
+ *
4
+ * No test in the suite ever drove a REAL terminal, so the interactive-cancel and
5
+ * no-freeze guarantees (H1–H3, C3/C4) were never exercised end to end. Confirming
6
+ * them needs a pseudo-terminal: spawn the compiled `pragma` under a PTY, send raw
7
+ * keystrokes (Ctrl-C = `\x03`, EOF = `\x04`), and observe the exit code + output.
8
+ *
9
+ * `node-pty` is NOT available in this environment (it is a native addon, absent
10
+ * from the dependency set — verified at authoring time), so the journey is
11
+ * scaffolded as `it.todo` behind a `node-pty` availability gate rather than
12
+ * blocking the P0 work. Each todo names the exact behaviour and the exit contract
13
+ * the CLI already promises (see `kernel/project/cli/dispatch.ts`):
14
+ *
15
+ * - H1: an at-prompt Ctrl-C is a DECLINE — a clean cancel, "Cancelled." on
16
+ * stderr, exit 0 (`EXIT.OK`).
17
+ * - H2: a Ctrl-C DURING execution is an INTERRUPT — aborts work underway,
18
+ * "Cancelled." on stderr, exit 130 (`EXIT.INTERRUPTED`, 128 + SIGINT).
19
+ * - H3: `create 2>/dev/null` must not freeze — interactivity gates on stderr
20
+ * being a TTY, so a redirected stderr picks the non-interactive strategy
21
+ * instead of mounting an invisible wizard that blocks on stdin.
22
+ * - C3: EOF (`\x04`) on a prompt resolves to a usage error, never a hang.
23
+ * - C4: an empty select (Enter on a no-default list) does not hang.
24
+ *
25
+ * TODO(AV-231/E4): when `node-pty` (or an equivalent PTY shim) is on the
26
+ * devDependency set, replace each `it.todo` with a PTY-driven `it` that spawns
27
+ * `dist/pragma create …` under a pty, writes the keystroke, and asserts the exit
28
+ * code + "Cancelled." line. The compiled binary is already provisioned for the
29
+ * spawn-e2e layer by `testing/perf/globalSetup.ts`.
30
+ */
31
+
32
+ import { describe, it } from "vitest";
33
+
34
+ /** True when a real PTY driver is importable (kept async-free: a resolve probe). */
35
+ async function ptyAvailable(): Promise<boolean> {
36
+ try {
37
+ await import("node-pty" as string);
38
+ return true;
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+
44
+ // Authoring-time: `node-pty` is absent, so this resolves to `describe.skip` and
45
+ // the todos below stand in as the recorded, unblocked plan. The probe is awaited
46
+ // lazily so a future environment with node-pty flips the gate with no edit here.
47
+ const hasPty = await ptyAvailable();
48
+ const ptyGate = hasPty ? describe : describe.skip;
49
+
50
+ ptyGate(
51
+ "interactive TTY journey — cancel + no-freeze (E4, node-pty) [SCAFFOLD]",
52
+ () => {
53
+ // These become PTY-driven `it`s once node-pty is available (see module TODO).
54
+ it.todo(
55
+ "H1: Ctrl-C at a wizard prompt cancels cleanly (stderr 'Cancelled.', exit 0)",
56
+ );
57
+ it.todo(
58
+ "H2: Ctrl-C during execution aborts the run (stderr 'Cancelled.', exit 130)",
59
+ );
60
+ it.todo(
61
+ "H3: `create 2>/dev/null` does not freeze (non-TTY stderr -> non-interactive)",
62
+ );
63
+ it.todo("C3: EOF on a prompt resolves to a usage error, never a hang");
64
+ it.todo("C4: an empty select does not hang");
65
+ },
66
+ );
@@ -0,0 +1,129 @@
1
+ /**
2
+ * E3 (AV-231, Backlog E) — the network-gated, NON-BLOCKING live-pack job.
3
+ *
4
+ * A handful of behaviours can only be confirmed against a REAL oxigraph store
5
+ * built from the REAL default pack: result ordering (A3/A9), which language a
6
+ * multilingual label resolves to under real store order (A7), ke truncation of
7
+ * long literals, and `buildIndex`'s `Promise.all` re-entrancy when the same pack
8
+ * is built concurrently. Those depend on the network (fetching the shipped pack)
9
+ * and on real build cost, so this suite is GATED OFF by default — it never runs
10
+ * (or flakes) in the ordinary `bun test` pass. Set `PRAGMA_LIVE_PACK=1` to run
11
+ * it (a dedicated, non-required CI job).
12
+ *
13
+ * The assertions here run against the VENDORED default pack as a faithful local
14
+ * proxy for the shipped one, so a maintainer who flips the gate gets real signal
15
+ * today. Each carries a TODO for wiring the ACTUAL network pack fetch — the one
16
+ * piece this environment cannot provide.
17
+ *
18
+ * TODO(AV-231/E3): resolve the real shipped default pack (git/npm source) here
19
+ * instead of the vendored TTL, so ordering/truncation are asserted against the
20
+ * bytes agents actually receive.
21
+ */
22
+
23
+ import { describe, expect, it } from "vitest";
24
+ import { buildPack } from "../../kernel/runtime/graphpack/build.js";
25
+ import {
26
+ DEFAULT_PACK_PREFIXES,
27
+ DEFAULT_PACK_TTL,
28
+ } from "../fixtures/graph/defaultPack.js";
29
+ import {
30
+ bootFixtureRuntime,
31
+ type FixtureGraph,
32
+ } from "../helpers/fixtureGraph.js";
33
+
34
+ /** Non-blocking gate: skipped unless a maintainer opts in (or CI's live job). */
35
+ const liveGate = process.env.PRAGMA_LIVE_PACK ? describe : describe.skip;
36
+
37
+ liveGate(
38
+ "live-pack journey — real oxigraph ordering + build re-entrancy (E3)",
39
+ () => {
40
+ it("a SELECT ... ORDER BY returns a STABLE, deterministic ordering (A3/A9)", async () => {
41
+ const fixture = await bootFixtureRuntime({ ttl: DEFAULT_PACK_TTL });
42
+ try {
43
+ const query =
44
+ "SELECT ?name WHERE { ?c a ds:Component ; ds:name ?name } ORDER BY ?name";
45
+ const first = await fixture.runtime.query.sparql(query);
46
+ const second = await fixture.runtime.query.sparql(query);
47
+ const names = (result: typeof first): string[] =>
48
+ result.type === "select"
49
+ ? result.bindings.map((binding) => String(binding.name))
50
+ : [];
51
+ // Repeated evaluation is identical, and ORDER BY is honoured.
52
+ expect(names(first)).toEqual(names(second));
53
+ expect(names(first)).toEqual(["Beta Badge", "Button", "Orphan Widget"]);
54
+ } finally {
55
+ await fixture.dispose();
56
+ }
57
+ });
58
+
59
+ it("a multilingual label resolves to a SINGLE, tag-stripped value under real store order (A7)", async () => {
60
+ // TODO(AV-231/E3): against the live pack, pin the EXACT language the store
61
+ // yields first, so the index-label choice is a confirmed contract rather than
62
+ // the store-order accident it is today (see the A7 note in the E1 journey).
63
+ const fixture = await bootFixtureRuntime({ ttl: DEFAULT_PACK_TTL });
64
+ try {
65
+ const result = await fixture.runtime.query.sparql(
66
+ "SELECT ?l WHERE { ds:button rdfs:label ?l } ORDER BY ?l",
67
+ );
68
+ const labels =
69
+ result.type === "select"
70
+ ? result.bindings.map((binding) => String(binding.l))
71
+ : [];
72
+ expect(labels).toEqual(["Bouton", "Button"]);
73
+ } finally {
74
+ await fixture.dispose();
75
+ }
76
+ });
77
+
78
+ it("building the same sources CONCURRENTLY is re-entrant — one hash, no torn pack (buildIndex Promise.all)", async () => {
79
+ // buildIndex fans out four bulk SPARQL queries under Promise.all, and buildPack
80
+ // publishes atomically; N concurrent builds of identical sources must converge
81
+ // on ONE content hash with every pack complete (no half-written cache).
82
+ const inputs = [
83
+ { path: "concurrent/pack.ttl", content: DEFAULT_PACK_TTL },
84
+ ];
85
+ const options = {
86
+ name: "pragma",
87
+ version: "0.0.0-e3",
88
+ sourceRef: "e3-reentrancy",
89
+ prefixes: DEFAULT_PACK_PREFIXES,
90
+ };
91
+ const results = await Promise.all(
92
+ Array.from({ length: 5 }, () => buildPack(inputs, options)),
93
+ );
94
+ const hashes = new Set(results.map((result) => result.contentHash));
95
+ expect(hashes.size).toBe(1);
96
+ for (const result of results) {
97
+ expect(result.contentHash).toMatch(/^[0-9a-f]{64}$/);
98
+ }
99
+ });
100
+
101
+ it("a long literal survives the build round-trip without silent ke truncation", async () => {
102
+ // TODO(AV-231/E3): confirm against the live pack, whose real summaries/guidelines
103
+ // are long enough to trip any downstream ke truncation (A7/A9 tail).
104
+ const longText = "x".repeat(20_000);
105
+ const ttl = `@prefix ds: <https://ds.canonical.com/> .
106
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
107
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
108
+ ds:Component a owl:Class .
109
+ ds:summary a owl:DatatypeProperty ; rdfs:range xsd:string .
110
+ ds:big a ds:Component ; ds:summary "${longText}" .`;
111
+ let fixture: FixtureGraph | undefined;
112
+ try {
113
+ fixture = await bootFixtureRuntime({ ttl });
114
+ const result = await fixture.runtime.query.sparql(
115
+ "SELECT ?s WHERE { ds:big ds:summary ?s }",
116
+ );
117
+ const value =
118
+ result.type === "select"
119
+ ? String(
120
+ (result.bindings.at(0) as { s?: string } | undefined)?.s ?? "",
121
+ )
122
+ : "";
123
+ expect(value.length).toBe(longText.length);
124
+ } finally {
125
+ await fixture?.dispose();
126
+ }
127
+ });
128
+ },
129
+ );
@@ -0,0 +1,179 @@
1
+ /**
2
+ * E2 (AV-231, Backlog E) — the MCP lifecycle journey over a LONG-LIVED,
3
+ * in-process server.
4
+ *
5
+ * Fresh-runtime unit tests structurally cannot catch the MCP memoization bugs:
6
+ * they build a new runtime per assertion, so a store/config memo that outlives a
7
+ * mutation is never exercised. The real MCP server boots ONE runtime for its
8
+ * whole lifetime and shares it across every tool call — the exact condition
9
+ * under which a cold-store rejection could be memoized forever, or a stale config
10
+ * layer could survive a `config_set`. This file drives that server the way an
11
+ * agent does, over a single connection:
12
+ *
13
+ * - COLD read → STORE_UNAVAILABLE → `sources_update` (confirm) → the SAME
14
+ * server's retry read SUCCEEDS. Guards C1: a rejected boot must NOT be
15
+ * memoized, and a real mutation must invalidate the shared store so the next
16
+ * read re-boots against the freshly built pack.
17
+ * - `config_set` → a subsequent `config_show` on the same server sees the new
18
+ * value. Guards C2: the mutation must drop the config memo the store boot
19
+ * depends on.
20
+ * - The discovery sequence resolves from a COLD store (the storeless
21
+ * `capabilities` catalog boots with no pack), then browse → inspect works once
22
+ * the store is warm.
23
+ *
24
+ * Uses the in-process `projectMcp` harness (SDK in-memory transport) — no stdio,
25
+ * no network — over the vendored default pack.
26
+ */
27
+
28
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
29
+ import { tmpdir } from "node:os";
30
+ import { join } from "node:path";
31
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
32
+ import { capabilities } from "../../capabilities/index.js";
33
+ import { readActivePack } from "../../kernel/runtime/paths.js";
34
+ import { DEFAULT_PACK_TTL } from "../fixtures/graph/defaultPack.js";
35
+ import { type McpHarness, projectMcp } from "../helpers/projectMcp.js";
36
+
37
+ /** The transient roots one test creates; removed in `afterEach`. */
38
+ let roots: string[] = [];
39
+ const tempDir = (prefix: string): string => {
40
+ const dir = mkdtempSync(join(tmpdir(), prefix));
41
+ roots.push(dir);
42
+ return dir;
43
+ };
44
+
45
+ /**
46
+ * A COLD project directory: a `pragma.config.ts` pointing at a real file
47
+ * package, but nothing built yet — so the store boots cold until `sources_update`.
48
+ */
49
+ function coldProject(ttl: string): string {
50
+ const pkg = tempDir("e2-pkg-");
51
+ mkdirSync(join(pkg, "definitions"), { recursive: true });
52
+ writeFileSync(join(pkg, "definitions", "pack.ttl"), ttl);
53
+ const cwd = tempDir("e2-proj-");
54
+ writeFileSync(
55
+ join(cwd, "pragma.config.ts"),
56
+ `export default { packs: [{ name: "default", source: "file://${pkg}" }] };\n`,
57
+ );
58
+ return cwd;
59
+ }
60
+
61
+ let mcp: McpHarness | undefined;
62
+ beforeEach(() => {
63
+ roots = [];
64
+ });
65
+ afterEach(async () => {
66
+ await mcp?.cleanup();
67
+ mcp = undefined;
68
+ for (const dir of roots) rmSync(dir, { recursive: true, force: true });
69
+ });
70
+
71
+ describe("MCP lifecycle — cold store recovers after sources_update (E2, C1)", () => {
72
+ it("a cold read fails STORE_UNAVAILABLE with the sources_update recovery, then the retry succeeds", async () => {
73
+ const cwd = coldProject(DEFAULT_PACK_TTL);
74
+ mcp = await projectMcp(capabilities, cwd);
75
+
76
+ // 1) COLD: the store has no pack — a store-backed read must fail cleanly,
77
+ // pointing the agent at the recovery TOOL (not just a CLI string).
78
+ const cold = await mcp.callTool("block_list");
79
+ expect(cold.ok).toBe(false);
80
+ const error = cold.error as {
81
+ code: string;
82
+ recovery?: { mcp?: { tool?: string } };
83
+ };
84
+ expect(error.code).toBe("STORE_UNAVAILABLE");
85
+ expect(error.recovery?.mcp?.tool).toBe("sources_update");
86
+ expect(readActivePack(cwd)).toBeUndefined();
87
+
88
+ // 2) The agent runs the recovery tool for real (confirm) — it builds the pack.
89
+ const update = await mcp.callTool("sources_update", { confirm: true });
90
+ expect(update.ok).toBe(true);
91
+ // A real execution, not a plan-only preview.
92
+ expect(update.meta).not.toMatchObject({ planOnly: true });
93
+ expect((update.data as { contentHash: string }).contentHash).toMatch(
94
+ /^[0-9a-f]{64}$/,
95
+ );
96
+ expect(readActivePack(cwd)).toMatch(/^[0-9a-f]{64}$/);
97
+
98
+ // 3) RETRY on the SAME server: the shared store was invalidated by the
99
+ // mutation, so the memoized cold rejection is gone and the read re-boots
100
+ // against the new pack. This is the guarantee a fresh-runtime test cannot
101
+ // make.
102
+ const warm = await mcp.callTool("block_list");
103
+ expect(warm.ok).toBe(true);
104
+ const names = (warm.data as { name: string }[])
105
+ .map((row) => row.name)
106
+ .sort();
107
+ // The whole pack — `block_list` is the declared, unfiltered list since
108
+ // L-OPEN-9, so no channel hides Beta Badge and no tier join hides the
109
+ // untiered Orphan Widget.
110
+ expect(names).toEqual(["Beta Badge", "Button", "Card", "Orphan Widget"]);
111
+ });
112
+ });
113
+
114
+ describe("MCP lifecycle — config_set is visible to the next read (E2, C2)", () => {
115
+ it("config_set updates the layer a subsequent config_show reads on the same server", async () => {
116
+ const cwd = tempDir("e2-cfg-");
117
+ mcp = await projectMcp(capabilities, cwd);
118
+
119
+ // Before: the default channel, from the default (unset) origin.
120
+ const before = await mcp.callTool("config_show");
121
+ const beforeData = before.data as {
122
+ config: { channel: string };
123
+ origins: { channel: string };
124
+ };
125
+ expect(beforeData.config.channel).toBe("normal");
126
+ expect(beforeData.origins.channel).toBe("default");
127
+
128
+ // Mutate: set the channel for real (confirm).
129
+ const set = await mcp.callTool("config_set", {
130
+ key: "channel",
131
+ value: "experimental",
132
+ confirm: true,
133
+ });
134
+ expect(set.ok).toBe(true);
135
+
136
+ // After: the SAME long-lived server's read reflects the write — proving the
137
+ // mutation dropped the config memo the boot depends on (not a stale layer).
138
+ const after = await mcp.callTool("config_show");
139
+ const afterData = after.data as {
140
+ config: { channel: string };
141
+ origins: { channel: string };
142
+ };
143
+ expect(afterData.config.channel).toBe("experimental");
144
+ // The write landed in the global layer, so the origin flips accordingly.
145
+ expect(afterData.origins.channel).toBe("global");
146
+ });
147
+ });
148
+
149
+ describe("MCP lifecycle — discovery from a cold store (E2)", () => {
150
+ it("the capabilities catalog resolves cold, then browse -> inspect works once warm", async () => {
151
+ const cwd = coldProject(DEFAULT_PACK_TTL);
152
+ mcp = await projectMcp(capabilities, cwd);
153
+
154
+ // Discovery is storeless: the capabilities catalog answers even with a cold
155
+ // store, so an agent can orient before the pack exists.
156
+ const catalog = await mcp.callTool("capabilities");
157
+ expect(catalog.ok).toBe(true);
158
+ const catalogData = catalog.data as {
159
+ discovery_sequence: unknown;
160
+ tools: unknown;
161
+ };
162
+ expect(catalogData.discovery_sequence).toBeDefined();
163
+ expect(catalogData.tools).toBeDefined();
164
+
165
+ // Warm the store, then follow a browse -> inspect discovery step end to end.
166
+ await mcp.callTool("sources_update", { confirm: true });
167
+ const list = await mcp.callTool("block_list");
168
+ expect(list.ok).toBe(true);
169
+ const first = (list.data as { name: string }[]).at(0);
170
+ expect(first?.name).toBeDefined();
171
+
172
+ const lookup = await mcp.callTool("block_lookup", {
173
+ name: [first?.name ?? ""],
174
+ });
175
+ expect(lookup.ok).toBe(true);
176
+ const results = (lookup.data as { results: { name: string }[] }).results;
177
+ expect(results.at(0)?.name).toBe(first?.name);
178
+ });
179
+ });
@@ -0,0 +1,188 @@
1
+ /**
2
+ * The package tier, end to end: a noun the binary never shipped.
3
+ *
4
+ * A package that ships `stories/*.json` and is declared as a `file:` pack
5
+ * contributes a working command to a project that declares it — resolved by
6
+ * `sources update`, hashed into the pack, written into the pack directory, read
7
+ * back storelessly at dispatch, and answered from the fixture graph. Proven both
8
+ * in-process and through the COMPILED binary in a real subprocess.
9
+ *
10
+ * The same package also ships a malformed story and a schema-invalid one. Those
11
+ * must be DROPPED, not fatal: package stories reach dispatch before the command
12
+ * tree exists, so a throw there fails every command — including `sources update`
13
+ * and `doctor`, the only two that can recover from it. Both of those are run
14
+ * here after the bad stories are in the pack.
15
+ */
16
+
17
+ import { readFileSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ import { runTask } from "@canonical/task/node";
20
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
21
+ import { runChecks } from "../../capabilities/doctor/runChecks.js";
22
+ import { capabilities } from "../../capabilities/index.js";
23
+ import { buildUpdateTask } from "../../capabilities/sources/runUpdate.js";
24
+ import { loadEffectiveModules } from "../../kernel/packs/collect.js";
25
+ import { packDir, readActivePack } from "../../kernel/runtime/paths.js";
26
+ import type { VerbSpec } from "../../kernel/spec/types.js";
27
+ import {
28
+ bootFixtureRuntime,
29
+ type FixtureGraph,
30
+ } from "../helpers/fixtureGraph.js";
31
+ import { runCli } from "../helpers/runCli.js";
32
+
33
+ const TTL = `
34
+ @prefix ex: <https://example.org/kitchen#> .
35
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
36
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
37
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
38
+
39
+ ex:Recipe a owl:Class .
40
+ ex:name a owl:DatatypeProperty ; rdfs:domain ex:Recipe ; rdfs:range xsd:string .
41
+
42
+ ex:soup a ex:Recipe ; ex:name "Soup" .
43
+ ex:stew a ex:Recipe ; ex:name "Stew" .
44
+ `;
45
+
46
+ /** A valid read story — the noun the binary never shipped. */
47
+ const RECIPE_STORY = JSON.stringify({
48
+ noun: "recipe",
49
+ description: "List recipes.",
50
+ colophon: "Recipes are modelled as ex:Recipe individuals.",
51
+ list: {
52
+ query:
53
+ "SELECT ?uri ?name WHERE { ?uri a ex:Recipe ; ex:name ?name } ORDER BY ?name",
54
+ columns: [{ field: "uri" }, { field: "name" }],
55
+ },
56
+ });
57
+
58
+ /** Schema-invalid: a well-formed JSON document the pack grammar rejects. */
59
+ const INVALID_STORY = JSON.stringify({ noun: "Bad Noun", list: {} });
60
+
61
+ let graph: FixtureGraph;
62
+ beforeAll(async () => {
63
+ graph = await bootFixtureRuntime({
64
+ ttl: TTL,
65
+ stories: {
66
+ "recipe.json": RECIPE_STORY,
67
+ // Not JSON at all.
68
+ "broken.json": "{ this is not json",
69
+ "invalid.json": INVALID_STORY,
70
+ },
71
+ });
72
+ }, 60_000);
73
+ afterAll(async () => {
74
+ await graph.dispose();
75
+ });
76
+
77
+ describe("a package's stories reach the pack", () => {
78
+ it("sources update carries all three story files into the pack, verbatim", () => {
79
+ const hash = readActivePack(graph.cwd);
80
+ expect(hash).toBeDefined();
81
+ const carried = JSON.parse(
82
+ readFileSync(join(packDir(hash as string), "stories.json"), "utf-8"),
83
+ ) as { source: string; content: string }[];
84
+ expect(carried.map((record) => record.source)).toEqual([
85
+ "fixture/stories/broken.json",
86
+ "fixture/stories/invalid.json",
87
+ "fixture/stories/recipe.json",
88
+ ]);
89
+ expect(
90
+ carried.find((r) => r.source === "fixture/stories/recipe.json")?.content,
91
+ ).toBe(RECIPE_STORY);
92
+ });
93
+ });
94
+
95
+ describe("a package-declared noun answers reads (in-process)", () => {
96
+ it("recipe list returns rows from the package's own graph", async () => {
97
+ const { modules } = await loadEffectiveModules(capabilities, graph.cwd);
98
+ const recipe = modules.find((module) => module.name === "recipe");
99
+ expect(recipe?.story).toBe(true);
100
+ expect(recipe?.colophon).toBe(
101
+ "Recipes are modelled as ex:Recipe individuals.",
102
+ );
103
+ const list = recipe?.verbs.find(
104
+ (verb) => verb.path.join(" ") === "recipe list",
105
+ ) as VerbSpec;
106
+ const rows = (await list.run({}, graph.runtime)) as { name: string }[];
107
+ expect(rows.map((row) => row.name)).toEqual(["Soup", "Stew"]);
108
+ });
109
+
110
+ it("names the unusable stories without failing the load", async () => {
111
+ const { problems } = await loadEffectiveModules(capabilities, graph.cwd);
112
+ expect(problems.map((problem) => problem.source).sort()).toEqual([
113
+ "fixture/stories/broken.json",
114
+ "fixture/stories/invalid.json",
115
+ ]);
116
+ // Each says WHY, not just that something was wrong.
117
+ const invalid = problems.find((p) => p.source.endsWith("invalid.json"));
118
+ expect(invalid?.message).toMatch(/Invalid story/);
119
+ });
120
+ });
121
+
122
+ describe("the two recoveries still run with bad stories in the pack", () => {
123
+ it("sources update re-resolves and reports the carried story count", async () => {
124
+ const result = await runTask(await buildUpdateTask(graph.runtime));
125
+ expect(result.packs).toEqual([
126
+ {
127
+ name: "fixture",
128
+ resolved: expect.any(String),
129
+ sourceCount: 1,
130
+ storyCount: 3,
131
+ },
132
+ ]);
133
+ });
134
+
135
+ it("doctor passes `pack refs` and lists each ignored story as a failing item", async () => {
136
+ const data = await runChecks(graph.runtime);
137
+ const check = data.checks.find((entry) => entry.name === "pack refs");
138
+ // The pack DOES answer reads, so the check passes…
139
+ expect(check?.status).toBe("pass");
140
+ expect(check?.detail).toContain("2 stories ignored");
141
+ // …with the unusable stories named underneath it.
142
+ expect(check?.items?.map((item) => item.label).sort()).toEqual([
143
+ "fixture/stories/broken.json",
144
+ "fixture/stories/invalid.json",
145
+ ]);
146
+ for (const item of check?.items ?? []) expect(item.status).toBe("fail");
147
+ });
148
+ });
149
+
150
+ describe("the compiled binary answers the package's noun (subprocess)", () => {
151
+ it("pragma recipe list --format json returns the package's rows", () => {
152
+ const result = runCli(["recipe", "list", "--format", "json"], {
153
+ cwd: graph.cwd,
154
+ });
155
+ expect(result.exitCode).toBe(0);
156
+ const rows = (JSON.parse(result.stdout) as { data: { name: string }[] })
157
+ .data;
158
+ expect(rows.map((row) => row.name)).toEqual(["Soup", "Stew"]);
159
+ // The bad stories are named on stderr, never on stdout — `--format json`
160
+ // stays machine-readable.
161
+ expect(result.stderr).toContain("fixture/stories/broken.json");
162
+ expect(result.stderr).toContain("fixture/stories/invalid.json");
163
+ });
164
+
165
+ it("pragma capabilities lists the package's tools — the surface agents read", () => {
166
+ // The MCP server registers its tools from the effective modules, so a
167
+ // catalog built from the STATIC registry would omit exactly the nouns
168
+ // `tools/list` advertises. `config show` points agents here for the verbs a
169
+ // story produces; that pointer has to be true.
170
+ const result = runCli(["capabilities", "--format", "json"], {
171
+ cwd: graph.cwd,
172
+ });
173
+ expect(result.exitCode).toBe(0);
174
+ const { data } = JSON.parse(result.stdout) as {
175
+ data: { tools: { name: string }[] };
176
+ };
177
+ expect(data.tools.map((tool) => tool.name)).toContain("recipe_list");
178
+ });
179
+
180
+ it("pragma --help does NOT list recipe — the documented fast-path limit", () => {
181
+ // `--help` reads the STATIC capability set (no config, no pack read), which
182
+ // is what keeps its budget. A package-declared noun is dispatch-only.
183
+ const help = runCli(["--help"], { cwd: graph.cwd });
184
+ expect(help.exitCode).toBe(0);
185
+ expect(help.stdout).not.toContain("recipe");
186
+ // …and the command it does not advertise still runs (asserted above).
187
+ });
188
+ });
@@ -61,9 +61,10 @@ export const liveVerbs: readonly LiveVerb[] = buildLiveVerbs();
61
61
  /**
62
62
  * Read nouns' `lookup` verbs, MCP-exposed, store-backed — restricted to the
63
63
  * PACK-lookup contract (a variadic `<name...>` positional), which the B-tier
64
- * list→lookup→batch suites drive uniformly. Bespoke single-name lookups
65
- * (`tier_lookup`, `prompt_lookup`) have their own tests, so they are excluded
66
- * here structurally (by param shape, not by naming the noun — R2).
64
+ * list→lookup→batch suites drive uniformly. The one bespoke single-name lookup
65
+ * left (`prompt_lookup`) has its own tests, so it is excluded here structurally
66
+ * (by param shape, not by naming the noun — R2). `tier_lookup` joined the pack
67
+ * contract in L-OPEN-9, so it is swept here like every other declared lookup.
67
68
  */
68
69
  export const lookupVerbs: readonly LiveVerb[] = liveVerbs.filter(
69
70
  (v) =>
@@ -21,6 +21,8 @@
21
21
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
22
22
  import { capabilities } from "../../capabilities/index.js";
23
23
  import { readPackIndex } from "../../kernel/completion/entitySource.js";
24
+ import type { PackIndex } from "../../kernel/runtime/graphpack/types.js";
25
+ import { resolveSources } from "../../kernel/runtime/resolveSources.js";
24
26
  import {
25
27
  ALL_VISIBLE_CONFIG,
26
28
  CANONICAL_TTL,
@@ -41,6 +43,8 @@ let mcp: Awaited<ReturnType<typeof projectMcp>>;
41
43
  let firstNameByNoun: Map<string, string>;
42
44
  /** The registered tool list, fetched once (annotations are call-invariant). */
43
45
  let registeredTools: Awaited<ReturnType<typeof mcp.listTools>>;
46
+ /** The index of the pack the fixture's boot decision names. */
47
+ let activeIndex: PackIndex | undefined;
44
48
 
45
49
  beforeAll(async () => {
46
50
  fixture = await bootFixtureRuntime({
@@ -49,6 +53,9 @@ beforeAll(async () => {
49
53
  });
50
54
  mcp = await projectMcp(capabilities, fixture.cwd);
51
55
  registeredTools = await mcp.listTools();
56
+ activeIndex = readPackIndex(
57
+ resolveSources(await fixture.runtime.loadConfig(), fixture.cwd),
58
+ );
52
59
 
53
60
  firstNameByNoun = new Map();
54
61
  for (const v of listVerbs) {
@@ -81,18 +88,16 @@ function representativeValue(
81
88
  param: LiveVerb["spec"]["params"][number],
82
89
  ): unknown {
83
90
  if (param.name === "prefix") {
84
- const prefix = Object.keys(readPackIndex(fixture.cwd)?.prefixes ?? {}).find(
91
+ const prefix = Object.keys(activeIndex?.prefixes ?? {}).find(
85
92
  (p) => p !== "owl" && p !== "rdfs" && p !== "rdf" && p !== "xsd",
86
93
  );
87
94
  return prefix ?? "ds";
88
95
  }
89
96
  if (param.name === "uri") {
90
- const entity = readPackIndex(fixture.cwd)?.entities.find(
91
- (e) => e.box === "abox",
92
- );
97
+ const entity = activeIndex?.entities.find((e) => e.box === "abox");
93
98
  return entity?.prefixed ?? entity?.name ?? "";
94
99
  }
95
- // An enum positional (e.g. `config channel <name>`, `config set <key>`) must
100
+ // An enum positional (e.g. `config set <key>`) must
96
101
  // get a real member — a placeholder would fail the tool's zod schema at the
97
102
  // SDK layer, before the handler, yielding a protocol error rather than a
98
103
  // well-formed envelope.