@cleocode/core 2026.4.11 → 2026.4.13

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 (184) 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 +9 -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 +96 -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__/backup-crypto.test.ts +101 -0
  128. package/src/store/__tests__/backup-pack.test.ts +491 -0
  129. package/src/store/__tests__/backup-unpack.test.ts +298 -0
  130. package/src/store/__tests__/conduit-sqlite.test.ts +413 -0
  131. package/src/store/__tests__/global-salt.test.ts +195 -0
  132. package/src/store/__tests__/migrate-signaldock-to-conduit.test.ts +715 -0
  133. package/src/store/__tests__/regenerators.test.ts +234 -0
  134. package/src/store/__tests__/restore-conflict-report.test.ts +274 -0
  135. package/src/store/__tests__/restore-json-merge.test.ts +521 -0
  136. package/src/store/__tests__/signaldock-sqlite.test.ts +652 -0
  137. package/src/store/__tests__/sqlite-backup-global.test.ts +307 -3
  138. package/src/store/__tests__/sqlite-backup.test.ts +5 -1
  139. package/src/store/__tests__/t310-integration.test.ts +1150 -0
  140. package/src/store/__tests__/t310-readiness.test.ts +111 -0
  141. package/src/store/__tests__/t311-integration.test.ts +661 -0
  142. package/src/store/agent-registry-accessor.ts +847 -140
  143. package/src/store/api-key-kdf.ts +104 -0
  144. package/src/store/backup-crypto.ts +209 -0
  145. package/src/store/backup-pack.ts +739 -0
  146. package/src/store/backup-unpack.ts +583 -0
  147. package/src/store/conduit-sqlite.ts +655 -0
  148. package/src/store/global-salt.ts +175 -0
  149. package/src/store/migrate-signaldock-to-conduit.ts +669 -0
  150. package/src/store/regenerators.ts +243 -0
  151. package/src/store/restore-conflict-report.ts +317 -0
  152. package/src/store/restore-json-merge.ts +653 -0
  153. package/src/store/signaldock-sqlite.ts +431 -254
  154. package/src/store/sqlite-backup.ts +185 -10
  155. package/src/store/t310-readiness.ts +119 -0
  156. package/src/system/backup.ts +2 -62
  157. package/src/system/runtime.ts +4 -6
  158. package/src/tasks/__tests__/error-hints.test.ts +256 -0
  159. package/src/tasks/add.ts +99 -9
  160. package/src/tasks/complete.ts +4 -1
  161. package/src/tasks/find.ts +4 -1
  162. package/src/tasks/labels.ts +4 -1
  163. package/src/tasks/relates.ts +16 -4
  164. package/src/tasks/show.ts +4 -1
  165. package/src/tasks/update.ts +32 -3
  166. package/src/validation/__tests__/error-hints.test.ts +97 -0
  167. package/src/validation/engine.ts +16 -1
  168. package/src/validation/param-utils.ts +10 -7
  169. package/src/validation/protocols/_shared.ts +14 -6
  170. package/src/validation/protocols/cant/architecture-decision.cant +80 -0
  171. package/src/validation/protocols/cant/artifact-publish.cant +95 -0
  172. package/src/validation/protocols/cant/consensus.cant +74 -0
  173. package/src/validation/protocols/cant/contribution.cant +82 -0
  174. package/src/validation/protocols/cant/decomposition.cant +92 -0
  175. package/src/validation/protocols/cant/implementation.cant +67 -0
  176. package/src/validation/protocols/cant/provenance.cant +88 -0
  177. package/src/validation/protocols/cant/release.cant +96 -0
  178. package/src/validation/protocols/cant/research.cant +66 -0
  179. package/src/validation/protocols/cant/specification.cant +67 -0
  180. package/src/validation/protocols/cant/testing.cant +88 -0
  181. package/src/validation/protocols/cant/validation.cant +65 -0
  182. package/src/validation/protocols/protocols-markdown/decomposition.md +0 -4
  183. package/templates/config.template.json +0 -1
  184. package/templates/global-config.template.json +0 -1
@@ -1,294 +1,399 @@
1
1
  /**
2
- * SQLite store for signaldock.db — local agent messaging database.
2
+ * SQLite store for global-tier signaldock.db — canonical agent identity database.
3
3
  *
4
- * Creates and manages .cleo/signaldock.db using node:sqlite directly.
5
- * Runs the consolidated Diesel migration SQL (from signaldock-storage crate)
6
- * to bootstrap all 22 tables for local agent infrastructure.
4
+ * Post-T310 (ADR-037), signaldock.db lives at `$XDG_DATA_HOME/cleo/signaldock.db`
5
+ * (resolved via getCleoHome()). It holds cross-project agent identity, capabilities
6
+ * catalog, and cloud-sync tables. Project-local messaging state has moved to
7
+ * conduit.db (managed by conduit-sqlite.ts, T344).
7
8
  *
8
- * This is the Node.js bootstrap path. In production cloud, the Rust
9
- * signaldock-storage crate manages this DB via Diesel ORM directly.
10
- * Locally, we create the DB here so that cleo init scaffolds the full
11
- * .cleo/ directory with all databases ready.
9
+ * GLOBAL-TIER ONLY. This module MUST NOT resolve paths under any project's .cleo/
10
+ * directory. The path guard in getGlobalSignaldockDbPath() enforces this invariant.
12
11
  *
13
- * @task T223
12
+ * @task T346
13
+ * @epic T310
14
+ * @related ADR-037
14
15
  */
15
16
  import { existsSync, mkdirSync } from 'node:fs';
16
17
  import { createRequire } from 'node:module';
17
- import { dirname, join } from 'node:path';
18
- import { getCleoDirAbsolute } from '../paths.js';
18
+ import { join } from 'node:path';
19
+ import { getCleoHome } from '../paths.js';
19
20
  const _require = createRequire(import.meta.url);
20
21
  const { DatabaseSync: DatabaseSyncClass } = _require('node:sqlite');
