@cleocode/core 2026.4.11 → 2026.4.12

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 (169) hide show
  1. package/dist/codebase-map/analyzers/architecture.d.ts.map +1 -1
  2. package/dist/codebase-map/analyzers/architecture.js +0 -1
  3. package/dist/codebase-map/analyzers/architecture.js.map +1 -1
  4. package/dist/conduit/local-transport.d.ts +18 -8
  5. package/dist/conduit/local-transport.d.ts.map +1 -1
  6. package/dist/conduit/local-transport.js +23 -13
  7. package/dist/conduit/local-transport.js.map +1 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +0 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/errors.d.ts +19 -0
  12. package/dist/errors.d.ts.map +1 -1
  13. package/dist/errors.js +6 -0
  14. package/dist/errors.js.map +1 -1
  15. package/dist/index.js +175 -68950
  16. package/dist/index.js.map +1 -7
  17. package/dist/init.d.ts +1 -2
  18. package/dist/init.d.ts.map +1 -1
  19. package/dist/init.js +1 -2
  20. package/dist/init.js.map +1 -1
  21. package/dist/internal.d.ts +8 -3
  22. package/dist/internal.d.ts.map +1 -1
  23. package/dist/internal.js +13 -6
  24. package/dist/internal.js.map +1 -1
  25. package/dist/memory/learnings.d.ts +2 -2
  26. package/dist/memory/patterns.d.ts +6 -6
  27. package/dist/output.d.ts +32 -11
  28. package/dist/output.d.ts.map +1 -1
  29. package/dist/output.js +67 -67
  30. package/dist/output.js.map +1 -1
  31. package/dist/paths.js +80 -14
  32. package/dist/paths.js.map +1 -1
  33. package/dist/skills/dynamic-skill-generator.d.ts +0 -2
  34. package/dist/skills/dynamic-skill-generator.d.ts.map +1 -1
  35. package/dist/skills/dynamic-skill-generator.js.map +1 -1
  36. package/dist/store/agent-registry-accessor.d.ts +203 -12
  37. package/dist/store/agent-registry-accessor.d.ts.map +1 -1
  38. package/dist/store/agent-registry-accessor.js +618 -100
  39. package/dist/store/agent-registry-accessor.js.map +1 -1
  40. package/dist/store/api-key-kdf.d.ts +73 -0
  41. package/dist/store/api-key-kdf.d.ts.map +1 -0
  42. package/dist/store/api-key-kdf.js +84 -0
  43. package/dist/store/api-key-kdf.js.map +1 -0
  44. package/dist/store/cleanup-legacy.js +171 -0
  45. package/dist/store/cleanup-legacy.js.map +1 -0
  46. package/dist/store/conduit-sqlite.d.ts +184 -0
  47. package/dist/store/conduit-sqlite.d.ts.map +1 -0
  48. package/dist/store/conduit-sqlite.js +570 -0
  49. package/dist/store/conduit-sqlite.js.map +1 -0
  50. package/dist/store/global-salt.d.ts +78 -0
  51. package/dist/store/global-salt.d.ts.map +1 -0
  52. package/dist/store/global-salt.js +147 -0
  53. package/dist/store/global-salt.js.map +1 -0
  54. package/dist/store/migrate-signaldock-to-conduit.d.ts +81 -0
  55. package/dist/store/migrate-signaldock-to-conduit.d.ts.map +1 -0
  56. package/dist/store/migrate-signaldock-to-conduit.js +555 -0
  57. package/dist/store/migrate-signaldock-to-conduit.js.map +1 -0
  58. package/dist/store/nexus-sqlite.js +28 -3
  59. package/dist/store/nexus-sqlite.js.map +1 -1
  60. package/dist/store/signaldock-sqlite.d.ts +122 -19
  61. package/dist/store/signaldock-sqlite.d.ts.map +1 -1
  62. package/dist/store/signaldock-sqlite.js +401 -251
  63. package/dist/store/signaldock-sqlite.js.map +1 -1
  64. package/dist/store/sqlite-backup.js +122 -4
  65. package/dist/store/sqlite-backup.js.map +1 -1
  66. package/dist/system/backup.d.ts +0 -26
  67. package/dist/system/backup.d.ts.map +1 -1
  68. package/dist/system/runtime.d.ts +0 -2
  69. package/dist/system/runtime.d.ts.map +1 -1
  70. package/dist/system/runtime.js +3 -3
  71. package/dist/system/runtime.js.map +1 -1
  72. package/dist/tasks/add.d.ts +1 -1
  73. package/dist/tasks/add.d.ts.map +1 -1
  74. package/dist/tasks/add.js +98 -23
  75. package/dist/tasks/add.js.map +1 -1
  76. package/dist/tasks/complete.d.ts.map +1 -1
  77. package/dist/tasks/complete.js +4 -1
  78. package/dist/tasks/complete.js.map +1 -1
  79. package/dist/tasks/find.d.ts.map +1 -1
  80. package/dist/tasks/find.js +4 -1
  81. package/dist/tasks/find.js.map +1 -1
  82. package/dist/tasks/labels.d.ts.map +1 -1
  83. package/dist/tasks/labels.js +4 -1
  84. package/dist/tasks/labels.js.map +1 -1
  85. package/dist/tasks/relates.d.ts.map +1 -1
  86. package/dist/tasks/relates.js +16 -4
  87. package/dist/tasks/relates.js.map +1 -1
  88. package/dist/tasks/show.d.ts.map +1 -1
  89. package/dist/tasks/show.js +4 -1
  90. package/dist/tasks/show.js.map +1 -1
  91. package/dist/tasks/update.d.ts.map +1 -1
  92. package/dist/tasks/update.js +32 -6
  93. package/dist/tasks/update.js.map +1 -1
  94. package/dist/validation/engine.d.ts.map +1 -1
  95. package/dist/validation/engine.js +16 -4
  96. package/dist/validation/engine.js.map +1 -1
  97. package/dist/validation/param-utils.d.ts +5 -3
  98. package/dist/validation/param-utils.d.ts.map +1 -1
  99. package/dist/validation/param-utils.js +8 -6
  100. package/dist/validation/param-utils.js.map +1 -1
  101. package/dist/validation/protocols/_shared.d.ts.map +1 -1
  102. package/dist/validation/protocols/_shared.js +13 -6
  103. package/dist/validation/protocols/_shared.js.map +1 -1
  104. package/package.json +7 -7
  105. package/src/adapters/__tests__/manager.test.ts +0 -1
  106. package/src/codebase-map/analyzers/architecture.ts +0 -1
  107. package/src/conduit/__tests__/local-credential-flow.test.ts +20 -18
  108. package/src/conduit/__tests__/local-transport.test.ts +14 -12
  109. package/src/conduit/local-transport.ts +23 -13
  110. package/src/config.ts +0 -1
  111. package/src/errors.ts +24 -0
  112. package/src/hooks/handlers/__tests__/hook-automation-e2e.test.ts +2 -5
  113. package/src/init.ts +1 -2
  114. package/src/internal.ts +49 -2
  115. package/src/lifecycle/cant/lifecycle-rcasd.cant +133 -0
  116. package/src/memory/__tests__/engine-compat.test.ts +2 -2
  117. package/src/memory/__tests__/pipeline-manifest-sqlite.test.ts +4 -4
  118. package/src/observability/__tests__/index.test.ts +4 -4
  119. package/src/observability/__tests__/log-filter.test.ts +4 -4
  120. package/src/output.ts +73 -75
  121. package/src/sessions/__tests__/session-grade.integration.test.ts +1 -1
  122. package/src/sessions/__tests__/session-grade.test.ts +2 -2
  123. package/src/skills/__tests__/dynamic-skill-generator.test.ts +0 -2
  124. package/src/skills/dynamic-skill-generator.ts +0 -2
  125. package/src/store/__tests__/agent-registry-accessor.test.ts +807 -0
  126. package/src/store/__tests__/api-key-kdf.test.ts +113 -0
  127. package/src/store/__tests__/conduit-sqlite.test.ts +413 -0
  128. package/src/store/__tests__/global-salt.test.ts +195 -0
  129. package/src/store/__tests__/migrate-signaldock-to-conduit.test.ts +715 -0
  130. package/src/store/__tests__/signaldock-sqlite.test.ts +652 -0
  131. package/src/store/__tests__/sqlite-backup-global.test.ts +307 -3
  132. package/src/store/__tests__/sqlite-backup.test.ts +5 -1
  133. package/src/store/__tests__/t310-integration.test.ts +1150 -0
  134. package/src/store/agent-registry-accessor.ts +847 -140
  135. package/src/store/api-key-kdf.ts +104 -0
  136. package/src/store/conduit-sqlite.ts +655 -0
  137. package/src/store/global-salt.ts +175 -0
  138. package/src/store/migrate-signaldock-to-conduit.ts +669 -0
  139. package/src/store/signaldock-sqlite.ts +431 -254
  140. package/src/store/sqlite-backup.ts +185 -10
  141. package/src/system/backup.ts +2 -62
  142. package/src/system/runtime.ts +4 -6
  143. package/src/tasks/__tests__/error-hints.test.ts +256 -0
  144. package/src/tasks/add.ts +99 -9
  145. package/src/tasks/complete.ts +4 -1
  146. package/src/tasks/find.ts +4 -1
  147. package/src/tasks/labels.ts +4 -1
  148. package/src/tasks/relates.ts +16 -4
  149. package/src/tasks/show.ts +4 -1
  150. package/src/tasks/update.ts +32 -3
  151. package/src/validation/__tests__/error-hints.test.ts +97 -0
  152. package/src/validation/engine.ts +16 -1
  153. package/src/validation/param-utils.ts +10 -7
  154. package/src/validation/protocols/_shared.ts +14 -6
  155. package/src/validation/protocols/cant/architecture-decision.cant +80 -0
  156. package/src/validation/protocols/cant/artifact-publish.cant +95 -0
  157. package/src/validation/protocols/cant/consensus.cant +74 -0
  158. package/src/validation/protocols/cant/contribution.cant +82 -0
  159. package/src/validation/protocols/cant/decomposition.cant +92 -0
  160. package/src/validation/protocols/cant/implementation.cant +67 -0
  161. package/src/validation/protocols/cant/provenance.cant +88 -0
  162. package/src/validation/protocols/cant/release.cant +96 -0
  163. package/src/validation/protocols/cant/research.cant +66 -0
  164. package/src/validation/protocols/cant/specification.cant +67 -0
  165. package/src/validation/protocols/cant/testing.cant +88 -0
  166. package/src/validation/protocols/cant/validation.cant +65 -0
  167. package/src/validation/protocols/protocols-markdown/decomposition.md +0 -4
  168. package/templates/config.template.json +0 -1
  169. package/templates/global-config.template.json +0 -1
