@canonical/pragma-cli 0.33.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 (250) hide show
  1. package/README.md +7 -7
  2. package/dist/pragma +0 -0
  3. package/package.json +18 -15
  4. package/pragma.conf.ts +664 -0
  5. package/src/bin.ts +13 -4
  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.verb.ts +31 -13
  9. package/src/capabilities/capabilities/catalog.ts +18 -10
  10. package/src/capabilities/capabilities/hints.ts +13 -19
  11. package/src/capabilities/colophon/collectColophon.ts +34 -19
  12. package/src/capabilities/colophon/colophon.verb.ts +8 -4
  13. package/src/capabilities/colophon/types.ts +16 -7
  14. package/src/capabilities/config/collectConfigShow.test.ts +57 -0
  15. package/src/capabilities/config/collectConfigShow.ts +41 -4
  16. package/src/capabilities/config/fields.ts +0 -27
  17. package/src/capabilities/config/set.verb.ts +7 -3
  18. package/src/capabilities/config/show.render.test.ts +77 -5
  19. package/src/capabilities/config/show.render.ts +7 -12
  20. package/src/capabilities/config/show.verb.ts +4 -3
  21. package/src/capabilities/config/types.ts +8 -0
  22. package/src/capabilities/create/byteEquality.test.ts +78 -100
  23. package/src/capabilities/create/compiledCreate.subprocess.test.ts +52 -15
  24. package/src/capabilities/create/constants.ts +75 -0
  25. package/src/capabilities/create/create.mcp.test.ts +2 -2
  26. package/src/capabilities/create/create.test.ts +162 -15
  27. package/src/capabilities/create/create.verb.ts +140 -69
  28. package/src/capabilities/create/index.ts +2 -10
  29. package/src/capabilities/create/pickGenerator.ts +34 -28
  30. package/src/capabilities/create/templates.embedded.generated.ts +4 -29
  31. package/src/capabilities/create/types.ts +10 -6
  32. package/src/capabilities/{standard/parity.test.ts → distribution.parity.test.ts} +24 -14
  33. package/src/capabilities/distribution.test.ts +120 -0
  34. package/src/capabilities/distribution.ts +59 -0
  35. package/src/capabilities/doctor/checks/checkConfigFile.ts +6 -5
  36. package/src/capabilities/doctor/checks/checkKeStore.ts +2 -2
  37. package/src/capabilities/doctor/checks/checkMcpConfigured.ts +7 -6
  38. package/src/capabilities/doctor/checks/checkPackageRefs.ts +79 -45
  39. package/src/capabilities/doctor/checks/checkPragmaVersion.ts +3 -2
  40. package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +61 -11
  41. package/src/capabilities/doctor/checks/checkShellCompletions.ts +63 -23
  42. package/src/capabilities/doctor/checks/checkSkillsSymlinked.ts +3 -2
  43. package/src/capabilities/doctor/doctor.render.test.ts +6 -6
  44. package/src/capabilities/doctor/doctor.render.ts +2 -1
  45. package/src/capabilities/doctor/doctor.test.ts +37 -8
  46. package/src/capabilities/doctor/doctor.verb.ts +6 -2
  47. package/src/capabilities/doctor/runChecks.ts +5 -5
  48. package/src/capabilities/doctor/types.ts +1 -1
  49. package/src/capabilities/emptyRecovery.test.ts +32 -15
  50. package/src/capabilities/graph/inspect.verb.ts +3 -2
  51. package/src/capabilities/graph/query.verb.ts +3 -2
  52. package/src/capabilities/graph/runQuery.ts +8 -5
  53. package/src/capabilities/index.ts +22 -18
  54. package/src/capabilities/info/collectInfo.ts +10 -6
  55. package/src/capabilities/info/info.render.ts +3 -2
  56. package/src/capabilities/info/info.verb.ts +3 -2
  57. package/src/capabilities/lazy.test.ts +93 -2
  58. package/src/capabilities/ontology/verbs.ts +21 -21
  59. package/src/capabilities/prompt/prompt.test.ts +12 -6
  60. package/src/capabilities/prompt/verbs.ts +24 -11
  61. package/src/capabilities/resources/provider.ts +30 -11
  62. package/src/capabilities/resources/resources.test.ts +85 -29
  63. package/src/capabilities/setup/operations/setupGenerator.ts +16 -9
  64. package/src/capabilities/setup/operations/setupMcp.ts +2 -2
  65. package/src/capabilities/setup/operations/setupSkills.ts +3 -3
  66. package/src/capabilities/setup/setup.verb.ts +12 -5
  67. package/src/capabilities/setup/shell.ts +29 -3
  68. package/src/capabilities/skill/discover.ts +11 -5
  69. package/src/capabilities/skill/render.ts +4 -5
  70. package/src/capabilities/skill/verbs.ts +7 -7
  71. package/src/capabilities/sources/collectStatus.ts +55 -50
  72. package/src/capabilities/sources/index.ts +1 -1
  73. package/src/capabilities/sources/installSkills.ts +2 -2
  74. package/src/capabilities/sources/runUpdate.ts +113 -122
  75. package/src/capabilities/sources/sources.test.ts +111 -117
  76. package/src/capabilities/sources/status.render.test.ts +53 -24
  77. package/src/capabilities/sources/status.render.ts +24 -29
  78. package/src/capabilities/sources/status.verb.ts +15 -8
  79. package/src/capabilities/sources/types.ts +18 -25
  80. package/src/capabilities/sources/update.render.ts +9 -2
  81. package/src/capabilities/sources/update.verb.ts +12 -23
  82. package/src/capabilities/surface.test.ts +33 -10
  83. package/src/capabilities/tier.parity.test.ts +144 -0
  84. package/src/capabilities/upgrade/upgrade.verb.ts +7 -3
  85. package/src/constants.test.ts +8 -6
  86. package/src/constants.ts +47 -21
  87. package/src/identity.test.ts +333 -0
  88. package/src/index.ts +2 -2
  89. package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +5 -2
  90. package/src/kernel/completion/emitScripts.test.ts +1 -1
  91. package/src/kernel/completion/emitScripts.ts +4 -2
  92. package/src/kernel/completion/entitySource.test.ts +185 -41
  93. package/src/kernel/completion/entitySource.ts +117 -107
  94. package/src/kernel/completion/model.test.ts +1 -1
  95. package/src/kernel/completion/model.ts +11 -0
  96. package/src/kernel/completion/safety.test.ts +82 -36
  97. package/src/kernel/completion/shellDrive.test.ts +592 -0
  98. package/src/kernel/completion/templates/fish.ts +28 -6
  99. package/src/kernel/completion/types.ts +3 -3
  100. package/src/kernel/config/__snapshots__/firstRun.test.ts.snap +1 -1
  101. package/src/kernel/config/defaults.test.ts +85 -0
  102. package/src/kernel/config/defaults.ts +12 -23
  103. package/src/kernel/config/evaluateProjectConfig.ts +2 -3
  104. package/src/kernel/config/findProjectConfig.ts +4 -3
  105. package/src/kernel/config/firstRun.ts +12 -5
  106. package/src/kernel/config/globalConfig.ts +2 -2
  107. package/src/kernel/config/index.ts +4 -5
  108. package/src/kernel/config/paths.ts +17 -17
  109. package/src/kernel/config/readConfig.test.ts +242 -6
  110. package/src/kernel/config/readConfig.ts +8 -8
  111. package/src/kernel/config/schema.test.ts +50 -0
  112. package/src/kernel/config/schema.ts +97 -10
  113. package/src/kernel/config/types.ts +73 -19
  114. package/src/kernel/copy.test.ts +453 -0
  115. package/src/kernel/error/PragmaError.test.ts +1 -1
  116. package/src/kernel/error/constants.ts +3 -2
  117. package/src/kernel/error/index.ts +1 -1
  118. package/src/kernel/error/recovery.test.ts +22 -45
  119. package/src/kernel/error/recovery.ts +22 -27
  120. package/src/kernel/error/types.ts +5 -4
  121. package/src/kernel/packs/collect.test.ts +195 -11
  122. package/src/kernel/packs/collect.ts +213 -58
  123. package/src/kernel/packs/compile.test.ts +163 -7
  124. package/src/kernel/packs/compile.ts +15 -12
  125. package/src/kernel/packs/disclosure.test.ts +5 -2
  126. package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +11 -10
  127. package/src/kernel/packs/graphqlSource.test.ts +6 -3
  128. package/src/kernel/packs/iri.ts +5 -7
  129. package/src/kernel/packs/renderPack.ts +5 -3
  130. package/src/kernel/packs/resolveEntity.ts +21 -17
  131. package/src/kernel/packs/runBodies.ts +15 -14
  132. package/src/kernel/packs/schema.ts +96 -10
  133. package/src/kernel/packs/sparql/runSelect.test.ts +104 -10
  134. package/src/kernel/packs/sparql/runSelect.ts +73 -34
  135. package/src/kernel/packs/toolDescription.test.ts +10 -2
  136. package/src/kernel/packs/types.ts +39 -4
  137. package/src/kernel/packs/uniqueness.ts +4 -4
  138. package/src/kernel/project/cli/buildProgram.test.ts +9 -4
  139. package/src/kernel/project/cli/dispatch.test.ts +67 -0
  140. package/src/kernel/project/cli/dispatch.ts +34 -12
  141. package/src/kernel/project/cli/helpFormat.ts +1 -1
  142. package/src/kernel/project/cli/rootHelp.ts +105 -105
  143. package/src/kernel/project/mcp/buildServer.ts +11 -6
  144. package/src/kernel/project/mcp/instructions.ts +11 -6
  145. package/src/kernel/project/mcp/parity.test.ts +39 -1
  146. package/src/kernel/project/mcp/prompts/prompts.test.ts +120 -5
  147. package/src/kernel/project/mcp/prompts/provider.ts +22 -8
  148. package/src/kernel/project/mcp/prompts/source.ts +68 -67
  149. package/src/kernel/project/mcp/registerVerb.ts +25 -6
  150. package/src/kernel/project/mcp/serve.ts +11 -3
  151. package/src/kernel/project/mcp/toolDescriptions.test.ts +6 -2
  152. package/src/kernel/render/disclosure.ts +4 -2
  153. package/src/kernel/render/prefixes.test.ts +36 -0
  154. package/src/kernel/render/prefixes.ts +40 -13
  155. package/src/kernel/render/renderers.test.ts +1 -1
  156. package/src/kernel/runtime/graphpack/build.ts +29 -3
  157. package/src/kernel/runtime/graphpack/buildIndex.test.ts +46 -0
  158. package/src/kernel/runtime/graphpack/buildIndex.ts +52 -17
  159. package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +5 -5
  160. package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +3 -3
  161. package/src/kernel/runtime/graphpack/embedded/pack.stories.generated.ts +3 -0
  162. package/src/kernel/runtime/graphpack/embedded.ts +37 -11
  163. package/src/kernel/runtime/graphpack/graphpack.test.ts +205 -5
  164. package/src/kernel/runtime/graphpack/hash.ts +3 -2
  165. package/src/kernel/runtime/graphpack/index.ts +1 -1
  166. package/src/kernel/runtime/graphpack/manifest.ts +114 -12
  167. package/src/kernel/runtime/graphpack/read.ts +4 -9
  168. package/src/kernel/runtime/graphpack/schemas.ts +74 -0
  169. package/src/kernel/runtime/graphpack/stories.ts +84 -0
  170. package/src/kernel/runtime/graphpack/types.ts +61 -48
  171. package/src/kernel/runtime/paths.ts +64 -15
  172. package/src/kernel/runtime/probe.test.ts +8 -4
  173. package/src/kernel/runtime/readEntity.ts +6 -5
  174. package/src/kernel/runtime/refs/gitOps.ts +1 -19
  175. package/src/kernel/runtime/refs/parseRef.ts +7 -7
  176. package/src/kernel/runtime/refs/resolve.ts +78 -62
  177. package/src/kernel/runtime/resolveSources.test.ts +63 -24
  178. package/src/kernel/runtime/resolveSources.ts +36 -25
  179. package/src/kernel/runtime/storeReadiness.ts +9 -8
  180. package/src/kernel/runtime/storeless.test.ts +1 -1
  181. package/src/kernel/runtime/wasmEmbed.test.ts +22 -19
  182. package/src/kernel/spec/emitReference.test.ts +3 -3
  183. package/src/kernel/spec/emitReference.ts +144 -11
  184. package/src/kernel/spec/emitSurface.ts +9 -5
  185. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  186. package/src/kernel/spec/surfaceConformance.ts +8 -0
  187. package/src/kernel/spec/types.ts +29 -9
  188. package/src/kernel/spec/validate.ts +2 -1
  189. package/src/kernel/vocabulary.test.ts +164 -0
  190. package/src/kernel/vocabulary.ts +146 -0
  191. package/src/testing/PARITY_GAPS.ts +8 -2
  192. package/src/testing/behavioral/README.md +3 -3
  193. package/src/testing/behavioral/completion.test.ts +19 -6
  194. package/src/testing/behavioral/errorMatrix.mcp.test.ts +4 -3
  195. package/src/testing/behavioral/errorMatrix.storeless.test.ts +1 -1
  196. package/src/testing/behavioral/exitCodes.e2e.test.ts +6 -5
  197. package/src/testing/behavioral/harness.foundation.test.ts +14 -5
  198. package/src/testing/behavioral/journeys.cli.test.ts +116 -75
  199. package/src/testing/behavioral/journeys.defaultPack.test.ts +82 -176
  200. package/src/testing/behavioral/journeys.firstInstall.test.ts +167 -0
  201. package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +9 -6
  202. package/src/testing/behavioral/journeys.packageStories.test.ts +188 -0
  203. package/src/testing/behavioral/liveReadSurface.ts +4 -3
  204. package/src/testing/behavioral/mcpSurface.test.ts +9 -4
  205. package/src/testing/behavioral/staleCommands.test.ts +10 -0
  206. package/src/testing/eval/__snapshots__/eval.test.ts.snap +7 -7
  207. package/src/testing/eval/cases/assembly.ts +13 -6
  208. package/src/testing/eval/cases/effectful.ts +25 -13
  209. package/src/testing/eval/cases/readNouns.ts +22 -20
  210. package/src/testing/eval/cases/stable.ts +5 -1
  211. package/src/testing/eval/eval.test.ts +7 -1
  212. package/src/testing/fixtures/fixtureCapability.ts +45 -1
  213. package/src/testing/fixtures/graph/canonical.ts +6 -3
  214. package/src/testing/fixtures/graph/defaultPack.ts +6 -5
  215. package/src/testing/fixtures/graph/owlExport.ts +7 -5
  216. package/src/testing/helpers/fixtureGraph.ts +33 -16
  217. package/src/testing/helpers/packRuntime.ts +2 -3
  218. package/src/testing/helpers/projectCli.ts +2 -1
  219. package/src/testing/helpers/projectMcp.ts +6 -0
  220. package/src/testing/perf/budgets.test.ts +4 -0
  221. package/src/testing/perf/budgets.ts +22 -7
  222. package/src/testing/perf/globalSetup.ts +121 -6
  223. package/src/types.ts +2 -2
  224. package/src/capabilities/block/blockList.test.ts +0 -79
  225. package/src/capabilities/block/blockList.verb.ts +0 -163
  226. package/src/capabilities/block/index.ts +0 -25
  227. package/src/capabilities/block/pack.ts +0 -166
  228. package/src/capabilities/block/parity.test.ts +0 -187
  229. package/src/capabilities/block/tierChain.ts +0 -78
  230. package/src/capabilities/colophon/pragmaColophon.ts +0 -66
  231. package/src/capabilities/modifier/index.ts +0 -15
  232. package/src/capabilities/modifier/pack.ts +0 -64
  233. package/src/capabilities/standard/index.ts +0 -18
  234. package/src/capabilities/standard/pack.ts +0 -128
  235. package/src/capabilities/tier/index.ts +0 -19
  236. package/src/capabilities/tier/lookup.render.test.ts +0 -72
  237. package/src/capabilities/tier/lookup.render.ts +0 -59
  238. package/src/capabilities/tier/lookup.test.ts +0 -83
  239. package/src/capabilities/tier/lookup.verb.ts +0 -44
  240. package/src/capabilities/tier/pack.ts +0 -28
  241. package/src/capabilities/tier/runLookup.ts +0 -87
  242. package/src/capabilities/token/addConfig.render.ts +0 -27
  243. package/src/capabilities/token/addConfig.test.ts +0 -62
  244. package/src/capabilities/token/addConfig.verb.ts +0 -40
  245. package/src/capabilities/token/index.ts +0 -19
  246. package/src/capabilities/token/pack.ts +0 -58
  247. package/src/capabilities/token/runAddConfig.ts +0 -95
  248. package/src/kernel/completion/bashDrive.test.ts +0 -73
  249. package/src/kernel/runtime/graphpack/embedded/sample.ttl +0 -39
  250. package/src/kernel/runtime/lock.ts +0 -90