21
- /** Database file name within .cleo/ directory. */
22
- const DB_FILENAME = 'signaldock.db';
23
- /** Schema version for signaldock databases. */
24
- export const SIGNALDOCK_SCHEMA_VERSION = '2026.3.76';
25
22
  /**
26
- * Get the path to the signaldock.db SQLite database file.
23
+ * Database file name within the global cleo home directory.
24
+ *
25
+ * @task T346
26
+ * @epic T310
27
+ */
28
+ export const GLOBAL_SIGNALDOCK_DB_FILENAME = 'signaldock.db';
29
+ /**
30
+ * Schema version for global signaldock databases.
31
+ *
32
+ * @task T346
33
+ * @epic T310
34
+ */
35
+ export const GLOBAL_SIGNALDOCK_SCHEMA_VERSION = '2026.4.12';
36
+ /**
37
+ * @deprecated Use GLOBAL_SIGNALDOCK_SCHEMA_VERSION. Retained during T310
38
+ * migration window. Will be removed after all callers migrate (T355).
39
+ */
40
+ export const SIGNALDOCK_SCHEMA_VERSION = GLOBAL_SIGNALDOCK_SCHEMA_VERSION;
41
+ // ---------------------------------------------------------------------------
42
+ // Path helpers
43
+ // ---------------------------------------------------------------------------
44
+ /**
45
+ * Returns the GLOBAL-tier signaldock.db path. Post-T310, signaldock.db
46
+ * holds canonical agent identity + cloud-sync tables. Project-local
47
+ * messaging state lives in conduit.db (T344).
48
+ *
49
+ * Resolves to `getCleoHome() + '/signaldock.db'`.
50
+ * Guard: asserts the resolved path starts with getCleoHome() (defense in depth,
51
+ * mirrors the ADR-036 pattern used by getNexusDbPath in nexus-sqlite.ts).
52
+ *
53
+ * @task T346
54
+ * @epic T310
55
+ * @why ADR-037 split single signaldock.db into project conduit + global signaldock
56
+ * @throws {Error} If resolved path is not under getCleoHome() — indicates a code
57
+ * path that bypasses canonical path resolution. Fix the caller, do not suppress.
58
+ */
59
+ export function getGlobalSignaldockDbPath() {
60
+ const cleoHome = getCleoHome();
61
+ const dbPath = join(cleoHome, GLOBAL_SIGNALDOCK_DB_FILENAME);
62
+ if (!dbPath.startsWith(cleoHome)) {
63
+ throw new Error(`BUG: getGlobalSignaldockDbPath() resolved to "${dbPath}" which is NOT under ` +
64
+ `getCleoHome() ("${cleoHome}"). signaldock.db is global-only per ADR-037. ` +
65
+ `This indicates a code path that bypasses path resolution — ` +
66
+ `fix the caller, do not suppress this error.`);
67
+ }
68
+ return dbPath;
69
+ }
70
+ /**
71
+ * @deprecated Use getGlobalSignaldockDbPath() directly. Retained during T310
72
+ * migration window so the TypeScript build does not break until all callers
73
+ * are updated (tracked in T355 accessor refactor).
74
+ *
75
+ * When called WITHOUT arguments: returns the global-tier path (forwards to
76
+ * getGlobalSignaldockDbPath()).
77
+ *
78
+ * When called WITH a non-undefined `cwd` argument: throws a migration error
79
+ * immediately. The project-tier path is now owned by conduit-sqlite.ts (T344).
80
+ *
81
+ * @param cwd - Must be undefined. Any other value throws a migration error.
82
+ * @task T346
83
+ * @epic T310
27
84
  */
28
85
  export function getSignaldockDbPath(cwd) {
29
- const cleoDir = cwd ? join(cwd, '.cleo') : getCleoDirAbsolute();
30
- return join(cleoDir, DB_FILENAME);
86
+ if (cwd !== undefined) {
87
+ throw new Error('getSignaldockDbPath(cwd) is removed as of T310 (v2026.4.12). ' +
88
+ 'signaldock.db is now global-only at $XDG_DATA_HOME/cleo/signaldock.db. ' +
89
+ 'Use getGlobalSignaldockDbPath(), or for project-local messaging use ' +
90
+ 'getConduitDbPath() from conduit-sqlite.ts (T344).');
91
+ }
92
+ return getGlobalSignaldockDbPath();
31
93
  }
32
94
  // ---------------------------------------------------------------------------
33
- // Embedded migration SQL — bundled so signaldock.db works in ANY project,
34
- // not just the monorepo where crates/signaldock-storage/ exists.
35
- // Source: crates/signaldock-storage/migrations/
95
+ // Embedded migration SQL — consolidated global-tier schema.
96
+ // Source: spec §2.2, ADR-037.
97
+ // All incremental ALTER TABLE migrations from the pre-T310 schema are
98
+ // collapsed into the single initial migration below. This avoids the
99
+ // multi-step ALTER pattern and ensures fresh installs get the full schema
100
+ // in one idempotent pass.
36
101
  // ---------------------------------------------------------------------------
37
102
  /**
38
- * Ordered migration entries. Each has a name (for tracking) and SQL.
103
+ * Ordered migration entries for the global signaldock.db.
39
104
  * Add new migrations to the END of this array.
105
+ *
106
+ * @task T346
107
+ * @epic T310
40
108
  */
