society-protocol 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/dist/adapters.d.ts +101 -0
  4. package/dist/adapters.d.ts.map +1 -0
  5. package/dist/adapters.js +764 -0
  6. package/dist/adapters.js.map +1 -0
  7. package/dist/agents-md.d.ts +59 -0
  8. package/dist/agents-md.d.ts.map +1 -0
  9. package/dist/agents-md.js +204 -0
  10. package/dist/agents-md.js.map +1 -0
  11. package/dist/autoconfig.d.ts +137 -0
  12. package/dist/autoconfig.d.ts.map +1 -0
  13. package/dist/autoconfig.js +452 -0
  14. package/dist/autoconfig.js.map +1 -0
  15. package/dist/bootstrap.d.ts +68 -0
  16. package/dist/bootstrap.d.ts.map +1 -0
  17. package/dist/bootstrap.js +304 -0
  18. package/dist/bootstrap.js.map +1 -0
  19. package/dist/bridges/a2a-bridge.d.ts +156 -0
  20. package/dist/bridges/a2a-bridge.d.ts.map +1 -0
  21. package/dist/bridges/a2a-bridge.js +337 -0
  22. package/dist/bridges/a2a-bridge.js.map +1 -0
  23. package/dist/bridges/mcp-bridge.d.ts +87 -0
  24. package/dist/bridges/mcp-bridge.d.ts.map +1 -0
  25. package/dist/bridges/mcp-bridge.js +332 -0
  26. package/dist/bridges/mcp-bridge.js.map +1 -0
  27. package/dist/cache.d.ts +130 -0
  28. package/dist/cache.d.ts.map +1 -0
  29. package/dist/cache.js +257 -0
  30. package/dist/cache.js.map +1 -0
  31. package/dist/capsules.d.ts +23 -0
  32. package/dist/capsules.d.ts.map +1 -0
  33. package/dist/capsules.js +75 -0
  34. package/dist/capsules.js.map +1 -0
  35. package/dist/cli/commands.d.ts +8 -0
  36. package/dist/cli/commands.d.ts.map +1 -0
  37. package/dist/cli/commands.js +263 -0
  38. package/dist/cli/commands.js.map +1 -0
  39. package/dist/coc.d.ts +121 -0
  40. package/dist/coc.d.ts.map +1 -0
  41. package/dist/coc.js +629 -0
  42. package/dist/coc.js.map +1 -0
  43. package/dist/coc.test.d.ts +2 -0
  44. package/dist/coc.test.d.ts.map +1 -0
  45. package/dist/coc.test.js +80 -0
  46. package/dist/coc.test.js.map +1 -0
  47. package/dist/compression.d.ts +125 -0
  48. package/dist/compression.d.ts.map +1 -0
  49. package/dist/compression.js +573 -0
  50. package/dist/compression.js.map +1 -0
  51. package/dist/cot-stream.d.ts +220 -0
  52. package/dist/cot-stream.d.ts.map +1 -0
  53. package/dist/cot-stream.js +673 -0
  54. package/dist/cot-stream.js.map +1 -0
  55. package/dist/crypto-wasm.d.ts +100 -0
  56. package/dist/crypto-wasm.d.ts.map +1 -0
  57. package/dist/crypto-wasm.js +229 -0
  58. package/dist/crypto-wasm.js.map +1 -0
  59. package/dist/federation.d.ts +200 -0
  60. package/dist/federation.d.ts.map +1 -0
  61. package/dist/federation.js +691 -0
  62. package/dist/federation.js.map +1 -0
  63. package/dist/federation.test.d.ts +2 -0
  64. package/dist/federation.test.d.ts.map +1 -0
  65. package/dist/federation.test.js +71 -0
  66. package/dist/federation.test.js.map +1 -0
  67. package/dist/gateway/capability-router.d.ts +77 -0
  68. package/dist/gateway/capability-router.d.ts.map +1 -0
  69. package/dist/gateway/capability-router.js +222 -0
  70. package/dist/gateway/capability-router.js.map +1 -0
  71. package/dist/gateway/demand-spawner.d.ts +155 -0
  72. package/dist/gateway/demand-spawner.d.ts.map +1 -0
  73. package/dist/gateway/demand-spawner.js +426 -0
  74. package/dist/gateway/demand-spawner.js.map +1 -0
  75. package/dist/identity.d.ts +46 -0
  76. package/dist/identity.d.ts.map +1 -0
  77. package/dist/identity.js +102 -0
  78. package/dist/identity.js.map +1 -0
  79. package/dist/identity.test.d.ts +2 -0
  80. package/dist/identity.test.d.ts.map +1 -0
  81. package/dist/identity.test.js +45 -0
  82. package/dist/identity.test.js.map +1 -0
  83. package/dist/index.d.ts +36 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +1572 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/integration.d.ts +210 -0
  88. package/dist/integration.d.ts.map +1 -0
  89. package/dist/integration.js +1105 -0
  90. package/dist/integration.js.map +1 -0
  91. package/dist/integration.test.d.ts +2 -0
  92. package/dist/integration.test.d.ts.map +1 -0
  93. package/dist/integration.test.js +155 -0
  94. package/dist/integration.test.js.map +1 -0
  95. package/dist/knowledge.d.ts +219 -0
  96. package/dist/knowledge.d.ts.map +1 -0
  97. package/dist/knowledge.js +543 -0
  98. package/dist/knowledge.js.map +1 -0
  99. package/dist/knowledge.test.d.ts +2 -0
  100. package/dist/knowledge.test.d.ts.map +1 -0
  101. package/dist/knowledge.test.js +72 -0
  102. package/dist/knowledge.test.js.map +1 -0
  103. package/dist/latent-space.d.ts +178 -0
  104. package/dist/latent-space.d.ts.map +1 -0
  105. package/dist/latent-space.js +385 -0
  106. package/dist/latent-space.js.map +1 -0
  107. package/dist/lib.d.ts +30 -0
  108. package/dist/lib.d.ts.map +1 -0
  109. package/dist/lib.js +30 -0
  110. package/dist/lib.js.map +1 -0
  111. package/dist/mcp/server.d.ts +74 -0
  112. package/dist/mcp/server.d.ts.map +1 -0
  113. package/dist/mcp/server.js +1392 -0
  114. package/dist/mcp/server.js.map +1 -0
  115. package/dist/metrics.d.ts +98 -0
  116. package/dist/metrics.d.ts.map +1 -0
  117. package/dist/metrics.js +222 -0
  118. package/dist/metrics.js.map +1 -0
  119. package/dist/p2p.d.ts +87 -0
  120. package/dist/p2p.d.ts.map +1 -0
  121. package/dist/p2p.js +606 -0
  122. package/dist/p2p.js.map +1 -0
  123. package/dist/persona/capabilities.d.ts +17 -0
  124. package/dist/persona/capabilities.d.ts.map +1 -0
  125. package/dist/persona/capabilities.js +224 -0
  126. package/dist/persona/capabilities.js.map +1 -0
  127. package/dist/persona/domains.d.ts +22 -0
  128. package/dist/persona/domains.d.ts.map +1 -0
  129. package/dist/persona/domains.js +176 -0
  130. package/dist/persona/domains.js.map +1 -0
  131. package/dist/persona/embeddings.d.ts +40 -0
  132. package/dist/persona/embeddings.d.ts.map +1 -0
  133. package/dist/persona/embeddings.js +265 -0
  134. package/dist/persona/embeddings.js.map +1 -0
  135. package/dist/persona/engine.d.ts +79 -0
  136. package/dist/persona/engine.d.ts.map +1 -0
  137. package/dist/persona/engine.js +1087 -0
  138. package/dist/persona/engine.js.map +1 -0
  139. package/dist/persona/index.d.ts +11 -0
  140. package/dist/persona/index.d.ts.map +1 -0
  141. package/dist/persona/index.js +11 -0
  142. package/dist/persona/index.js.map +1 -0
  143. package/dist/persona/lifecycle.d.ts +17 -0
  144. package/dist/persona/lifecycle.d.ts.map +1 -0
  145. package/dist/persona/lifecycle.js +36 -0
  146. package/dist/persona/lifecycle.js.map +1 -0
  147. package/dist/persona/retrieval.d.ts +6 -0
  148. package/dist/persona/retrieval.d.ts.map +1 -0
  149. package/dist/persona/retrieval.js +122 -0
  150. package/dist/persona/retrieval.js.map +1 -0
  151. package/dist/persona/sync.d.ts +15 -0
  152. package/dist/persona/sync.d.ts.map +1 -0
  153. package/dist/persona/sync.js +92 -0
  154. package/dist/persona/sync.js.map +1 -0
  155. package/dist/persona/types.d.ts +283 -0
  156. package/dist/persona/types.d.ts.map +1 -0
  157. package/dist/persona/types.js +2 -0
  158. package/dist/persona/types.js.map +1 -0
  159. package/dist/persona/zkp/engine.d.ts +26 -0
  160. package/dist/persona/zkp/engine.d.ts.map +1 -0
  161. package/dist/persona/zkp/engine.js +370 -0
  162. package/dist/persona/zkp/engine.js.map +1 -0
  163. package/dist/persona/zkp/types.d.ts +39 -0
  164. package/dist/persona/zkp/types.d.ts.map +1 -0
  165. package/dist/persona/zkp/types.js +2 -0
  166. package/dist/persona/zkp/types.js.map +1 -0
  167. package/dist/planner.d.ts +114 -0
  168. package/dist/planner.d.ts.map +1 -0
  169. package/dist/planner.js +522 -0
  170. package/dist/planner.js.map +1 -0
  171. package/dist/proactive/checkpoints.d.ts +9 -0
  172. package/dist/proactive/checkpoints.d.ts.map +1 -0
  173. package/dist/proactive/checkpoints.js +20 -0
  174. package/dist/proactive/checkpoints.js.map +1 -0
  175. package/dist/proactive/engine.d.ts +59 -0
  176. package/dist/proactive/engine.d.ts.map +1 -0
  177. package/dist/proactive/engine.js +406 -0
  178. package/dist/proactive/engine.js.map +1 -0
  179. package/dist/proactive/scheduler.d.ts +11 -0
  180. package/dist/proactive/scheduler.d.ts.map +1 -0
  181. package/dist/proactive/scheduler.js +45 -0
  182. package/dist/proactive/scheduler.js.map +1 -0
  183. package/dist/proactive/swarm-controller.d.ts +189 -0
  184. package/dist/proactive/swarm-controller.d.ts.map +1 -0
  185. package/dist/proactive/swarm-controller.js +477 -0
  186. package/dist/proactive/swarm-controller.js.map +1 -0
  187. package/dist/proactive/swarm-registry.d.ts +13 -0
  188. package/dist/proactive/swarm-registry.d.ts.map +1 -0
  189. package/dist/proactive/swarm-registry.js +122 -0
  190. package/dist/proactive/swarm-registry.js.map +1 -0
  191. package/dist/proactive/types.d.ts +145 -0
  192. package/dist/proactive/types.d.ts.map +1 -0
  193. package/dist/proactive/types.js +25 -0
  194. package/dist/proactive/types.js.map +1 -0
  195. package/dist/registry.d.ts +35 -0
  196. package/dist/registry.d.ts.map +1 -0
  197. package/dist/registry.js +88 -0
  198. package/dist/registry.js.map +1 -0
  199. package/dist/reputation.d.ts +123 -0
  200. package/dist/reputation.d.ts.map +1 -0
  201. package/dist/reputation.js +366 -0
  202. package/dist/reputation.js.map +1 -0
  203. package/dist/reputation.test.d.ts +5 -0
  204. package/dist/reputation.test.d.ts.map +1 -0
  205. package/dist/reputation.test.js +265 -0
  206. package/dist/reputation.test.js.map +1 -0
  207. package/dist/rooms.d.ts +96 -0
  208. package/dist/rooms.d.ts.map +1 -0
  209. package/dist/rooms.js +410 -0
  210. package/dist/rooms.js.map +1 -0
  211. package/dist/sdk/client.d.ts +290 -0
  212. package/dist/sdk/client.d.ts.map +1 -0
  213. package/dist/sdk/client.js +1287 -0
  214. package/dist/sdk/client.js.map +1 -0
  215. package/dist/sdk/index.d.ts +32 -0
  216. package/dist/sdk/index.d.ts.map +1 -0
  217. package/dist/sdk/index.js +70 -0
  218. package/dist/sdk/index.js.map +1 -0
  219. package/dist/security.d.ts +230 -0
  220. package/dist/security.d.ts.map +1 -0
  221. package/dist/security.js +652 -0
  222. package/dist/security.js.map +1 -0
  223. package/dist/skills/engine.d.ts +262 -0
  224. package/dist/skills/engine.d.ts.map +1 -0
  225. package/dist/skills/engine.js +788 -0
  226. package/dist/skills/engine.js.map +1 -0
  227. package/dist/skills/engine.test.d.ts +2 -0
  228. package/dist/skills/engine.test.d.ts.map +1 -0
  229. package/dist/skills/engine.test.js +134 -0
  230. package/dist/skills/engine.test.js.map +1 -0
  231. package/dist/skills/parser.d.ts +129 -0
  232. package/dist/skills/parser.d.ts.map +1 -0
  233. package/dist/skills/parser.js +318 -0
  234. package/dist/skills/parser.js.map +1 -0
  235. package/dist/social.d.ts +149 -0
  236. package/dist/social.d.ts.map +1 -0
  237. package/dist/social.js +401 -0
  238. package/dist/social.js.map +1 -0
  239. package/dist/storage-optimized.d.ts +116 -0
  240. package/dist/storage-optimized.d.ts.map +1 -0
  241. package/dist/storage-optimized.js +264 -0
  242. package/dist/storage-optimized.js.map +1 -0
  243. package/dist/storage.d.ts +584 -0
  244. package/dist/storage.d.ts.map +1 -0
  245. package/dist/storage.js +2703 -0
  246. package/dist/storage.js.map +1 -0
  247. package/dist/storage.test.d.ts +2 -0
  248. package/dist/storage.test.d.ts.map +1 -0
  249. package/dist/storage.test.js +78 -0
  250. package/dist/storage.test.js.map +1 -0
  251. package/dist/swp.d.ts +443 -0
  252. package/dist/swp.d.ts.map +1 -0
  253. package/dist/swp.js +223 -0
  254. package/dist/swp.js.map +1 -0
  255. package/dist/swp.test.d.ts +5 -0
  256. package/dist/swp.test.d.ts.map +1 -0
  257. package/dist/swp.test.js +127 -0
  258. package/dist/swp.test.js.map +1 -0
  259. package/dist/templates.d.ts +25 -0
  260. package/dist/templates.d.ts.map +1 -0
  261. package/dist/templates.js +1048 -0
  262. package/dist/templates.js.map +1 -0
  263. package/dist/test-e2e.d.ts +14 -0
  264. package/dist/test-e2e.d.ts.map +1 -0
  265. package/dist/test-e2e.js +266 -0
  266. package/dist/test-e2e.js.map +1 -0
  267. package/dist/workers/research-worker.d.ts +19 -0
  268. package/dist/workers/research-worker.d.ts.map +1 -0
  269. package/dist/workers/research-worker.js +141 -0
  270. package/dist/workers/research-worker.js.map +1 -0
  271. package/package.json +110 -0