@@ -0,0 +1,652 @@
1
+ /**
2
+ * Unit tests for signaldock-sqlite.ts — global-tier refactor (T346, T310).
3
+ *
4
+ * All tests use a tmpdir override via vi.doMock('../../paths.js') so they
5
+ * NEVER touch the real $XDG_DATA_HOME/cleo/ directory.
6
+ *
7
+ * Coverage (AC from T346):
8
+ * - TC-020: getGlobalSignaldockDbPath returns path within getCleoHome()
9
+ * - TC-021: ensureGlobalSignaldockDb creates file with global schema on fresh install
10
+ * - TC-022: ensureGlobalSignaldockDb is idempotent
11
+ * - TC-023: agents table contains requires_reauth column
12
+ * - TC-024: All cloud-sync tables present (users, organization, accounts, sessions,
13
+ * verifications, claim_codes, org_agent_keys) with zero rows
14
+ * - TC-025: capabilities and skills tables present
15
+ * - TC-026: agent_capabilities and agent_skills junction tables present
16
+ * - TC-027: agent_connections table present
17
+ * Additional:
18
+ * - getSignaldockDbPath() (no args) returns global path (deprecated alias)
19
+ * - getSignaldockDbPath(cwd) THROWS migration error
20
+ * - ensureSignaldockDb() (no args) forwards to ensureGlobalSignaldockDb
21
+ * - ensureSignaldockDb(cwd) THROWS migration error
22
+ * - checkGlobalSignaldockDbHealth returns health report for existing DB
23
+ * - checkSignaldockDbHealth(cwd) THROWS migration error
24
+ * - getGlobalSignaldockNativeDb returns null before init, handle after ensureGlobalSignaldockDb
25
+ * - _resetGlobalSignaldockDb_TESTING_ONLY clears singleton
26
+ * - GLOBAL_SIGNALDOCK_DB_FILENAME constant value
27
+ * - GLOBAL_SIGNALDOCK_SCHEMA_VERSION constant value
28
+ * - SIGNALDOCK_SCHEMA_VERSION deprecated alias equals GLOBAL_SIGNALDOCK_SCHEMA_VERSION
29
+ *
30
+ * @task T346
31
+ * @epic T310
32
+ */
33
+
34
+ import { existsSync, mkdirSync, rmSync } from 'node:fs';
35
+ import { tmpdir } from 'node:os';
36
+ import { join } from 'node:path';
37
+ import { DatabaseSync } from 'node:sqlite';
38
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Helper: create an isolated tmp dir for each test
42
+ // ---------------------------------------------------------------------------
43
+
44
+ function makeTmpDir(suffix: string): string {
45
+ const dir = join(tmpdir(), `cleo-signaldock-test-${suffix}-${Date.now()}`);
46
+ mkdirSync(dir, { recursive: true });
47
+ return dir;
48
+ }
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // TC-020: getGlobalSignaldockDbPath returns path within getCleoHome()
52
+ // ---------------------------------------------------------------------------
53
+
54
+ describe('getGlobalSignaldockDbPath', () => {
55
+ beforeEach(() => {
56
+ vi.resetModules();
57
+ });
58
+ afterEach(() => {
59
+ vi.restoreAllMocks();
60
+ });
61
+
62
+ it('TC-020: returns a path that starts with the mocked getCleoHome() value', async () => {
63
+ const cleoHome = makeTmpDir('path-tc020');
64
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
65
+
66
+ const { getGlobalSignaldockDbPath } = await import('../signaldock-sqlite.js');
67
+ const result = getGlobalSignaldockDbPath();
68
+
69
+ expect(result.startsWith(cleoHome)).toBe(true);
70
+ expect(result).toBe(join(cleoHome, 'signaldock.db'));
71
+ });
72
+
73
+ it('returns path ending with signaldock.db', async () => {
74
+ const cleoHome = makeTmpDir('path-filename');
75
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
76
+
77
+ const { getGlobalSignaldockDbPath } = await import('../signaldock-sqlite.js');
78
+ const result = getGlobalSignaldockDbPath();
79
+
80
+ expect(result.endsWith('signaldock.db')).toBe(true);
81
+ });
82
+ });
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Deprecated alias: getSignaldockDbPath
86
+ // ---------------------------------------------------------------------------
87
+
88
+ describe('getSignaldockDbPath (deprecated alias)', () => {
89
+ beforeEach(() => {
90
+ vi.resetModules();
91
+ });
92
+
93
+ it('with no args returns the global path', async () => {
94
+ const cleoHome = makeTmpDir('deprecated-path-noargs');
95
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
96
+
97
+ const { getSignaldockDbPath, getGlobalSignaldockDbPath } = await import(
98
+ '../signaldock-sqlite.js'
99
+ );
100
+ expect(getSignaldockDbPath()).toBe(getGlobalSignaldockDbPath());
101
+ });
102
+
103
+ it('with a cwd argument THROWS a migration error', async () => {
104
+ const cleoHome = makeTmpDir('deprecated-path-cwd');
105
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
106
+
107
+ const { getSignaldockDbPath } = await import('../signaldock-sqlite.js');
108
+ expect(() => getSignaldockDbPath('/some/project')).toThrow('T310');
109
+ expect(() => getSignaldockDbPath('/some/project')).toThrow('conduit-sqlite.ts');
110
+ });
111
+ });
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // TC-021 + TC-022: ensureGlobalSignaldockDb creates DB and is idempotent
115
+ // ---------------------------------------------------------------------------
116
+
117
+ describe('ensureGlobalSignaldockDb', () => {
118
+ let cleoHome: string;
119
+
120
+ beforeEach(() => {
121
+ vi.resetModules();
122
+ cleoHome = makeTmpDir('ensure-global');
123
+ });
124
+
125
+ afterEach(() => {
126
+ vi.restoreAllMocks();
127
+ try {
128
+ rmSync(cleoHome, { recursive: true, force: true });
129
+ } catch {
130
+ // ignore cleanup errors
131
+ }
132
+ });
133
+
134
+ it('TC-021: creates the signaldock.db file on fresh install', async () => {
135
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
136
+
137
+ const {
138
+ ensureGlobalSignaldockDb,
139
+ getGlobalSignaldockDbPath,
140
+ _resetGlobalSignaldockDb_TESTING_ONLY,
141
+ } = await import('../signaldock-sqlite.js');
142
+
143
+ const result = await ensureGlobalSignaldockDb();
144
+ _resetGlobalSignaldockDb_TESTING_ONLY();
145
+
146
+ expect(result.action).toBe('created');
147
+ expect(result.path).toBe(getGlobalSignaldockDbPath());
148
+ expect(existsSync(result.path)).toBe(true);
149
+ });
150
+
151
+ it('TC-022: is idempotent — second call returns action="exists"', async () => {
152
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
153
+
154
+ const { ensureGlobalSignaldockDb, _resetGlobalSignaldockDb_TESTING_ONLY } = await import(
155
+ '../signaldock-sqlite.js'
156
+ );
157
+
158
+ const first = await ensureGlobalSignaldockDb();
159
+ _resetGlobalSignaldockDb_TESTING_ONLY();
160
+
161
+ const second = await ensureGlobalSignaldockDb();
162
+ _resetGlobalSignaldockDb_TESTING_ONLY();
163
+
164
+ expect(first.action).toBe('created');
165
+ expect(second.action).toBe('exists');
166
+ expect(second.path).toBe(first.path);
167
+ });
168
+
169
+ it('creates global cleo home directory if it does not exist', async () => {
170
+ const nestedHome = join(cleoHome, 'deep', 'nested');
171
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => nestedHome }));
172
+
173
+ const { ensureGlobalSignaldockDb, _resetGlobalSignaldockDb_TESTING_ONLY } = await import(
174
+ '../signaldock-sqlite.js'
175
+ );
176
+
177
+ await ensureGlobalSignaldockDb();
178
+ _resetGlobalSignaldockDb_TESTING_ONLY();
179
+
180
+ expect(existsSync(nestedHome)).toBe(true);
181
+ expect(existsSync(join(nestedHome, 'signaldock.db'))).toBe(true);
182
+ });
183
+ });
184
+
185
+ // ---------------------------------------------------------------------------
186
+ // TC-023: agents table has requires_reauth column
187
+ // ---------------------------------------------------------------------------
188
+
189
+ describe('agents table schema', () => {
190
+ let cleoHome: string;
191
+
192
+ beforeEach(() => {
193
+ vi.resetModules();
194
+ cleoHome = makeTmpDir('schema-agents');
195
+ });
196
+
197
+ afterEach(() => {
198
+ vi.restoreAllMocks();
199
+ try {
200
+ rmSync(cleoHome, { recursive: true, force: true });
201
+ } catch {
202
+ // ignore
203
+ }
204
+ });
205
+
206
+ it('TC-023: agents table contains requires_reauth column', async () => {
207
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
208
+
209
+ const { ensureGlobalSignaldockDb, _resetGlobalSignaldockDb_TESTING_ONLY } = await import(
210
+ '../signaldock-sqlite.js'
211
+ );
212
+ const { path: dbPath } = await ensureGlobalSignaldockDb();
213
+ _resetGlobalSignaldockDb_TESTING_ONLY();
214
+
215
+ const db = new DatabaseSync(dbPath);
216
+ try {
217
+ const cols = db.prepare('PRAGMA table_info(agents)').all() as Array<{
218
+ name: string;
219
+ type: string;
220
+ notnull: number;
221
+ dflt_value: string | null;
222
+ }>;
223
+ const colNames = cols.map((c) => c.name);
224
+ expect(colNames).toContain('requires_reauth');
225
+
226
+ const reauthCol = cols.find((c) => c.name === 'requires_reauth');
227
+ expect(reauthCol).toBeDefined();
228
+ expect(reauthCol?.notnull).toBe(1);
229
+ expect(reauthCol?.dflt_value).toBe('0');
230
+ } finally {
231
+ db.close();
232
+ }
233
+ });
234
+
235
+ it('agents table has is_active column from former migration 000003', async () => {
236
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
237
+
238
+ const { ensureGlobalSignaldockDb, _resetGlobalSignaldockDb_TESTING_ONLY } = await import(
239
+ '../signaldock-sqlite.js'
240
+ );
241
+ const { path: dbPath } = await ensureGlobalSignaldockDb();
242
+ _resetGlobalSignaldockDb_TESTING_ONLY();
243
+
244
+ const db = new DatabaseSync(dbPath);
245
+ try {
246
+ const cols = db.prepare('PRAGMA table_info(agents)').all() as Array<{ name: string }>;
247
+ const colNames = cols.map((c) => c.name);
248
+ expect(colNames).toContain('is_active');
249
+ expect(colNames).toContain('api_key_encrypted');
250
+ expect(colNames).toContain('api_base_url');
251
+ expect(colNames).toContain('transport_type');
252
+ expect(colNames).toContain('transport_config');
253
+ } finally {
254
+ db.close();
255
+ }
256
+ });
257
+ });
258
+
259
+ // ---------------------------------------------------------------------------
260
+ // TC-024: Cloud-sync tables present with zero rows
261
+ // ---------------------------------------------------------------------------
262
+
263
+ describe('cloud-sync tables', () => {
264
+ let cleoHome: string;
265
+
266
+ beforeEach(() => {
267
+ vi.resetModules();
268
+ cleoHome = makeTmpDir('cloud-sync');
269
+ });
270
+
271
+ afterEach(() => {
272
+ vi.restoreAllMocks();
273
+ try {
274
+ rmSync(cleoHome, { recursive: true, force: true });
275
+ } catch {
276
+ // ignore
277
+ }
278
+ });
279
+
280
+ it('TC-024: users, organization, accounts, sessions, verifications, claim_codes, org_agent_keys all present with zero rows', async () => {
281
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
282
+
283
+ const { ensureGlobalSignaldockDb, _resetGlobalSignaldockDb_TESTING_ONLY } = await import(
284
+ '../signaldock-sqlite.js'
285
+ );
286
+ const { path: dbPath } = await ensureGlobalSignaldockDb();
287
+ _resetGlobalSignaldockDb_TESTING_ONLY();
288
+
289
+ const db = new DatabaseSync(dbPath);
290
+ try {
291
+ const cloudTables = [
292
+ 'users',
293
+ 'organization',
294
+ 'accounts',
295
+ 'sessions',
296
+ 'verifications',
297
+ 'claim_codes',
298
+ 'org_agent_keys',
299
+ ];
300
+ for (const tbl of cloudTables) {
301
+ const row = db
302
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?")
303
+ .get(tbl) as { name: string } | undefined;
304
+ expect(row, `Expected table ${tbl} to exist`).toBeDefined();
305
+
306
+ const count = db.prepare(`SELECT COUNT(*) as n FROM ${tbl}`).get() as { n: number };
307
+ expect(count.n, `Expected table ${tbl} to have 0 rows on fresh install`).toBe(0);
308
+ }
309
+ } finally {
310
+ db.close();
311
+ }
312
+ });
313
+ });
314
+
315
+ // ---------------------------------------------------------------------------
316
+ // TC-025 + TC-026: capabilities, skills, junction tables present
317
+ // ---------------------------------------------------------------------------
318
+
319
+ describe('identity catalog tables', () => {
320
+ let cleoHome: string;
321
+
322
+ beforeEach(() => {
323
+ vi.resetModules();
324
+ cleoHome = makeTmpDir('catalog');
325
+ });
326
+
327
+ afterEach(() => {
328
+ vi.restoreAllMocks();
329
+ try {
330
+ rmSync(cleoHome, { recursive: true, force: true });
331
+ } catch {
332
+ // ignore
333
+ }
334
+ });
335
+
336
+ it('TC-025: capabilities and skills tables are present', async () => {
337
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
338
+
339
+ const { ensureGlobalSignaldockDb, _resetGlobalSignaldockDb_TESTING_ONLY } = await import(
340
+ '../signaldock-sqlite.js'
341
+ );
342
+ const { path: dbPath } = await ensureGlobalSignaldockDb();
343
+ _resetGlobalSignaldockDb_TESTING_ONLY();
344
+
345
+ const db = new DatabaseSync(dbPath);
346
+ try {
347
+ for (const tbl of ['capabilities', 'skills']) {
348
+ const row = db
349
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?")
350
+ .get(tbl) as { name: string } | undefined;
351
+ expect(row, `Expected table ${tbl} to exist`).toBeDefined();
352
+ }
353
+ } finally {
354
+ db.close();
355
+ }
356
+ });
357
+
358
+ it('TC-026: agent_capabilities and agent_skills junction tables are present', async () => {
359
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
360
+
361
+ const { ensureGlobalSignaldockDb, _resetGlobalSignaldockDb_TESTING_ONLY } = await import(
362
+ '../signaldock-sqlite.js'
363
+ );
364
+ const { path: dbPath } = await ensureGlobalSignaldockDb();
365
+ _resetGlobalSignaldockDb_TESTING_ONLY();
366
+
367
+ const db = new DatabaseSync(dbPath);
368
+ try {
369
+ for (const tbl of ['agent_capabilities', 'agent_skills']) {
370
+ const row = db
371
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?")
372
+ .get(tbl) as { name: string } | undefined;
373
+ expect(row, `Expected junction table ${tbl} to exist`).toBeDefined();
374
+ }
375
+ } finally {
376
+ db.close();
377
+ }
378
+ });
379
+
380
+ it('TC-027: agent_connections table is present with correct columns', async () => {
381
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
382
+
383
+ const { ensureGlobalSignaldockDb, _resetGlobalSignaldockDb_TESTING_ONLY } = await import(
384
+ '../signaldock-sqlite.js'
385
+ );
386
+ const { path: dbPath } = await ensureGlobalSignaldockDb();
387
+ _resetGlobalSignaldockDb_TESTING_ONLY();
388
+
389
+ const db = new DatabaseSync(dbPath);
390
+ try {
391
+ const row = db
392
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='agent_connections'")
393
+ .get() as { name: string } | undefined;
394
+ expect(row).toBeDefined();
395
+
396
+ const cols = db.prepare('PRAGMA table_info(agent_connections)').all() as Array<{
397
+ name: string;
398
+ }>;
399
+ const colNames = cols.map((c) => c.name);
400
+ expect(colNames).toContain('agent_id');
401
+ expect(colNames).toContain('transport_type');
402
+ expect(colNames).toContain('last_heartbeat');
403
+ } finally {
404
+ db.close();
405
+ }
406
+ });
407
+ });
408
+
409
+ // ---------------------------------------------------------------------------
410
+ // No project-local tables in global signaldock.db
411
+ // ---------------------------------------------------------------------------
412
+
413
+ describe('project-local tables absent from global signaldock.db', () => {
414
+ let cleoHome: string;
415
+
416
+ beforeEach(() => {
417
+ vi.resetModules();
418
+ cleoHome = makeTmpDir('no-project-tables');
419
+ });
420
+
421
+ afterEach(() => {
422
+ vi.restoreAllMocks();
423
+ try {
424
+ rmSync(cleoHome, { recursive: true, force: true });
425
+ } catch {
426
+ // ignore
427
+ }
428
+ });
429
+
430
+ it('conversations, messages, delivery_jobs, dead_letters tables are NOT present', async () => {
431
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
432
+
433
+ const { ensureGlobalSignaldockDb, _resetGlobalSignaldockDb_TESTING_ONLY } = await import(
434
+ '../signaldock-sqlite.js'
435
+ );
436
+ const { path: dbPath } = await ensureGlobalSignaldockDb();
437
+ _resetGlobalSignaldockDb_TESTING_ONLY();
438
+
439
+ const db = new DatabaseSync(dbPath);
440
+ try {
441
+ const projectTables = ['conversations', 'messages', 'delivery_jobs', 'dead_letters'];
442
+ for (const tbl of projectTables) {
443
+ const row = db
444
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?")
445
+ .get(tbl) as { name: string } | undefined;
446
+ expect(
447
+ row,
448
+ `Table ${tbl} should NOT exist in global signaldock.db (belongs in conduit.db)`,
449
+ ).toBeUndefined();
450
+ }
451
+ } finally {
452
+ db.close();
453
+ }
454
+ });
455
+ });
456
+
457
+ // ---------------------------------------------------------------------------
458
+ // checkGlobalSignaldockDbHealth
459
+ // ---------------------------------------------------------------------------
460
+
461
+ describe('checkGlobalSignaldockDbHealth', () => {
462
+ let cleoHome: string;
463
+
464
+ beforeEach(() => {
465
+ vi.resetModules();
466
+ cleoHome = makeTmpDir('health');
467
+ });
468
+
469
+ afterEach(() => {
470
+ vi.restoreAllMocks();
471
+ try {
472
+ rmSync(cleoHome, { recursive: true, force: true });
473
+ } catch {
474
+ // ignore
475
+ }
476
+ });
477
+
478
+ it('returns exists=false when DB does not exist', async () => {
479
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
480
+
481
+ const { checkGlobalSignaldockDbHealth } = await import('../signaldock-sqlite.js');
482
+ const health = await checkGlobalSignaldockDbHealth();
483
+
484
+ expect(health).not.toBeNull();
485
+ expect(health?.exists).toBe(false);
486
+ expect(health?.tableCount).toBe(0);
487
+ });
488
+
489
+ it('returns exists=true with correct table count after ensureGlobalSignaldockDb', async () => {
490
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
491
+
492
+ const {
493
+ ensureGlobalSignaldockDb,
494
+ checkGlobalSignaldockDbHealth,
495
+ _resetGlobalSignaldockDb_TESTING_ONLY,
496
+ } = await import('../signaldock-sqlite.js');
497
+
498
+ await ensureGlobalSignaldockDb();
499
+ _resetGlobalSignaldockDb_TESTING_ONLY();
500
+
501
+ const health = await checkGlobalSignaldockDbHealth();
502
+ expect(health?.exists).toBe(true);
503
+ expect(health?.schemaVersion).toBe('2026.4.12');
504
+ // Global schema has: users, organization, agents, claim_codes, capabilities, skills,
505
+ // agent_capabilities, agent_skills, agent_connections, accounts, sessions, verifications,
506
+ // org_agent_keys, _signaldock_meta, _signaldock_migrations (15 tables)
507
+ expect(health?.tableCount).toBeGreaterThanOrEqual(13);
508
+ });
509
+ });
510
+
511
+ // ---------------------------------------------------------------------------
512
+ // Deprecated: checkSignaldockDbHealth
513
+ // ---------------------------------------------------------------------------
514
+
515
+ describe('checkSignaldockDbHealth (deprecated alias)', () => {
516
+ beforeEach(() => {
517
+ vi.resetModules();
518
+ });
519
+
520
+ it('with cwd argument THROWS migration error', async () => {
521
+ const cleoHome = makeTmpDir('deprecated-health-cwd');
522
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
523
+
524
+ const { checkSignaldockDbHealth } = await import('../signaldock-sqlite.js');
525
+ await expect(checkSignaldockDbHealth('/some/project')).rejects.toThrow('T310');
526
+ });
527
+
528
+ it('with no args forwards to global health check', async () => {
529
+ const cleoHome = makeTmpDir('deprecated-health-noargs');
530
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
531
+
532
+ const { checkSignaldockDbHealth } = await import('../signaldock-sqlite.js');
533
+ const health = await checkSignaldockDbHealth();
534
+ // DB doesn't exist yet; should return exists=false
535
+ expect(health?.exists).toBe(false);
536
+ });
537
+ });
538
+
539
+ // ---------------------------------------------------------------------------
540
+ // Deprecated: ensureSignaldockDb
541
+ // ---------------------------------------------------------------------------
542
+
543
+ describe('ensureSignaldockDb (deprecated alias)', () => {
544
+ beforeEach(() => {
545
+ vi.resetModules();
546
+ });
547
+
548
+ it('with cwd argument THROWS migration error', async () => {
549
+ const cleoHome = makeTmpDir('deprecated-ensure-cwd');
550
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
551
+
552
+ const { ensureSignaldockDb } = await import('../signaldock-sqlite.js');
553
+ await expect(ensureSignaldockDb('/some/project')).rejects.toThrow('T310');
554
+ });
555
+
556
+ it('with no args forwards to ensureGlobalSignaldockDb', async () => {
557
+ const cleoHome = makeTmpDir('deprecated-ensure-noargs');
558
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
559
+
560
+ const { ensureSignaldockDb, _resetGlobalSignaldockDb_TESTING_ONLY } = await import(
561
+ '../signaldock-sqlite.js'
562
+ );
563
+ const result = await ensureSignaldockDb();
564
+ _resetGlobalSignaldockDb_TESTING_ONLY();
565
+
566
+ expect(result.action).toBe('created');
567
+ expect(result.path).toContain('signaldock.db');
568
+ expect(existsSync(result.path)).toBe(true);
569
+ });
570
+ });
571
+
572
+ // ---------------------------------------------------------------------------
573
+ // getGlobalSignaldockNativeDb and _resetGlobalSignaldockDb_TESTING_ONLY
574
+ // ---------------------------------------------------------------------------
575
+
576
+ describe('getGlobalSignaldockNativeDb', () => {
577
+ let cleoHome: string;
578
+
579
+ beforeEach(() => {
580
+ vi.resetModules();
581
+ cleoHome = makeTmpDir('native-db');
582
+ });
583
+
584
+ afterEach(() => {
585
+ vi.restoreAllMocks();
586
+ try {
587
+ rmSync(cleoHome, { recursive: true, force: true });
588
+ } catch {
589
+ // ignore
590
+ }
591
+ });
592
+
593
+ it('returns null before ensureGlobalSignaldockDb is called', async () => {
594
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
595
+
596
+ const { getGlobalSignaldockNativeDb } = await import('../signaldock-sqlite.js');
597
+ expect(getGlobalSignaldockNativeDb()).toBeNull();
598
+ });
599
+
600
+ it('returns a DatabaseSync handle after ensureGlobalSignaldockDb', async () => {
601
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
602
+
603
+ const {
604
+ ensureGlobalSignaldockDb,
605
+ getGlobalSignaldockNativeDb,
606
+ _resetGlobalSignaldockDb_TESTING_ONLY,
607
+ } = await import('../signaldock-sqlite.js');
608
+
609
+ await ensureGlobalSignaldockDb();
610
+ const handle = getGlobalSignaldockNativeDb();
611
+ expect(handle).not.toBeNull();
612
+
613
+ _resetGlobalSignaldockDb_TESTING_ONLY();
614
+ expect(getGlobalSignaldockNativeDb()).toBeNull();
615
+ });
616
+ });
617
+
618
+ // ---------------------------------------------------------------------------
619
+ // Constants
620
+ // ---------------------------------------------------------------------------
621
+
622
+ describe('exported constants', () => {
623
+ beforeEach(() => {
624
+ vi.resetModules();
625
+ });
626
+
627
+ it('GLOBAL_SIGNALDOCK_DB_FILENAME is "signaldock.db"', async () => {
628
+ const cleoHome = makeTmpDir('constants');
629
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
630
+
631
+ const { GLOBAL_SIGNALDOCK_DB_FILENAME } = await import('../signaldock-sqlite.js');
632
+ expect(GLOBAL_SIGNALDOCK_DB_FILENAME).toBe('signaldock.db');
633
+ });
634
+
635
+ it('GLOBAL_SIGNALDOCK_SCHEMA_VERSION is "2026.4.12"', async () => {
636
+ const cleoHome = makeTmpDir('constants2');
637
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
638
+
639
+ const { GLOBAL_SIGNALDOCK_SCHEMA_VERSION } = await import('../signaldock-sqlite.js');
640
+ expect(GLOBAL_SIGNALDOCK_SCHEMA_VERSION).toBe('2026.4.12');
641
+ });
642
+
643
+ it('SIGNALDOCK_SCHEMA_VERSION deprecated alias equals GLOBAL_SIGNALDOCK_SCHEMA_VERSION', async () => {
644
+ const cleoHome = makeTmpDir('constants3');
645
+ vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
646
+
647
+ const { SIGNALDOCK_SCHEMA_VERSION, GLOBAL_SIGNALDOCK_SCHEMA_VERSION } = await import(
648
+ '../signaldock-sqlite.js'
649
+ );
650
+ expect(SIGNALDOCK_SCHEMA_VERSION).toBe(GLOBAL_SIGNALDOCK_SCHEMA_VERSION);
651
+ });
652
+ });