@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,570 @@
1
+ /**
2
+ * SQLite store for conduit.db — project-tier messaging and agent-ref database.
3
+ *
4
+ * Creates and manages .cleo/conduit.db using node:sqlite directly.
5
+ * Applies the full conduit.db DDL (from spec §2.1) to bootstrap all
6
+ * project-local messaging tables and the project_agent_refs override table.
7
+ *
8
+ * Architecture (ADR-037):
9
+ * conduit.db — project-scoped (this module) — messaging, delivery, attachments,
10
+ * project_agent_refs
11
+ * signaldock.db — global-scoped (T346) — agents, capabilities, cloud-sync tables
12
+ *
13
+ * CRUD accessors for project_agent_refs land in T353.
14
+ * Cross-DB join accessor changes land in T355.
15
+ * Migration executor from signaldock.db → conduit.db lands in T358.
16
+ *
17
+ * @task T344
18
+ * @epic T310
19
+ * @why ADR-037 splits single signaldock.db into project-tier conduit.db
20
+ * (this module) and global-tier signaldock.db (T346). This module owns
21
+ * the project-tier path helper, initializer, schema DDL, and health check.
22
+ * @what Path helper, database initializer, schema applier, health check, and
23
+ * native DB accessor for project-local tables. No CRUD, no migrations.
24
+ */
25
+ import { existsSync, mkdirSync } from 'node:fs';
26
+ import { createRequire } from 'node:module';
27
+ import { dirname, join } from 'node:path';
28
+ const _require = createRequire(import.meta.url);
29
+ const { DatabaseSync } = _require('node:sqlite');
30
+ /** Database file name within .cleo/ directory. */
31
+ export const CONDUIT_DB_FILENAME = 'conduit.db';
32
+ /** Schema version for conduit.db — updated when DDL changes. */
33
+ export const CONDUIT_SCHEMA_VERSION = '2026.4.12';
34
+ // ---------------------------------------------------------------------------
35
+ // Singleton state
36
+ // ---------------------------------------------------------------------------
37
+ let _conduitNativeDb = null;
38
+ let _conduitDbPath = null;
39
+ // ---------------------------------------------------------------------------
40
+ // DDL
41
+ // ---------------------------------------------------------------------------
42
+ /**
43
+ * Full conduit.db schema SQL.
44
+ *
45
+ * All tables use CREATE TABLE IF NOT EXISTS / CREATE INDEX IF NOT EXISTS /
46
+ * CREATE TRIGGER IF NOT EXISTS for idempotency. Carried over verbatim from
47
+ * the project-local tables in signaldock-sqlite.ts (migration
48
+ * `2026-03-28-000000_initial` + subsequent migrations), minus the global-
49
+ * identity tables (agents, capabilities, skills, agent_capabilities,
50
+ * agent_skills, agent_connections, users, organization, accounts, sessions,
51
+ * verifications, claim_codes, org_agent_keys) which move to global-tier
52
+ * signaldock.db (T346).
53
+ *
54
+ * Additional new table: project_agent_refs (ADR-037 §3, Q6=A).
55
+ *
56
+ * NOTE: The `connections` table from the original migration is a cross-agent
57
+ * social graph that references `agents(id)` — it is a global-identity
58
+ * concern and stays with signaldock.db (T346). It is NOT included here.
59
+ */
60
+ const CONDUIT_SCHEMA_SQL = `
61
+ -- -------------------------------------------------------------------------
62
+ -- Project-scoped conversations (LocalTransport DM threads).
63
+ -- -------------------------------------------------------------------------
64
+ CREATE TABLE IF NOT EXISTS conversations (
65
+ id TEXT PRIMARY KEY,
66
+ participants TEXT NOT NULL,
67
+ visibility TEXT NOT NULL DEFAULT 'private',
68
+ message_count INTEGER NOT NULL DEFAULT 0,
69
+ last_message_at INTEGER,
70
+ created_at INTEGER NOT NULL,
71
+ updated_at INTEGER NOT NULL
72
+ );
73
+
74
+ -- -------------------------------------------------------------------------
75
+ -- Project-scoped agent-to-agent messages (LocalTransport content).
76
+ -- -------------------------------------------------------------------------
77
+ CREATE TABLE IF NOT EXISTS messages (
78
+ id TEXT PRIMARY KEY,
79
+ conversation_id TEXT NOT NULL REFERENCES conversations(id),
80
+ from_agent_id TEXT NOT NULL,
81
+ to_agent_id TEXT NOT NULL,
82
+ content TEXT NOT NULL,
83
+ content_type TEXT NOT NULL DEFAULT 'text',
84
+ status TEXT NOT NULL DEFAULT 'pending',
85
+ attachments TEXT NOT NULL DEFAULT '[]',
86
+ group_id TEXT,
87
+ metadata TEXT DEFAULT '{}',
88
+ reply_to TEXT,
89
+ created_at INTEGER NOT NULL,
90
+ delivered_at INTEGER,
91
+ 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
+
100
+ -- -------------------------------------------------------------------------
101
+ -- FTS5 virtual table for full-text search on message content.
102
+ -- NOTE: Must be migrated using VACUUM INTO, not DDL-only copy, to preserve
103
+ -- triggers. The INSERT INTO messages_fts(messages_fts) VALUES('rebuild')
104
+ -- is idempotent — safe to run on every open.
105
+ -- -------------------------------------------------------------------------
106
+ CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts
107
+ USING fts5(content, from_agent_id, content='messages', content_rowid='rowid');
108
+ INSERT INTO messages_fts(messages_fts) VALUES('rebuild');
109
+ CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN
110
+ INSERT INTO messages_fts(rowid, content, from_agent_id)
111
+ VALUES (new.rowid, new.content, new.from_agent_id);
112
+ END;
113
+ CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN
114
+ INSERT INTO messages_fts(messages_fts, rowid, content, from_agent_id)
115
+ VALUES('delete', old.rowid, old.content, old.from_agent_id);
116
+ END;
117
+ CREATE TRIGGER IF NOT EXISTS messages_au AFTER UPDATE ON messages BEGIN
118
+ INSERT INTO messages_fts(messages_fts, rowid, content, from_agent_id)
119
+ VALUES('delete', old.rowid, old.content, old.from_agent_id);
120
+ INSERT INTO messages_fts(rowid, content, from_agent_id)
121
+ VALUES (new.rowid, new.content, new.from_agent_id);
122
+ END;
123
+
124
+ -- -------------------------------------------------------------------------
125
+ -- Async delivery queue for deferred message dispatch.
126
+ -- -------------------------------------------------------------------------
127
+ CREATE TABLE IF NOT EXISTS delivery_jobs (
128
+ id TEXT PRIMARY KEY,
129
+ message_id TEXT NOT NULL,
130
+ payload TEXT NOT NULL,
131
+ status TEXT NOT NULL DEFAULT 'pending',
132
+ attempts INTEGER NOT NULL DEFAULT 0,
133
+ max_attempts INTEGER NOT NULL DEFAULT 6,
134
+ next_attempt_at INTEGER NOT NULL,
135
+ last_error TEXT,
136
+ created_at INTEGER NOT NULL,
137
+ updated_at INTEGER NOT NULL
138
+ );
139
+ CREATE INDEX IF NOT EXISTS idx_delivery_jobs_status ON delivery_jobs(status, next_attempt_at);
140
+
141
+ -- -------------------------------------------------------------------------
142
+ -- Dead-letter queue for messages that exceeded max delivery attempts.
143
+ -- -------------------------------------------------------------------------
144
+ CREATE TABLE IF NOT EXISTS dead_letters (
145
+ id TEXT PRIMARY KEY,
146
+ message_id TEXT NOT NULL,
147
+ job_id TEXT NOT NULL,
148
+ reason TEXT NOT NULL,
149
+ attempts INTEGER NOT NULL,
150
+ created_at INTEGER NOT NULL
151
+ );
152
+ CREATE INDEX IF NOT EXISTS idx_dead_letters_message ON dead_letters(message_id);
153
+
154
+ -- -------------------------------------------------------------------------
155
+ -- Pinned messages within a conversation.
156
+ -- -------------------------------------------------------------------------
157
+ CREATE TABLE IF NOT EXISTS message_pins (
158
+ id TEXT PRIMARY KEY,
159
+ message_id TEXT NOT NULL,
160
+ conversation_id TEXT NOT NULL,
161
+ pinned_by TEXT NOT NULL,
162
+ note TEXT,
163
+ created_at INTEGER NOT NULL,
164
+ UNIQUE(message_id, pinned_by)
165
+ );
166
+ CREATE INDEX IF NOT EXISTS idx_pins_conversation ON message_pins(conversation_id);
167
+ CREATE INDEX IF NOT EXISTS idx_pins_agent ON message_pins(pinned_by);
168
+
169
+ -- -------------------------------------------------------------------------
170
+ -- File/blob attachments associated with messages.
171
+ -- -------------------------------------------------------------------------
172
+ CREATE TABLE IF NOT EXISTS attachments (
173
+ slug TEXT PRIMARY KEY,
174
+ conversation_id TEXT NOT NULL,
175
+ from_agent_id TEXT NOT NULL,
176
+ content BLOB NOT NULL,
177
+ original_size INTEGER NOT NULL,
178
+ compressed_size INTEGER NOT NULL,
179
+ content_hash TEXT NOT NULL,
180
+ format TEXT NOT NULL DEFAULT 'text',
181
+ title TEXT,
182
+ tokens INTEGER NOT NULL DEFAULT 0,
183
+ expires_at INTEGER NOT NULL DEFAULT 0,
184
+ storage_key TEXT,
185
+ mode TEXT NOT NULL DEFAULT 'draft',
186
+ version_count INTEGER NOT NULL DEFAULT 1,
187
+ current_version INTEGER NOT NULL DEFAULT 1,
188
+ created_at INTEGER NOT NULL
189
+ );
190
+ CREATE INDEX IF NOT EXISTS attachments_conversation_idx ON attachments(conversation_id);
191
+ CREATE INDEX IF NOT EXISTS attachments_agent_idx ON attachments(from_agent_id);
192
+
193
+ -- -------------------------------------------------------------------------
194
+ -- Version history for attachments (collaborative editing).
195
+ -- -------------------------------------------------------------------------
196
+ CREATE TABLE IF NOT EXISTS attachment_versions (
197
+ id TEXT PRIMARY KEY,
198
+ slug TEXT NOT NULL REFERENCES attachments(slug) ON DELETE CASCADE,
199
+ version_number INTEGER NOT NULL,
200
+ author_agent_id TEXT NOT NULL,
201
+ change_type TEXT NOT NULL DEFAULT 'patch',
202
+ patch_text TEXT,
203
+ storage_key TEXT NOT NULL,
204
+ content_hash TEXT NOT NULL,
205
+ original_size INTEGER NOT NULL,
206
+ compressed_size INTEGER NOT NULL,
207
+ tokens INTEGER NOT NULL,
208
+ change_summary TEXT,
209
+ sections_modified TEXT NOT NULL DEFAULT '[]',
210
+ tokens_added INTEGER NOT NULL DEFAULT 0,
211
+ tokens_removed INTEGER NOT NULL DEFAULT 0,
212
+ created_at INTEGER NOT NULL,
213
+ UNIQUE(slug, version_number)
214
+ );
215
+ CREATE INDEX IF NOT EXISTS idx_attachment_versions_slug ON attachment_versions(slug);
216
+ CREATE INDEX IF NOT EXISTS idx_attachment_versions_author ON attachment_versions(author_agent_id);
217
+
218
+ -- -------------------------------------------------------------------------
219
+ -- Approval records for attachment content review.
220
+ -- -------------------------------------------------------------------------
221
+ CREATE TABLE IF NOT EXISTS attachment_approvals (
222
+ id TEXT PRIMARY KEY,
223
+ slug TEXT NOT NULL REFERENCES attachments(slug) ON DELETE CASCADE,
224
+ reviewer_agent_id TEXT NOT NULL,
225
+ status TEXT NOT NULL DEFAULT 'pending',
226
+ comment TEXT,
227
+ version_reviewed INTEGER NOT NULL,
228
+ created_at INTEGER NOT NULL,
229
+ updated_at INTEGER NOT NULL,
230
+ UNIQUE(slug, reviewer_agent_id)
231
+ );
232
+ CREATE INDEX IF NOT EXISTS idx_attachment_approvals_slug ON attachment_approvals(slug);
233
+
234
+ -- -------------------------------------------------------------------------
235
+ -- Contributor statistics per attachment (who edited, how much).
236
+ -- -------------------------------------------------------------------------
237
+ CREATE TABLE IF NOT EXISTS attachment_contributors (
238
+ slug TEXT NOT NULL REFERENCES attachments(slug) ON DELETE CASCADE,
239
+ agent_id TEXT NOT NULL,
240
+ version_count INTEGER NOT NULL DEFAULT 0,
241
+ total_tokens_added INTEGER NOT NULL DEFAULT 0,
242
+ total_tokens_removed INTEGER NOT NULL DEFAULT 0,
243
+ first_contribution_at INTEGER NOT NULL,
244
+ last_contribution_at INTEGER NOT NULL,
245
+ PRIMARY KEY (slug, agent_id)
246
+ );
247
+
248
+ -- -------------------------------------------------------------------------
249
+ -- NEW: Per-project agent reference overrides (ADR-037 §3, Q6=A).
250
+ -- agent_id is a SOFT FK to global signaldock.db:agents.agent_id.
251
+ -- Cross-DB FK enforcement is not possible in SQLite; the accessor layer
252
+ -- (T355) validates on every cross-DB join.
253
+ -- -------------------------------------------------------------------------
254
+ CREATE TABLE IF NOT EXISTS project_agent_refs (
255
+ agent_id TEXT PRIMARY KEY,
256
+ attached_at TEXT NOT NULL,
257
+ role TEXT,
258
+ capabilities_override TEXT,
259
+ last_used_at TEXT,
260
+ enabled INTEGER NOT NULL DEFAULT 1
261
+ );
262
+ -- Partial index: covers the dominant query path (list enabled agents).
263
+ CREATE INDEX IF NOT EXISTS idx_project_agent_refs_enabled
264
+ ON project_agent_refs(enabled) WHERE enabled = 1;
265
+
266
+ -- -------------------------------------------------------------------------
267
+ -- Schema tracking tables (mirrors _signaldock_meta / _signaldock_migrations).
268
+ -- -------------------------------------------------------------------------
269
+ CREATE TABLE IF NOT EXISTS _conduit_meta (
270
+ key TEXT PRIMARY KEY,
271
+ value TEXT NOT NULL,
272
+ updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
273
+ );
274
+ CREATE TABLE IF NOT EXISTS _conduit_migrations (
275
+ name TEXT PRIMARY KEY,
276
+ applied_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
277
+ );
278
+ `;
279
+ // ---------------------------------------------------------------------------
280
+ // Public API
281
+ // ---------------------------------------------------------------------------
282
+ /**
283
+ * Returns the project-tier conduit.db path.
284
+ *
285
+ * Always resolves to `<projectRoot>/.cleo/conduit.db`. The caller is
286
+ * responsible for supplying the absolute project root (e.g. via
287
+ * `getProjectRoot()` from `../paths.js`).
288
+ *
289
+ * @task T344
290
+ * @epic T310
291
+ * @param projectRoot - Absolute path to the project root directory.
292
+ * @returns Absolute path to `<projectRoot>/.cleo/conduit.db`.
293
+ */
294
+ export function getConduitDbPath(projectRoot) {
295
+ return join(projectRoot, '.cleo', CONDUIT_DB_FILENAME);
296
+ }
297
+ /**
298
+ * Applies the conduit.db schema idempotently using CREATE TABLE IF NOT EXISTS.
299
+ *
300
+ * Exposed for the migration executor (T358) which needs to apply the schema
301
+ * to a newly created conduit.db during the signaldock.db → conduit.db
302
+ * migration. Also called internally by `ensureConduitDb` on every open.
303
+ *
304
+ * @task T344
305
+ * @epic T310
306
+ * @param db - An open node:sqlite DatabaseSync instance.
307
+ */
308
+ export function applyConduitSchema(db) {
309
+ db.exec(CONDUIT_SCHEMA_SQL);
310
+ }
311
+ /**
312
+ * Opens or creates conduit.db for the given project root.
313
+ *
314
+ * On first call for a given projectRoot:
315
+ * 1. Creates `<projectRoot>/.cleo/` directory if missing.
316
+ * 2. Opens (or creates) the SQLite file.
317
+ * 3. Sets WAL mode and enables foreign keys.
318
+ * 4. Applies all DDL via `applyConduitSchema` (idempotent).
319
+ * 5. Records `schema_version` in `_conduit_meta`.
320
+ * 6. Records the initial migration in `_conduit_migrations`.
321
+ * 7. Stores the open handle in the module singleton.
322
+ *
323
+ * On subsequent calls the existing singleton is returned immediately if the
324
+ * resolved path matches; otherwise the previous handle is closed and a new
325
+ * one is opened (test-isolation safety).
326
+ *
327
+ * Caller MUST call `closeConduitDb()` when done to release the handle.
328
+ *
329
+ * @task T344
330
+ * @epic T310
331
+ * @param projectRoot - Absolute path to the project root directory.
332
+ * @returns Object with `action` (`'created'` | `'exists'`) and `path`.
333
+ */
334
+ export function ensureConduitDb(projectRoot) {
335
+ const dbPath = getConduitDbPath(projectRoot);
336
+ // If singleton already open at the same path, skip re-initialization.
337
+ if (_conduitNativeDb && _conduitDbPath === dbPath) {
338
+ return { action: 'exists', path: dbPath };
339
+ }
340
+ // Close any stale singleton pointing at a different path (e.g. between tests).
341
+ if (_conduitNativeDb) {
342
+ closeConduitDb();
343
+ }
344
+ const alreadyExists = existsSync(dbPath);
345
+ // Ensure parent .cleo/ directory exists.
346
+ mkdirSync(dirname(dbPath), { recursive: true });
347
+ const db = new DatabaseSync(dbPath);
348
+ db.exec('PRAGMA journal_mode = WAL');
349
+ db.exec('PRAGMA busy_timeout = 5000');
350
+ db.exec('PRAGMA synchronous = NORMAL');
351
+ db.exec('PRAGMA foreign_keys = ON');
352
+ db.exec('PRAGMA cache_size = -64000'); // 64 MB
353
+ // Check whether the schema sentinel table already exists before applying DDL.
354
+ const hasSchema = (() => {
355
+ try {
356
+ const result = db
357
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='conversations'")
358
+ .get();
359
+ return !!result;
360
+ }
361
+ catch {
362
+ return false;
363
+ }
364
+ })();
365
+ // Apply schema (idempotent — all statements use IF NOT EXISTS).
366
+ applyConduitSchema(db);
367
+ // Record schema version and initial migration.
368
+ db.exec(`INSERT OR REPLACE INTO _conduit_meta (key, value, updated_at)
369
+ VALUES ('schema_version', '${CONDUIT_SCHEMA_VERSION}', strftime('%s', 'now'))`);
370
+ db.prepare(`INSERT OR IGNORE INTO _conduit_migrations (name, applied_at)
371
+ VALUES (?, strftime('%s', 'now'))`).run('2026-04-12-000000_initial_conduit');
372
+ _conduitNativeDb = db;
373
+ _conduitDbPath = dbPath;
374
+ return {
375
+ action: alreadyExists && hasSchema ? 'exists' : 'created',
376
+ path: dbPath,
377
+ };
378
+ }
379
+ /**
380
+ * Returns the live node:sqlite DatabaseSync handle for conduit.db.
381
+ *
382
+ * Returns `null` if `ensureConduitDb` has not been called yet for this
383
+ * process, or if `closeConduitDb` has been called since the last open.
384
+ *
385
+ * @task T344
386
+ * @epic T310
387
+ * @returns The open DatabaseSync instance, or `null` if not initialized.
388
+ */
389
+ export function getConduitNativeDb() {
390
+ return _conduitNativeDb;
391
+ }
392
+ /**
393
+ * Closes the conduit.db connection and resets the module singleton.
394
+ *
395
+ * Safe to call multiple times. No-op if the database is already closed.
396
+ *
397
+ * @task T344
398
+ * @epic T310
399
+ */
400
+ export function closeConduitDb() {
401
+ if (_conduitNativeDb) {
402
+ try {
403
+ if (_conduitNativeDb.isOpen) {
404
+ _conduitNativeDb.close();
405
+ }
406
+ }
407
+ catch {
408
+ // Ignore close errors — the handle is being discarded regardless.
409
+ }
410
+ _conduitNativeDb = null;
411
+ }
412
+ _conduitDbPath = null;
413
+ }
414
+ // ---------------------------------------------------------------------------
415
+ // project_agent_refs CRUD accessors (T353)
416
+ // ---------------------------------------------------------------------------
417
+ /**
418
+ * Attaches an agent to the current project. If a row exists with enabled=0,
419
+ * re-enables it (update attached_at timestamp). If a row exists with enabled=1,
420
+ * no-op. Inserts a new row otherwise.
421
+ *
422
+ * @param db - conduit.db handle (from ensureConduitDb).
423
+ * @param agentId - Global signaldock.db:agents.id (soft FK, not validated here).
424
+ * @param opts - Optional role and capabilities override.
425
+ * @task T353
426
+ * @epic T310
427
+ */
428
+ export function attachAgentToProject(db, agentId, opts) {
429
+ const now = new Date().toISOString();
430
+ db.prepare(`INSERT INTO project_agent_refs (agent_id, attached_at, role, capabilities_override, last_used_at, enabled)
431
+ VALUES (?, ?, ?, ?, NULL, 1)
432
+ ON CONFLICT(agent_id) DO UPDATE SET
433
+ enabled = 1,
434
+ attached_at = CASE WHEN project_agent_refs.enabled = 0 THEN excluded.attached_at ELSE project_agent_refs.attached_at END,
435
+ role = excluded.role,
436
+ capabilities_override = excluded.capabilities_override`).run(agentId, now, opts?.role ?? null, opts?.capabilitiesOverride ?? null);
437
+ }
438
+ /**
439
+ * Detaches an agent from the current project by setting enabled=0.
440
+ * Does NOT delete the row (preserves attachment history for audit).
441
+ *
442
+ * @param db - conduit.db handle (from ensureConduitDb).
443
+ * @param agentId - Agent ID to detach.
444
+ * @task T353
445
+ * @epic T310
446
+ */
447
+ export function detachAgentFromProject(db, agentId) {
448
+ db.prepare(`UPDATE project_agent_refs SET enabled = 0 WHERE agent_id = ?`).run(agentId);
449
+ }
450
+ /**
451
+ * Lists project_agent_refs rows. By default returns only enabled=1 rows.
452
+ * Pass enabledOnly=false to return all rows regardless of enabled state.
453
+ *
454
+ * @param db - conduit.db handle (from ensureConduitDb).
455
+ * @param opts - Filter options. Defaults to `{ enabledOnly: true }`.
456
+ * @returns Array of ProjectAgentRef rows ordered by attached_at DESC.
457
+ * @task T353
458
+ * @epic T310
459
+ */
460
+ export function listProjectAgentRefs(db, opts) {
461
+ const enabledOnly = opts?.enabledOnly ?? true;
462
+ const sql = enabledOnly
463
+ ? `SELECT agent_id, attached_at, role, capabilities_override, last_used_at, enabled
464
+ FROM project_agent_refs WHERE enabled = 1
465
+ ORDER BY attached_at DESC`
466
+ : `SELECT agent_id, attached_at, role, capabilities_override, last_used_at, enabled
467
+ FROM project_agent_refs
468
+ ORDER BY attached_at DESC`;
469
+ const rows = db.prepare(sql).all();
470
+ return rows.map((r) => ({
471
+ agentId: r.agent_id,
472
+ attachedAt: r.attached_at,
473
+ role: r.role,
474
+ capabilitiesOverride: r.capabilities_override,
475
+ lastUsedAt: r.last_used_at,
476
+ enabled: r.enabled,
477
+ }));
478
+ }
479
+ /**
480
+ * Returns a single project_agent_refs row by agentId, or null if not found.
481
+ *
482
+ * @param db - conduit.db handle (from ensureConduitDb).
483
+ * @param agentId - Agent ID to look up.
484
+ * @returns The ProjectAgentRef row, or null if the agent is not attached.
485
+ * @task T353
486
+ * @epic T310
487
+ */
488
+ export function getProjectAgentRef(db, agentId) {
489
+ const row = db
490
+ .prepare(`SELECT agent_id, attached_at, role, capabilities_override, last_used_at, enabled
491
+ FROM project_agent_refs WHERE agent_id = ?`)
492
+ .get(agentId);
493
+ if (!row)
494
+ return null;
495
+ return {
496
+ agentId: row.agent_id,
497
+ attachedAt: row.attached_at,
498
+ role: row.role,
499
+ capabilitiesOverride: row.capabilities_override,
500
+ lastUsedAt: row.last_used_at,
501
+ enabled: row.enabled,
502
+ };
503
+ }
504
+ /**
505
+ * Updates the last_used_at timestamp for an agent to now.
506
+ * No-op if the agent_id does not exist in project_agent_refs.
507
+ *
508
+ * @param db - conduit.db handle (from ensureConduitDb).
509
+ * @param agentId - Agent ID to update.
510
+ * @task T353
511
+ * @epic T310
512
+ */
513
+ export function updateProjectAgentLastUsed(db, agentId) {
514
+ db.prepare(`UPDATE project_agent_refs SET last_used_at = ? WHERE agent_id = ?`).run(new Date().toISOString(), agentId);
515
+ }
516
+ /**
517
+ * Checks conduit.db health — table count, WAL mode, schema version, and
518
+ * foreign keys status.
519
+ *
520
+ * Used by `cleo doctor` to verify conduit.db integrity. Does NOT require
521
+ * `ensureConduitDb` to have been called; opens and closes the DB internally.
522
+ *
523
+ * @task T344
524
+ * @epic T310
525
+ * @param projectRoot - Absolute path to the project root directory.
526
+ * @returns Health report object. `exists: false` when conduit.db is absent.
527
+ */
528
+ export function checkConduitDbHealth(projectRoot) {
529
+ const dbPath = getConduitDbPath(projectRoot);
530
+ if (!existsSync(dbPath)) {
531
+ return {
532
+ exists: false,
533
+ path: dbPath,
534
+ tableCount: 0,
535
+ walMode: false,
536
+ schemaVersion: null,
537
+ foreignKeysEnabled: false,
538
+ };
539
+ }
540
+ const db = new DatabaseSync(dbPath);
541
+ try {
542
+ const tables = db
543
+ .prepare("SELECT COUNT(*) as count FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
544
+ .get();
545
+ const journalMode = db.prepare('PRAGMA journal_mode').get();
546
+ const fkEnabled = db.prepare('PRAGMA foreign_keys').get();
547
+ let schemaVersion = null;
548
+ try {
549
+ const meta = db
550
+ .prepare("SELECT value FROM _conduit_meta WHERE key = 'schema_version'")
551
+ .get();
552
+ schemaVersion = meta?.value ?? null;
553
+ }
554
+ catch {
555
+ // _conduit_meta may not exist on a partially-initialized DB.
556
+ }
557
+ return {
558
+ exists: true,
559
+ path: dbPath,
560
+ tableCount: tables.count,
561
+ walMode: journalMode.journal_mode === 'wal',
562
+ schemaVersion,
563
+ foreignKeysEnabled: fkEnabled.foreign_keys === 1,
564
+ };
565
+ }
566
+ finally {
567
+ db.close();
568
+ }
569
+ }
570
+ //# sourceMappingURL=conduit-sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conduit-sqlite.js","sourceRoot":"","sources":["../../src/store/conduit-sqlite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Global-salt subsystem for the CLEO API key KDF.
3
+ *
4
+ * @task T348
5
+ * @epic T310
6
+ * @why ADR-037 §5 — API key KDF uses machine-key + global-salt + agentId.
7
+ * global-salt must persist across process restarts but is machine-local.
8
+ * @what Atomic first-run generation, memoized read, permission/size validation.
9
+ */
10
+ /** Filename for the global salt file under CLEO home. */
11
+ export declare const GLOBAL_SALT_FILENAME = "global-salt";
12
+ /** Required size of the global salt in bytes. */
13
+ export declare const GLOBAL_SALT_SIZE = 32;
14
+ /**
15
+ * Returns the absolute path to the global-salt file.
16
+ *
17
+ * @returns Absolute path: `{cleoHome}/global-salt`
18
+ *
19
+ * @task T348
20
+ * @epic T310
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const saltPath = getGlobalSaltPath();
25
+ * // Linux: "/home/user/.local/share/cleo/global-salt"
26
+ * ```
27
+ */
28
+ export declare function getGlobalSaltPath(): string;
29
+ /**
30
+ * Returns the 32-byte global salt. Generates and persists atomically on first
31
+ * call when the file does not exist. Subsequent calls return the memoized value.
32
+ *
33
+ * Never overwrites an existing salt — doing so would invalidate every stored
34
+ * API key derived from it.
35
+ *
36
+ * @returns A 32-byte Buffer containing the global salt
37
+ * @throws {Error} If the salt file exists with wrong size or wrong permissions
38
+ *
39
+ * @task T348
40
+ * @epic T310
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const salt = getGlobalSalt(); // Buffer(32) [...]
45
+ * ```
46
+ */
47
+ export declare function getGlobalSalt(): Buffer;
48
+ /**
49
+ * Runtime validation helper for startup integrity checks.
50
+ *
51
+ * Throws if the salt file exists but is malformed (wrong size or permissions).
52
+ * Safe to call when the file does not yet exist — returns silently in that case
53
+ * because first-run generation is handled lazily by `getGlobalSalt()`.
54
+ *
55
+ * @throws {Error} If the salt file exists with wrong size or wrong permissions
56
+ *
57
+ * @task T348
58
+ * @epic T310
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * // Called at process startup to catch accidental salt corruption early
63
+ * validateGlobalSalt();
64
+ * ```
65
+ */
66
+ export declare function validateGlobalSalt(): void;
67
+ /**
68
+ * Clears the in-process memoization cache so tests can exercise the
69
+ * first-call generation path independently.
70
+ *
71
+ * @internal TEST ONLY — do NOT export through internal.ts or re-export
72
+ * from any public barrel. This symbol must never appear in production call paths.
73
+ *
74
+ * @task T348
75
+ * @epic T310
76
+ */
77
+ export declare function __clearGlobalSaltCache(): void;
78
+ //# sourceMappingURL=global-salt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-salt.d.ts","sourceRoot":"","sources":["../../src/store/global-salt.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,yDAAyD;AACzD,eAAO,MAAM,oBAAoB,gBAAgB,CAAC;AAElD,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAWnC;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAkDtC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAwBzC;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}