41
- const EMBEDDED_MIGRATIONS = [
109
+ const GLOBAL_EMBEDDED_MIGRATIONS = [
42
110
  {
43
- name: '2026-03-28-000000_initial',
44
- sql: `-- Consolidated initial migration for SignalDock storage (19 sqlx migrations merged).
111
+ name: '2026-04-12-000000_initial_global_signaldock',
112
+ sql: `-- Global-tier signaldock.db initial migration (T310, ADR-037 §2.2).
113
+ -- Consolidated from pre-T310 incremental migrations. Global identity and
114
+ -- cloud-sync tables only. Project-local messaging state is in conduit.db.
115
+
116
+ -- Cloud-sync: user accounts (zero rows in pure-local mode).
45
117
  CREATE TABLE IF NOT EXISTS users (
46
- id TEXT PRIMARY KEY, email TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL,
47
- name TEXT, slug TEXT, default_agent_id TEXT, username TEXT, display_username TEXT,
48
- email_verified INTEGER NOT NULL DEFAULT 0, image TEXT, role TEXT NOT NULL DEFAULT 'user',
49
- banned INTEGER NOT NULL DEFAULT 0, ban_reason TEXT, ban_expires TEXT,
50
- two_factor_enabled INTEGER NOT NULL DEFAULT 0, metadata TEXT,
51
- created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
118
+ id TEXT PRIMARY KEY,
119
+ email TEXT NOT NULL UNIQUE,
120
+ password_hash TEXT NOT NULL,
121
+ name TEXT,
122
+ slug TEXT,
123
+ default_agent_id TEXT,
124
+ username TEXT,
125
+ display_username TEXT,
126
+ email_verified INTEGER NOT NULL DEFAULT 0,
127
+ image TEXT,
128
+ role TEXT NOT NULL DEFAULT 'user',
129
+ banned INTEGER NOT NULL DEFAULT 0,
130
+ ban_reason TEXT,
131
+ ban_expires TEXT,
132
+ two_factor_enabled INTEGER NOT NULL DEFAULT 0,
133
+ metadata TEXT,
134
+ created_at INTEGER NOT NULL,
135
+ updated_at INTEGER NOT NULL
52
136
  );
53
137
  CREATE UNIQUE INDEX IF NOT EXISTS idx_users_slug ON users(slug);
54
138
 
139
+ -- Cloud-sync: organization/team records.
55
140
  CREATE TABLE IF NOT EXISTS organization (
56
- id TEXT PRIMARY KEY NOT NULL, name TEXT NOT NULL, slug TEXT, logo TEXT, metadata TEXT,
57
- owner_id TEXT, created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
141
+ id TEXT PRIMARY KEY NOT NULL,
142
+ name TEXT NOT NULL,
143
+ slug TEXT,
144
+ logo TEXT,
145
+ metadata TEXT,
146
+ owner_id TEXT,
147
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
58
148
  updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now'))
59
149
  );
60
150
  CREATE UNIQUE INDEX IF NOT EXISTS idx_organization_slug ON organization(slug);
61
151
 
152
+ -- Global identity: canonical agent registry (cross-project).
153
+ -- api_key_encrypted uses KDF: HMAC-SHA256(machine-key || global-salt, agentId) — ADR-037 §5.
154
+ -- requires_reauth=1 is set during T310 migration for all pre-existing agents.
62
155
  CREATE TABLE IF NOT EXISTS agents (
63
- id TEXT PRIMARY KEY, agent_id TEXT NOT NULL UNIQUE, name TEXT NOT NULL,
64
- description TEXT, class TEXT NOT NULL DEFAULT 'custom',
65
- privacy_tier TEXT NOT NULL DEFAULT 'public', owner_id TEXT REFERENCES users(id),
66
- endpoint TEXT, webhook_secret TEXT, capabilities TEXT NOT NULL DEFAULT '[]',
67
- skills TEXT NOT NULL DEFAULT '[]', avatar TEXT, messages_sent INTEGER NOT NULL DEFAULT 0,
68
- messages_received INTEGER NOT NULL DEFAULT 0, conversation_count INTEGER NOT NULL DEFAULT 0,
69
- friend_count INTEGER NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT 'online',
70
- last_seen INTEGER, payment_config TEXT, api_key_hash TEXT,
156
+ id TEXT PRIMARY KEY,
157
+ agent_id TEXT NOT NULL UNIQUE,
158
+ name TEXT NOT NULL,
159
+ description TEXT,
160
+ class TEXT NOT NULL DEFAULT 'custom',
161
+ privacy_tier TEXT NOT NULL DEFAULT 'public',
162
+ owner_id TEXT REFERENCES users(id),
163
+ endpoint TEXT,
164
+ webhook_secret TEXT,
165
+ capabilities TEXT NOT NULL DEFAULT '[]',
166
+ skills TEXT NOT NULL DEFAULT '[]',
167
+ avatar TEXT,
168
+ messages_sent INTEGER NOT NULL DEFAULT 0,
169
+ messages_received INTEGER NOT NULL DEFAULT 0,
170
+ conversation_count INTEGER NOT NULL DEFAULT 0,
171
+ friend_count INTEGER NOT NULL DEFAULT 0,
172
+ status TEXT NOT NULL DEFAULT 'online',
173
+ last_seen INTEGER,
174
+ payment_config TEXT,
175
+ api_key_hash TEXT,
71
176
  organization_id TEXT REFERENCES organization(id) ON DELETE SET NULL,
72
- created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
177
+ created_at INTEGER NOT NULL,
178
+ updated_at INTEGER NOT NULL,
179
+ transport_type TEXT NOT NULL DEFAULT 'http',
180
+ api_key_encrypted TEXT,
181
+ api_base_url TEXT NOT NULL DEFAULT 'https://api.signaldock.io',
182
+ classification TEXT,
183
+ transport_config TEXT NOT NULL DEFAULT '{}',
184
+ is_active INTEGER NOT NULL DEFAULT 1,
185
+ last_used_at INTEGER,
186
+ requires_reauth INTEGER NOT NULL DEFAULT 0
73
187
  );
74
188
  CREATE UNIQUE INDEX IF NOT EXISTS agents_agent_id_idx ON agents(agent_id);
75
189
  CREATE INDEX IF NOT EXISTS agents_owner_idx ON agents(owner_id);
76
190
  CREATE INDEX IF NOT EXISTS agents_class_idx ON agents(class);
77
191
  CREATE INDEX IF NOT EXISTS agents_privacy_idx ON agents(privacy_tier);
78
192
  CREATE INDEX IF NOT EXISTS agents_org_idx ON agents(organization_id);
193
+ CREATE INDEX IF NOT EXISTS idx_agents_transport_type ON agents(transport_type);
194
+ CREATE INDEX IF NOT EXISTS idx_agents_is_active ON agents(is_active);
195
+ CREATE INDEX IF NOT EXISTS idx_agents_last_used ON agents(last_used_at);
196
+ CREATE INDEX IF NOT EXISTS idx_agents_reauth ON agents(requires_reauth) WHERE requires_reauth = 1;
79
197
 
80
- CREATE TABLE IF NOT EXISTS conversations (
81
- id TEXT PRIMARY KEY, participants TEXT NOT NULL,
82
- visibility TEXT NOT NULL DEFAULT 'private', message_count INTEGER NOT NULL DEFAULT 0,
83
- last_message_at INTEGER, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
84
- );
85
-
86
- CREATE TABLE IF NOT EXISTS messages (
87
- id TEXT PRIMARY KEY, conversation_id TEXT NOT NULL REFERENCES conversations(id),
88
- from_agent_id TEXT NOT NULL, to_agent_id TEXT NOT NULL, content TEXT NOT NULL,
89
- content_type TEXT NOT NULL DEFAULT 'text', status TEXT NOT NULL DEFAULT 'pending',
90
- attachments TEXT NOT NULL DEFAULT '[]', group_id TEXT, metadata TEXT DEFAULT '{}',
91
- reply_to TEXT, created_at INTEGER NOT NULL, delivered_at INTEGER, read_at INTEGER
92
- );
93
- CREATE INDEX IF NOT EXISTS messages_conversation_idx ON messages(conversation_id);
94
- CREATE INDEX IF NOT EXISTS messages_from_agent_idx ON messages(from_agent_id);
95
- CREATE INDEX IF NOT EXISTS messages_to_agent_idx ON messages(to_agent_id);
96
- CREATE INDEX IF NOT EXISTS messages_created_at_idx ON messages(created_at);
97
- CREATE INDEX IF NOT EXISTS idx_messages_group_id ON messages(group_id) WHERE group_id IS NOT NULL;
98
- CREATE INDEX IF NOT EXISTS idx_messages_reply_to ON messages(reply_to) WHERE reply_to IS NOT NULL;
99
-
198
+ -- Cloud-sync: one-time agent claim tokens (api.signaldock.io provisioning).
100
199
  CREATE TABLE IF NOT EXISTS claim_codes (
101
- id TEXT PRIMARY KEY, agent_id TEXT NOT NULL REFERENCES agents(id),
102
- code TEXT NOT NULL UNIQUE, expires_at INTEGER NOT NULL, used_at INTEGER,
103
- used_by TEXT REFERENCES users(id), created_at INTEGER NOT NULL
200
+ id TEXT PRIMARY KEY,
201
+ agent_id TEXT NOT NULL REFERENCES agents(id),
202
+ code TEXT NOT NULL UNIQUE,
203
+ expires_at INTEGER NOT NULL,
204
+ used_at INTEGER,
205
+ used_by TEXT REFERENCES users(id),
206
+ created_at INTEGER NOT NULL
104
207
  );
105
208
  CREATE UNIQUE INDEX IF NOT EXISTS claim_codes_code_idx ON claim_codes(code);
106
209
  CREATE INDEX IF NOT EXISTS claim_codes_agent_idx ON claim_codes(agent_id);
107
210
 
108
- CREATE TABLE IF NOT EXISTS connections (
109
- id TEXT PRIMARY KEY, agent_a TEXT NOT NULL REFERENCES agents(id),
110
- agent_b TEXT NOT NULL REFERENCES agents(id), status TEXT NOT NULL DEFAULT 'pending',
111
- initiated_by TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
112
- );
113
- CREATE INDEX IF NOT EXISTS connections_agent_a_idx ON connections(agent_a);
114
- CREATE INDEX IF NOT EXISTS connections_agent_b_idx ON connections(agent_b);
115
-
116
- CREATE TABLE IF NOT EXISTS delivery_jobs (
117
- id TEXT PRIMARY KEY, message_id TEXT NOT NULL, payload TEXT NOT NULL,
118
- status TEXT NOT NULL DEFAULT 'pending', attempts INTEGER NOT NULL DEFAULT 0,
119
- max_attempts INTEGER NOT NULL DEFAULT 6, next_attempt_at INTEGER NOT NULL,
120
- last_error TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL
121
- );
122
- CREATE INDEX IF NOT EXISTS idx_delivery_jobs_status ON delivery_jobs(status, next_attempt_at);
123
-
124
- CREATE TABLE IF NOT EXISTS dead_letters (
125
- id TEXT PRIMARY KEY, message_id TEXT NOT NULL, job_id TEXT NOT NULL,
126
- reason TEXT NOT NULL, attempts INTEGER NOT NULL, created_at INTEGER NOT NULL
127
- );
128
- CREATE INDEX IF NOT EXISTS idx_dead_letters_message ON dead_letters(message_id);
129
-
130
- CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(content, from_agent_id, content='messages', content_rowid='rowid');
131
- INSERT INTO messages_fts(messages_fts) VALUES('rebuild');
132
- CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN
133
- INSERT INTO messages_fts(rowid, content, from_agent_id) VALUES (new.rowid, new.content, new.from_agent_id);
134
- END;
135
- CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN
136
- INSERT INTO messages_fts(messages_fts, rowid, content, from_agent_id) VALUES('delete', old.rowid, old.content, old.from_agent_id);
137
- END;
138
- CREATE TRIGGER IF NOT EXISTS messages_au AFTER UPDATE ON messages BEGIN
139
- INSERT INTO messages_fts(messages_fts, rowid, content, from_agent_id) VALUES('delete', old.rowid, old.content, old.from_agent_id);
140
- INSERT INTO messages_fts(rowid, content, from_agent_id) VALUES (new.rowid, new.content, new.from_agent_id);
141
- END;
142
-
143
- CREATE TABLE IF NOT EXISTS message_pins (
144
- id TEXT PRIMARY KEY, message_id TEXT NOT NULL, conversation_id TEXT NOT NULL,
145
- pinned_by TEXT NOT NULL, note TEXT, created_at INTEGER NOT NULL, UNIQUE(message_id, pinned_by)
146
- );
147
- CREATE INDEX IF NOT EXISTS idx_pins_conversation ON message_pins(conversation_id);
148
- CREATE INDEX IF NOT EXISTS idx_pins_agent ON message_pins(pinned_by);
149
-
150
- CREATE TABLE IF NOT EXISTS attachments (
151
- slug TEXT PRIMARY KEY, conversation_id TEXT NOT NULL, from_agent_id TEXT NOT NULL,
152
- content BLOB NOT NULL, original_size INTEGER NOT NULL, compressed_size INTEGER NOT NULL,
153
- content_hash TEXT NOT NULL, format TEXT NOT NULL DEFAULT 'text', title TEXT,
154
- tokens INTEGER NOT NULL DEFAULT 0, expires_at INTEGER NOT NULL DEFAULT 0,
155
- storage_key TEXT, mode TEXT NOT NULL DEFAULT 'draft',
156
- version_count INTEGER NOT NULL DEFAULT 1, current_version INTEGER NOT NULL DEFAULT 1,
211
+ -- Identity catalog: pre-seeded capability slugs (19 entries).
212
+ CREATE TABLE IF NOT EXISTS capabilities (
213
+ id TEXT PRIMARY KEY,
214
+ slug TEXT NOT NULL UNIQUE,
215
+ name TEXT NOT NULL,
216
+ description TEXT NOT NULL,
217
+ category TEXT NOT NULL,
157
218
  created_at INTEGER NOT NULL
158
219
  );
159
- CREATE INDEX IF NOT EXISTS attachments_conversation_idx ON attachments(conversation_id);
160
- CREATE INDEX IF NOT EXISTS attachments_agent_idx ON attachments(from_agent_id);
161
220
 
162
- CREATE TABLE IF NOT EXISTS capabilities (
163
- id TEXT PRIMARY KEY, slug TEXT NOT NULL UNIQUE, name TEXT NOT NULL,
164
- description TEXT NOT NULL, category TEXT NOT NULL, created_at INTEGER NOT NULL
165
- );
221
+ -- Identity catalog: pre-seeded skill slugs (36 entries).
166
222
  CREATE TABLE IF NOT EXISTS skills (
167
- id TEXT PRIMARY KEY, slug TEXT NOT NULL UNIQUE, name TEXT NOT NULL,
168
- description TEXT NOT NULL, category TEXT NOT NULL, created_at INTEGER NOT NULL
223
+ id TEXT PRIMARY KEY,
224
+ slug TEXT NOT NULL UNIQUE,
225
+ name TEXT NOT NULL,
226
+ description TEXT NOT NULL,
227
+ category TEXT NOT NULL,
228
+ created_at INTEGER NOT NULL
169
229
  );
230
+
231
+ -- Junction: agent <-> capability catalog bindings.
170
232
  CREATE TABLE IF NOT EXISTS agent_capabilities (
171
- agent_id TEXT NOT NULL REFERENCES agents(id), capability_id TEXT NOT NULL REFERENCES capabilities(id),
233
+ agent_id TEXT NOT NULL REFERENCES agents(id),
234
+ capability_id TEXT NOT NULL REFERENCES capabilities(id),
172
235
  PRIMARY KEY (agent_id, capability_id)
173
236
  );
237
+
238
+ -- Junction: agent <-> skill catalog bindings.
174
239
  CREATE TABLE IF NOT EXISTS agent_skills (
175
- agent_id TEXT NOT NULL REFERENCES agents(id), skill_id TEXT NOT NULL REFERENCES skills(id),
240
+ agent_id TEXT NOT NULL REFERENCES agents(id),
241
+ skill_id TEXT NOT NULL REFERENCES skills(id),
176
242
  PRIMARY KEY (agent_id, skill_id)
177
243
  );
178
244
 
245
+ -- Live transport connection tracking (heartbeat state).
246
+ CREATE TABLE IF NOT EXISTS agent_connections (
247
+ id TEXT PRIMARY KEY NOT NULL,
248
+ agent_id TEXT NOT NULL,
249
+ transport_type TEXT NOT NULL DEFAULT 'http',
250
+ connection_id TEXT,
251
+ connected_at BIGINT NOT NULL,
252
+ last_heartbeat BIGINT NOT NULL,
253
+ connection_metadata TEXT,
254
+ created_at BIGINT NOT NULL,
255
+ FOREIGN KEY (agent_id) REFERENCES agents(agent_id) ON DELETE CASCADE,
256
+ UNIQUE(agent_id, connection_id)
257
+ );
258
+ CREATE INDEX IF NOT EXISTS idx_agent_connections_agent ON agent_connections(agent_id);
259
+ CREATE INDEX IF NOT EXISTS idx_agent_connections_transport ON agent_connections(transport_type);
260
+ CREATE INDEX IF NOT EXISTS idx_agent_connections_heartbeat ON agent_connections(last_heartbeat);
261
+
262
+ -- Cloud-sync: OAuth/provider accounts.
179
263
  CREATE TABLE IF NOT EXISTS accounts (
180
- id TEXT PRIMARY KEY NOT NULL, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
181
- account_id TEXT NOT NULL, provider_id TEXT NOT NULL, access_token TEXT, refresh_token TEXT,
182
- id_token TEXT, access_token_expires_at TEXT, refresh_token_expires_at TEXT, scope TEXT,
183
- password TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
264
+ id TEXT PRIMARY KEY NOT NULL,
265
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
266
+ account_id TEXT NOT NULL,
267
+ provider_id TEXT NOT NULL,
268
+ access_token TEXT,
269
+ refresh_token TEXT,
270
+ id_token TEXT,
271
+ access_token_expires_at TEXT,
272
+ refresh_token_expires_at TEXT,
273
+ scope TEXT,
274
+ password TEXT,
275
+ created_at TEXT NOT NULL,
276
+ updated_at TEXT NOT NULL
184
277
  );
185
278
  CREATE INDEX IF NOT EXISTS idx_accounts_user_id ON accounts(user_id);
186
279
  CREATE UNIQUE INDEX IF NOT EXISTS idx_accounts_provider ON accounts(provider_id, account_id);
187
280
 
281
+ -- Cloud-sync: authenticated sessions.
188
282
  CREATE TABLE IF NOT EXISTS sessions (
189
- id TEXT PRIMARY KEY NOT NULL, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
190
- token TEXT NOT NULL UNIQUE, ip_address TEXT, user_agent TEXT, expires_at TEXT NOT NULL,
191
- active_organization_id TEXT, impersonated_by TEXT, active INTEGER NOT NULL DEFAULT 1,
192
- created_at TEXT NOT NULL, updated_at TEXT NOT NULL
283
+ id TEXT PRIMARY KEY NOT NULL,
284
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
285
+ token TEXT NOT NULL UNIQUE,
286
+ ip_address TEXT,
287
+ user_agent TEXT,
288
+ expires_at TEXT NOT NULL,
289
+ active_organization_id TEXT,
290
+ impersonated_by TEXT,
291
+ active INTEGER NOT NULL DEFAULT 1,
292
+ created_at TEXT NOT NULL,
293
+ updated_at TEXT NOT NULL
193
294
  );
194
295
  CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token);
195
296
  CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
196
297
 
298
+ -- Cloud-sync: email/2FA verification tokens.
197
299
  CREATE TABLE IF NOT EXISTS verifications (
198
- id TEXT PRIMARY KEY NOT NULL, identifier TEXT NOT NULL, value TEXT NOT NULL,
199
- expires_at TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
300
+ id TEXT PRIMARY KEY NOT NULL,
301
+ identifier TEXT NOT NULL,
302
+ value TEXT NOT NULL,
303
+ expires_at TEXT NOT NULL,
304
+ created_at TEXT NOT NULL,
305
+ updated_at TEXT NOT NULL
200
306
  );
201
307
  CREATE INDEX IF NOT EXISTS idx_verifications_identifier ON verifications(identifier);
202
308
 
309
+ -- Org-scoped agent API keys (cloud use; zero rows locally).
203
310
  CREATE TABLE IF NOT EXISTS org_agent_keys (
204
- id TEXT PRIMARY KEY NOT NULL, organization_id TEXT NOT NULL REFERENCES organization(id) ON DELETE CASCADE,
311
+ id TEXT PRIMARY KEY NOT NULL,
312
+ organization_id TEXT NOT NULL REFERENCES organization(id) ON DELETE CASCADE,
205
313
  agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
206
- created_by TEXT NOT NULL, created_at INTEGER NOT NULL
314
+ created_by TEXT NOT NULL,
315
+ created_at INTEGER NOT NULL
207
316
  );
208
317
  CREATE INDEX IF NOT EXISTS org_agent_keys_org_idx ON org_agent_keys(organization_id);
209
- CREATE INDEX IF NOT EXISTS org_agent_keys_agent_idx ON org_agent_keys(agent_id);
210
-
211
- CREATE TABLE IF NOT EXISTS attachment_versions (
212
- id TEXT PRIMARY KEY, slug TEXT NOT NULL REFERENCES attachments(slug) ON DELETE CASCADE,
213
- version_number INTEGER NOT NULL, author_agent_id TEXT NOT NULL,
214
- change_type TEXT NOT NULL DEFAULT 'patch', patch_text TEXT, storage_key TEXT NOT NULL,
215
- content_hash TEXT NOT NULL, original_size INTEGER NOT NULL, compressed_size INTEGER NOT NULL,
216
- tokens INTEGER NOT NULL, change_summary TEXT, sections_modified TEXT NOT NULL DEFAULT '[]',
217
- tokens_added INTEGER NOT NULL DEFAULT 0, tokens_removed INTEGER NOT NULL DEFAULT 0,
218
- created_at INTEGER NOT NULL, UNIQUE(slug, version_number)
219
- );
220
- CREATE INDEX IF NOT EXISTS idx_attachment_versions_slug ON attachment_versions(slug);
221
- CREATE INDEX IF NOT EXISTS idx_attachment_versions_author ON attachment_versions(author_agent_id);
222
-
223
- CREATE TABLE IF NOT EXISTS attachment_approvals (
224
- id TEXT PRIMARY KEY, slug TEXT NOT NULL REFERENCES attachments(slug) ON DELETE CASCADE,
225
- reviewer_agent_id TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', comment TEXT,
226
- version_reviewed INTEGER NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL,
227
- UNIQUE(slug, reviewer_agent_id)
228
- );
229
- CREATE INDEX IF NOT EXISTS idx_attachment_approvals_slug ON attachment_approvals(slug);
230
-
231
- CREATE TABLE IF NOT EXISTS attachment_contributors (
232
- slug TEXT NOT NULL REFERENCES attachments(slug) ON DELETE CASCADE,
233
- agent_id TEXT NOT NULL, version_count INTEGER NOT NULL DEFAULT 0,
234
- total_tokens_added INTEGER NOT NULL DEFAULT 0, total_tokens_removed INTEGER NOT NULL DEFAULT 0,
235
- first_contribution_at INTEGER NOT NULL, last_contribution_at INTEGER NOT NULL,
236
- PRIMARY KEY (slug, agent_id)
237
- );`,
238
- },
239
- {
240
- name: '2026-03-30-000001_agent_connections',
241
- sql: `-- Add transport_type to agents table for connection mode classification.
242
- ALTER TABLE agents ADD COLUMN transport_type TEXT NOT NULL DEFAULT 'http';
243
- CREATE INDEX idx_agents_transport_type ON agents(transport_type);
244
-
245
- CREATE TABLE agent_connections (
246
- id TEXT PRIMARY KEY NOT NULL, agent_id TEXT NOT NULL,
247
- transport_type TEXT NOT NULL DEFAULT 'http', connection_id TEXT,
248
- connected_at BIGINT NOT NULL, last_heartbeat BIGINT NOT NULL,
249
- connection_metadata TEXT, created_at BIGINT NOT NULL,
250
- FOREIGN KEY (agent_id) REFERENCES agents(agent_id) ON DELETE CASCADE,
251
- UNIQUE(agent_id, connection_id)
252
- );
253
- CREATE INDEX idx_agent_connections_agent ON agent_connections(agent_id);
254
- CREATE INDEX idx_agent_connections_transport ON agent_connections(transport_type);
255
- CREATE INDEX idx_agent_connections_heartbeat ON agent_connections(last_heartbeat);`,
256
- },
257
- {
258
- name: '2026-03-31-000001_agent_credentials',
259
- sql: `-- Move agent credentials into signaldock.db agents table (T234 clean-cut).
260
- ALTER TABLE agents ADD COLUMN api_key_encrypted TEXT;
261
- ALTER TABLE agents ADD COLUMN api_base_url TEXT NOT NULL DEFAULT 'https://api.signaldock.io';
262
- ALTER TABLE agents ADD COLUMN classification TEXT;
263
- ALTER TABLE agents ADD COLUMN transport_config TEXT NOT NULL DEFAULT '{}';
264
- ALTER TABLE agents ADD COLUMN is_active INTEGER NOT NULL DEFAULT 1;
265
- ALTER TABLE agents ADD COLUMN last_used_at INTEGER;
266
- CREATE INDEX IF NOT EXISTS idx_agents_is_active ON agents(is_active);
267
- CREATE INDEX IF NOT EXISTS idx_agents_last_used ON agents(last_used_at);`,
318
+ CREATE INDEX IF NOT EXISTS org_agent_keys_agent_idx ON org_agent_keys(agent_id);`,
268
319
  },
269
320
  ];
321
+ // ---------------------------------------------------------------------------
322
+ // Database lifecycle
323
+ // ---------------------------------------------------------------------------
324
+ /** Singleton native DatabaseSync handle for the current process. */
325
+ let _globalSignaldockNativeDb = null;
270
326
  /**
271
- * Ensure signaldock.db exists and has the full schema applied.
327
+ * Apply the global signaldock schema to an already-open database.
328
+ * Idempotent — uses `CREATE TABLE IF NOT EXISTS` and migration tracking.
272
329
  *
273
- * Idempotent safe to call multiple times. Uses `CREATE TABLE IF NOT EXISTS`
274
- * and `CREATE INDEX IF NOT EXISTS` throughout.
330
+ * @param db - An open DatabaseSync instance at the global path
331
+ * @task T346
332
+ * @epic T310
333
+ */
334
+ function applyGlobalSignaldockSchema(db) {
335
+ // Ensure migration tracking tables exist
336
+ db.exec(`
337
+ CREATE TABLE IF NOT EXISTS _signaldock_meta (
338
+ key TEXT PRIMARY KEY,
339
+ value TEXT NOT NULL,
340
+ updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
341
+ )
342
+ `);
343
+ db.exec(`
344
+ CREATE TABLE IF NOT EXISTS _signaldock_migrations (
345
+ name TEXT PRIMARY KEY,
346
+ applied_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
347
+ )
348
+ `);
349
+ // Apply embedded migrations (skips already-applied ones)
350
+ for (const migration of GLOBAL_EMBEDDED_MIGRATIONS) {
351
+ const applied = db
352
+ .prepare('SELECT name FROM _signaldock_migrations WHERE name = ?')
353
+ .get(migration.name);
354
+ if (applied)
355
+ continue;
356
+ db.exec('BEGIN TRANSACTION');
357
+ try {
358
+ db.exec(migration.sql);
359
+ db.prepare('INSERT INTO _signaldock_migrations (name) VALUES (?)').run(migration.name);
360
+ db.exec('COMMIT');
361
+ }
362
+ catch (err) {
363
+ db.exec('ROLLBACK');
364
+ throw err;
365
+ }
366
+ }
367
+ // Record schema version
368
+ db.exec(`
369
+ INSERT OR REPLACE INTO _signaldock_meta (key, value, updated_at)
370
+ VALUES ('schema_version', '${GLOBAL_SIGNALDOCK_SCHEMA_VERSION}', strftime('%s', 'now'))
371
+ `);
372
+ }
373
+ /**
374
+ * Ensure global signaldock.db exists with the full global schema applied.
375
+ * Creates the global cleo home directory if it doesn't exist.
376
+ * Idempotent — safe to call multiple times.
275
377
  *
276
- * @returns Object with action ('created' | 'exists') and the database path.
378
+ * @returns Object with action ('created' | 'exists') and the database path
379
+ * @task T346
380
+ * @epic T310
277
381
  */
278
- export async function ensureSignaldockDb(cwd) {
279
- const dbPath = getSignaldockDbPath(cwd);
382
+ export async function ensureGlobalSignaldockDb() {
383
+ const dbPath = getGlobalSignaldockDbPath();
280
384
  const alreadyExists = existsSync(dbPath);
281
- // Ensure parent directory exists
282
- mkdirSync(dirname(dbPath), { recursive: true });
283
- // Open or create the database
385
+ // Ensure global cleo home directory exists
386
+ const cleoHome = getCleoHome();
387
+ if (!existsSync(cleoHome)) {
388
+ mkdirSync(cleoHome, { recursive: true });
389
+ }
284
390
  const db = new DatabaseSyncClass(dbPath);
285
391
  try {
286
- // Set pragmas for optimal performance
287
392
  db.exec('PRAGMA journal_mode = WAL');
288
393
  db.exec('PRAGMA busy_timeout = 5000');
289
394
  db.exec('PRAGMA synchronous = NORMAL');
290
395
  db.exec('PRAGMA foreign_keys = ON');
291
- db.exec('PRAGMA cache_size = -64000'); // 64MB
396
+ db.exec('PRAGMA cache_size = -64000'); // 64 MB
292
397
  // Check if schema already applied (agents table as sentinel)
293
398
  const hasSchema = (() => {
294
399
  try {
@@ -301,62 +406,53 @@ export async function ensureSignaldockDb(cwd) {
301
406
  return false;
302
407
  }
303
408
  })();
304
- // Ensure migration tracking tables exist
305
- db.exec(`
306
- CREATE TABLE IF NOT EXISTS _signaldock_meta (
307
- key TEXT PRIMARY KEY,
308
- value TEXT NOT NULL,
309
- updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
310
- )
311
- `);
312
- db.exec(`
313
- CREATE TABLE IF NOT EXISTS _signaldock_migrations (
314
- name TEXT PRIMARY KEY,
315
- applied_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
316
- )
317
- `);
318
- // Apply embedded migrations (works in ANY project, not just monorepo)
319
- for (const migration of EMBEDDED_MIGRATIONS) {
320
- // Skip already-applied migrations
321
- const applied = db
322
- .prepare('SELECT name FROM _signaldock_migrations WHERE name = ?')
323
- .get(migration.name);
324
- if (applied)
325
- continue;
326
- db.exec('BEGIN TRANSACTION');
327
- try {
328
- db.exec(migration.sql);
329
- db.prepare('INSERT INTO _signaldock_migrations (name) VALUES (?)').run(migration.name);
330
- db.exec('COMMIT');
331
- }
332
- catch (err) {
333
- db.exec('ROLLBACK');
334
- throw err;
335
- }
336
- }
337
- // Record schema version
338
- db.exec(`
339
- INSERT OR REPLACE INTO _signaldock_meta (key, value, updated_at)
340
- VALUES ('schema_version', '${SIGNALDOCK_SCHEMA_VERSION}', strftime('%s', 'now'))
341
- `);
409
+ applyGlobalSignaldockSchema(db);
410
+ // Store native handle for backup integration (getGlobalSignaldockNativeDb)
411
+ _globalSignaldockNativeDb = db;
342
412
  return {
343
413
  action: alreadyExists && hasSchema ? 'exists' : 'created',
344
414
  path: dbPath,
345
415
  };
346
416
  }
347
- finally {
417
+ catch (err) {
348
418
  db.close();
419
+ _globalSignaldockNativeDb = null;
420
+ throw err;
349
421
  }
422
+ // NOTE: We intentionally do NOT close `db` here — the native handle is
423
+ // retained as _globalSignaldockNativeDb for backup integration. Callers
424
+ // that need a short-lived open/close pattern should open the DB themselves.
350
425
  }
351
426
  /**
352
- * Check signaldock.db health table count, WAL mode, schema version.
427
+ * @deprecated Use ensureGlobalSignaldockDb(). Retained during T310 migration
428
+ * window for callers in init.ts and agent-registry-accessor.ts.
353
429
  *
354
- * Used by `cleo doctor` to verify signaldock.db integrity.
430
+ * When called WITHOUT arguments: forwards to ensureGlobalSignaldockDb().
431
+ * When called WITH a non-undefined `cwd` argument: throws a migration error.
355
432
  *
356
- * @returns Health report object or null if DB doesn't exist.
433
+ * @param cwd - Must be undefined. Any other value throws a migration error.
434
+ * @task T346
435
+ * @epic T310
357
436
  */
358
- export async function checkSignaldockDbHealth(cwd) {
359
- const dbPath = getSignaldockDbPath(cwd);
437
+ export async function ensureSignaldockDb(cwd) {
438
+ if (cwd !== undefined) {
439
+ throw new Error('ensureSignaldockDb(cwd) is removed as of T310 (v2026.4.12). ' +
440
+ 'signaldock.db is now global-only. ' +
441
+ 'Use ensureGlobalSignaldockDb() for global identity, or ' +
442
+ 'ensureConduitDb(cwd) from conduit-sqlite.ts for project messaging (T344).');
443
+ }
444
+ return ensureGlobalSignaldockDb();
445
+ }
446
+ /**
447
+ * Check global signaldock.db health: table count, WAL mode, schema version.
448
+ * Used by `cleo doctor` to verify global signaldock.db integrity.
449
+ *
450
+ * @returns Health report object, or object with exists=false if the DB does not exist.
451
+ * @task T346
452
+ * @epic T310
453
+ */
454
+ export async function checkGlobalSignaldockDbHealth() {
455
+ const dbPath = getGlobalSignaldockDbPath();
360
456
  if (!existsSync(dbPath)) {
361
457
  return {
362
458
  exists: false,
@@ -382,7 +478,7 @@ export async function checkSignaldockDbHealth(cwd) {
382
478
  schemaVersion = meta?.value ?? null;
383
479
  }
384
480
  catch {
385
- // Meta table may not exist
481
+ // Meta table may not exist on very old or partially-initialized DBs
386
482
  }
387
483
  return {
388
484
  exists: true,
@@ -397,4 +493,58 @@ export async function checkSignaldockDbHealth(cwd) {
397
493
  db.close();
398
494
  }
399
495
  }
496
+ /**
497
+ * @deprecated Use checkGlobalSignaldockDbHealth(). Retained during T310 migration
498
+ * window for callers in `cleo doctor` and other diagnostics.
499
+ *
500
+ * When called WITHOUT arguments: forwards to checkGlobalSignaldockDbHealth().
501
+ * When called WITH a non-undefined `cwd` argument: throws a migration error.
502
+ *
503
+ * @param cwd - Must be undefined. Any other value throws a migration error.
504
+ * @task T346
505
+ * @epic T310
506
+ */
507
+ export async function checkSignaldockDbHealth(cwd) {
508
+ if (cwd !== undefined) {
509
+ throw new Error('checkSignaldockDbHealth(cwd) is removed as of T310 (v2026.4.12). ' +
510
+ 'signaldock.db is now global-only. ' +
511
+ 'Use checkGlobalSignaldockDbHealth() for global signaldock health, or ' +
512
+ 'checkConduitDbHealth(cwd) from conduit-sqlite.ts for project conduit health (T344).');
513
+ }
514
+ return checkGlobalSignaldockDbHealth();
515
+ }
516
+ /**
517
+ * Get the underlying node:sqlite DatabaseSync instance for global signaldock.db.
518
+ * Returns the handle stored by the most recent ensureGlobalSignaldockDb() call,
519
+ * or null if the database has not yet been initialized in this process.
520
+ *
521
+ * Used by sqlite-backup.ts to activate the signaldock GLOBAL_SNAPSHOT_TARGET
522
+ * (spec §6.2, T310).
523
+ *
524
+ * @task T346
525
+ * @epic T310
526
+ */
527
+ export function getGlobalSignaldockNativeDb() {
528
+ return _globalSignaldockNativeDb;
529
+ }
530
+ /**
531
+ * Reset the in-process global signaldock.db singleton.
532
+ * ONLY for use in test isolation — never call in production code.
533
+ *
534
+ * @task T346
535
+ * @epic T310
536
+ */
537
+ export function _resetGlobalSignaldockDb_TESTING_ONLY() {
538
+ if (_globalSignaldockNativeDb) {
539
+ try {
540
+ if (_globalSignaldockNativeDb.isOpen) {
541
+ _globalSignaldockNativeDb.close();
542
+ }
543
+ }
544
+ catch {
545
+ // Ignore close errors
546
+ }
547
+ _globalSignaldockNativeDb = null;
548
+ }
549
+ }
400
550
  //# sourceMappingURL=signaldock-sqlite.js.map