@@ -5,9 +5,9 @@
5
5
  * single-typed 6-entity fixture, never the PRODUCT against a cold, real,
6
6
  * multi-typed pack. This file drives the REAL path end to end over the vendored
7
7
  * {@link DEFAULT_PACK_TTL} default pack — `sources update` (resolve → build →
8
- * lock, via `bootFixtureRuntime`) → boot → `graph query` → `block list` /
8
+ * point, via `bootFixtureRuntime`) → boot → `graph query` → `block list` /
9
9
  * `ontology list` (populated AND empty) → the error paths (malformed `.ttl`,
10
- * corrupt `schema.json`, a missing pinned commit).
10
+ * a torn `schema.json`, a corrupt `schema.json`).
11
11
  *
12
12
  * It also pins, in-process, the real-data shapes the clean fixture masked — the
13
13
  * `entityTotal` double-count (A1), the untiered block that `block list` drops
@@ -17,7 +17,6 @@
17
17
  * use" becomes "CI catches it".
18
18
  */
19
19
 
20
- import { execFileSync } from "node:child_process";
21
20
  import {
22
21
  mkdirSync,
23
22
  mkdtempSync,
@@ -27,31 +26,26 @@ import {
27
26
  } from "node:fs";
28
27
  import { tmpdir } from "node:os";
29
28
  import { join } from "node:path";
30
- import { runTask } from "@canonical/task/node";
31
29
  import { afterAll, describe, expect, it } from "vitest";
32
- import { blockModule } from "../../capabilities/block/index.js";
30
+ import { storyModules } from "../../capabilities/distribution.js";
33
31
  import { graphQueryVerb } from "../../capabilities/graph/query.verb.js";
34
32
  import {
35
33
  ontologyListVerb,
36
34
  ontologyShowVerb,
37
35
  } from "../../capabilities/ontology/verbs.js";
38
- import { buildUpdateTask } from "../../capabilities/sources/runUpdate.js";
39
36
  import { updateVerb } from "../../capabilities/sources/update.verb.js";
40
- import { VERSION } from "../../constants.js";
41
37
  import {
42
38
  entityTotal,
43
39
  readPackIndex,
44
40
  } from "../../kernel/completion/entitySource.js";
45
- import type { ConfigLayers, PackageEntry } from "../../kernel/config/types.js";
46
41
  import { PragmaError } from "../../kernel/error/PragmaError.js";
47
42
  import { executeVerb } from "../../kernel/project/cli/dispatch.js";
48
43
  import { bootRuntime } from "../../kernel/runtime/boot.js";
49
- import { createQueryFacade } from "../../kernel/runtime/facade.js";
44
+ import type { PackIndex } from "../../kernel/runtime/graphpack/types.js";
50
45
  import { SCHEMA_FILE } from "../../kernel/runtime/graphpack/types.js";
51
- import { readLock } from "../../kernel/runtime/lock.js";
52
- import { packDir } from "../../kernel/runtime/paths.js";
53
- import { createLazyStore } from "../../kernel/runtime/store.js";
54
- import type { GlobalFlags, PragmaRuntime } from "../../kernel/runtime/types.js";
46
+ import { packDir, readActivePack } from "../../kernel/runtime/paths.js";
47
+ import { resolveSources } from "../../kernel/runtime/resolveSources.js";
48
+ import type { GlobalFlags } from "../../kernel/runtime/types.js";
55
49
  import type { VerbSpec } from "../../kernel/spec/types.js";
56
50
  import {
57
51
  DEFAULT_PACK_ALL_VISIBLE_CONFIG,
@@ -76,6 +70,11 @@ const JSON_FLAGS: GlobalFlags = {
76
70
  };
77
71
  const NO_MUTATION = { dryRun: false, undo: false, yes: false };
78
72
 
73
+ const blockModule = storyModules.get("block");
74
+ if (!blockModule) {
75
+ throw new Error('pragma.conf.ts declares no story for "block"');
76
+ }
77
+
79
78
  const blockListVerb = blockModule.verbs.find(
80
79
  (verb) => verb.path.join(" ") === "block list",
81
80
  ) as VerbSpec;
@@ -99,6 +98,15 @@ afterAll(async () => {
99
98
  await Promise.all(fixtures.map((fixture) => fixture.dispose()));
100
99
  });
101
100
 
101
+ /** The index of the pack a fixture's boot decision names — what `info` reads. */
102
+ async function activeIndex(
103
+ fixture: FixtureGraph,
104
+ ): Promise<PackIndex | undefined> {
105
+ return readPackIndex(
106
+ resolveSources(await fixture.runtime.loadConfig(), fixture.cwd),
107
+ );
108
+ }
109
+
102
110
  /** The `--format json` envelope a read verb renders. */
103
111
  interface Envelope {
104
112
  readonly ok: boolean;
@@ -122,21 +130,16 @@ async function readVerb(
122
130
  }
123
131
 
124
132
  /** The sorted `name` column of a `block list` invocation. */
125
- async function blockListNames(
126
- cwd: string,
127
- allTiers: boolean,
128
- ): Promise<string[]> {
129
- const envelope = await readVerb(blockListVerb, { allTiers }, cwd);
133
+ async function blockListNames(cwd: string): Promise<string[]> {
134
+ const envelope = await readVerb(blockListVerb, {}, cwd);
130
135
  const rows = envelope.data as { name: string }[];
131
136
  return rows.map((row) => row.name).sort();
132
137
  }
133
138
 
134
139
  describe("default-pack journey — sources update, build, boot (E1)", () => {
135
- it("bootFixtureRuntime runs the real resolve/build/lock and writes a content-addressed lock", async () => {
140
+ it("bootFixtureRuntime runs the real resolve/build and points the project at the pack", async () => {
136
141
  const fixture = await boot(DEFAULT_PACK_TTL, DEFAULT_PACK_CONFIG);
137
- const lock = readLock(fixture.cwd);
138
- expect(lock?.contentHash).toMatch(/^[0-9a-f]{64}$/);
139
- expect(lock?.packs).toHaveLength(1);
142
+ expect(readActivePack(fixture.cwd)).toMatch(/^[0-9a-f]{64}$/);
140
143
  // The booted store answers a read against the freshly built pack.
141
144
  const ask = await fixture.runtime.query.sparql(
142
145
  "ASK { ds:button a ds:Component }",
@@ -145,17 +148,17 @@ describe("default-pack journey — sources update, build, boot (E1)", () => {
145
148
  });
146
149
 
147
150
  it("drives the literal `sources update` verb over a cold project, then boots a read", async () => {
148
- // A cold file-package project with NO lock yet — the real CLI command path.
151
+ // A cold file-package project with nothing built yet — the real CLI path.
149
152
  const pkg = mkdtempSync(join(tmpdir(), "e1-pkg-"));
150
153
  mkdirSync(join(pkg, "definitions"), { recursive: true });
151
154
  writeFileSync(join(pkg, "definitions", "pack.ttl"), DEFAULT_PACK_TTL);
152
155
  const cwd = mkdtempSync(join(tmpdir(), "e1-proj-"));
153
156
  writeFileSync(
154
157
  join(cwd, "pragma.config.ts"),
155
- `export default { packages: [{ name: "default", source: "file://${pkg}" }] };\n`,
158
+ `export default { packs: [{ name: "default", source: "file://${pkg}" }] };\n`,
156
159
  );
157
160
  try {
158
- // `sources update` is a real mutation — resolve, build, write the lock.
161
+ // `sources update` is a real mutation — resolve, build, point the project.
159
162
  const outcome = await executeVerb(
160
163
  updateVerb,
161
164
  {},
@@ -163,18 +166,18 @@ describe("default-pack journey — sources update, build, boot (E1)", () => {
163
166
  bootRuntime(JSON_FLAGS, cwd),
164
167
  );
165
168
  expect(outcome.exitCode).toBe(0);
166
- expect(readLock(cwd)?.contentHash).toMatch(/^[0-9a-f]{64}$/);
167
- // A SEPARATE, fresh runtime boots the just-built pack from the lock. No
168
- // channel is configured, so the default `normal` channel hides the
169
- // beta-gated Beta Badge. Assert the two global blocks are present while
170
- // staying TOLERANT of the untiered Orphan Widget: whether it appears under
171
- // --all-tiers is the A2 behavior, asserted in exactly one place below (the
172
- // A2 regression guard). Filtering Orphan out keeps this test focused on the
173
- // two global blocks, and still pins that Beta Badge is hidden.
174
- const globalBlocks = (await blockListNames(cwd, true)).filter(
175
- (name) => name !== UNTIERED_BLOCK_NAME,
176
- );
177
- expect(globalBlocks).toEqual(["Button", "Card"]);
169
+ expect(readActivePack(cwd)).toMatch(/^[0-9a-f]{64}$/);
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).
175
+ expect(await blockListNames(cwd)).toEqual([
176
+ "Beta Badge",
177
+ "Button",
178
+ "Card",
179
+ UNTIERED_BLOCK_NAME,
180
+ ]);
178
181
  } finally {
179
182
  rmSync(pkg, { recursive: true, force: true });
180
183
  rmSync(cwd, { recursive: true, force: true });
@@ -233,52 +236,52 @@ describe("default-pack journey — graph query over the real pack (E1)", () => {
233
236
  });
234
237
 
235
238
  describe("default-pack journey — block list, populated and empty (E1)", () => {
236
- it("normal channel lists the two global blocks, hiding the beta-gated one", async () => {
239
+ /** The pack's WHOLE block set — what the declared list returns, always. */
240
+ const ALL_FOUR = ["Beta Badge", "Button", "Card", UNTIERED_BLOCK_NAME];
241
+
242
+ it("the `normal` channel lists the beta-gated block too (L-OPEN-9)", async () => {
243
+ // This used to answer ["Button", "Card"]: the hand-written query hid Beta
244
+ // Badge behind the channel and Orphan Widget behind the required tier join.
245
+ // The declared list has neither filter, so the signed-off consequence is
246
+ // pinned right where the old scoping was.
237
247
  const fixture = await boot(DEFAULT_PACK_TTL, DEFAULT_PACK_CONFIG);
238
- expect(await blockListNames(fixture.cwd, false)).toEqual([
239
- "Button",
240
- "Card",
241
- ]);
248
+ expect(await blockListNames(fixture.cwd)).toEqual(ALL_FOUR);
242
249
  });
243
250
 
244
- it("prerelease channel reveals the beta-gated block across the tier chain", async () => {
251
+ it("the `prerelease` channel returns the SAME rows — channel scopes nothing", async () => {
245
252
  const fixture = await boot(
246
253
  DEFAULT_PACK_TTL,
247
254
  DEFAULT_PACK_ALL_VISIBLE_CONFIG,
248
255
  );
249
- // Same inputs as the A2 regression guard below (ALL_VISIBLE + allTiers).
250
- // Assert the tiered blocks (prerelease reveals the beta-gated Beta Badge)
251
- // while staying TOLERANT of the untiered Orphan Widget — its --all-tiers
252
- // visibility is the A2 truth, asserted in exactly one place below. Filtering
253
- // Orphan out keeps this green pre-A2 (absent) and post-A2 (present), so it
254
- // never contradicts that hand-off.
255
- const tieredBlocks = (await blockListNames(fixture.cwd, true)).filter(
256
- (name) => name !== UNTIERED_BLOCK_NAME,
257
- );
258
- expect(tieredBlocks).toEqual(["Beta Badge", "Button", "Card"]);
256
+ expect(await blockListNames(fixture.cwd)).toEqual(ALL_FOUR);
259
257
  });
260
258
 
261
- it("the apps tier inherits its parent chain (global) plus its own block", async () => {
259
+ it("a configured tier returns the SAME rows — there is no chain to walk", async () => {
262
260
  const fixture = await boot(DEFAULT_PACK_TTL, {
263
261
  tier: "apps",
264
262
  channel: "prerelease",
265
263
  });
266
- // resolveTierChain("apps") = [global, apps]: Button/Card (global, inherited)
267
- // + Beta Badge (apps, own).
268
- expect(await blockListNames(fixture.cwd, false)).toEqual([
269
- "Beta Badge",
270
- "Button",
271
- "Card",
272
- ]);
264
+ // The hand-written verb expanded `apps` to [global, apps] and listed the
265
+ // union; the declared list never reads the tier at all, so `apps` and the
266
+ // unset default are indistinguishable.
267
+ expect(await blockListNames(fixture.cwd)).toEqual(ALL_FOUR);
273
268
  });
274
269
 
275
- it("an ontology-only pack with no tiered blocks lists empty — a calm exit 0", async () => {
270
+ it("an ontology-only pack with no blocks lists empty — a calm exit 0", async () => {
271
+ // D10-A, MEASURED at the dispatch seam the binary uses: the declared list
272
+ // body (`makeListRun`) returns `[]` and never throws on emptiness, so the
273
+ // process still exits 0 — no exit-code delta against the hand-written verb.
274
+ // Only the empty MESSAGE moved (the story's `emptyRecovery` replaces the
275
+ // tier/channel wording and the `--all-tiers` hint).
276
276
  const fixture = await boot(NO_BLOCKS_TTL);
277
- const envelope = await readVerb(
277
+ const outcome = await executeVerb(
278
278
  blockListVerb,
279
- { allTiers: true },
280
- fixture.cwd,
279
+ {},
280
+ NO_MUTATION,
281
+ bootRuntime(JSON_FLAGS, fixture.cwd),
281
282
  );
283
+ expect(outcome.exitCode).toBe(0);
284
+ const envelope = JSON.parse(outcome.stdout as string) as Envelope;
282
285
  expect(envelope.ok).toBe(true);
283
286
  expect(envelope.data).toEqual([]);
284
287
  });
@@ -347,15 +350,15 @@ describe("default-pack journey — error paths (E1)", () => {
347
350
 
348
351
  it("a torn (emptied) schema.json boots to STORE_UNAVAILABLE with the update recovery", async () => {
349
352
  const fixture = await boot(DEFAULT_PACK_TTL, DEFAULT_PACK_CONFIG);
350
- const lock = readLock(fixture.cwd);
353
+ const active = readActivePack(fixture.cwd) ?? "";
351
354
  // Simulate a torn/evicted extraction: truncate schema.json in the pack cache.
352
- truncateSync(join(packDir(lock?.contentHash ?? ""), SCHEMA_FILE), 0);
355
+ truncateSync(join(packDir(active), SCHEMA_FILE), 0);
353
356
  // A brand-new runtime (fresh store memo) boots against the now-incomplete pack.
354
357
  let caught: unknown;
355
358
  try {
356
359
  await executeVerb(
357
360
  blockListVerb,
358
- { allTiers: true },
361
+ {},
359
362
  NO_MUTATION,
360
363
  bootRuntime(JSON_FLAGS, fixture.cwd),
361
364
  );
@@ -366,69 +369,12 @@ describe("default-pack journey — error paths (E1)", () => {
366
369
  expect((caught as PragmaError).code).toBe("STORE_UNAVAILABLE");
367
370
  expect((caught as PragmaError).recovery?.cli).toBe("pragma sources update");
368
371
  });
369
-
370
- it("a --frozen update whose pinned commit is gone fails with a named CONFIG_ERROR", async () => {
371
- const runGit = (repo: string, args: string[]): string =>
372
- execFileSync("git", args, {
373
- cwd: repo,
374
- stdio: "pipe",
375
- env: {
376
- ...process.env,
377
- GIT_AUTHOR_NAME: "t",
378
- GIT_AUTHOR_EMAIL: "t@t",
379
- GIT_COMMITTER_NAME: "t",
380
- GIT_COMMITTER_EMAIL: "t@t",
381
- },
382
- }).toString();
383
-
384
- const repo = mkdtempSync(join(tmpdir(), "e1-repo-"));
385
- const cwd = mkdtempSync(join(tmpdir(), "e1-frozen-"));
386
- try {
387
- runGit(repo, ["init", "-b", "main"]);
388
- mkdirSync(join(repo, "definitions"), { recursive: true });
389
- writeFileSync(join(repo, "definitions", "pack.ttl"), NO_BLOCKS_TTL);
390
- runGit(repo, ["add", "-A"]);
391
- runGit(repo, ["commit", "-m", "one"]);
392
-
393
- const runtime = gitRuntimeFor(cwd, [
394
- { name: "pkg-git", source: `git+file://${repo}#main` },
395
- ]);
396
- // First update locks the current commit SHA.
397
- await runTask(await buildUpdateTask(runtime, false));
398
- expect(readLock(cwd)?.packs.at(0)?.resolved).toMatch(/^[0-9a-f]{40}$/);
399
-
400
- // Orphan the pinned commit: amend main, then expire the reflog + prune so
401
- // the previously-locked SHA is unreachable.
402
- writeFileSync(
403
- join(repo, "definitions", "pack.ttl"),
404
- `${NO_BLOCKS_TTL}\nds:extra a owl:Class .\n`,
405
- );
406
- runGit(repo, ["add", "-A"]);
407
- runGit(repo, ["commit", "--amend", "-m", "amended"]);
408
- runGit(repo, ["reflog", "expire", "--expire=now", "--all"]);
409
- runGit(repo, ["gc", "--prune=now"]);
410
-
411
- // A --frozen update must re-resolve the pinned (now-missing) SHA and refuse.
412
- let caught: unknown;
413
- try {
414
- await runTask(await buildUpdateTask(runtime, true));
415
- } catch (error) {
416
- caught = error;
417
- }
418
- expect(caught).toBeInstanceOf(PragmaError);
419
- expect((caught as PragmaError).code).toBe("CONFIG_ERROR");
420
- expect((caught as PragmaError).message).toContain("pkg-git");
421
- } finally {
422
- rmSync(repo, { recursive: true, force: true });
423
- rmSync(cwd, { recursive: true, force: true });
424
- }
425
- });
426
372
  });
427
373
 
428
374
  describe("default-pack journey — real-data shapes the clean fixture masked (E1, hand-off to lanes A)", () => {
429
375
  it("every block is co-typed owl:NamedIndividual and indexed as an individual (abox)", async () => {
430
376
  const fixture = await boot(DEFAULT_PACK_TTL, DEFAULT_PACK_CONFIG);
431
- const index = readPackIndex(fixture.cwd);
377
+ const index = await activeIndex(fixture);
432
378
  // The vendored pack indexes 17 distinct entities (5 classes + 4 properties +
433
379
  // 2 tiers + 2 channels + 4 blocks).
434
380
  expect(index?.entities).toHaveLength(17);
@@ -460,7 +406,7 @@ describe("default-pack journey — real-data shapes the clean fixture masked (E1
460
406
  // whichever the store yields first (no `@en` preference). E3 (live oxigraph)
461
407
  // is where that ordering is confirmed against the real pack; here we only
462
408
  // assert the tag is stripped and the value is one of the two declared forms.
463
- const label = readPackIndex(fixture.cwd)?.entities.find(
409
+ const label = (await activeIndex(fixture))?.entities.find(
464
410
  (entity) => entity.name === "ds:button",
465
411
  )?.label;
466
412
  expect(label).not.toMatch(/@(en|fr)/);
@@ -472,7 +418,7 @@ describe("default-pack journey — real-data shapes the clean fixture masked (E1
472
418
 
473
419
  it("A1: info's entity total must not exceed the distinct entity count (owl:NamedIndividual double-count)", async () => {
474
420
  const fixture = await boot(DEFAULT_PACK_TTL, DEFAULT_PACK_CONFIG);
475
- const index = readPackIndex(fixture.cwd);
421
+ const index = await activeIndex(fixture);
476
422
  // `entityTotal` (the figure `info`/`doctor` report) once SUMMED per-type
477
423
  // instance counts, double-counting each entity per asserted rdf:type (each
478
424
  // block under owl:NamedIndividual AND its domain class; ds:tier/ds:release
@@ -484,18 +430,17 @@ describe("default-pack journey — real-data shapes the clean fixture masked (E1
484
430
  );
485
431
  });
486
432
 
487
- it("A2: an untiered block must still appear in `block list --all-tiers`", async () => {
433
+ it("A2: an untiered block must still appear in `block list`", async () => {
488
434
  const fixture = await boot(
489
435
  DEFAULT_PACK_TTL,
490
436
  DEFAULT_PACK_ALL_VISIBLE_CONFIG,
491
437
  );
492
438
  // `block list`'s SELECT once inner-joined `?c ds:tier ?t`, dropping a
493
439
  // ds:Component with no ds:tier even under --all-tiers (though `graph query`
494
- // found it, proven above). Lane A made the tier join OPTIONAL, so an
495
- // untiered block now appears under --all-tiers — guard it stays visible.
496
- expect(await blockListNames(fixture.cwd, true)).toContain(
497
- UNTIERED_BLOCK_NAME,
498
- );
440
+ // found it, proven above). Lane A made the tier join OPTIONAL; L-OPEN-9 then
441
+ // removed the filtering entirely, so the untiered block is visible in the
442
+ // ONE view that now exists — guard it stays that way.
443
+ expect(await blockListNames(fixture.cwd)).toContain(UNTIERED_BLOCK_NAME);
499
444
  });
500
445
 
501
446
  // KNOWN GAP / product follow-up (NOT a lane hand-off): a corrupt, NON-EMPTY
@@ -511,16 +456,13 @@ describe("default-pack journey — real-data shapes the clean fixture masked (E1
511
456
  // to expect a STORE_UNAVAILABLE PragmaError then.
512
457
  it("corrupt (non-empty, invalid) schema.json currently surfaces an UNCLASSIFIED error (known gap)", async () => {
513
458
  const fixture = await boot(DEFAULT_PACK_TTL, DEFAULT_PACK_CONFIG);
514
- const lock = readLock(fixture.cwd);
515
- writeFileSync(
516
- join(packDir(lock?.contentHash ?? ""), SCHEMA_FILE),
517
- "{ not valid json ]",
518
- );
459
+ const active = readActivePack(fixture.cwd) ?? "";
460
+ writeFileSync(join(packDir(active), SCHEMA_FILE), "{ not valid json ]");
519
461
  let caught: unknown;
520
462
  try {
521
463
  await executeVerb(
522
464
  blockListVerb,
523
- { allTiers: true },
465
+ {},
524
466
  NO_MUTATION,
525
467
  bootRuntime(JSON_FLAGS, fixture.cwd),
526
468
  );
@@ -533,39 +475,3 @@ describe("default-pack journey — real-data shapes the clean fixture masked (E1
533
475
  expect(caught).not.toBeInstanceOf(PragmaError);
534
476
  });
535
477
  });
536
-
537
- /**
538
- * A synthetic runtime whose config is a fixed package list — drives the
539
- * git-source build Task without a config file (mirrors sources.test.ts).
540
- *
541
- * @param cwd - The project directory the lock is written to.
542
- * @param packages - The configured package entries.
543
- * @returns A runtime wired to a lazy store over `cwd`.
544
- * @note Impure — constructs a store handle over the working directory.
545
- */
546
- function gitRuntimeFor(cwd: string, packages: PackageEntry[]): PragmaRuntime {
547
- const layers: ConfigLayers = {
548
- config: { channel: "normal", packages },
549
- origins: {
550
- tier: "default",
551
- channel: "default",
552
- detail: "default",
553
- packages: "project",
554
- stories: "default",
555
- prefixes: "default",
556
- prompts: "default",
557
- },
558
- global: { path: "/nonexistent", exists: false },
559
- project: { exists: false },
560
- };
561
- const loadConfig = async (): Promise<ConfigLayers> => layers;
562
- const store = createLazyStore({ cwd, loadConfig });
563
- return {
564
- cwd,
565
- version: VERSION,
566
- globalFlags: JSON_FLAGS,
567
- loadConfig,
568
- store,
569
- query: createQueryFacade(store),
570
- };
571
- }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * The first-install journey — this PR's whole point, pinned.
3
+ *
4
+ * A machine with no cache, no pointer, no network and an empty cwd must answer
5
+ * real store-backed reads from the embedded pack, and must say honestly where
6
+ * those answers come from. Equally: a project that declared its OWN packs and
7
+ * never built them must NOT be handed the distribution's graph — it is a
8
+ * different graph, and quietly serving it is the failure the boot decision
9
+ * exists to prevent.
10
+ *
11
+ * Assertions pin MEMBERSHIP, never counts: the row counts move whenever the
12
+ * upstream design system does, but a design system with no `Button`, or a tier
13
+ * hierarchy with no `Global`, is a change a human should be made to look at.
14
+ */
15
+
16
+ import { mkdtempSync, writeFileSync } from "node:fs";
17
+ import { tmpdir } from "node:os";
18
+ import { join } from "node:path";
19
+ import { describe, expect, it } from "vitest";
20
+ import { storyModules } from "../../capabilities/distribution.js";
21
+ import { checkPackageRefs } from "../../capabilities/doctor/checks/checkPackageRefs.js";
22
+ import { promptListVerb } from "../../capabilities/prompt/verbs.js";
23
+ import { collectStatus } from "../../capabilities/sources/collectStatus.js";
24
+ import { PragmaError } from "../../kernel/error/PragmaError.js";
25
+ import { executeVerb } from "../../kernel/project/cli/dispatch.js";
26
+ import { bootRuntime } from "../../kernel/runtime/boot.js";
27
+ import type { GlobalFlags } from "../../kernel/runtime/types.js";
28
+ import type { CapabilityModule, VerbSpec } from "../../kernel/spec/types.js";
29
+
30
+ const JSON_FLAGS: GlobalFlags = {
31
+ llm: false,
32
+ autoLlm: false,
33
+ format: "json",
34
+ verbose: false,
35
+ };
36
+ const NO_MUTATION = { dryRun: false, undo: false, yes: false };
37
+
38
+ const blockModule = storyModules.get("block");
39
+ if (!blockModule) {
40
+ throw new Error('pragma.conf.ts declares no story for "block"');
41
+ }
42
+ const standardModule = storyModules.get("standard");
43
+ if (!standardModule) {
44
+ throw new Error('pragma.conf.ts declares no story for "standard"');
45
+ }
46
+ const tierModule = storyModules.get("tier");
47
+ if (!tierModule) {
48
+ throw new Error('pragma.conf.ts declares no story for "tier"');
49
+ }
50
+ const tokenModule = storyModules.get("token");
51
+ if (!tokenModule) {
52
+ throw new Error('pragma.conf.ts declares no story for "token"');
53
+ }
54
+
55
+ const verbOf = (module: CapabilityModule, path: string): VerbSpec =>
56
+ module.verbs.find((verb) => verb.path.join(" ") === path) as VerbSpec;
57
+
58
+ /** A cwd with nothing in it — the cold-install shape (the XDG dirs are isolated). */
59
+ const emptyCwd = (): string => mkdtempSync(join(tmpdir(), "pragma-first-"));
60
+
61
+ /**
62
+ * A cwd whose `pragma.config.ts` declares its own packs and has never been
63
+ * built — `origins.packs` is "project", so the embedded pack must not answer.
64
+ */
65
+ function unbuiltProjectCwd(): string {
66
+ const cwd = mkdtempSync(join(tmpdir(), "pragma-unbuilt-"));
67
+ writeFileSync(
68
+ join(cwd, "pragma.config.ts"),
69
+ 'export default { packs: [{ name: "mine", source: "file:///pragma-never-built" }] };\n',
70
+ );
71
+ return cwd;
72
+ }
73
+
74
+ /** Run a read verb at a cwd and return the parsed `data` payload. */
75
+ async function readData(verb: VerbSpec, cwd: string): Promise<unknown> {
76
+ const outcome = await executeVerb(
77
+ verb,
78
+ {},
79
+ NO_MUTATION,
80
+ bootRuntime(JSON_FLAGS, cwd),
81
+ );
82
+ return (JSON.parse(outcome.stdout as string) as { data: unknown }).data;
83
+ }
84
+
85
+ describe("first install — an empty cwd answers real reads offline", () => {
86
+ it("block list resolves the design system's blocks", async () => {
87
+ const rows = (await readData(
88
+ verbOf(blockModule, "block list"),
89
+ emptyCwd(),
90
+ )) as { name: string }[];
91
+ expect(rows.map((row) => row.name)).toContain("Button");
92
+ });
93
+
94
+ it("standard list resolves the code standards", async () => {
95
+ const rows = (await readData(
96
+ verbOf(standardModule, "standard list"),
97
+ emptyCwd(),
98
+ )) as { uri: string; name: string }[];
99
+ expect(rows.length).toBeGreaterThan(0);
100
+ // Every row is a code standard from the standards pack's own namespace.
101
+ for (const row of rows) {
102
+ expect(row.uri).toContain("codestandards#");
103
+ }
104
+ });
105
+
106
+ it("tier list resolves the tier hierarchy", async () => {
107
+ const rows = (await readData(
108
+ verbOf(tierModule, "tier list"),
109
+ emptyCwd(),
110
+ )) as { name: string }[];
111
+ expect(rows.map((row) => row.name)).toContain("Global");
112
+ });
113
+ });
114
+
115
+ describe("first install — empty results are honest, not papered over", () => {
116
+ it("token list exits calmly with no rows (the graph carries no ds:Token)", async () => {
117
+ expect(
118
+ await readData(verbOf(tokenModule, "token list"), emptyCwd()),
119
+ ).toEqual([]);
120
+ });
121
+
122
+ it("prompt list exits calmly with no prompts (the graph carries no ds:Prompt)", async () => {
123
+ expect(await readData(promptListVerb as VerbSpec, emptyCwd())).toEqual({
124
+ prompts: [],
125
+ });
126
+ });
127
+ });
128
+
129
+ describe("first install — a project with its own unbuilt packs is refused", () => {
130
+ it("a read throws STORE_UNAVAILABLE naming `pragma sources update`", async () => {
131
+ let caught: unknown;
132
+ try {
133
+ await readData(verbOf(blockModule, "block list"), unbuiltProjectCwd());
134
+ } catch (error) {
135
+ caught = error;
136
+ }
137
+ expect(caught).toBeInstanceOf(PragmaError);
138
+ expect((caught as PragmaError).code).toBe("STORE_UNAVAILABLE");
139
+ expect((caught as PragmaError).recovery?.cli).toBe("pragma sources update");
140
+ });
141
+
142
+ it("doctor reports it as a failure, not a healthy install", async () => {
143
+ const result = await checkPackageRefs(
144
+ bootRuntime(JSON_FLAGS, unbuiltProjectCwd()),
145
+ );
146
+ expect(result.status).toBe("fail");
147
+ expect(result.detail).toContain("the store has not been built");
148
+ expect(result.remedy).toBe("pragma sources update");
149
+ });
150
+ });
151
+
152
+ describe("first install — the surfaces say where the answers come from", () => {
153
+ it("sources status reports the embedded snapshot with its provenance", async () => {
154
+ const status = await collectStatus(bootRuntime(JSON_FLAGS, emptyCwd()));
155
+ expect(status.store).toBe("embedded");
156
+ // The upstream revisions the snapshot was compiled from — never "up to date".
157
+ expect(status.sourceRef).toContain("@canonical/design-system@");
158
+ expect(status.entityCount).toBeGreaterThan(0);
159
+ });
160
+
161
+ it("doctor passes, naming the embedded snapshot rather than the config", async () => {
162
+ const result = await checkPackageRefs(bootRuntime(JSON_FLAGS, emptyCwd()));
163
+ expect(result.status).toBe("pass");
164
+ expect(result.detail).toContain("embedded snapshot @ ");
165
+ expect(result.detail).toContain("pragma sources update");
166
+ });
167
+ });
@@ -30,7 +30,7 @@ import { tmpdir } from "node:os";
30
30
  import { join } from "node:path";
31
31
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
32
32
  import { capabilities } from "../../capabilities/index.js";
33
- import { readLock } from "../../kernel/runtime/lock.js";
33
+ import { readActivePack } from "../../kernel/runtime/paths.js";
34
34
  import { DEFAULT_PACK_TTL } from "../fixtures/graph/defaultPack.js";
35
35
  import { type McpHarness, projectMcp } from "../helpers/projectMcp.js";
36
36
 
@@ -44,7 +44,7 @@ const tempDir = (prefix: string): string => {
44
44
 
45
45
  /**
46
46
  * A COLD project directory: a `pragma.config.ts` pointing at a real file
47
- * package, but NO lock yet — so the store boots cold until `sources_update`.
47
+ * package, but nothing built yet — so the store boots cold until `sources_update`.
48
48
  */
49
49
  function coldProject(ttl: string): string {
50
50
  const pkg = tempDir("e2-pkg-");
@@ -53,7 +53,7 @@ function coldProject(ttl: string): string {
53
53
  const cwd = tempDir("e2-proj-");
54
54
  writeFileSync(
55
55
  join(cwd, "pragma.config.ts"),
56
- `export default { packages: [{ name: "default", source: "file://${pkg}" }] };\n`,
56
+ `export default { packs: [{ name: "default", source: "file://${pkg}" }] };\n`,
57
57
  );
58
58
  return cwd;
59
59
  }
@@ -83,7 +83,7 @@ describe("MCP lifecycle — cold store recovers after sources_update (E2, C1)",
83
83
  };
84
84
  expect(error.code).toBe("STORE_UNAVAILABLE");
85
85
  expect(error.recovery?.mcp?.tool).toBe("sources_update");
86
- expect(readLock(cwd)).toBeUndefined();
86
+ expect(readActivePack(cwd)).toBeUndefined();
87
87
 
88
88
  // 2) The agent runs the recovery tool for real (confirm) — it builds the pack.
89
89
  const update = await mcp.callTool("sources_update", { confirm: true });
@@ -93,7 +93,7 @@ describe("MCP lifecycle — cold store recovers after sources_update (E2, C1)",
93
93
  expect((update.data as { contentHash: string }).contentHash).toMatch(
94
94
  /^[0-9a-f]{64}$/,
95
95
  );
96
- expect(readLock(cwd)?.contentHash).toMatch(/^[0-9a-f]{64}$/);
96
+ expect(readActivePack(cwd)).toMatch(/^[0-9a-f]{64}$/);
97
97
 
98
98
  // 3) RETRY on the SAME server: the shared store was invalidated by the
99
99
  // mutation, so the memoized cold rejection is gone and the read re-boots
@@ -104,7 +104,10 @@ describe("MCP lifecycle — cold store recovers after sources_update (E2, C1)",
104
104
  const names = (warm.data as { name: string }[])
105
105
  .map((row) => row.name)
106
106
  .sort();
107
- expect(names).toEqual(["Button", "Card"]);
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"]);
108
111
  });
109
112
  });
110
113