@@ -0,0 +1,2703 @@
1
+ /**
2
+ * Society Protocol — SQLite Storage Module v1.0
3
+ *
4
+ * Local-first persistence: identity, rooms, messages, presence,
5
+ * CoC chains/steps/events, adapters, reputation, and artifact lineage.
6
+ * Uses better-sqlite3 for synchronous, fast SQLite access.
7
+ */
8
+ import Database from 'better-sqlite3';
9
+ import path from 'path';
10
+ import fs from 'fs';
11
+ import os from 'os';
12
+ import * as sqliteVec from 'sqlite-vec';
13
+ const SCHEMA_VERSION = 14;
14
+ export class Storage {
15
+ db;
16
+ constructor(options = {}) {
17
+ const dbPath = options.dbPath ?? path.join(os.homedir(), '.society', 'society.db');
18
+ const isProd = process.env.NODE_ENV === 'production';
19
+ // Ensure directory exists
20
+ const dir = path.dirname(dbPath);
21
+ if (!fs.existsSync(dir)) {
22
+ fs.mkdirSync(dir, { recursive: true });
23
+ }
24
+ this.db = new Database(dbPath);
25
+ const encryptionKey = options.encryptionKey || process.env.SOCIETY_DB_KEY;
26
+ const allowPlaintextFallback = options.allowPlaintextDev || process.env.SOCIETY_ALLOW_PLAINTEXT_DEV === '1';
27
+ if (isProd && !encryptionKey) {
28
+ throw new Error('SQLCipher key is required in production (SOCIETY_DB_KEY).');
29
+ }
30
+ if (encryptionKey) {
31
+ this.db.pragma(`key = '${String(encryptionKey).replace(/'/g, "''")}'`);
32
+ this.db.pragma('cipher_compatibility = 4');
33
+ const cipherVersion = this.db.pragma('cipher_version', { simple: true });
34
+ if (!cipherVersion || String(cipherVersion).trim().length === 0) {
35
+ throw new Error('SQLCipher is required but cipher_version is unavailable.');
36
+ }
37
+ }
38
+ else if (!allowPlaintextFallback) {
39
+ if (isProd) {
40
+ throw new Error('Plaintext database is not allowed in production.');
41
+ }
42
+ }
43
+ try {
44
+ const loader = sqliteVec?.load ||
45
+ sqliteVec?.default?.load;
46
+ if (typeof loader !== 'function') {
47
+ throw new Error('sqlite-vec load() not available');
48
+ }
49
+ loader(this.db);
50
+ }
51
+ catch (error) {
52
+ if (isProd) {
53
+ throw new Error(`sqlite-vec extension is required in production: ${error.message}`);
54
+ }
55
+ console.warn(`[storage] sqlite-vec unavailable, vector search degraded: ${error.message}`);
56
+ }
57
+ this.db.pragma('journal_mode = WAL');
58
+ this.db.pragma('foreign_keys = ON');
59
+ this.db.pragma('synchronous = NORMAL');
60
+ this.migrate();
61
+ }
62
+ /**
63
+ * Run schema migrations.
64
+ */
65
+ migrate() {
66
+ this.db.exec(`
67
+ CREATE TABLE IF NOT EXISTS schema_version (
68
+ version INTEGER NOT NULL
69
+ );
70
+ `);
71
+ const row = this.db.prepare('SELECT version FROM schema_version').get();
72
+ const currentVersion = row?.version ?? 0;
73
+ if (currentVersion < SCHEMA_VERSION) {
74
+ this.db.transaction(() => {
75
+ this.applyMigrations(currentVersion);
76
+ this.db.prepare('INSERT OR REPLACE INTO schema_version (version) VALUES (?)')
77
+ .run(SCHEMA_VERSION);
78
+ })();
79
+ }
80
+ }
81
+ applyMigrations(fromVersion) {
82
+ if (fromVersion < 1) {
83
+ this.db.exec(`
84
+ -- Identity: local keypair + profile
85
+ CREATE TABLE IF NOT EXISTS identity (
86
+ did TEXT PRIMARY KEY,
87
+ private_key_hex TEXT NOT NULL,
88
+ public_key_hex TEXT NOT NULL,
89
+ display_name TEXT NOT NULL,
90
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
91
+ );
92
+
93
+ -- Rooms
94
+ CREATE TABLE IF NOT EXISTS rooms (
95
+ room_id TEXT PRIMARY KEY,
96
+ name TEXT NOT NULL,
97
+ created_by TEXT NOT NULL,
98
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
99
+ FOREIGN KEY (created_by) REFERENCES identity(did)
100
+ );
101
+
102
+ -- Room members
103
+ CREATE TABLE IF NOT EXISTS room_members (
104
+ room_id TEXT NOT NULL,
105
+ member_did TEXT NOT NULL,
106
+ display_name TEXT,
107
+ joined_at INTEGER NOT NULL DEFAULT (unixepoch()),
108
+ PRIMARY KEY (room_id, member_did)
109
+ );
110
+
111
+ -- Messages (chat)
112
+ CREATE TABLE IF NOT EXISTS messages (
113
+ id TEXT PRIMARY KEY,
114
+ room_id TEXT NOT NULL,
115
+ from_did TEXT NOT NULL,
116
+ from_name TEXT,
117
+ text TEXT NOT NULL,
118
+ reply_to TEXT,
119
+ ts INTEGER NOT NULL,
120
+ received_at INTEGER NOT NULL DEFAULT (unixepoch()),
121
+ FOREIGN KEY (room_id) REFERENCES rooms(room_id)
122
+ );
123
+ CREATE INDEX IF NOT EXISTS idx_messages_room ON messages(room_id, ts);
124
+
125
+ -- Presence snapshots
126
+ CREATE TABLE IF NOT EXISTS presence (
127
+ peer_did TEXT NOT NULL,
128
+ peer_name TEXT,
129
+ status TEXT NOT NULL DEFAULT 'online',
130
+ capabilities TEXT,
131
+ load REAL,
132
+ last_seen INTEGER NOT NULL DEFAULT (unixepoch()),
133
+ room_id TEXT,
134
+ PRIMARY KEY (peer_did)
135
+ );
136
+
137
+ -- CoC chains
138
+ CREATE TABLE IF NOT EXISTS coc_chains (
139
+ chain_id TEXT PRIMARY KEY,
140
+ room_id TEXT NOT NULL,
141
+ goal TEXT NOT NULL,
142
+ template_id TEXT,
143
+ status TEXT NOT NULL DEFAULT 'open',
144
+ priority TEXT DEFAULT 'normal',
145
+ created_by TEXT NOT NULL,
146
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
147
+ closed_at INTEGER,
148
+ timeout_at INTEGER,
149
+ final_report TEXT,
150
+ FOREIGN KEY (room_id) REFERENCES rooms(room_id)
151
+ );
152
+
153
+ -- CoC steps (DAG nodes)
154
+ CREATE TABLE IF NOT EXISTS coc_steps (
155
+ step_id TEXT PRIMARY KEY,
156
+ chain_id TEXT NOT NULL,
157
+ kind TEXT NOT NULL,
158
+ title TEXT NOT NULL,
159
+ description TEXT,
160
+ depends_on TEXT,
161
+ requirements_json TEXT,
162
+ assignee_did TEXT,
163
+ status TEXT NOT NULL DEFAULT 'proposed',
164
+ lease_ms INTEGER,
165
+ lease_started_at INTEGER,
166
+ timeout_ms INTEGER,
167
+ memo TEXT,
168
+ artifacts_json TEXT,
169
+ metrics_json TEXT,
170
+ retry_count INTEGER DEFAULT 0,
171
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
172
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
173
+ FOREIGN KEY (chain_id) REFERENCES coc_chains(chain_id)
174
+ );
175
+ CREATE INDEX IF NOT EXISTS idx_coc_steps_chain ON coc_steps(chain_id);
176
+ CREATE INDEX IF NOT EXISTS idx_coc_steps_assignee ON coc_steps(assignee_did, status);
177
+
178
+ -- CoC events (audit log)
179
+ CREATE TABLE IF NOT EXISTS coc_events (
180
+ event_id TEXT PRIMARY KEY,
181
+ chain_id TEXT NOT NULL,
182
+ step_id TEXT,
183
+ event_type TEXT NOT NULL,
184
+ actor_did TEXT,
185
+ data TEXT,
186
+ ts INTEGER NOT NULL DEFAULT (unixepoch()),
187
+ FOREIGN KEY (chain_id) REFERENCES coc_chains(chain_id)
188
+ );
189
+ CREATE INDEX IF NOT EXISTS idx_coc_events_chain ON coc_events(chain_id, ts);
190
+
191
+ -- Adapters registry
192
+ CREATE TABLE IF NOT EXISTS adapters (
193
+ adapter_id TEXT PRIMARY KEY,
194
+ runtime TEXT NOT NULL,
195
+ display_name TEXT NOT NULL,
196
+ specialties TEXT,
197
+ kinds TEXT,
198
+ max_concurrency INTEGER DEFAULT 1,
199
+ endpoint TEXT NOT NULL,
200
+ auth_type TEXT DEFAULT 'none',
201
+ registered_at INTEGER NOT NULL DEFAULT (unixepoch()),
202
+ last_seen INTEGER
203
+ );
204
+
205
+ -- Replay cache (bounded LRU for SWP message deduplication)
206
+ CREATE TABLE IF NOT EXISTS replay_cache (
207
+ from_did TEXT NOT NULL,
208
+ message_id TEXT NOT NULL,
209
+ ts INTEGER NOT NULL DEFAULT (unixepoch()),
210
+ PRIMARY KEY (from_did, message_id)
211
+ );
212
+ CREATE INDEX IF NOT EXISTS idx_replay_cache_ts ON replay_cache(ts);
213
+ `);
214
+ }
215
+ if (fromVersion < 2) {
216
+ this.db.exec(`
217
+ -- Reputation tracking
218
+ CREATE TABLE IF NOT EXISTS reputation (
219
+ did TEXT PRIMARY KEY,
220
+ overall_score REAL NOT NULL DEFAULT 0.5,
221
+ trust_tier TEXT DEFAULT 'unverified',
222
+ metrics_json TEXT,
223
+ specialties_json TEXT,
224
+ first_seen INTEGER NOT NULL DEFAULT (unixepoch()),
225
+ last_updated INTEGER NOT NULL DEFAULT (unixepoch()),
226
+ version INTEGER DEFAULT 1
227
+ );
228
+
229
+ -- Task outcomes for reputation calculation
230
+ CREATE TABLE IF NOT EXISTS task_outcomes (
231
+ outcome_id TEXT PRIMARY KEY,
232
+ did TEXT NOT NULL,
233
+ chain_id TEXT NOT NULL,
234
+ step_id TEXT NOT NULL,
235
+ status TEXT NOT NULL,
236
+ quality_score REAL,
237
+ latency_ms INTEGER,
238
+ lease_ms INTEGER,
239
+ accepted BOOLEAN,
240
+ tokens_used INTEGER,
241
+ cost_usd REAL,
242
+ specialties_json TEXT,
243
+ timestamp INTEGER NOT NULL DEFAULT (unixepoch()),
244
+ FOREIGN KEY (did) REFERENCES reputation(did)
245
+ );
246
+ CREATE INDEX IF NOT EXISTS idx_task_outcomes_did ON task_outcomes(did, timestamp);
247
+
248
+ -- Peer reviews
249
+ CREATE TABLE IF NOT EXISTS peer_reviews (
250
+ review_id TEXT PRIMARY KEY,
251
+ reviewer_did TEXT NOT NULL,
252
+ subject_did TEXT NOT NULL,
253
+ chain_id TEXT NOT NULL,
254
+ step_id TEXT NOT NULL,
255
+ rating REAL NOT NULL,
256
+ feedback TEXT,
257
+ timestamp INTEGER NOT NULL DEFAULT (unixepoch()),
258
+ FOREIGN KEY (subject_did) REFERENCES reputation(did)
259
+ );
260
+ CREATE INDEX IF NOT EXISTS idx_peer_reviews_subject ON peer_reviews(subject_did);
261
+
262
+ -- Artifacts with content addressing
263
+ CREATE TABLE IF NOT EXISTS artifacts (
264
+ artifact_id TEXT PRIMARY KEY,
265
+ artifact_type TEXT NOT NULL,
266
+ content_hash TEXT NOT NULL UNIQUE,
267
+ size_bytes INTEGER NOT NULL,
268
+ storage_path TEXT,
269
+ inline_content TEXT,
270
+ metadata_json TEXT,
271
+ created_by TEXT,
272
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
273
+ );
274
+ CREATE INDEX IF NOT EXISTS idx_artifacts_hash ON artifacts(content_hash);
275
+
276
+ -- Artifact lineage (provenance)
277
+ CREATE TABLE IF NOT EXISTS artifact_lineage (
278
+ child_artifact_id TEXT NOT NULL,
279
+ parent_artifact_id TEXT NOT NULL,
280
+ relationship_type TEXT DEFAULT 'derived_from',
281
+ PRIMARY KEY (child_artifact_id, parent_artifact_id),
282
+ FOREIGN KEY (child_artifact_id) REFERENCES artifacts(artifact_id),
283
+ FOREIGN KEY (parent_artifact_id) REFERENCES artifacts(artifact_id)
284
+ );
285
+
286
+ -- Encrypted messages
287
+ CREATE TABLE IF NOT EXISTS encrypted_messages (
288
+ message_id TEXT PRIMARY KEY,
289
+ room_id TEXT NOT NULL,
290
+ from_did TEXT NOT NULL,
291
+ encrypted_payload TEXT NOT NULL,
292
+ ephemeral_public_key TEXT NOT NULL,
293
+ nonce TEXT NOT NULL,
294
+ recipients_json TEXT NOT NULL,
295
+ ts INTEGER NOT NULL,
296
+ received_at INTEGER NOT NULL DEFAULT (unixepoch())
297
+ );
298
+
299
+ -- Lease expiry tracking
300
+ CREATE TABLE IF NOT EXISTS lease_monitor (
301
+ chain_id TEXT NOT NULL,
302
+ step_id TEXT NOT NULL,
303
+ assignee_did TEXT NOT NULL,
304
+ lease_started_at INTEGER NOT NULL,
305
+ lease_ms INTEGER NOT NULL,
306
+ expiry_at INTEGER NOT NULL,
307
+ notified BOOLEAN DEFAULT FALSE,
308
+ PRIMARY KEY (chain_id, step_id)
309
+ );
310
+ CREATE INDEX IF NOT EXISTS idx_lease_monitor_expiry ON lease_monitor(expiry_at, notified);
311
+ `);
312
+ }
313
+ if (fromVersion < 3) {
314
+ this.db.exec(`
315
+ -- Federations
316
+ CREATE TABLE IF NOT EXISTS federations (
317
+ id TEXT PRIMARY KEY,
318
+ data TEXT NOT NULL,
319
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
320
+ );
321
+
322
+ -- Knowledge Spaces
323
+ CREATE TABLE IF NOT EXISTS knowledge_spaces (
324
+ id TEXT PRIMARY KEY,
325
+ data TEXT NOT NULL,
326
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
327
+ );
328
+
329
+ -- Knowledge Cards
330
+ CREATE TABLE IF NOT EXISTS knowledge_cards (
331
+ id TEXT PRIMARY KEY,
332
+ space_id TEXT NOT NULL,
333
+ data TEXT NOT NULL,
334
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
335
+ );
336
+ CREATE INDEX IF NOT EXISTS idx_knowledge_cards_space ON knowledge_cards(space_id);
337
+
338
+ -- Knowledge Links
339
+ CREATE TABLE IF NOT EXISTS knowledge_links (
340
+ id TEXT PRIMARY KEY,
341
+ data TEXT NOT NULL
342
+ );
343
+
344
+ -- Collective Unconscious
345
+ CREATE TABLE IF NOT EXISTS collective_unconscious (
346
+ id TEXT PRIMARY KEY,
347
+ space_id TEXT NOT NULL,
348
+ data TEXT NOT NULL,
349
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
350
+ );
351
+ CREATE INDEX IF NOT EXISTS idx_collective_unconscious_space ON collective_unconscious(space_id);
352
+
353
+ -- Audit Log
354
+ CREATE TABLE IF NOT EXISTS audit_log (
355
+ id TEXT PRIMARY KEY,
356
+ type TEXT NOT NULL,
357
+ severity TEXT NOT NULL,
358
+ actor TEXT NOT NULL,
359
+ resource TEXT NOT NULL,
360
+ action TEXT NOT NULL,
361
+ result TEXT NOT NULL,
362
+ details TEXT,
363
+ ip TEXT,
364
+ session_id TEXT,
365
+ signature TEXT,
366
+ timestamp INTEGER NOT NULL DEFAULT (unixepoch())
367
+ );
368
+ CREATE INDEX IF NOT EXISTS idx_audit_log_timestamp ON audit_log(timestamp);
369
+ CREATE INDEX IF NOT EXISTS idx_audit_log_actor ON audit_log(actor);
370
+
371
+ -- Threat Intelligence
372
+ CREATE TABLE IF NOT EXISTS threat_intel (
373
+ id TEXT PRIMARY KEY,
374
+ type TEXT NOT NULL,
375
+ value TEXT NOT NULL UNIQUE,
376
+ severity TEXT NOT NULL,
377
+ category TEXT NOT NULL,
378
+ first_seen INTEGER NOT NULL DEFAULT (unixepoch()),
379
+ last_seen INTEGER NOT NULL DEFAULT (unixepoch()),
380
+ occurrences INTEGER DEFAULT 1,
381
+ confidence REAL DEFAULT 0.5
382
+ );
383
+ CREATE INDEX IF NOT EXISTS idx_threat_intel_value ON threat_intel(value);
384
+
385
+ -- CoC Knowledge Bindings
386
+ CREATE TABLE IF NOT EXISTS coc_knowledge_bindings (
387
+ coc_id TEXT PRIMARY KEY,
388
+ knowledge_space_id TEXT NOT NULL,
389
+ auto_index_steps BOOLEAN DEFAULT 1,
390
+ index_artifacts BOOLEAN DEFAULT 1,
391
+ index_decisions BOOLEAN DEFAULT 1,
392
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
393
+ );
394
+
395
+ -- Federation Rooms
396
+ CREATE TABLE IF NOT EXISTS federation_rooms (
397
+ federation_id TEXT NOT NULL,
398
+ room_id TEXT NOT NULL,
399
+ data TEXT NOT NULL,
400
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
401
+ PRIMARY KEY (federation_id, room_id)
402
+ );
403
+ CREATE INDEX IF NOT EXISTS idx_federation_rooms_room ON federation_rooms(room_id);
404
+ `);
405
+ }
406
+ if (fromVersion < 4) {
407
+ this.db.exec(`
408
+ -- Generic KV Store (compatibility layer)
409
+ CREATE TABLE IF NOT EXISTS kv_store (
410
+ key TEXT PRIMARY KEY,
411
+ value TEXT NOT NULL,
412
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
413
+ );
414
+ CREATE INDEX IF NOT EXISTS idx_kv_store_updated_at ON kv_store(updated_at);
415
+
416
+ -- Federation Governance
417
+ CREATE TABLE IF NOT EXISTS federation_proposals (
418
+ proposal_id TEXT PRIMARY KEY,
419
+ federation_id TEXT NOT NULL,
420
+ proposer_did TEXT NOT NULL,
421
+ policy_json TEXT NOT NULL,
422
+ status TEXT NOT NULL DEFAULT 'open',
423
+ created_at INTEGER NOT NULL,
424
+ closed_at INTEGER
425
+ );
426
+ CREATE INDEX IF NOT EXISTS idx_federation_proposals_federation ON federation_proposals(federation_id, created_at);
427
+
428
+ CREATE TABLE IF NOT EXISTS federation_votes (
429
+ proposal_id TEXT NOT NULL,
430
+ voter_did TEXT NOT NULL,
431
+ vote TEXT NOT NULL,
432
+ voting_power REAL NOT NULL,
433
+ voted_at INTEGER NOT NULL,
434
+ PRIMARY KEY (proposal_id, voter_did)
435
+ );
436
+ CREATE INDEX IF NOT EXISTS idx_federation_votes_proposal ON federation_votes(proposal_id, voted_at);
437
+ `);
438
+ }
439
+ if (fromVersion < 5) {
440
+ this.db.exec(`
441
+ -- Federation Mesh Peerings
442
+ CREATE TABLE IF NOT EXISTS federation_peerings (
443
+ peering_id TEXT PRIMARY KEY,
444
+ source_federation_id TEXT NOT NULL,
445
+ source_federation_did TEXT NOT NULL,
446
+ target_federation_did TEXT NOT NULL,
447
+ policy_json TEXT NOT NULL,
448
+ status TEXT NOT NULL,
449
+ reason TEXT,
450
+ created_at INTEGER NOT NULL,
451
+ updated_at INTEGER NOT NULL,
452
+ responded_at INTEGER
453
+ );
454
+ CREATE INDEX IF NOT EXISTS idx_federation_peerings_source
455
+ ON federation_peerings(source_federation_id, status, updated_at);
456
+ CREATE INDEX IF NOT EXISTS idx_federation_peerings_target
457
+ ON federation_peerings(target_federation_did, status, updated_at);
458
+
459
+ -- Federation Mesh Bridges
460
+ CREATE TABLE IF NOT EXISTS federation_bridges (
461
+ bridge_id TEXT PRIMARY KEY,
462
+ peering_id TEXT NOT NULL,
463
+ local_federation_id TEXT NOT NULL,
464
+ local_room_id TEXT NOT NULL,
465
+ remote_room_id TEXT NOT NULL,
466
+ rules_json TEXT NOT NULL,
467
+ status TEXT NOT NULL DEFAULT 'active',
468
+ events_in INTEGER NOT NULL DEFAULT 0,
469
+ events_out INTEGER NOT NULL DEFAULT 0,
470
+ last_sync_at INTEGER,
471
+ created_at INTEGER NOT NULL,
472
+ updated_at INTEGER NOT NULL
473
+ );
474
+ CREATE INDEX IF NOT EXISTS idx_federation_bridges_federation
475
+ ON federation_bridges(local_federation_id, status, updated_at);
476
+ CREATE INDEX IF NOT EXISTS idx_federation_bridges_peering
477
+ ON federation_bridges(peering_id, status, updated_at);
478
+
479
+ -- Federation Mesh Sync Cursor (idempotent replay point per direction)
480
+ CREATE TABLE IF NOT EXISTS federation_sync_cursor (
481
+ bridge_id TEXT NOT NULL,
482
+ direction TEXT NOT NULL,
483
+ cursor_id TEXT NOT NULL,
484
+ updated_at INTEGER NOT NULL,
485
+ PRIMARY KEY (bridge_id, direction)
486
+ );
487
+
488
+ -- Federation Mesh Sync Log (dedup + audit trail)
489
+ CREATE TABLE IF NOT EXISTS federation_sync_log (
490
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
491
+ bridge_id TEXT NOT NULL,
492
+ envelope_id TEXT NOT NULL,
493
+ direction TEXT NOT NULL,
494
+ message_type TEXT NOT NULL,
495
+ from_federation_id TEXT,
496
+ to_federation_id TEXT,
497
+ status TEXT NOT NULL DEFAULT 'processed',
498
+ error TEXT,
499
+ ts INTEGER NOT NULL,
500
+ UNIQUE (bridge_id, envelope_id, direction)
501
+ );
502
+ CREATE INDEX IF NOT EXISTS idx_federation_sync_log_bridge_ts
503
+ ON federation_sync_log(bridge_id, ts DESC);
504
+ `);
505
+ }
506
+ if (fromVersion < 6) {
507
+ this.db.exec(`
508
+ -- Persona Vaults
509
+ CREATE TABLE IF NOT EXISTS persona_vaults (
510
+ id TEXT PRIMARY KEY,
511
+ owner_did TEXT NOT NULL,
512
+ name TEXT NOT NULL,
513
+ data TEXT NOT NULL,
514
+ created_at INTEGER NOT NULL,
515
+ updated_at INTEGER NOT NULL
516
+ );
517
+ CREATE INDEX IF NOT EXISTS idx_persona_vaults_owner ON persona_vaults(owner_did, updated_at DESC);
518
+
519
+ -- Persona nodes (episodic/semantic/procedural)
520
+ CREATE TABLE IF NOT EXISTS persona_nodes (
521
+ id TEXT PRIMARY KEY,
522
+ vault_id TEXT NOT NULL,
523
+ domain TEXT NOT NULL,
524
+ node_type TEXT NOT NULL,
525
+ title TEXT NOT NULL,
526
+ content TEXT NOT NULL,
527
+ tags_text TEXT,
528
+ confidence REAL NOT NULL DEFAULT 0.8,
529
+ metadata_json TEXT,
530
+ source_json TEXT,
531
+ valid_from INTEGER,
532
+ valid_to INTEGER,
533
+ data TEXT NOT NULL,
534
+ created_at INTEGER NOT NULL,
535
+ updated_at INTEGER NOT NULL,
536
+ deleted_at INTEGER
537
+ );
538
+ CREATE INDEX IF NOT EXISTS idx_persona_nodes_vault_domain
539
+ ON persona_nodes(vault_id, domain, node_type, updated_at DESC);
540
+ CREATE INDEX IF NOT EXISTS idx_persona_nodes_updated
541
+ ON persona_nodes(vault_id, updated_at DESC);
542
+
543
+ -- Persona edges
544
+ CREATE TABLE IF NOT EXISTS persona_edges (
545
+ id TEXT PRIMARY KEY,
546
+ vault_id TEXT NOT NULL,
547
+ source_node_id TEXT NOT NULL,
548
+ target_node_id TEXT NOT NULL,
549
+ edge_type TEXT NOT NULL,
550
+ weight REAL NOT NULL DEFAULT 0.8,
551
+ confidence REAL NOT NULL DEFAULT 0.8,
552
+ metadata_json TEXT,
553
+ data TEXT NOT NULL,
554
+ valid_from INTEGER,
555
+ valid_to INTEGER,
556
+ updated_at INTEGER NOT NULL,
557
+ deleted_at INTEGER
558
+ );
559
+ CREATE INDEX IF NOT EXISTS idx_persona_edges_vault
560
+ ON persona_edges(vault_id, source_node_id, target_node_id, updated_at DESC);
561
+
562
+ -- Persona hyper-edges
563
+ CREATE TABLE IF NOT EXISTS persona_hyperedges (
564
+ id TEXT PRIMARY KEY,
565
+ vault_id TEXT NOT NULL,
566
+ node_ids_json TEXT NOT NULL,
567
+ edge_type TEXT NOT NULL,
568
+ metadata_json TEXT,
569
+ data TEXT NOT NULL,
570
+ updated_at INTEGER NOT NULL,
571
+ deleted_at INTEGER
572
+ );
573
+ CREATE INDEX IF NOT EXISTS idx_persona_hyperedges_vault
574
+ ON persona_hyperedges(vault_id, updated_at DESC);
575
+
576
+ -- Persona CRDT docs
577
+ CREATE TABLE IF NOT EXISTS persona_crdt_docs (
578
+ doc_id TEXT PRIMARY KEY,
579
+ vault_id TEXT NOT NULL,
580
+ domain TEXT NOT NULL,
581
+ data_json TEXT NOT NULL,
582
+ clock_json TEXT NOT NULL,
583
+ updated_at INTEGER NOT NULL
584
+ );
585
+ CREATE INDEX IF NOT EXISTS idx_persona_crdt_docs_vault_domain
586
+ ON persona_crdt_docs(vault_id, domain, updated_at DESC);
587
+
588
+ -- Persona embeddings
589
+ CREATE TABLE IF NOT EXISTS persona_embeddings (
590
+ node_id TEXT PRIMARY KEY,
591
+ vault_id TEXT NOT NULL,
592
+ model TEXT NOT NULL,
593
+ dim INTEGER NOT NULL,
594
+ vector BLOB,
595
+ data_json TEXT,
596
+ updated_at INTEGER NOT NULL
597
+ );
598
+ CREATE INDEX IF NOT EXISTS idx_persona_embeddings_vault
599
+ ON persona_embeddings(vault_id, updated_at DESC);
600
+
601
+ -- Persona capabilities
602
+ CREATE TABLE IF NOT EXISTS persona_capabilities (
603
+ id TEXT PRIMARY KEY,
604
+ vault_id TEXT NOT NULL,
605
+ service_did TEXT NOT NULL,
606
+ scope TEXT NOT NULL,
607
+ caveats_json TEXT NOT NULL,
608
+ token_hash TEXT NOT NULL UNIQUE,
609
+ status TEXT NOT NULL DEFAULT 'active',
610
+ issued_at INTEGER NOT NULL,
611
+ expires_at INTEGER,
612
+ revoked_at INTEGER,
613
+ reason TEXT
614
+ );
615
+ CREATE INDEX IF NOT EXISTS idx_persona_capabilities_vault
616
+ ON persona_capabilities(vault_id, status, issued_at DESC);
617
+
618
+ -- Persona access log
619
+ CREATE TABLE IF NOT EXISTS persona_access_log (
620
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
621
+ vault_id TEXT NOT NULL,
622
+ token_id TEXT,
623
+ service_did TEXT NOT NULL,
624
+ operation TEXT NOT NULL,
625
+ resource TEXT NOT NULL,
626
+ result TEXT NOT NULL,
627
+ details_json TEXT,
628
+ ts INTEGER NOT NULL,
629
+ signature TEXT
630
+ );
631
+ CREATE INDEX IF NOT EXISTS idx_persona_access_log_vault_ts
632
+ ON persona_access_log(vault_id, ts DESC);
633
+
634
+ -- Persona sync cursors/state
635
+ CREATE TABLE IF NOT EXISTS persona_sync_state (
636
+ peer_did TEXT NOT NULL,
637
+ vault_id TEXT NOT NULL,
638
+ cursor_id TEXT NOT NULL,
639
+ clock_json TEXT NOT NULL,
640
+ updated_at INTEGER NOT NULL,
641
+ PRIMARY KEY (peer_did, vault_id)
642
+ );
643
+ CREATE INDEX IF NOT EXISTS idx_persona_sync_state_vault
644
+ ON persona_sync_state(vault_id, updated_at DESC);
645
+
646
+ -- Full-text search table for persona nodes
647
+ CREATE VIRTUAL TABLE IF NOT EXISTS persona_fts USING fts5(
648
+ node_id UNINDEXED,
649
+ vault_id UNINDEXED,
650
+ title,
651
+ content,
652
+ tags
653
+ );
654
+
655
+ CREATE TRIGGER IF NOT EXISTS persona_nodes_ai AFTER INSERT ON persona_nodes BEGIN
656
+ INSERT INTO persona_fts (node_id, vault_id, title, content, tags)
657
+ VALUES (new.id, new.vault_id, new.title, new.content, COALESCE(new.tags_text, ''));
658
+ END;
659
+
660
+ CREATE TRIGGER IF NOT EXISTS persona_nodes_au AFTER UPDATE ON persona_nodes BEGIN
661
+ DELETE FROM persona_fts WHERE node_id = old.id;
662
+ INSERT INTO persona_fts (node_id, vault_id, title, content, tags)
663
+ VALUES (new.id, new.vault_id, new.title, new.content, COALESCE(new.tags_text, ''));
664
+ END;
665
+
666
+ CREATE TRIGGER IF NOT EXISTS persona_nodes_ad AFTER DELETE ON persona_nodes BEGIN
667
+ DELETE FROM persona_fts WHERE node_id = old.id;
668
+ END;
669
+ `);
670
+ }
671
+ if (fromVersion < 7) {
672
+ this.safeAddColumn('persona_embeddings', 'vec_rowid INTEGER');
673
+ this.db.exec(`
674
+ CREATE INDEX IF NOT EXISTS idx_persona_embeddings_vec_rowid
675
+ ON persona_embeddings(vec_rowid);
676
+ `);
677
+ }
678
+ if (fromVersion < 8) {
679
+ this.db.exec(`
680
+ CREATE TABLE IF NOT EXISTS persona_sync_applied (
681
+ from_did TEXT NOT NULL,
682
+ delta_id TEXT NOT NULL,
683
+ vault_id TEXT NOT NULL,
684
+ applied_at INTEGER NOT NULL,
685
+ PRIMARY KEY (from_did, delta_id)
686
+ );
687
+ CREATE INDEX IF NOT EXISTS idx_persona_sync_applied_vault
688
+ ON persona_sync_applied(vault_id, applied_at DESC);
689
+ `);
690
+ }
691
+ if (fromVersion < 9) {
692
+ this.safeAddColumn('persona_capabilities', 'parent_token_id TEXT');
693
+ this.safeAddColumn('persona_access_log', 'signer_did TEXT');
694
+ this.safeAddColumn('persona_access_log', 'sig_alg TEXT');
695
+ this.db.exec(`
696
+ CREATE INDEX IF NOT EXISTS idx_persona_capabilities_parent
697
+ ON persona_capabilities(parent_token_id);
698
+ `);
699
+ }
700
+ if (fromVersion < 10) {
701
+ this.db.exec(`
702
+ CREATE TABLE IF NOT EXISTS persona_claims (
703
+ id TEXT PRIMARY KEY,
704
+ vault_id TEXT NOT NULL,
705
+ subject_did TEXT NOT NULL,
706
+ issuer_did TEXT,
707
+ schema TEXT NOT NULL,
708
+ payload_enc TEXT NOT NULL,
709
+ status TEXT NOT NULL DEFAULT 'active',
710
+ issued_at INTEGER NOT NULL,
711
+ expires_at INTEGER,
712
+ revoked_at INTEGER,
713
+ signature TEXT NOT NULL
714
+ );
715
+ CREATE INDEX IF NOT EXISTS idx_persona_claims_vault
716
+ ON persona_claims(vault_id, status, issued_at DESC);
717
+
718
+ CREATE TABLE IF NOT EXISTS persona_zkp_proofs (
719
+ id TEXT PRIMARY KEY,
720
+ vault_id TEXT NOT NULL,
721
+ circuit_id TEXT NOT NULL,
722
+ proof_blob TEXT NOT NULL,
723
+ public_inputs_json TEXT NOT NULL,
724
+ claim_ids_json TEXT NOT NULL,
725
+ created_at INTEGER NOT NULL,
726
+ expires_at INTEGER
727
+ );
728
+ CREATE INDEX IF NOT EXISTS idx_persona_zkp_proofs_vault
729
+ ON persona_zkp_proofs(vault_id, circuit_id, created_at DESC);
730
+
731
+ CREATE TABLE IF NOT EXISTS persona_zkp_circuits (
732
+ circuit_id TEXT PRIMARY KEY,
733
+ version TEXT NOT NULL,
734
+ vk_blob TEXT,
735
+ metadata_json TEXT,
736
+ active INTEGER NOT NULL DEFAULT 1
737
+ );
738
+ CREATE INDEX IF NOT EXISTS idx_persona_zkp_circuits_active
739
+ ON persona_zkp_circuits(active);
740
+ `);
741
+ }
742
+ if (fromVersion < 11) {
743
+ this.db.exec(`
744
+ CREATE TABLE IF NOT EXISTS persona_retention_state (
745
+ vault_id TEXT NOT NULL,
746
+ domain TEXT NOT NULL,
747
+ last_cleanup_at INTEGER NOT NULL,
748
+ PRIMARY KEY (vault_id, domain)
749
+ );
750
+ `);
751
+ }
752
+ if (fromVersion < 12) {
753
+ this.db.exec(`
754
+ CREATE TABLE IF NOT EXISTS persona_graph_cache (
755
+ vault_id TEXT NOT NULL,
756
+ domain TEXT NOT NULL,
757
+ graph_version TEXT NOT NULL,
758
+ ppr_blob TEXT NOT NULL,
759
+ updated_at INTEGER NOT NULL,
760
+ PRIMARY KEY (vault_id, domain, graph_version)
761
+ );
762
+ CREATE INDEX IF NOT EXISTS idx_persona_graph_cache_updated
763
+ ON persona_graph_cache(vault_id, domain, updated_at DESC);
764
+
765
+ CREATE TABLE IF NOT EXISTS persona_metrics (
766
+ ts INTEGER NOT NULL,
767
+ metric TEXT NOT NULL,
768
+ value REAL NOT NULL,
769
+ labels_json TEXT
770
+ );
771
+ CREATE INDEX IF NOT EXISTS idx_persona_metrics_metric_ts
772
+ ON persona_metrics(metric, ts DESC);
773
+
774
+ CREATE INDEX IF NOT EXISTS idx_persona_access_log_signer_ts
775
+ ON persona_access_log(signer_did, ts DESC);
776
+ CREATE INDEX IF NOT EXISTS idx_persona_retention_state_domain_ts
777
+ ON persona_retention_state(domain, last_cleanup_at DESC);
778
+ `);
779
+ }
780
+ if (fromVersion < 13) {
781
+ this.ensureColumn('adapters', 'owner_did', 'TEXT');
782
+ this.ensureColumn('adapters', 'room_id', 'TEXT');
783
+ this.ensureColumn('adapters', 'mission_tags', 'TEXT');
784
+ this.ensureColumn('adapters', 'health', "TEXT DEFAULT 'healthy'");
785
+ this.ensureColumn('adapters', 'queue_depth', 'INTEGER DEFAULT 0');
786
+ this.ensureColumn('adapters', 'success_rate', 'REAL');
787
+ this.ensureColumn('adapters', 'last_heartbeat_at', 'INTEGER');
788
+ this.ensureColumn('adapters', 'host_id', 'TEXT');
789
+ this.ensureColumn('adapters', 'peer_id', 'TEXT');
790
+ this.db.exec(`
791
+ CREATE TABLE IF NOT EXISTS proactive_missions (
792
+ mission_id TEXT PRIMARY KEY,
793
+ room_id TEXT NOT NULL,
794
+ goal TEXT NOT NULL,
795
+ mission_type TEXT NOT NULL,
796
+ template_id TEXT,
797
+ mode TEXT NOT NULL,
798
+ status TEXT NOT NULL DEFAULT 'active',
799
+ leader_did TEXT NOT NULL,
800
+ cadence_ms INTEGER NOT NULL,
801
+ policy_json TEXT NOT NULL,
802
+ research_json TEXT NOT NULL,
803
+ knowledge_json TEXT,
804
+ active_chain_ids TEXT NOT NULL DEFAULT '[]',
805
+ last_tick_at INTEGER,
806
+ next_tick_at INTEGER,
807
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
808
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
809
+ );
810
+ CREATE INDEX IF NOT EXISTS idx_proactive_missions_room_status
811
+ ON proactive_missions(room_id, status);
812
+
813
+ CREATE TABLE IF NOT EXISTS mission_runs (
814
+ run_id TEXT PRIMARY KEY,
815
+ mission_id TEXT NOT NULL,
816
+ cycle INTEGER NOT NULL,
817
+ chain_id TEXT,
818
+ status TEXT NOT NULL,
819
+ summary TEXT,
820
+ started_at INTEGER NOT NULL DEFAULT (unixepoch()),
821
+ ended_at INTEGER
822
+ );
823
+ CREATE INDEX IF NOT EXISTS idx_mission_runs_mission
824
+ ON mission_runs(mission_id, cycle DESC);
825
+
826
+ CREATE TABLE IF NOT EXISTS mission_checkpoints (
827
+ checkpoint_id TEXT PRIMARY KEY,
828
+ mission_id TEXT NOT NULL,
829
+ summary TEXT NOT NULL,
830
+ frontier_json TEXT NOT NULL,
831
+ knowledge_json TEXT NOT NULL,
832
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
833
+ );
834
+ CREATE INDEX IF NOT EXISTS idx_mission_checkpoints_mission
835
+ ON mission_checkpoints(mission_id, created_at DESC);
836
+
837
+ CREATE TABLE IF NOT EXISTS mission_events (
838
+ event_id TEXT PRIMARY KEY,
839
+ mission_id TEXT NOT NULL,
840
+ event_type TEXT NOT NULL,
841
+ actor_did TEXT,
842
+ payload_json TEXT,
843
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
844
+ );
845
+ CREATE INDEX IF NOT EXISTS idx_mission_events_mission
846
+ ON mission_events(mission_id, created_at DESC);
847
+
848
+ CREATE TABLE IF NOT EXISTS swarm_workers (
849
+ worker_did TEXT PRIMARY KEY,
850
+ peer_id TEXT,
851
+ room_id TEXT NOT NULL,
852
+ host_id TEXT NOT NULL,
853
+ runtime TEXT NOT NULL,
854
+ specialties_json TEXT NOT NULL DEFAULT '[]',
855
+ capabilities_json TEXT NOT NULL DEFAULT '[]',
856
+ kinds_json TEXT NOT NULL DEFAULT '[]',
857
+ max_concurrency INTEGER NOT NULL DEFAULT 1,
858
+ load REAL NOT NULL DEFAULT 0,
859
+ health TEXT NOT NULL DEFAULT 'healthy',
860
+ mission_tags_json TEXT,
861
+ adapter_id TEXT,
862
+ display_name TEXT,
863
+ endpoint TEXT,
864
+ success_rate REAL,
865
+ queue_depth INTEGER,
866
+ metadata_json TEXT,
867
+ last_seen INTEGER NOT NULL DEFAULT (unixepoch())
868
+ );
869
+ CREATE INDEX IF NOT EXISTS idx_swarm_workers_room_health
870
+ ON swarm_workers(room_id, health, last_seen DESC);
871
+
872
+ CREATE TABLE IF NOT EXISTS research_artifacts (
873
+ artifact_id TEXT PRIMARY KEY,
874
+ mission_id TEXT,
875
+ worker_did TEXT,
876
+ artifact_type TEXT NOT NULL,
877
+ source_url TEXT,
878
+ external_id TEXT,
879
+ content_hash TEXT,
880
+ metadata_json TEXT,
881
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
882
+ );
883
+ CREATE INDEX IF NOT EXISTS idx_research_artifacts_mission
884
+ ON research_artifacts(mission_id, created_at DESC);
885
+ CREATE INDEX IF NOT EXISTS idx_research_artifacts_hash
886
+ ON research_artifacts(content_hash);
887
+ `);
888
+ }
889
+ if (fromVersion < 14) {
890
+ this.db.exec(`
891
+ CREATE TABLE IF NOT EXISTS mission_leases (
892
+ mission_id TEXT PRIMARY KEY,
893
+ holder_instance_id TEXT NOT NULL,
894
+ holder_did TEXT NOT NULL,
895
+ expires_at INTEGER NOT NULL,
896
+ updated_at INTEGER NOT NULL
897
+ );
898
+ CREATE INDEX IF NOT EXISTS idx_mission_leases_expiry
899
+ ON mission_leases(expires_at);
900
+ `);
901
+ }
902
+ this.ensurePersonaVectorIndex();
903
+ }
904
+ ensureColumn(table, column, sqlType) {
905
+ const rows = this.db.prepare(`PRAGMA table_info(${table})`).all();
906
+ if (rows.some((row) => row.name === column)) {
907
+ return;
908
+ }
909
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${sqlType}`);
910
+ }
911
+ // ─── Compatibility KV API ───────────────────────────────────
912
+ transaction(fn) {
913
+ return this.db.transaction(fn)();
914
+ }
915
+ set(key, value) {
916
+ this.db.prepare(`
917
+ INSERT OR REPLACE INTO kv_store (key, value, updated_at)
918
+ VALUES (?, ?, ?)
919
+ `).run(key, JSON.stringify(value), Date.now());
920
+ }
921
+ get(key) {
922
+ const row = this.db.prepare('SELECT value FROM kv_store WHERE key = ?').get(key);
923
+ if (!row)
924
+ return undefined;
925
+ return JSON.parse(row.value);
926
+ }
927
+ delete(key) {
928
+ this.db.prepare('DELETE FROM kv_store WHERE key = ?').run(key);
929
+ }
930
+ query(sql, params = []) {
931
+ return this.db.prepare(sql).all(...params);
932
+ }
933
+ // ─── Identity CRUD ────────────────────────────────────────────
934
+ saveIdentity(did, privateKeyHex, publicKeyHex, displayName) {
935
+ this.db
936
+ .prepare(`INSERT OR REPLACE INTO identity (did, private_key_hex, public_key_hex, display_name)
937
+ VALUES (?, ?, ?, ?)`)
938
+ .run(did, privateKeyHex, publicKeyHex, displayName);
939
+ }
940
+ getIdentity() {
941
+ return this.db.prepare('SELECT * FROM identity LIMIT 1').get();
942
+ }
943
+ // ─── Rooms CRUD ───────────────────────────────────────────────
944
+ createRoom(roomId, name, createdBy) {
945
+ this.db
946
+ .prepare('INSERT OR IGNORE INTO rooms (room_id, name, created_by) VALUES (?, ?, ?)')
947
+ .run(roomId, name, createdBy);
948
+ }
949
+ getRooms() {
950
+ return this.db.prepare('SELECT * FROM rooms ORDER BY created_at DESC').all();
951
+ }
952
+ // ─── Room Members ─────────────────────────────────────────────
953
+ addRoomMember(roomId, memberDid, displayName) {
954
+ this.db
955
+ .prepare('INSERT OR REPLACE INTO room_members (room_id, member_did, display_name) VALUES (?, ?, ?)')
956
+ .run(roomId, memberDid, displayName ?? null);
957
+ }
958
+ getRoomMembers(roomId) {
959
+ return this.db
960
+ .prepare('SELECT member_did, display_name FROM room_members WHERE room_id = ?')
961
+ .all(roomId);
962
+ }
963
+ // ─── Messages CRUD ────────────────────────────────────────────
964
+ saveMessage(id, roomId, fromDid, fromName, text, replyTo, ts) {
965
+ this.db
966
+ .prepare(`INSERT OR IGNORE INTO messages (id, room_id, from_did, from_name, text, reply_to, ts)
967
+ VALUES (?, ?, ?, ?, ?, ?, ?)`)
968
+ .run(id, roomId, fromDid, fromName, text, replyTo, ts);
969
+ }
970
+ getMessages(roomId, limit = 100) {
971
+ return this.db
972
+ .prepare('SELECT * FROM messages WHERE room_id = ? ORDER BY ts DESC LIMIT ?')
973
+ .all(roomId, limit);
974
+ }
975
+ // ─── Presence CRUD ────────────────────────────────────────────
976
+ upsertPresence(peerDid, peerName, status, capabilities, load, roomId) {
977
+ this.db
978
+ .prepare(`INSERT OR REPLACE INTO presence (peer_did, peer_name, status, capabilities, load, last_seen, room_id)
979
+ VALUES (?, ?, ?, ?, ?, unixepoch(), ?)`)
980
+ .run(peerDid, peerName, status, capabilities ? JSON.stringify(capabilities) : null, load, roomId ?? null);
981
+ }
982
+ getOnlinePeers(staleSeconds = 30) {
983
+ return this.db
984
+ .prepare('SELECT * FROM presence WHERE last_seen > (unixepoch() - ?)')
985
+ .all(staleSeconds);
986
+ }
987
+ // ─── Replay Cache ─────────────────────────────────────────────
988
+ hasReplay(fromDid, messageId) {
989
+ const row = this.db
990
+ .prepare('SELECT 1 FROM replay_cache WHERE from_did = ? AND message_id = ?')
991
+ .get(fromDid, messageId);
992
+ return !!row;
993
+ }
994
+ addReplay(fromDid, messageId) {
995
+ this.db
996
+ .prepare('INSERT OR IGNORE INTO replay_cache (from_did, message_id) VALUES (?, ?)')
997
+ .run(fromDid, messageId);
998
+ }
999
+ pruneReplayCache(maxAgeSeconds = 86400) {
1000
+ this.db
1001
+ .prepare('DELETE FROM replay_cache WHERE ts < (unixepoch() - ?)')
1002
+ .run(maxAgeSeconds);
1003
+ }
1004
+ // ─── Adapters CRUD ────────────────────────────────────────────
1005
+ registerAdapter(adapterId, runtime, displayName, specialties, kinds, maxConcurrency, endpoint, authType = 'none', options = {}) {
1006
+ this.db
1007
+ .prepare(`INSERT OR REPLACE INTO adapters
1008
+ (adapter_id, runtime, display_name, specialties, kinds, max_concurrency, endpoint, auth_type,
1009
+ owner_did, room_id, mission_tags, health, queue_depth, success_rate, host_id, peer_id, last_seen, last_heartbeat_at)
1010
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, unixepoch(), unixepoch())`)
1011
+ .run(adapterId, runtime, displayName, JSON.stringify(specialties), JSON.stringify(kinds), maxConcurrency, endpoint, authType, options.ownerDid ?? null, options.roomId ?? null, options.missionTags ? JSON.stringify(options.missionTags) : null, options.health ?? 'healthy', options.queueDepth ?? 0, options.successRate ?? null, options.hostId ?? null, options.peerId ?? null);
1012
+ }
1013
+ getAdapters() {
1014
+ return this.db.prepare('SELECT * FROM adapters').all();
1015
+ }
1016
+ getAdapter(adapterId) {
1017
+ return this.db.prepare('SELECT * FROM adapters WHERE adapter_id = ?').get(adapterId);
1018
+ }
1019
+ updateAdapterHeartbeat(adapterId, health, queueDepth, successRate) {
1020
+ this.db
1021
+ .prepare(`UPDATE adapters
1022
+ SET health = ?, queue_depth = ?, success_rate = ?, last_seen = unixepoch(), last_heartbeat_at = unixepoch()
1023
+ WHERE adapter_id = ?`)
1024
+ .run(health, queueDepth, successRate ?? null, adapterId);
1025
+ }
1026
+ upsertSwarmWorker(worker) {
1027
+ this.db
1028
+ .prepare(`INSERT OR REPLACE INTO swarm_workers
1029
+ (worker_did, peer_id, room_id, host_id, runtime, specialties_json, capabilities_json, kinds_json,
1030
+ max_concurrency, load, health, mission_tags_json, adapter_id, display_name, endpoint, success_rate,
1031
+ queue_depth, metadata_json, last_seen)
1032
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1033
+ .run(worker.did, worker.peerId ?? null, worker.roomId, worker.hostId, worker.runtime, JSON.stringify(worker.specialties || []), JSON.stringify(worker.capabilities || []), JSON.stringify(worker.kinds || []), worker.maxConcurrency || 1, worker.load ?? 0, worker.health || 'healthy', worker.missionTags ? JSON.stringify(worker.missionTags) : null, worker.adapterId ?? null, worker.displayName ?? null, worker.endpoint ?? null, worker.successRate ?? null, worker.queueDepth ?? null, worker.metadata ? JSON.stringify(worker.metadata) : null, worker.lastSeen ?? Date.now());
1034
+ }
1035
+ getSwarmWorker(workerDid) {
1036
+ const row = this.db
1037
+ .prepare('SELECT * FROM swarm_workers WHERE worker_did = ?')
1038
+ .get(workerDid);
1039
+ return row ? this.mapSwarmWorker(row) : undefined;
1040
+ }
1041
+ getVisibleWorkers(roomId, maxAgeMs = 60_000) {
1042
+ const minLastSeen = Date.now() - maxAgeMs;
1043
+ const rows = roomId
1044
+ ? this.db.prepare('SELECT * FROM swarm_workers WHERE room_id = ? AND last_seen >= ? ORDER BY last_seen DESC').all(roomId, minLastSeen)
1045
+ : this.db.prepare('SELECT * FROM swarm_workers WHERE last_seen >= ? ORDER BY last_seen DESC').all(minLastSeen);
1046
+ return rows.map((row) => this.mapSwarmWorker(row));
1047
+ }
1048
+ mapSwarmWorker(row) {
1049
+ return {
1050
+ did: row.worker_did,
1051
+ peerId: row.peer_id ?? undefined,
1052
+ roomId: row.room_id,
1053
+ hostId: row.host_id,
1054
+ runtime: row.runtime,
1055
+ specialties: row.specialties_json ? JSON.parse(row.specialties_json) : [],
1056
+ capabilities: row.capabilities_json ? JSON.parse(row.capabilities_json) : [],
1057
+ kinds: row.kinds_json ? JSON.parse(row.kinds_json) : [],
1058
+ maxConcurrency: row.max_concurrency,
1059
+ load: row.load ?? 0,
1060
+ health: row.health,
1061
+ missionTags: row.mission_tags_json ? JSON.parse(row.mission_tags_json) : [],
1062
+ adapterId: row.adapter_id ?? undefined,
1063
+ displayName: row.display_name ?? undefined,
1064
+ endpoint: row.endpoint ?? undefined,
1065
+ successRate: row.success_rate ?? undefined,
1066
+ queueDepth: row.queue_depth ?? undefined,
1067
+ lastSeen: row.last_seen ?? undefined,
1068
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : undefined,
1069
+ };
1070
+ }
1071
+ createMission(input) {
1072
+ this.db
1073
+ .prepare(`INSERT INTO proactive_missions
1074
+ (mission_id, room_id, goal, mission_type, template_id, mode, status, leader_did, cadence_ms,
1075
+ policy_json, research_json, knowledge_json, active_chain_ids, last_tick_at, next_tick_at, updated_at)
1076
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1077
+ .run(input.missionId, input.roomId, input.goal, input.missionType, input.templateId ?? null, input.mode, input.status, input.leaderDid, input.cadenceMs, JSON.stringify(input.policy), JSON.stringify(input.research), input.knowledge ? JSON.stringify(input.knowledge) : null, JSON.stringify(input.activeChainIds || []), input.lastTickAt ?? null, input.nextTickAt ?? null, Date.now());
1078
+ }
1079
+ getMission(missionId) {
1080
+ const row = this.db.prepare('SELECT * FROM proactive_missions WHERE mission_id = ?').get(missionId);
1081
+ return row ? this.mapMission(row) : undefined;
1082
+ }
1083
+ listMissions(roomId) {
1084
+ const rows = roomId
1085
+ ? this.db.prepare('SELECT * FROM proactive_missions WHERE room_id = ? ORDER BY updated_at DESC').all(roomId)
1086
+ : this.db.prepare('SELECT * FROM proactive_missions ORDER BY updated_at DESC').all();
1087
+ return rows.map((row) => this.mapMission(row));
1088
+ }
1089
+ updateMissionStatus(missionId, status, updates = {}) {
1090
+ const fields = ['status = ?', 'updated_at = ?'];
1091
+ const values = [status, Date.now()];
1092
+ if (updates.activeChainIds !== undefined) {
1093
+ fields.push('active_chain_ids = ?');
1094
+ values.push(JSON.stringify(updates.activeChainIds));
1095
+ }
1096
+ if (updates.lastTickAt !== undefined) {
1097
+ fields.push('last_tick_at = ?');
1098
+ values.push(updates.lastTickAt);
1099
+ }
1100
+ if (updates.nextTickAt !== undefined) {
1101
+ fields.push('next_tick_at = ?');
1102
+ values.push(updates.nextTickAt);
1103
+ }
1104
+ values.push(missionId);
1105
+ this.db.prepare(`UPDATE proactive_missions SET ${fields.join(', ')} WHERE mission_id = ?`).run(...values);
1106
+ }
1107
+ appendMissionChain(missionId, chainId) {
1108
+ const mission = this.getMission(missionId);
1109
+ if (!mission)
1110
+ return;
1111
+ const activeChainIds = Array.from(new Set([...(mission.activeChainIds || []), chainId]));
1112
+ this.updateMissionStatus(missionId, mission.status, { activeChainIds });
1113
+ this.addMissionEvent(missionId, 'chain_attached', mission.leaderDid, { chain_id: chainId });
1114
+ }
1115
+ findMissionByChain(chainId) {
1116
+ try {
1117
+ const row = this.db
1118
+ .prepare(`SELECT m.*
1119
+ FROM proactive_missions m
1120
+ JOIN json_each(m.active_chain_ids) AS j
1121
+ WHERE j.value = ?
1122
+ LIMIT 1`)
1123
+ .get(chainId);
1124
+ return row ? this.mapMission(row) : undefined;
1125
+ }
1126
+ catch {
1127
+ return this.listMissions().find((mission) => mission.activeChainIds.includes(chainId));
1128
+ }
1129
+ }
1130
+ acquireMissionLease(missionId, holderInstanceId, holderDid, ttlMs, now = Date.now()) {
1131
+ const expiresAt = now + ttlMs;
1132
+ const result = this.db
1133
+ .prepare(`INSERT INTO mission_leases (mission_id, holder_instance_id, holder_did, expires_at, updated_at)
1134
+ VALUES (?, ?, ?, ?, ?)
1135
+ ON CONFLICT(mission_id) DO UPDATE SET
1136
+ holder_instance_id = excluded.holder_instance_id,
1137
+ holder_did = excluded.holder_did,
1138
+ expires_at = excluded.expires_at,
1139
+ updated_at = excluded.updated_at
1140
+ WHERE mission_leases.holder_instance_id = excluded.holder_instance_id
1141
+ OR mission_leases.expires_at <= ?`)
1142
+ .run(missionId, holderInstanceId, holderDid, expiresAt, now, now);
1143
+ return (result.changes || 0) > 0;
1144
+ }
1145
+ renewMissionLease(missionId, holderInstanceId, holderDid, ttlMs, now = Date.now()) {
1146
+ const expiresAt = now + ttlMs;
1147
+ const result = this.db
1148
+ .prepare(`UPDATE mission_leases
1149
+ SET expires_at = ?, updated_at = ?
1150
+ WHERE mission_id = ?
1151
+ AND holder_instance_id = ?
1152
+ AND holder_did = ?
1153
+ AND expires_at > ?`)
1154
+ .run(expiresAt, now, missionId, holderInstanceId, holderDid, now);
1155
+ return (result.changes || 0) > 0;
1156
+ }
1157
+ releaseMissionLease(missionId, holderInstanceId) {
1158
+ const result = this.db
1159
+ .prepare('DELETE FROM mission_leases WHERE mission_id = ? AND holder_instance_id = ?')
1160
+ .run(missionId, holderInstanceId);
1161
+ return (result.changes || 0) > 0;
1162
+ }
1163
+ getMissionLease(missionId) {
1164
+ const row = this.db.prepare('SELECT * FROM mission_leases WHERE mission_id = ?').get(missionId);
1165
+ return row
1166
+ ? {
1167
+ missionId: row.mission_id,
1168
+ holderInstanceId: row.holder_instance_id,
1169
+ holderDid: row.holder_did,
1170
+ expiresAt: row.expires_at,
1171
+ updatedAt: row.updated_at,
1172
+ }
1173
+ : undefined;
1174
+ }
1175
+ createMissionRun(run) {
1176
+ this.db
1177
+ .prepare(`INSERT OR REPLACE INTO mission_runs
1178
+ (run_id, mission_id, cycle, chain_id, status, summary, started_at, ended_at)
1179
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
1180
+ .run(run.runId, run.missionId, run.cycle, run.chainId ?? null, run.status, run.summary ?? null, run.startedAt ?? Date.now(), run.endedAt ?? null);
1181
+ }
1182
+ saveMissionCheckpoint(checkpoint) {
1183
+ this.db
1184
+ .prepare(`INSERT OR REPLACE INTO mission_checkpoints
1185
+ (checkpoint_id, mission_id, summary, frontier_json, knowledge_json, created_at)
1186
+ VALUES (?, ?, ?, ?, ?, ?)`)
1187
+ .run(checkpoint.checkpointId, checkpoint.missionId, checkpoint.summary, JSON.stringify(checkpoint.frontier), JSON.stringify(checkpoint.knowledge), checkpoint.createdAt);
1188
+ }
1189
+ getLatestMissionCheckpoint(missionId) {
1190
+ const row = this.db
1191
+ .prepare('SELECT * FROM mission_checkpoints WHERE mission_id = ? ORDER BY created_at DESC LIMIT 1')
1192
+ .get(missionId);
1193
+ return row
1194
+ ? {
1195
+ checkpointId: row.checkpoint_id,
1196
+ missionId: row.mission_id,
1197
+ summary: row.summary,
1198
+ frontier: row.frontier_json ? JSON.parse(row.frontier_json) : {},
1199
+ knowledge: row.knowledge_json ? JSON.parse(row.knowledge_json) : {},
1200
+ createdAt: row.created_at,
1201
+ }
1202
+ : undefined;
1203
+ }
1204
+ addMissionEvent(missionId, eventType, actorDid, payload) {
1205
+ const eventId = `${missionId}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
1206
+ this.db
1207
+ .prepare(`INSERT INTO mission_events (event_id, mission_id, event_type, actor_did, payload_json, created_at)
1208
+ VALUES (?, ?, ?, ?, ?, ?)`)
1209
+ .run(eventId, missionId, eventType, actorDid, payload ? JSON.stringify(payload) : null, Date.now());
1210
+ }
1211
+ saveResearchArtifact(input) {
1212
+ this.db
1213
+ .prepare(`INSERT OR REPLACE INTO research_artifacts
1214
+ (artifact_id, mission_id, worker_did, artifact_type, source_url, external_id, content_hash, metadata_json, created_at)
1215
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1216
+ .run(input.artifactId, input.missionId ?? null, input.workerDid ?? null, input.artifactType, input.sourceUrl ?? null, input.externalId ?? null, input.contentHash ?? null, input.metadata ? JSON.stringify(input.metadata) : null, Date.now());
1217
+ }
1218
+ mapMission(row) {
1219
+ return {
1220
+ missionId: row.mission_id,
1221
+ roomId: row.room_id,
1222
+ goal: row.goal,
1223
+ missionType: row.mission_type,
1224
+ templateId: row.template_id ?? undefined,
1225
+ mode: row.mode,
1226
+ status: row.status,
1227
+ leaderDid: row.leader_did,
1228
+ cadenceMs: row.cadence_ms,
1229
+ policy: row.policy_json ? JSON.parse(row.policy_json) : {},
1230
+ research: row.research_json ? JSON.parse(row.research_json) : {},
1231
+ knowledge: row.knowledge_json ? JSON.parse(row.knowledge_json) : undefined,
1232
+ activeChainIds: row.active_chain_ids ? JSON.parse(row.active_chain_ids) : [],
1233
+ lastTickAt: row.last_tick_at ?? undefined,
1234
+ nextTickAt: row.next_tick_at ?? undefined,
1235
+ createdAt: row.created_at,
1236
+ updatedAt: row.updated_at,
1237
+ };
1238
+ }
1239
+ // ─── CoC CRUD ─────────────────────────────────────────────────
1240
+ createChain(chainId, roomId, goal, templateId, createdBy, priority = 'normal', timeoutAt) {
1241
+ this.db
1242
+ .prepare(`INSERT INTO coc_chains (chain_id, room_id, goal, template_id, created_by, priority, timeout_at)
1243
+ VALUES (?, ?, ?, ?, ?, ?, ?)`)
1244
+ .run(chainId, roomId, goal, templateId, createdBy, priority, timeoutAt ?? null);
1245
+ }
1246
+ updateChainStatus(chainId, status, finalReport) {
1247
+ this.db
1248
+ .prepare(`UPDATE coc_chains SET status = ?, closed_at = unixepoch(), final_report = ? WHERE chain_id = ?`)
1249
+ .run(status, finalReport ?? null, chainId);
1250
+ }
1251
+ createStep(stepId, chainId, kind, title, description, dependsOn, requirements, timeoutMs) {
1252
+ this.db
1253
+ .prepare(`INSERT OR IGNORE INTO coc_steps (step_id, chain_id, kind, title, description, depends_on, requirements_json, timeout_ms)
1254
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
1255
+ .run(stepId, chainId, kind, title, description, JSON.stringify(dependsOn), requirements ? JSON.stringify(requirements) : null, timeoutMs ?? null);
1256
+ }
1257
+ updateStepStatus(stepId, status, updates = {}) {
1258
+ const fields = ['status = ?', 'updated_at = unixepoch()'];
1259
+ const values = [status];
1260
+ if (updates.assigneeDid !== undefined) {
1261
+ fields.push('assignee_did = ?');
1262
+ values.push(updates.assigneeDid);
1263
+ }
1264
+ if (updates.leaseMs !== undefined) {
1265
+ fields.push('lease_ms = ?');
1266
+ values.push(updates.leaseMs);
1267
+ if (updates.leaseMs !== null) {
1268
+ fields.push('lease_started_at = unixepoch()');
1269
+ }
1270
+ }
1271
+ if (updates.memo !== undefined) {
1272
+ fields.push('memo = ?');
1273
+ values.push(updates.memo);
1274
+ }
1275
+ if (updates.artifacts !== undefined) {
1276
+ fields.push('artifacts_json = ?');
1277
+ values.push(JSON.stringify(updates.artifacts));
1278
+ }
1279
+ if (updates.metrics !== undefined) {
1280
+ fields.push('metrics_json = ?');
1281
+ values.push(JSON.stringify(updates.metrics));
1282
+ }
1283
+ if (updates.retryCount !== undefined) {
1284
+ fields.push('retry_count = ?');
1285
+ values.push(updates.retryCount);
1286
+ }
1287
+ values.push(stepId);
1288
+ this.db
1289
+ .prepare(`UPDATE coc_steps SET ${fields.join(', ')} WHERE step_id = ?`)
1290
+ .run(...values);
1291
+ }
1292
+ addCocEvent(chainId, stepId, eventType, actorDid, data) {
1293
+ const eventId = `${chainId}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
1294
+ this.db
1295
+ .prepare(`INSERT INTO coc_events (event_id, chain_id, step_id, event_type, actor_did, data)
1296
+ VALUES (?, ?, ?, ?, ?, ?)`)
1297
+ .run(eventId, chainId, stepId, eventType, actorDid, data ? JSON.stringify(data) : null);
1298
+ }
1299
+ getChain(chainId) {
1300
+ return this.db.prepare('SELECT * FROM coc_chains WHERE chain_id = ?').get(chainId);
1301
+ }
1302
+ getChainSteps(chainId) {
1303
+ return this.db.prepare('SELECT * FROM coc_steps WHERE chain_id = ?').all(chainId);
1304
+ }
1305
+ getStepRecord(stepId) {
1306
+ return this.db.prepare('SELECT * FROM coc_steps WHERE step_id = ?').get(stepId);
1307
+ }
1308
+ getAssignedStepsForDid(assigneeDid) {
1309
+ return this.db
1310
+ .prepare(`SELECT s.*, c.room_id
1311
+ FROM coc_steps s
1312
+ JOIN coc_chains c ON c.chain_id = s.chain_id
1313
+ WHERE s.assignee_did = ?
1314
+ AND s.status = 'assigned'
1315
+ ORDER BY s.updated_at ASC`)
1316
+ .all(assigneeDid);
1317
+ }
1318
+ getChainEvents(chainId) {
1319
+ return this.db.prepare('SELECT * FROM coc_events WHERE chain_id = ? ORDER BY ts ASC').all(chainId);
1320
+ }
1321
+ // ─── Reputation CRUD ──────────────────────────────────────────
1322
+ saveReputation(reputation) {
1323
+ this.db
1324
+ .prepare(`INSERT OR REPLACE INTO reputation
1325
+ (did, overall_score, trust_tier, metrics_json, specialties_json, first_seen, last_updated, version)
1326
+ VALUES (?, ?, ?, ?, ?, ?, unixepoch(), ?)`)
1327
+ .run(reputation.did, reputation.overall, reputation.trust_tier, JSON.stringify(reputation.metrics), JSON.stringify(reputation.specialties), reputation.first_seen, reputation.version);
1328
+ }
1329
+ getReputationRecord(did) {
1330
+ return this.db.prepare('SELECT * FROM reputation WHERE did = ?').get(did);
1331
+ }
1332
+ saveTaskOutcome(outcome) {
1333
+ const outcomeId = `outcome-${Date.now()}-${Math.random().toString(36).slice(2)}`;
1334
+ this.db
1335
+ .prepare(`INSERT INTO task_outcomes
1336
+ (outcome_id, did, chain_id, step_id, status, quality_score, latency_ms, lease_ms,
1337
+ accepted, tokens_used, cost_usd, specialties_json, timestamp)
1338
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1339
+ .run(outcomeId, outcome.did, outcome.chain_id, outcome.step_id, outcome.status, outcome.quality_score ?? null, outcome.latency_ms, outcome.lease_ms, outcome.accepted ? 1 : 0, outcome.tokens_used ?? null, outcome.cost_usd ?? null, JSON.stringify(outcome.specialties_used), outcome.timestamp);
1340
+ }
1341
+ getAllTaskOutcomes(did) {
1342
+ const rows = this.db
1343
+ .prepare('SELECT * FROM task_outcomes WHERE did = ? ORDER BY timestamp DESC')
1344
+ .all(did);
1345
+ return rows.map(row => ({
1346
+ did: row.did,
1347
+ chain_id: row.chain_id,
1348
+ step_id: row.step_id,
1349
+ status: row.status,
1350
+ quality_score: row.quality_score,
1351
+ latency_ms: row.latency_ms,
1352
+ lease_ms: row.lease_ms,
1353
+ accepted: Boolean(row.accepted),
1354
+ tokens_used: row.tokens_used,
1355
+ cost_usd: row.cost_usd,
1356
+ specialties_used: JSON.parse(row.specialties_json || '[]'),
1357
+ timestamp: row.timestamp,
1358
+ }));
1359
+ }
1360
+ getTaskOutcomes(did, days) {
1361
+ const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
1362
+ const rows = this.db
1363
+ .prepare('SELECT * FROM task_outcomes WHERE did = ? AND timestamp > ? ORDER BY timestamp DESC')
1364
+ .all(did, cutoff);
1365
+ return rows.map(row => ({
1366
+ did: row.did,
1367
+ chain_id: row.chain_id,
1368
+ step_id: row.step_id,
1369
+ status: row.status,
1370
+ quality_score: row.quality_score,
1371
+ latency_ms: row.latency_ms,
1372
+ lease_ms: row.lease_ms,
1373
+ accepted: Boolean(row.accepted),
1374
+ tokens_used: row.tokens_used,
1375
+ cost_usd: row.cost_usd,
1376
+ specialties_used: JSON.parse(row.specialties_json || '[]'),
1377
+ timestamp: row.timestamp,
1378
+ }));
1379
+ }
1380
+ savePeerReview(review) {
1381
+ const reviewId = `review-${Date.now()}-${Math.random().toString(36).slice(2)}`;
1382
+ this.db
1383
+ .prepare(`INSERT INTO peer_reviews
1384
+ (review_id, reviewer_did, subject_did, chain_id, step_id, rating, feedback, timestamp)
1385
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
1386
+ .run(reviewId, review.reviewer_did, review.subject_did, review.chain_id, review.step_id, review.rating, review.feedback ?? null, review.timestamp);
1387
+ }
1388
+ getPeerReviews(subjectDid) {
1389
+ return this.db
1390
+ .prepare('SELECT rating, timestamp FROM peer_reviews WHERE subject_did = ? ORDER BY timestamp DESC')
1391
+ .all(subjectDid);
1392
+ }
1393
+ // ─── Artifact CRUD ────────────────────────────────────────────
1394
+ saveArtifact(artifact) {
1395
+ this.db
1396
+ .prepare(`INSERT OR REPLACE INTO artifacts
1397
+ (artifact_id, artifact_type, content_hash, size_bytes, storage_path,
1398
+ inline_content, metadata_json, created_by, created_at)
1399
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1400
+ .run(artifact.artifact_id, artifact.artifact_type, artifact.content_hash, artifact.size_bytes, artifact.uri ?? null, artifact.content ?? null, artifact.metadata ? JSON.stringify(artifact.metadata) : null, artifact.created_by ?? null, Date.now());
1401
+ // Save lineage if present
1402
+ if (artifact.provenance && artifact.provenance.length > 0) {
1403
+ for (const parentId of artifact.provenance) {
1404
+ this.db
1405
+ .prepare(`INSERT OR IGNORE INTO artifact_lineage (child_artifact_id, parent_artifact_id)
1406
+ VALUES (?, ?)`)
1407
+ .run(artifact.artifact_id, parentId);
1408
+ }
1409
+ }
1410
+ }
1411
+ getArtifact(artifactId) {
1412
+ return this.db.prepare('SELECT * FROM artifacts WHERE artifact_id = ?').get(artifactId);
1413
+ }
1414
+ getArtifactLineage(artifactId) {
1415
+ const rows = this.db
1416
+ .prepare('SELECT parent_artifact_id FROM artifact_lineage WHERE child_artifact_id = ?')
1417
+ .all(artifactId);
1418
+ return rows.map(r => r.parent_artifact_id);
1419
+ }
1420
+ // ─── Lease Monitor ────────────────────────────────────────────
1421
+ trackLease(chainId, stepId, assigneeDid, leaseMs) {
1422
+ const expiryAt = Date.now() + leaseMs;
1423
+ this.db
1424
+ .prepare(`INSERT OR REPLACE INTO lease_monitor
1425
+ (chain_id, step_id, assignee_did, lease_started_at, lease_ms, expiry_at, notified)
1426
+ VALUES (?, ?, ?, unixepoch(), ?, ?, FALSE)`)
1427
+ .run(chainId, stepId, assigneeDid, leaseMs, expiryAt);
1428
+ }
1429
+ getExpiredLeases() {
1430
+ return this.db
1431
+ .prepare(`SELECT chain_id, step_id, assignee_did FROM lease_monitor
1432
+ WHERE expiry_at < ? AND notified = FALSE`)
1433
+ .all(Date.now());
1434
+ }
1435
+ /**
1436
+ * Atomically claim an expired lease: marks it notified and resets the step
1437
+ * in a single transaction. Returns true if this call won the race.
1438
+ */
1439
+ claimExpiredLease(chainId, stepId) {
1440
+ const result = this.db.transaction(() => {
1441
+ const updated = this.db
1442
+ .prepare(`UPDATE lease_monitor SET notified = TRUE
1443
+ WHERE chain_id = ? AND step_id = ? AND notified = FALSE AND expiry_at < ?`)
1444
+ .run(chainId, stepId, Date.now());
1445
+ return updated.changes > 0;
1446
+ })();
1447
+ return result;
1448
+ }
1449
+ markLeaseNotified(chainId, stepId) {
1450
+ this.db
1451
+ .prepare('UPDATE lease_monitor SET notified = TRUE WHERE chain_id = ? AND step_id = ?')
1452
+ .run(chainId, stepId);
1453
+ }
1454
+ removeLease(chainId, stepId) {
1455
+ this.db
1456
+ .prepare('DELETE FROM lease_monitor WHERE chain_id = ? AND step_id = ?')
1457
+ .run(chainId, stepId);
1458
+ }
1459
+ // ─── Federation Storage ───────────────────────────────────────
1460
+ saveFederation(federation) {
1461
+ this.db.prepare(`
1462
+ INSERT OR REPLACE INTO federations
1463
+ (id, data, updated_at) VALUES (?, ?, ?)
1464
+ `).run(federation.id, this.encode(federation), Date.now());
1465
+ }
1466
+ getFederations() {
1467
+ const rows = this.db.prepare('SELECT data FROM federations').all();
1468
+ return rows.map(r => this.decode(r.data));
1469
+ }
1470
+ // ─── Knowledge Storage ────────────────────────────────────────
1471
+ saveKnowledgeCard(card) {
1472
+ this.db.prepare(`
1473
+ INSERT OR REPLACE INTO knowledge_cards
1474
+ (id, space_id, data, updated_at) VALUES (?, ?, ?, ?)
1475
+ `).run(card.id, card.spaceId, this.encode(card), Date.now());
1476
+ }
1477
+ saveKnowledgeSpace(space) {
1478
+ this.db.prepare(`
1479
+ INSERT OR REPLACE INTO knowledge_spaces
1480
+ (id, data, updated_at) VALUES (?, ?, ?)
1481
+ `).run(space.id, this.encode(space), Date.now());
1482
+ }
1483
+ getKnowledgeSpaces() {
1484
+ try {
1485
+ const rows = this.db.prepare('SELECT data FROM knowledge_spaces').all();
1486
+ return rows.map(r => this.decode(r.data));
1487
+ }
1488
+ catch {
1489
+ return [];
1490
+ }
1491
+ }
1492
+ getKnowledgeCards(spaceId) {
1493
+ try {
1494
+ const rows = spaceId
1495
+ ? this.db.prepare('SELECT data FROM knowledge_cards WHERE space_id = ?').all(spaceId)
1496
+ : this.db.prepare('SELECT data FROM knowledge_cards').all();
1497
+ return rows.map(r => this.decode(r.data));
1498
+ }
1499
+ catch {
1500
+ return [];
1501
+ }
1502
+ }
1503
+ saveKnowledgeLink(link) {
1504
+ this.db.prepare(`
1505
+ INSERT OR REPLACE INTO knowledge_links
1506
+ (id, data) VALUES (?, ?)
1507
+ `).run(link.id, this.encode(link));
1508
+ }
1509
+ getKnowledgeLinks() {
1510
+ try {
1511
+ const rows = this.db.prepare('SELECT data FROM knowledge_links').all();
1512
+ return rows.map(r => this.decode(r.data));
1513
+ }
1514
+ catch {
1515
+ return [];
1516
+ }
1517
+ }
1518
+ saveCollectiveUnconscious(cu) {
1519
+ this.db.prepare(`
1520
+ INSERT OR REPLACE INTO collective_unconscious
1521
+ (id, space_id, data, updated_at) VALUES (?, ?, ?, ?)
1522
+ `).run(cu.id, cu.spaceId, this.encode(cu), Date.now());
1523
+ }
1524
+ getCollectiveUnconscious(spaceId) {
1525
+ try {
1526
+ const rows = spaceId
1527
+ ? this.db.prepare('SELECT data FROM collective_unconscious WHERE space_id = ?').all(spaceId)
1528
+ : this.db.prepare('SELECT data FROM collective_unconscious').all();
1529
+ return rows.map(r => this.decode(r.data));
1530
+ }
1531
+ catch {
1532
+ return [];
1533
+ }
1534
+ }
1535
+ // ─── Integration Storage ──────────────────────────────────────
1536
+ saveCoCKnowledgeBinding(binding) {
1537
+ this.db.prepare(`
1538
+ INSERT OR REPLACE INTO coc_knowledge_bindings
1539
+ (coc_id, knowledge_space_id, auto_index_steps, index_artifacts, index_decisions)
1540
+ VALUES (?, ?, ?, ?, ?)
1541
+ `).run(binding.cocId, binding.knowledgeSpaceId, binding.autoIndexSteps ? 1 : 0, binding.indexArtifacts ? 1 : 0, binding.indexDecisions ? 1 : 0);
1542
+ }
1543
+ getCoCKnowledgeBinding(cocId) {
1544
+ return this.db.prepare('SELECT * FROM coc_knowledge_bindings WHERE coc_id = ?').get(cocId);
1545
+ }
1546
+ saveFederationRoom(federationId, roomId, data) {
1547
+ this.db.prepare(`
1548
+ INSERT OR REPLACE INTO federation_rooms
1549
+ (federation_id, room_id, data, updated_at) VALUES (?, ?, ?, ?)
1550
+ `).run(federationId, roomId, JSON.stringify(data), Date.now());
1551
+ }
1552
+ getFederationRooms(federationId) {
1553
+ try {
1554
+ const rows = this.db.prepare('SELECT data FROM federation_rooms WHERE federation_id = ?').all(federationId);
1555
+ return rows.map(r => JSON.parse(r.data));
1556
+ }
1557
+ catch {
1558
+ return [];
1559
+ }
1560
+ }
1561
+ // ─── Federation Governance Storage ────────────────────────────
1562
+ saveFederationProposal(proposalId, federationId, proposerDid, policy, status = 'open') {
1563
+ this.db.prepare(`
1564
+ INSERT OR REPLACE INTO federation_proposals
1565
+ (proposal_id, federation_id, proposer_did, policy_json, status, created_at, closed_at)
1566
+ VALUES (?, ?, ?, ?, ?, ?, NULL)
1567
+ `).run(proposalId, federationId, proposerDid, this.encode(policy), status, Date.now());
1568
+ }
1569
+ setFederationProposalStatus(proposalId, status) {
1570
+ this.db.prepare(`
1571
+ UPDATE federation_proposals
1572
+ SET status = ?, closed_at = ?
1573
+ WHERE proposal_id = ?
1574
+ `).run(status, Date.now(), proposalId);
1575
+ }
1576
+ getFederationProposal(proposalId) {
1577
+ const row = this.db.prepare('SELECT * FROM federation_proposals WHERE proposal_id = ?').get(proposalId);
1578
+ if (!row)
1579
+ return undefined;
1580
+ return {
1581
+ ...row,
1582
+ policy: this.decode(row.policy_json),
1583
+ };
1584
+ }
1585
+ getFederationProposals(federationId) {
1586
+ const rows = federationId
1587
+ ? this.db.prepare('SELECT * FROM federation_proposals WHERE federation_id = ?').all(federationId)
1588
+ : this.db.prepare('SELECT * FROM federation_proposals').all();
1589
+ return rows.map((row) => ({
1590
+ ...row,
1591
+ policy: this.decode(row.policy_json),
1592
+ }));
1593
+ }
1594
+ saveFederationVote(proposalId, voterDid, vote, votingPower) {
1595
+ this.db.prepare(`
1596
+ INSERT OR REPLACE INTO federation_votes
1597
+ (proposal_id, voter_did, vote, voting_power, voted_at)
1598
+ VALUES (?, ?, ?, ?, ?)
1599
+ `).run(proposalId, voterDid, vote, votingPower, Date.now());
1600
+ }
1601
+ getFederationVotes(proposalId) {
1602
+ return this.db.prepare(`
1603
+ SELECT voter_did, vote, voting_power, voted_at
1604
+ FROM federation_votes
1605
+ WHERE proposal_id = ?
1606
+ ORDER BY voted_at ASC
1607
+ `).all(proposalId);
1608
+ }
1609
+ // ─── Federation Mesh Storage ─────────────────────────────────
1610
+ saveFederationPeering(peering) {
1611
+ this.db.prepare(`
1612
+ INSERT OR REPLACE INTO federation_peerings
1613
+ (
1614
+ peering_id,
1615
+ source_federation_id,
1616
+ source_federation_did,
1617
+ target_federation_did,
1618
+ policy_json,
1619
+ status,
1620
+ reason,
1621
+ created_at,
1622
+ updated_at,
1623
+ responded_at
1624
+ )
1625
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1626
+ `).run(peering.peeringId, peering.sourceFederationId, peering.sourceFederationDid, peering.targetFederationDid, this.encode(peering.policy), peering.status, peering.reason ?? null, peering.createdAt, peering.updatedAt, peering.respondedAt ?? null);
1627
+ }
1628
+ updateFederationPeeringStatus(peeringId, status, reason, respondedAt) {
1629
+ this.db.prepare(`
1630
+ UPDATE federation_peerings
1631
+ SET status = ?, reason = ?, responded_at = ?, updated_at = ?
1632
+ WHERE peering_id = ?
1633
+ `).run(status, reason ?? null, respondedAt ?? null, Date.now(), peeringId);
1634
+ }
1635
+ getFederationPeering(peeringId) {
1636
+ const row = this.db.prepare('SELECT * FROM federation_peerings WHERE peering_id = ?').get(peeringId);
1637
+ if (!row)
1638
+ return undefined;
1639
+ return {
1640
+ peeringId: row.peering_id,
1641
+ sourceFederationId: row.source_federation_id,
1642
+ sourceFederationDid: row.source_federation_did,
1643
+ targetFederationDid: row.target_federation_did,
1644
+ policy: this.decode(row.policy_json),
1645
+ status: row.status,
1646
+ reason: row.reason ?? undefined,
1647
+ createdAt: row.created_at,
1648
+ updatedAt: row.updated_at,
1649
+ respondedAt: row.responded_at ?? undefined
1650
+ };
1651
+ }
1652
+ listFederationPeerings(federationId, status, federationDid) {
1653
+ const clauses = [];
1654
+ const params = [];
1655
+ if (federationId && federationDid) {
1656
+ clauses.push('(source_federation_id = ? OR target_federation_did = ?)');
1657
+ params.push(federationId, federationDid);
1658
+ }
1659
+ else if (federationId) {
1660
+ clauses.push('source_federation_id = ?');
1661
+ params.push(federationId);
1662
+ }
1663
+ else if (federationDid) {
1664
+ clauses.push('target_federation_did = ?');
1665
+ params.push(federationDid);
1666
+ }
1667
+ if (status) {
1668
+ clauses.push('status = ?');
1669
+ params.push(status);
1670
+ }
1671
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(' AND ')}` : '';
1672
+ const rows = this.db.prepare(`
1673
+ SELECT * FROM federation_peerings
1674
+ ${where}
1675
+ ORDER BY updated_at DESC
1676
+ `).all(...params);
1677
+ return rows.map((row) => ({
1678
+ peeringId: row.peering_id,
1679
+ sourceFederationId: row.source_federation_id,
1680
+ sourceFederationDid: row.source_federation_did,
1681
+ targetFederationDid: row.target_federation_did,
1682
+ policy: this.decode(row.policy_json),
1683
+ status: row.status,
1684
+ reason: row.reason ?? undefined,
1685
+ createdAt: row.created_at,
1686
+ updatedAt: row.updated_at,
1687
+ respondedAt: row.responded_at ?? undefined
1688
+ }));
1689
+ }
1690
+ saveFederationBridge(bridge) {
1691
+ this.db.prepare(`
1692
+ INSERT OR REPLACE INTO federation_bridges
1693
+ (
1694
+ bridge_id,
1695
+ peering_id,
1696
+ local_federation_id,
1697
+ local_room_id,
1698
+ remote_room_id,
1699
+ rules_json,
1700
+ status,
1701
+ events_in,
1702
+ events_out,
1703
+ last_sync_at,
1704
+ created_at,
1705
+ updated_at
1706
+ )
1707
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1708
+ `).run(bridge.bridgeId, bridge.peeringId, bridge.localFederationId, bridge.localRoomId, bridge.remoteRoomId, this.encode(bridge.rules), bridge.status, bridge.eventsIn, bridge.eventsOut, bridge.lastSyncAt ?? null, bridge.createdAt, bridge.updatedAt);
1709
+ }
1710
+ updateFederationBridgeStatus(bridgeId, status, lastSyncAt) {
1711
+ this.db.prepare(`
1712
+ UPDATE federation_bridges
1713
+ SET status = ?, last_sync_at = ?, updated_at = ?
1714
+ WHERE bridge_id = ?
1715
+ `).run(status, lastSyncAt ?? null, Date.now(), bridgeId);
1716
+ }
1717
+ incrementFederationBridgeCounters(bridgeId, direction, count = 1, lastSyncAt = Date.now()) {
1718
+ if (direction === 'in') {
1719
+ this.db.prepare(`
1720
+ UPDATE federation_bridges
1721
+ SET events_in = events_in + ?, last_sync_at = ?, updated_at = ?
1722
+ WHERE bridge_id = ?
1723
+ `).run(count, lastSyncAt, Date.now(), bridgeId);
1724
+ return;
1725
+ }
1726
+ this.db.prepare(`
1727
+ UPDATE federation_bridges
1728
+ SET events_out = events_out + ?, last_sync_at = ?, updated_at = ?
1729
+ WHERE bridge_id = ?
1730
+ `).run(count, lastSyncAt, Date.now(), bridgeId);
1731
+ }
1732
+ getFederationBridge(bridgeId) {
1733
+ const row = this.db.prepare('SELECT * FROM federation_bridges WHERE bridge_id = ?').get(bridgeId);
1734
+ if (!row)
1735
+ return undefined;
1736
+ return {
1737
+ bridgeId: row.bridge_id,
1738
+ peeringId: row.peering_id,
1739
+ localFederationId: row.local_federation_id,
1740
+ localRoomId: row.local_room_id,
1741
+ remoteRoomId: row.remote_room_id,
1742
+ rules: this.decode(row.rules_json),
1743
+ status: row.status,
1744
+ eventsIn: row.events_in,
1745
+ eventsOut: row.events_out,
1746
+ lastSyncAt: row.last_sync_at ?? undefined,
1747
+ createdAt: row.created_at,
1748
+ updatedAt: row.updated_at
1749
+ };
1750
+ }
1751
+ listFederationBridges(federationId, status) {
1752
+ const clauses = [];
1753
+ const params = [];
1754
+ if (federationId) {
1755
+ clauses.push('local_federation_id = ?');
1756
+ params.push(federationId);
1757
+ }
1758
+ if (status) {
1759
+ clauses.push('status = ?');
1760
+ params.push(status);
1761
+ }
1762
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(' AND ')}` : '';
1763
+ const rows = this.db.prepare(`
1764
+ SELECT * FROM federation_bridges
1765
+ ${where}
1766
+ ORDER BY updated_at DESC
1767
+ `).all(...params);
1768
+ return rows.map((row) => ({
1769
+ bridgeId: row.bridge_id,
1770
+ peeringId: row.peering_id,
1771
+ localFederationId: row.local_federation_id,
1772
+ localRoomId: row.local_room_id,
1773
+ remoteRoomId: row.remote_room_id,
1774
+ rules: this.decode(row.rules_json),
1775
+ status: row.status,
1776
+ eventsIn: row.events_in,
1777
+ eventsOut: row.events_out,
1778
+ lastSyncAt: row.last_sync_at ?? undefined,
1779
+ createdAt: row.created_at,
1780
+ updatedAt: row.updated_at
1781
+ }));
1782
+ }
1783
+ saveFederationSyncCursor(cursor) {
1784
+ this.db.prepare(`
1785
+ INSERT OR REPLACE INTO federation_sync_cursor
1786
+ (bridge_id, direction, cursor_id, updated_at)
1787
+ VALUES (?, ?, ?, ?)
1788
+ `).run(cursor.bridgeId, cursor.direction, cursor.cursorId, cursor.updatedAt);
1789
+ }
1790
+ getFederationSyncCursor(bridgeId, direction) {
1791
+ const row = this.db.prepare(`
1792
+ SELECT bridge_id, direction, cursor_id, updated_at
1793
+ FROM federation_sync_cursor
1794
+ WHERE bridge_id = ? AND direction = ?
1795
+ `).get(bridgeId, direction);
1796
+ if (!row)
1797
+ return undefined;
1798
+ return {
1799
+ bridgeId: row.bridge_id,
1800
+ direction: row.direction,
1801
+ cursorId: row.cursor_id,
1802
+ updatedAt: row.updated_at
1803
+ };
1804
+ }
1805
+ appendFederationSyncLog(entry) {
1806
+ this.db.prepare(`
1807
+ INSERT OR IGNORE INTO federation_sync_log
1808
+ (
1809
+ bridge_id,
1810
+ envelope_id,
1811
+ direction,
1812
+ message_type,
1813
+ from_federation_id,
1814
+ to_federation_id,
1815
+ status,
1816
+ error,
1817
+ ts
1818
+ )
1819
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
1820
+ `).run(entry.bridgeId, entry.envelopeId, entry.direction, entry.messageType, entry.fromFederationId ?? null, entry.toFederationId ?? null, entry.status, entry.error ?? null, entry.ts);
1821
+ }
1822
+ hasFederationSyncLog(bridgeId, envelopeId, direction) {
1823
+ const row = this.db.prepare(`
1824
+ SELECT 1 FROM federation_sync_log
1825
+ WHERE bridge_id = ? AND envelope_id = ? AND direction = ?
1826
+ `).get(bridgeId, envelopeId, direction);
1827
+ return !!row;
1828
+ }
1829
+ listFederationSyncLog(bridgeId, limit = 100) {
1830
+ const rows = this.db.prepare(`
1831
+ SELECT id, bridge_id, envelope_id, direction, message_type,
1832
+ from_federation_id, to_federation_id, status, error, ts
1833
+ FROM federation_sync_log
1834
+ WHERE bridge_id = ?
1835
+ ORDER BY ts DESC
1836
+ LIMIT ?
1837
+ `).all(bridgeId, limit);
1838
+ return rows.map((row) => ({
1839
+ id: row.id,
1840
+ bridgeId: row.bridge_id,
1841
+ envelopeId: row.envelope_id,
1842
+ direction: row.direction,
1843
+ messageType: row.message_type,
1844
+ fromFederationId: row.from_federation_id ?? undefined,
1845
+ toFederationId: row.to_federation_id ?? undefined,
1846
+ status: row.status,
1847
+ error: row.error ?? undefined,
1848
+ ts: row.ts
1849
+ }));
1850
+ }
1851
+ getFederationMeshStats(federationId) {
1852
+ const where = federationId ? 'WHERE local_federation_id = ?' : '';
1853
+ const row = this.db.prepare(`
1854
+ SELECT
1855
+ COUNT(*) AS bridge_count,
1856
+ SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) AS active_bridges,
1857
+ COALESCE(SUM(events_in), 0) AS events_in,
1858
+ COALESCE(SUM(events_out), 0) AS events_out,
1859
+ MAX(last_sync_at) AS last_sync_at
1860
+ FROM federation_bridges
1861
+ ${where}
1862
+ `).get(...(federationId ? [federationId] : []));
1863
+ return {
1864
+ bridgeCount: row?.bridge_count ?? 0,
1865
+ activeBridges: row?.active_bridges ?? 0,
1866
+ eventsIn: row?.events_in ?? 0,
1867
+ eventsOut: row?.events_out ?? 0,
1868
+ lastSyncAt: row?.last_sync_at ?? undefined
1869
+ };
1870
+ }
1871
+ // ─── Persona Vault Storage ────────────────────────────────────
1872
+ savePersonaVault(vault) {
1873
+ const createdAt = vault.createdAt || Date.now();
1874
+ const updatedAt = vault.updatedAt || Date.now();
1875
+ this.db.prepare(`
1876
+ INSERT OR REPLACE INTO persona_vaults
1877
+ (id, owner_did, name, data, created_at, updated_at)
1878
+ VALUES (?, ?, ?, ?, ?, ?)
1879
+ `).run(vault.id, vault.ownerDid, vault.name, this.encode(vault), createdAt, updatedAt);
1880
+ }
1881
+ getPersonaVault(vaultId) {
1882
+ const row = this.db.prepare('SELECT data FROM persona_vaults WHERE id = ?').get(vaultId);
1883
+ if (!row)
1884
+ return undefined;
1885
+ return this.decode(row.data);
1886
+ }
1887
+ getPersonaVaults(ownerDid) {
1888
+ const rows = ownerDid
1889
+ ? this.db.prepare('SELECT data FROM persona_vaults WHERE owner_did = ? ORDER BY updated_at DESC').all(ownerDid)
1890
+ : this.db.prepare('SELECT data FROM persona_vaults ORDER BY updated_at DESC').all();
1891
+ return rows.map((r) => this.decode(r.data));
1892
+ }
1893
+ upsertPersonaNode(node) {
1894
+ this.db.prepare(`
1895
+ INSERT OR REPLACE INTO persona_nodes
1896
+ (
1897
+ id,
1898
+ vault_id,
1899
+ domain,
1900
+ node_type,
1901
+ title,
1902
+ content,
1903
+ tags_text,
1904
+ confidence,
1905
+ metadata_json,
1906
+ source_json,
1907
+ valid_from,
1908
+ valid_to,
1909
+ data,
1910
+ created_at,
1911
+ updated_at,
1912
+ deleted_at
1913
+ )
1914
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1915
+ `).run(node.id, node.vaultId, node.domain, node.type, node.title, node.content, (node.tags || []).join(' '), node.confidence ?? 0.8, this.encode(node.metadata || {}), node.source ? this.encode(node.source) : null, node.validFrom ?? null, node.validTo ?? null, this.encode(node), node.createdAt || Date.now(), node.updatedAt || Date.now(), node.deletedAt ?? null);
1916
+ }
1917
+ getPersonaNode(nodeId) {
1918
+ const row = this.db.prepare('SELECT data FROM persona_nodes WHERE id = ?').get(nodeId);
1919
+ if (!row)
1920
+ return undefined;
1921
+ return this.decode(row.data);
1922
+ }
1923
+ listPersonaNodes(vaultId, options = {}) {
1924
+ const clauses = ['vault_id = ?'];
1925
+ const params = [vaultId];
1926
+ if (!options.includeDeleted) {
1927
+ clauses.push('deleted_at IS NULL');
1928
+ }
1929
+ if (options.domain) {
1930
+ clauses.push('domain = ?');
1931
+ params.push(options.domain);
1932
+ }
1933
+ if (options.domains?.length) {
1934
+ clauses.push(`domain IN (${options.domains.map(() => '?').join(',')})`);
1935
+ params.push(...options.domains);
1936
+ }
1937
+ if (options.types?.length) {
1938
+ clauses.push(`node_type IN (${options.types.map(() => '?').join(',')})`);
1939
+ params.push(...options.types);
1940
+ }
1941
+ if (options.tags?.length) {
1942
+ for (const tag of options.tags) {
1943
+ clauses.push('tags_text LIKE ?');
1944
+ params.push(`%${tag}%`);
1945
+ }
1946
+ }
1947
+ const where = clauses.length ? `WHERE ${clauses.join(' AND ')}` : '';
1948
+ const rows = this.db.prepare(`
1949
+ SELECT data
1950
+ FROM persona_nodes
1951
+ ${where}
1952
+ ORDER BY updated_at DESC
1953
+ `).all(...params);
1954
+ return rows.map((r) => this.decode(r.data));
1955
+ }
1956
+ searchPersonaNodes(vaultId, query, limit = 20) {
1957
+ try {
1958
+ const rows = this.db.prepare(`
1959
+ SELECT node_id, bm25(persona_fts) AS rank
1960
+ FROM persona_fts
1961
+ WHERE vault_id = ? AND persona_fts MATCH ?
1962
+ ORDER BY rank
1963
+ LIMIT ?
1964
+ `).all(vaultId, query, limit);
1965
+ return rows.map((row) => ({
1966
+ id: row.node_id,
1967
+ score: Math.max(0, 1 / (1 + Math.abs(row.rank || 1))),
1968
+ }));
1969
+ }
1970
+ catch {
1971
+ const rows = this.db.prepare(`
1972
+ SELECT id, title, content
1973
+ FROM persona_nodes
1974
+ WHERE vault_id = ? AND deleted_at IS NULL AND (
1975
+ title LIKE ? OR content LIKE ? OR tags_text LIKE ?
1976
+ )
1977
+ ORDER BY updated_at DESC
1978
+ LIMIT ?
1979
+ `).all(vaultId, `%${query}%`, `%${query}%`, `%${query}%`, limit);
1980
+ return rows.map((row) => ({
1981
+ id: row.id,
1982
+ score: 0.1,
1983
+ }));
1984
+ }
1985
+ }
1986
+ softDeletePersonaNode(nodeId, deletedAt = Date.now()) {
1987
+ this.db.prepare(`
1988
+ UPDATE persona_nodes
1989
+ SET deleted_at = ?, updated_at = ?
1990
+ WHERE id = ?
1991
+ `).run(deletedAt, Date.now(), nodeId);
1992
+ }
1993
+ upsertPersonaEdge(edge) {
1994
+ this.db.prepare(`
1995
+ INSERT OR REPLACE INTO persona_edges
1996
+ (
1997
+ id,
1998
+ vault_id,
1999
+ source_node_id,
2000
+ target_node_id,
2001
+ edge_type,
2002
+ weight,
2003
+ confidence,
2004
+ metadata_json,
2005
+ data,
2006
+ valid_from,
2007
+ valid_to,
2008
+ updated_at,
2009
+ deleted_at
2010
+ )
2011
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2012
+ `).run(edge.id, edge.vaultId, edge.sourceNodeId, edge.targetNodeId, edge.type, edge.weight ?? 0.8, edge.confidence ?? 0.8, this.encode(edge.metadata || {}), this.encode(edge), edge.validFrom ?? null, edge.validTo ?? null, edge.updatedAt || Date.now(), edge.deletedAt ?? null);
2013
+ }
2014
+ getPersonaEdge(edgeId) {
2015
+ const row = this.db.prepare('SELECT data FROM persona_edges WHERE id = ?').get(edgeId);
2016
+ if (!row)
2017
+ return undefined;
2018
+ return this.decode(row.data);
2019
+ }
2020
+ listPersonaEdges(vaultId) {
2021
+ const rows = this.db.prepare(`
2022
+ SELECT data
2023
+ FROM persona_edges
2024
+ WHERE vault_id = ?
2025
+ ORDER BY updated_at DESC
2026
+ `).all(vaultId);
2027
+ return rows.map((r) => this.decode(r.data));
2028
+ }
2029
+ softDeletePersonaEdge(edgeId, deletedAt = Date.now()) {
2030
+ this.db.prepare(`
2031
+ UPDATE persona_edges
2032
+ SET deleted_at = ?, updated_at = ?
2033
+ WHERE id = ?
2034
+ `).run(deletedAt, Date.now(), edgeId);
2035
+ }
2036
+ upsertPersonaHyperEdge(edge) {
2037
+ this.db.prepare(`
2038
+ INSERT OR REPLACE INTO persona_hyperedges
2039
+ (
2040
+ id,
2041
+ vault_id,
2042
+ node_ids_json,
2043
+ edge_type,
2044
+ metadata_json,
2045
+ data,
2046
+ updated_at,
2047
+ deleted_at
2048
+ )
2049
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
2050
+ `).run(edge.id, edge.vaultId, this.encode(edge.nodeIds || []), edge.type, this.encode(edge.metadata || {}), this.encode(edge), edge.updatedAt || Date.now(), edge.deletedAt ?? null);
2051
+ }
2052
+ listPersonaHyperEdges(vaultId) {
2053
+ const rows = this.db.prepare(`
2054
+ SELECT data
2055
+ FROM persona_hyperedges
2056
+ WHERE vault_id = ?
2057
+ ORDER BY updated_at DESC
2058
+ `).all(vaultId);
2059
+ return rows.map((r) => this.decode(r.data));
2060
+ }
2061
+ upsertPersonaCrdtDoc(doc) {
2062
+ this.db.prepare(`
2063
+ INSERT OR REPLACE INTO persona_crdt_docs
2064
+ (doc_id, vault_id, domain, data_json, clock_json, updated_at)
2065
+ VALUES (?, ?, ?, ?, ?, ?)
2066
+ `).run(doc.docId, doc.vaultId, doc.domain, this.encode(doc.data || {}), this.encode(doc.clock || {}), doc.updatedAt || Date.now());
2067
+ }
2068
+ getPersonaCrdtDoc(docId) {
2069
+ const row = this.db.prepare(`
2070
+ SELECT doc_id, vault_id, domain, data_json, clock_json, updated_at
2071
+ FROM persona_crdt_docs
2072
+ WHERE doc_id = ?
2073
+ `).get(docId);
2074
+ if (!row)
2075
+ return undefined;
2076
+ return {
2077
+ docId: row.doc_id,
2078
+ vaultId: row.vault_id,
2079
+ domain: row.domain,
2080
+ data: this.decode(row.data_json),
2081
+ clock: this.decode(row.clock_json),
2082
+ updatedAt: row.updated_at,
2083
+ };
2084
+ }
2085
+ upsertPersonaEmbedding(record) {
2086
+ const updatedAt = record.updatedAt || Date.now();
2087
+ this.db.prepare(`
2088
+ INSERT OR REPLACE INTO persona_embeddings
2089
+ (node_id, vault_id, model, dim, vector, data_json, updated_at, vec_rowid)
2090
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
2091
+ `).run(record.nodeId, record.vaultId, record.model, record.dim, record.vector ? Buffer.from(JSON.stringify(record.vector), 'utf8') : null, this.encode({ vector: record.vector || [], vecRowId: record.vecRowId }), updatedAt, record.vecRowId ?? null);
2092
+ }
2093
+ upsertPersonaVector(rowId, vector) {
2094
+ try {
2095
+ this.db.prepare(`
2096
+ INSERT OR REPLACE INTO persona_vec(rowid, embedding)
2097
+ VALUES (?, ?)
2098
+ `).run(rowId, `[${vector.join(',')}]`);
2099
+ }
2100
+ catch (error) {
2101
+ if (process.env.NODE_ENV === 'production') {
2102
+ throw new Error(`persona_vec insert failed in production: ${error.message}`);
2103
+ }
2104
+ }
2105
+ }
2106
+ deletePersonaEmbedding(nodeId) {
2107
+ const row = this.db.prepare(`
2108
+ SELECT vec_rowid
2109
+ FROM persona_embeddings
2110
+ WHERE node_id = ?
2111
+ `).get(nodeId);
2112
+ if (row?.vec_rowid) {
2113
+ try {
2114
+ this.db.prepare('DELETE FROM persona_vec WHERE rowid = ?').run(row.vec_rowid);
2115
+ }
2116
+ catch {
2117
+ // no-op when vec index is unavailable in non-production environments
2118
+ }
2119
+ }
2120
+ this.db.prepare('DELETE FROM persona_embeddings WHERE node_id = ?').run(nodeId);
2121
+ }
2122
+ getPersonaEmbedding(nodeId) {
2123
+ const row = this.db.prepare(`
2124
+ SELECT node_id, vault_id, model, dim, data_json, updated_at, vec_rowid
2125
+ FROM persona_embeddings
2126
+ WHERE node_id = ?
2127
+ `).get(nodeId);
2128
+ if (!row)
2129
+ return undefined;
2130
+ const parsed = row.data_json ? this.decode(row.data_json) : {};
2131
+ return {
2132
+ nodeId: row.node_id,
2133
+ vaultId: row.vault_id,
2134
+ model: row.model,
2135
+ dim: row.dim,
2136
+ vector: Array.isArray(parsed?.vector) ? parsed.vector : undefined,
2137
+ vecRowId: row.vec_rowid ?? undefined,
2138
+ updatedAt: row.updated_at,
2139
+ };
2140
+ }
2141
+ searchPersonaVector(vaultId, vector, limit = 20) {
2142
+ const query = `[${vector.join(',')}]`;
2143
+ let rows = [];
2144
+ try {
2145
+ rows = this.db.prepare(`
2146
+ SELECT e.node_id, v.distance
2147
+ FROM persona_vec v
2148
+ JOIN persona_embeddings e ON e.vec_rowid = v.rowid
2149
+ WHERE e.vault_id = ? AND v.embedding MATCH ?
2150
+ ORDER BY v.distance ASC
2151
+ LIMIT ?
2152
+ `).all(vaultId, query, limit);
2153
+ }
2154
+ catch {
2155
+ return [];
2156
+ }
2157
+ return rows.map((row) => ({
2158
+ nodeId: row.node_id,
2159
+ distance: Number(row.distance || 0),
2160
+ score: 1 / (1 + Number(row.distance || 0)),
2161
+ }));
2162
+ }
2163
+ savePersonaCapability(capability) {
2164
+ this.db.prepare(`
2165
+ INSERT OR REPLACE INTO persona_capabilities
2166
+ (
2167
+ id,
2168
+ vault_id,
2169
+ service_did,
2170
+ scope,
2171
+ caveats_json,
2172
+ token_hash,
2173
+ status,
2174
+ issued_at,
2175
+ expires_at,
2176
+ revoked_at,
2177
+ reason,
2178
+ parent_token_id
2179
+ )
2180
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)
2181
+ `).run(capability.id, capability.vaultId, capability.serviceDid, capability.scope, this.encode(capability.caveats || {}), capability.tokenHash, capability.status, capability.issuedAt, capability.expiresAt ?? null, capability.revokedAt ?? null, capability.parentTokenId ?? null);
2182
+ }
2183
+ updatePersonaCapabilityStatus(tokenId, status, reason, updatedAt = Date.now()) {
2184
+ const revokedAt = status === 'revoked' ? updatedAt : null;
2185
+ this.db.prepare(`
2186
+ UPDATE persona_capabilities
2187
+ SET status = ?, revoked_at = ?, reason = ?
2188
+ WHERE id = ?
2189
+ `).run(status, revokedAt, reason ?? null, tokenId);
2190
+ }
2191
+ getPersonaCapabilityByHash(tokenHash) {
2192
+ const row = this.db.prepare(`
2193
+ SELECT *
2194
+ FROM persona_capabilities
2195
+ WHERE token_hash = ?
2196
+ LIMIT 1
2197
+ `).get(tokenHash);
2198
+ if (!row)
2199
+ return undefined;
2200
+ return {
2201
+ id: row.id,
2202
+ vaultId: row.vault_id,
2203
+ serviceDid: row.service_did,
2204
+ scope: row.scope,
2205
+ caveats: this.decode(row.caveats_json),
2206
+ tokenHash: row.token_hash,
2207
+ status: row.status,
2208
+ issuedAt: row.issued_at,
2209
+ expiresAt: row.expires_at ?? undefined,
2210
+ revokedAt: row.revoked_at ?? undefined,
2211
+ parentTokenId: row.parent_token_id ?? undefined,
2212
+ };
2213
+ }
2214
+ getPersonaCapability(tokenId) {
2215
+ const row = this.db.prepare(`
2216
+ SELECT *
2217
+ FROM persona_capabilities
2218
+ WHERE id = ?
2219
+ LIMIT 1
2220
+ `).get(tokenId);
2221
+ if (!row)
2222
+ return undefined;
2223
+ return {
2224
+ id: row.id,
2225
+ vaultId: row.vault_id,
2226
+ serviceDid: row.service_did,
2227
+ scope: row.scope,
2228
+ caveats: this.decode(row.caveats_json),
2229
+ tokenHash: row.token_hash,
2230
+ status: row.status,
2231
+ issuedAt: row.issued_at,
2232
+ expiresAt: row.expires_at ?? undefined,
2233
+ revokedAt: row.revoked_at ?? undefined,
2234
+ parentTokenId: row.parent_token_id ?? undefined,
2235
+ };
2236
+ }
2237
+ listPersonaCapabilities(vaultId) {
2238
+ const rows = this.db.prepare(`
2239
+ SELECT *
2240
+ FROM persona_capabilities
2241
+ WHERE vault_id = ?
2242
+ ORDER BY issued_at DESC
2243
+ `).all(vaultId);
2244
+ return rows.map((row) => ({
2245
+ id: row.id,
2246
+ vaultId: row.vault_id,
2247
+ serviceDid: row.service_did,
2248
+ scope: row.scope,
2249
+ caveats: this.decode(row.caveats_json),
2250
+ tokenHash: row.token_hash,
2251
+ status: row.status,
2252
+ issuedAt: row.issued_at,
2253
+ expiresAt: row.expires_at ?? undefined,
2254
+ revokedAt: row.revoked_at ?? undefined,
2255
+ parentTokenId: row.parent_token_id ?? undefined,
2256
+ }));
2257
+ }
2258
+ appendPersonaAccessLog(entry) {
2259
+ this.db.prepare(`
2260
+ INSERT INTO persona_access_log
2261
+ (
2262
+ vault_id,
2263
+ token_id,
2264
+ service_did,
2265
+ operation,
2266
+ resource,
2267
+ result,
2268
+ details_json,
2269
+ ts,
2270
+ signature,
2271
+ signer_did,
2272
+ sig_alg
2273
+ )
2274
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2275
+ `).run(entry.vaultId, entry.tokenId ?? null, entry.serviceDid, entry.operation, entry.resource, entry.result, entry.details ? this.encode(entry.details) : null, entry.ts, entry.signature ?? null, entry.signerDid ?? null, entry.sigAlg ?? null);
2276
+ }
2277
+ listPersonaAccessLogs(vaultId, limit = 100) {
2278
+ const rows = this.db.prepare(`
2279
+ SELECT *
2280
+ FROM persona_access_log
2281
+ WHERE vault_id = ?
2282
+ ORDER BY ts DESC
2283
+ LIMIT ?
2284
+ `).all(vaultId, limit);
2285
+ return rows.map((row) => ({
2286
+ id: row.id,
2287
+ vaultId: row.vault_id,
2288
+ tokenId: row.token_id ?? undefined,
2289
+ serviceDid: row.service_did,
2290
+ operation: row.operation,
2291
+ resource: row.resource,
2292
+ result: row.result,
2293
+ details: row.details_json ? this.decode(row.details_json) : undefined,
2294
+ ts: row.ts,
2295
+ signature: row.signature ?? undefined,
2296
+ signerDid: row.signer_did ?? undefined,
2297
+ sigAlg: row.sig_alg ?? undefined,
2298
+ }));
2299
+ }
2300
+ getPersonaAccessLog(logId) {
2301
+ const row = this.db.prepare(`
2302
+ SELECT *
2303
+ FROM persona_access_log
2304
+ WHERE id = ?
2305
+ LIMIT 1
2306
+ `).get(logId);
2307
+ if (!row)
2308
+ return undefined;
2309
+ return {
2310
+ id: row.id,
2311
+ vaultId: row.vault_id,
2312
+ tokenId: row.token_id ?? undefined,
2313
+ serviceDid: row.service_did,
2314
+ operation: row.operation,
2315
+ resource: row.resource,
2316
+ result: row.result,
2317
+ details: row.details_json ? this.decode(row.details_json) : undefined,
2318
+ ts: row.ts,
2319
+ signature: row.signature ?? undefined,
2320
+ signerDid: row.signer_did ?? undefined,
2321
+ sigAlg: row.sig_alg ?? undefined,
2322
+ };
2323
+ }
2324
+ savePersonaSyncState(state) {
2325
+ this.db.prepare(`
2326
+ INSERT OR REPLACE INTO persona_sync_state
2327
+ (peer_did, vault_id, cursor_id, clock_json, updated_at)
2328
+ VALUES (?, ?, ?, ?, ?)
2329
+ `).run(state.peerDid, state.vaultId, state.cursorId, this.encode(state.clock || {}), state.updatedAt);
2330
+ }
2331
+ getPersonaSyncState(peerDid, vaultId) {
2332
+ const row = this.db.prepare(`
2333
+ SELECT peer_did, vault_id, cursor_id, clock_json, updated_at
2334
+ FROM persona_sync_state
2335
+ WHERE peer_did = ? AND vault_id = ?
2336
+ `).get(peerDid, vaultId);
2337
+ if (!row)
2338
+ return undefined;
2339
+ return {
2340
+ peerDid: row.peer_did,
2341
+ vaultId: row.vault_id,
2342
+ cursorId: row.cursor_id,
2343
+ clock: this.decode(row.clock_json),
2344
+ updatedAt: row.updated_at,
2345
+ };
2346
+ }
2347
+ markPersonaSyncApplied(fromDid, deltaId, vaultId) {
2348
+ try {
2349
+ this.db.prepare(`
2350
+ INSERT INTO persona_sync_applied (from_did, delta_id, vault_id, applied_at)
2351
+ VALUES (?, ?, ?, ?)
2352
+ `).run(fromDid, deltaId, vaultId, Date.now());
2353
+ return true;
2354
+ }
2355
+ catch {
2356
+ return false;
2357
+ }
2358
+ }
2359
+ hasPersonaSyncApplied(fromDid, deltaId) {
2360
+ const row = this.db.prepare(`
2361
+ SELECT 1
2362
+ FROM persona_sync_applied
2363
+ WHERE from_did = ? AND delta_id = ?
2364
+ LIMIT 1
2365
+ `).get(fromDid, deltaId);
2366
+ return !!row;
2367
+ }
2368
+ upsertPersonaRetentionState(record) {
2369
+ this.db.prepare(`
2370
+ INSERT OR REPLACE INTO persona_retention_state
2371
+ (vault_id, domain, last_cleanup_at)
2372
+ VALUES (?, ?, ?)
2373
+ `).run(record.vaultId, record.domain, record.lastCleanupAt);
2374
+ }
2375
+ getPersonaRetentionState(vaultId, domain) {
2376
+ const row = this.db.prepare(`
2377
+ SELECT vault_id, domain, last_cleanup_at
2378
+ FROM persona_retention_state
2379
+ WHERE vault_id = ? AND domain = ?
2380
+ LIMIT 1
2381
+ `).get(vaultId, domain);
2382
+ if (!row)
2383
+ return undefined;
2384
+ return {
2385
+ vaultId: row.vault_id,
2386
+ domain: row.domain,
2387
+ lastCleanupAt: row.last_cleanup_at,
2388
+ };
2389
+ }
2390
+ listPersonaRetentionStates(vaultId) {
2391
+ const rows = vaultId
2392
+ ? this.db.prepare(`
2393
+ SELECT vault_id, domain, last_cleanup_at
2394
+ FROM persona_retention_state
2395
+ WHERE vault_id = ?
2396
+ ORDER BY domain ASC
2397
+ `).all(vaultId)
2398
+ : this.db.prepare(`
2399
+ SELECT vault_id, domain, last_cleanup_at
2400
+ FROM persona_retention_state
2401
+ ORDER BY vault_id ASC, domain ASC
2402
+ `).all();
2403
+ return rows.map((row) => ({
2404
+ vaultId: row.vault_id,
2405
+ domain: row.domain,
2406
+ lastCleanupAt: row.last_cleanup_at,
2407
+ }));
2408
+ }
2409
+ upsertPersonaGraphCache(record) {
2410
+ this.db.prepare(`
2411
+ INSERT OR REPLACE INTO persona_graph_cache
2412
+ (vault_id, domain, graph_version, ppr_blob, updated_at)
2413
+ VALUES (?, ?, ?, ?, ?)
2414
+ `).run(record.vaultId, record.domain, record.graphVersion, this.encode(record.ppr || {}), record.updatedAt);
2415
+ }
2416
+ getPersonaGraphCache(vaultId, domain, graphVersion) {
2417
+ const row = this.db.prepare(`
2418
+ SELECT vault_id, domain, graph_version, ppr_blob, updated_at
2419
+ FROM persona_graph_cache
2420
+ WHERE vault_id = ? AND domain = ? AND graph_version = ?
2421
+ LIMIT 1
2422
+ `).get(vaultId, domain, graphVersion);
2423
+ if (!row)
2424
+ return undefined;
2425
+ return {
2426
+ vaultId: row.vault_id,
2427
+ domain: row.domain,
2428
+ graphVersion: row.graph_version,
2429
+ ppr: this.decode(row.ppr_blob),
2430
+ updatedAt: row.updated_at,
2431
+ };
2432
+ }
2433
+ prunePersonaGraphCache(vaultId, domain, keep = 8) {
2434
+ this.db.prepare(`
2435
+ DELETE FROM persona_graph_cache
2436
+ WHERE vault_id = ? AND domain = ? AND graph_version NOT IN (
2437
+ SELECT graph_version
2438
+ FROM persona_graph_cache
2439
+ WHERE vault_id = ? AND domain = ?
2440
+ ORDER BY updated_at DESC
2441
+ LIMIT ?
2442
+ )
2443
+ `).run(vaultId, domain, vaultId, domain, keep);
2444
+ }
2445
+ appendPersonaMetric(record) {
2446
+ this.db.prepare(`
2447
+ INSERT INTO persona_metrics (ts, metric, value, labels_json)
2448
+ VALUES (?, ?, ?, ?)
2449
+ `).run(record.ts, record.metric, record.value, record.labels ? this.encode(record.labels) : null);
2450
+ }
2451
+ listPersonaMetrics(metric, limit = 100, sinceTs) {
2452
+ const rows = sinceTs !== undefined
2453
+ ? this.db.prepare(`
2454
+ SELECT ts, metric, value, labels_json
2455
+ FROM persona_metrics
2456
+ WHERE metric = ? AND ts >= ?
2457
+ ORDER BY ts DESC
2458
+ LIMIT ?
2459
+ `).all(metric, sinceTs, limit)
2460
+ : this.db.prepare(`
2461
+ SELECT ts, metric, value, labels_json
2462
+ FROM persona_metrics
2463
+ WHERE metric = ?
2464
+ ORDER BY ts DESC
2465
+ LIMIT ?
2466
+ `).all(metric, limit);
2467
+ return rows.map((row) => ({
2468
+ ts: row.ts,
2469
+ metric: row.metric,
2470
+ value: row.value,
2471
+ labels: row.labels_json ? this.decode(row.labels_json) : undefined,
2472
+ }));
2473
+ }
2474
+ savePersonaClaim(record) {
2475
+ this.db.prepare(`
2476
+ INSERT OR REPLACE INTO persona_claims
2477
+ (id, vault_id, subject_did, issuer_did, schema, payload_enc, status, issued_at, expires_at, revoked_at, signature)
2478
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2479
+ `).run(record.id, record.vaultId, record.subjectDid, record.issuerDid ?? null, record.schema, record.payloadEnc, record.status, record.issuedAt, record.expiresAt ?? null, record.revokedAt ?? null, record.signature);
2480
+ }
2481
+ getPersonaClaim(claimId) {
2482
+ const row = this.db.prepare(`
2483
+ SELECT *
2484
+ FROM persona_claims
2485
+ WHERE id = ?
2486
+ LIMIT 1
2487
+ `).get(claimId);
2488
+ if (!row)
2489
+ return undefined;
2490
+ return {
2491
+ id: row.id,
2492
+ vaultId: row.vault_id,
2493
+ subjectDid: row.subject_did,
2494
+ issuerDid: row.issuer_did ?? undefined,
2495
+ schema: row.schema,
2496
+ payloadEnc: row.payload_enc,
2497
+ status: row.status,
2498
+ issuedAt: row.issued_at,
2499
+ expiresAt: row.expires_at ?? undefined,
2500
+ revokedAt: row.revoked_at ?? undefined,
2501
+ signature: row.signature,
2502
+ };
2503
+ }
2504
+ listPersonaClaims(vaultId, options = {}) {
2505
+ const rows = options.includeRevoked
2506
+ ? this.db.prepare(`
2507
+ SELECT *
2508
+ FROM persona_claims
2509
+ WHERE vault_id = ?
2510
+ ORDER BY issued_at DESC
2511
+ `).all(vaultId)
2512
+ : this.db.prepare(`
2513
+ SELECT *
2514
+ FROM persona_claims
2515
+ WHERE vault_id = ? AND status = 'active'
2516
+ ORDER BY issued_at DESC
2517
+ `).all(vaultId);
2518
+ return rows.map((row) => ({
2519
+ id: row.id,
2520
+ vaultId: row.vault_id,
2521
+ subjectDid: row.subject_did,
2522
+ issuerDid: row.issuer_did ?? undefined,
2523
+ schema: row.schema,
2524
+ payloadEnc: row.payload_enc,
2525
+ status: row.status,
2526
+ issuedAt: row.issued_at,
2527
+ expiresAt: row.expires_at ?? undefined,
2528
+ revokedAt: row.revoked_at ?? undefined,
2529
+ signature: row.signature,
2530
+ }));
2531
+ }
2532
+ updatePersonaClaimStatus(claimId, status) {
2533
+ this.db.prepare(`
2534
+ UPDATE persona_claims
2535
+ SET status = ?, revoked_at = CASE WHEN ? = 'revoked' THEN ? ELSE revoked_at END
2536
+ WHERE id = ?
2537
+ `).run(status, status, Date.now(), claimId);
2538
+ }
2539
+ savePersonaZkpProof(record) {
2540
+ this.db.prepare(`
2541
+ INSERT OR REPLACE INTO persona_zkp_proofs
2542
+ (id, vault_id, circuit_id, proof_blob, public_inputs_json, claim_ids_json, created_at, expires_at)
2543
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
2544
+ `).run(record.id, record.vaultId, record.circuitId, record.proofBlob, this.encode(record.publicInputs || {}), this.encode(record.claimIds || []), record.createdAt, record.expiresAt ?? null);
2545
+ }
2546
+ getPersonaZkpProof(proofId) {
2547
+ const row = this.db.prepare(`
2548
+ SELECT *
2549
+ FROM persona_zkp_proofs
2550
+ WHERE id = ?
2551
+ LIMIT 1
2552
+ `).get(proofId);
2553
+ if (!row)
2554
+ return undefined;
2555
+ return {
2556
+ id: row.id,
2557
+ vaultId: row.vault_id,
2558
+ circuitId: row.circuit_id,
2559
+ proofBlob: row.proof_blob,
2560
+ publicInputs: this.decode(row.public_inputs_json),
2561
+ claimIds: this.decode(row.claim_ids_json),
2562
+ createdAt: row.created_at,
2563
+ expiresAt: row.expires_at ?? undefined,
2564
+ };
2565
+ }
2566
+ listPersonaZkpProofs(vaultId) {
2567
+ const rows = this.db.prepare(`
2568
+ SELECT *
2569
+ FROM persona_zkp_proofs
2570
+ WHERE vault_id = ?
2571
+ ORDER BY created_at DESC
2572
+ `).all(vaultId);
2573
+ return rows.map((row) => ({
2574
+ id: row.id,
2575
+ vaultId: row.vault_id,
2576
+ circuitId: row.circuit_id,
2577
+ proofBlob: row.proof_blob,
2578
+ publicInputs: this.decode(row.public_inputs_json),
2579
+ claimIds: this.decode(row.claim_ids_json),
2580
+ createdAt: row.created_at,
2581
+ expiresAt: row.expires_at ?? undefined,
2582
+ }));
2583
+ }
2584
+ savePersonaZkpCircuit(record) {
2585
+ this.db.prepare(`
2586
+ INSERT OR REPLACE INTO persona_zkp_circuits
2587
+ (circuit_id, version, vk_blob, metadata_json, active)
2588
+ VALUES (?, ?, ?, ?, ?)
2589
+ `).run(record.circuitId, record.version, record.vkBlob ?? null, this.encode(record.metadata || {}), record.active ? 1 : 0);
2590
+ }
2591
+ getPersonaZkpCircuit(circuitId) {
2592
+ const row = this.db.prepare(`
2593
+ SELECT *
2594
+ FROM persona_zkp_circuits
2595
+ WHERE circuit_id = ?
2596
+ LIMIT 1
2597
+ `).get(circuitId);
2598
+ if (!row)
2599
+ return undefined;
2600
+ return {
2601
+ circuitId: row.circuit_id,
2602
+ version: row.version,
2603
+ vkBlob: row.vk_blob ?? undefined,
2604
+ metadata: row.metadata_json ? this.decode(row.metadata_json) : {},
2605
+ active: !!row.active,
2606
+ };
2607
+ }
2608
+ listPersonaZkpCircuits() {
2609
+ const rows = this.db.prepare(`
2610
+ SELECT *
2611
+ FROM persona_zkp_circuits
2612
+ ORDER BY circuit_id ASC
2613
+ `).all();
2614
+ return rows.map((row) => ({
2615
+ circuitId: row.circuit_id,
2616
+ version: row.version,
2617
+ vkBlob: row.vk_blob ?? undefined,
2618
+ metadata: row.metadata_json ? this.decode(row.metadata_json) : {},
2619
+ active: !!row.active,
2620
+ }));
2621
+ }
2622
+ safeAddColumn(table, spec) {
2623
+ try {
2624
+ this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${spec}`);
2625
+ }
2626
+ catch {
2627
+ // no-op when column already exists
2628
+ }
2629
+ }
2630
+ ensurePersonaVectorIndex() {
2631
+ try {
2632
+ this.db.exec(`
2633
+ CREATE VIRTUAL TABLE IF NOT EXISTS persona_vec USING vec0(
2634
+ embedding float[384]
2635
+ );
2636
+ `);
2637
+ }
2638
+ catch (error) {
2639
+ if (process.env.NODE_ENV === 'production') {
2640
+ throw new Error(`persona_vec index is required in production: ${error.message}`);
2641
+ }
2642
+ console.warn(`[storage] persona_vec unavailable, using non-vector fallback: ${error.message}`);
2643
+ }
2644
+ }
2645
+ // ─── Cleanup ──────────────────────────────────────────────────
2646
+ close() {
2647
+ this.db.close();
2648
+ }
2649
+ encode(value) {
2650
+ return JSON.stringify(this.toStorable(value));
2651
+ }
2652
+ decode(value) {
2653
+ return this.fromStorable(JSON.parse(value));
2654
+ }
2655
+ toStorable(value) {
2656
+ if (value instanceof Map) {
2657
+ return {
2658
+ __societyType: 'Map',
2659
+ entries: Array.from(value.entries()).map(([k, v]) => [this.toStorable(k), this.toStorable(v)]),
2660
+ };
2661
+ }
2662
+ if (value instanceof Set) {
2663
+ return {
2664
+ __societyType: 'Set',
2665
+ values: Array.from(value.values()).map((v) => this.toStorable(v)),
2666
+ };
2667
+ }
2668
+ if (Array.isArray(value)) {
2669
+ return value.map((v) => this.toStorable(v));
2670
+ }
2671
+ if (value && typeof value === 'object') {
2672
+ const out = {};
2673
+ for (const [k, v] of Object.entries(value)) {
2674
+ out[k] = this.toStorable(v);
2675
+ }
2676
+ return out;
2677
+ }
2678
+ return value;
2679
+ }
2680
+ fromStorable(value) {
2681
+ if (!value || typeof value !== 'object') {
2682
+ return value;
2683
+ }
2684
+ if (value.__societyType === 'Map' && Array.isArray(value.entries)) {
2685
+ return new Map(value.entries.map((entry) => [
2686
+ this.fromStorable(entry[0]),
2687
+ this.fromStorable(entry[1]),
2688
+ ]));
2689
+ }
2690
+ if (value.__societyType === 'Set' && Array.isArray(value.values)) {
2691
+ return new Set(value.values.map((v) => this.fromStorable(v)));
2692
+ }
2693
+ if (Array.isArray(value)) {
2694
+ return value.map((v) => this.fromStorable(v));
2695
+ }
2696
+ const out = {};
2697
+ for (const [k, v] of Object.entries(value)) {
2698
+ out[k] = this.fromStorable(v);
2699
+ }
2700
+ return out;
2701
+ }
2702
+ }
2703
+ //# sourceMappingURL=storage.js.map