@the-ai-company/cbio-node-runtime 1.69.0 → 1.70.1

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 (157) hide show
  1. package/README.md +24 -0
  2. package/dist/clients/owner/client.js +9 -0
  3. package/dist/clients/owner/client.js.map +1 -1
  4. package/dist/clients/owner/contracts.d.ts +3 -1
  5. package/dist/public-types.d.ts +2 -1
  6. package/dist/public-types.js.map +1 -1
  7. package/dist/runtime/index.d.ts +4 -2
  8. package/dist/runtime/index.js +1 -1
  9. package/dist/runtime/index.js.map +1 -1
  10. package/dist/vault-core/contracts.d.ts +12 -22
  11. package/dist/vault-core/contracts.js +0 -6
  12. package/dist/vault-core/contracts.js.map +1 -1
  13. package/dist/vault-core/core.d.ts +2 -1
  14. package/dist/vault-core/core.js +39 -15
  15. package/dist/vault-core/core.js.map +1 -1
  16. package/dist/vault-core/defaults.d.ts +5 -6
  17. package/dist/vault-core/defaults.js +47 -15
  18. package/dist/vault-core/defaults.js.map +1 -1
  19. package/dist/vault-core/index.d.ts +3 -3
  20. package/dist/vault-core/index.js +1 -1
  21. package/dist/vault-core/index.js.map +1 -1
  22. package/dist/vault-core/persistence.d.ts +33 -37
  23. package/dist/vault-core/persistence.js +259 -369
  24. package/dist/vault-core/persistence.js.map +1 -1
  25. package/dist/vault-core/ports.d.ts +4 -3
  26. package/dist/vault-ingress/index.d.ts +1 -0
  27. package/dist/vault-ingress/index.js +3 -0
  28. package/dist/vault-ingress/index.js.map +1 -1
  29. package/dist/vault-ingress/server-utils.d.ts +30 -0
  30. package/dist/vault-ingress/server-utils.js +151 -0
  31. package/dist/vault-ingress/server-utils.js.map +1 -1
  32. package/docs/REFERENCE.md +3 -0
  33. package/docs/api/README.md +5 -2
  34. package/docs/api/classes/IdentityError.md +1 -1
  35. package/docs/api/classes/OwnerClientError.md +1 -1
  36. package/docs/api/classes/PersistentVaultAgentIdentityRegistry.md +5 -5
  37. package/docs/api/classes/PersistentVaultAgentSecretGrantRegistry.md +5 -5
  38. package/docs/api/classes/PersistentVaultAuditLog.md +29 -5
  39. package/docs/api/classes/PersistentVaultSecretCustody.md +5 -5
  40. package/docs/api/classes/PersistentVaultSecretDestinationGrantRegistry.md +5 -5
  41. package/docs/api/classes/PersistentVaultSecretRepository.md +5 -5
  42. package/docs/api/classes/VaultCore.md +17 -1
  43. package/docs/api/classes/VaultCoreError.md +1 -1
  44. package/docs/api/enumerations/AuditOperation.md +1 -37
  45. package/docs/api/enumerations/DispatchStatus.md +1 -1
  46. package/docs/api/enumerations/IdentityErrorCode.md +1 -1
  47. package/docs/api/enumerations/OwnerClientErrorCode.md +1 -1
  48. package/docs/api/functions/createAgentClient.md +1 -1
  49. package/docs/api/functions/createIdentity.md +1 -1
  50. package/docs/api/functions/createOwnerClient.md +1 -1
  51. package/docs/api/functions/createPersistentVaultCoreDependencies.md +1 -1
  52. package/docs/api/functions/createVault.md +1 -1
  53. package/docs/api/functions/createVaultCore.md +1 -1
  54. package/docs/api/functions/createVaultCoreDependencies.md +1 -1
  55. package/docs/api/functions/createVaultService.md +1 -1
  56. package/docs/api/functions/createWorkspaceStorage.md +1 -1
  57. package/docs/api/functions/deriveRootAgentId.md +1 -1
  58. package/docs/api/functions/deriveVaultWorkingKeyFromPassword.md +1 -1
  59. package/docs/api/functions/getDefaultWorkspaceDir.md +1 -1
  60. package/docs/api/functions/handleVaultAgentControlHttp.md +1 -1
  61. package/docs/api/functions/handleVaultAuditSse.md +30 -0
  62. package/docs/api/functions/handleVaultHttpDispatch.md +1 -1
  63. package/docs/api/functions/handleVaultPendingDispatchSse.md +30 -0
  64. package/docs/api/functions/initializeVaultCustody.md +1 -1
  65. package/docs/api/functions/listVaults.md +1 -1
  66. package/docs/api/functions/openOwnerSession.md +1 -1
  67. package/docs/api/functions/readVaultProfile.md +1 -1
  68. package/docs/api/functions/recoverVault.md +1 -1
  69. package/docs/api/functions/recoverVaultWorkingKey.md +1 -1
  70. package/docs/api/functions/restoreIdentity.md +1 -1
  71. package/docs/api/functions/updateVaultMetadata.md +1 -1
  72. package/docs/api/functions/writeVaultProfile.md +1 -1
  73. package/docs/api/interfaces/AgentClient.md +1 -1
  74. package/docs/api/interfaces/AgentDispatchIntent.md +1 -1
  75. package/docs/api/interfaces/AgentDispatchTransport.md +1 -1
  76. package/docs/api/interfaces/AgentIdentity.md +1 -1
  77. package/docs/api/interfaces/AgentIdentityRecord.md +1 -1
  78. package/docs/api/interfaces/AgentRequestRecord.md +1 -1
  79. package/docs/api/interfaces/AgentRuntimeManifest.md +1 -1
  80. package/docs/api/interfaces/AgentSecretGrant.md +1 -1
  81. package/docs/api/interfaces/AgentSigner.md +1 -1
  82. package/docs/api/interfaces/AgentVisibleRequestRecord.md +1 -1
  83. package/docs/api/interfaces/AgentVisibleSecretRecord.md +1 -1
  84. package/docs/api/interfaces/AuditEntry.md +1 -1
  85. package/docs/api/interfaces/CbioRuntime.md +59 -1
  86. package/docs/api/interfaces/CreateAgentClientOptions.md +1 -1
  87. package/docs/api/interfaces/CreateIdentityOptions.md +1 -1
  88. package/docs/api/interfaces/CreateOwnerClientOptions.md +1 -1
  89. package/docs/api/interfaces/CreatePersistentVaultCoreDependenciesOptions.md +1 -1
  90. package/docs/api/interfaces/CreateVaultOptions.md +1 -1
  91. package/docs/api/interfaces/CreatedVault.md +1 -1
  92. package/docs/api/interfaces/DefaultPolicyEngineOptions.md +1 -23
  93. package/docs/api/interfaces/DispatchAuthorization.md +1 -1
  94. package/docs/api/interfaces/DispatchInstruction.md +1 -1
  95. package/docs/api/interfaces/DispatchRequest.md +1 -1
  96. package/docs/api/interfaces/DispatchResult.md +1 -1
  97. package/docs/api/interfaces/IStorageProvider.md +1 -1
  98. package/docs/api/interfaces/InitializeVaultCustodyOptions.md +1 -1
  99. package/docs/api/interfaces/InitializedVaultCustody.md +1 -1
  100. package/docs/api/interfaces/OpenOwnerSessionOptions.md +1 -1
  101. package/docs/api/interfaces/OwnerAgentProvisionResult.md +1 -1
  102. package/docs/api/interfaces/OwnerAuditSubscription.md +45 -0
  103. package/docs/api/interfaces/OwnerClient.md +17 -1
  104. package/docs/api/interfaces/OwnerCreateSecretInput.md +1 -1
  105. package/docs/api/interfaces/OwnerPendingDispatchSubscription.md +1 -1
  106. package/docs/api/interfaces/OwnerRemoveSecretInput.md +1 -1
  107. package/docs/api/interfaces/OwnerRequestRecord.md +1 -1
  108. package/docs/api/interfaces/OwnerSensitiveActionConfirmation.md +1 -1
  109. package/docs/api/interfaces/OwnerSensitiveActionContext.md +1 -1
  110. package/docs/api/interfaces/OwnerSession.md +1 -1
  111. package/docs/api/interfaces/OwnerUpdateSecretInput.md +9 -3
  112. package/docs/api/interfaces/OwnerVisibleRequestRecord.md +1 -1
  113. package/docs/api/interfaces/PendingDispatchEvent.md +1 -1
  114. package/docs/api/interfaces/RecoverVaultOptions.md +1 -1
  115. package/docs/api/interfaces/RecoveredVault.md +1 -1
  116. package/docs/api/interfaces/RequestRecord.md +1 -1
  117. package/docs/api/interfaces/RestoreIdentityOptions.md +1 -1
  118. package/docs/api/interfaces/SecretAlias.md +1 -1
  119. package/docs/api/interfaces/SecretDestinationGrant.md +1 -1
  120. package/docs/api/interfaces/SecretId.md +1 -1
  121. package/docs/api/interfaces/SecretRecord.md +1 -1
  122. package/docs/api/interfaces/Signer.md +1 -1
  123. package/docs/api/interfaces/VaultApproveDispatchInput.md +1 -1
  124. package/docs/api/interfaces/VaultAuditQueryInput.md +1 -1
  125. package/docs/api/interfaces/VaultCoreDependenciesOptions.md +1 -1
  126. package/docs/api/interfaces/VaultCreateAgentInput.md +1 -1
  127. package/docs/api/interfaces/VaultExportSecretInput.md +1 -1
  128. package/docs/api/interfaces/VaultGetRequestInput.md +1 -1
  129. package/docs/api/interfaces/VaultGrantAgentSecretInput.md +1 -1
  130. package/docs/api/interfaces/VaultGrantSecretDestinationInput.md +1 -1
  131. package/docs/api/interfaces/VaultId.md +1 -1
  132. package/docs/api/interfaces/VaultImportAgentInput.md +1 -1
  133. package/docs/api/interfaces/VaultIssueSessionTokenInput.md +1 -1
  134. package/docs/api/interfaces/VaultListAgentsInput.md +1 -1
  135. package/docs/api/interfaces/VaultListGrantsInput.md +1 -1
  136. package/docs/api/interfaces/VaultListRequestsInput.md +1 -1
  137. package/docs/api/interfaces/VaultListSecretsInput.md +1 -1
  138. package/docs/api/interfaces/VaultMetadata.md +1 -1
  139. package/docs/api/interfaces/VaultObject.md +1 -1
  140. package/docs/api/interfaces/VaultPrincipal.md +1 -1
  141. package/docs/api/interfaces/VaultProfile.md +1 -1
  142. package/docs/api/interfaces/VaultReadAgentPrivateKeyInput.md +1 -1
  143. package/docs/api/interfaces/VaultReadSecretPlaintextInput.md +1 -1
  144. package/docs/api/interfaces/VaultRevokeAgentSecretInput.md +1 -1
  145. package/docs/api/interfaces/VaultRevokeSecretDestinationInput.md +1 -1
  146. package/docs/api/interfaces/VaultRevokeSessionTokenInput.md +1 -1
  147. package/docs/api/interfaces/VaultService.md +17 -1
  148. package/docs/api/interfaces/VaultUpdateAgentInput.md +1 -1
  149. package/docs/api/type-aliases/AgentId.md +1 -1
  150. package/docs/api/type-aliases/AgentRequestResult.md +1 -1
  151. package/docs/api/type-aliases/CbioRuntimeModule.md +1 -1
  152. package/docs/api/type-aliases/DispatchApprovalDecision.md +1 -1
  153. package/docs/api/type-aliases/GrantStatus.md +1 -1
  154. package/docs/api/type-aliases/SecretLifecycleStatus.md +1 -1
  155. package/docs/api/type-aliases/VaultPrincipalKind.md +2 -2
  156. package/docs/api/variables/DEFAULT_VAULT_KEY_CUSTODY_BLOB_KEY.md +1 -1
  157. package/package.json +5 -3
@@ -1,456 +1,321 @@
1
- import * as fs from "node:fs/promises";
2
1
  import * as path from "node:path";
3
2
  import * as crypto from "node:crypto";
3
+ import * as fs from "node:fs";
4
+ import Database from "better-sqlite3";
4
5
  import { DispatchStatus, } from "./contracts.js";
5
6
  import { DefaultPolicyEngine, HttpDispatchExecutor, InMemoryReplayGuard, RandomIdGenerator, SignatureAgentProofVerifier, SystemClock, } from "./defaults.js";
6
- async function ensureDir(dir) {
7
- await fs.mkdir(dir, { recursive: true });
8
- }
9
- export class FileSecretRepository {
10
- _baseDir;
11
- constructor(baseDir) {
12
- this._baseDir = path.join(baseDir, "secrets");
13
- }
14
- _getPath(vault_id, secret_id) {
15
- return path.join(this._baseDir, vault_id.value, `${secret_id.value}.json`);
16
- }
17
- _getAliasPath(vault_id, alias) {
18
- return path.join(this._baseDir, vault_id.value, `alias_${Buffer.from(alias).toString("hex")}.link`);
7
+ export class SqliteSecretRepository {
8
+ db;
9
+ constructor(db) {
10
+ this.db = db;
19
11
  }
20
12
  async save(record) {
21
- const filePath = this._getPath(record.vault_id, record.secret_id);
22
- const aliasPath = this._getAliasPath(record.vault_id, record.alias.value);
23
- await ensureDir(path.dirname(filePath));
24
- await fs.writeFile(filePath, JSON.stringify(record, null, 2));
25
- await fs.writeFile(aliasPath, record.secret_id.value);
13
+ const stmt = this.db.prepare(`
14
+ INSERT INTO secrets (secret_id, vault_id, alias, record)
15
+ VALUES (?, ?, ?, ?)
16
+ ON CONFLICT(secret_id) DO UPDATE SET record = excluded.record, alias = excluded.alias
17
+ `);
18
+ stmt.run(record.secret_id.value, record.vault_id.value, record.alias.value, JSON.stringify(record));
26
19
  }
27
20
  async delete(secret_id) {
28
- // Incomplete for multi-vault but sufficient for CBIO node-runtime
21
+ this.db.prepare(`DELETE FROM secrets WHERE secret_id = ?`).run(secret_id.value);
29
22
  }
30
23
  async getByAlias(alias) {
31
- try {
32
- const vaultDirs = await fs.readdir(this._baseDir);
33
- for (const v of vaultDirs) {
34
- const aliasPath = path.join(this._baseDir, v, `alias_${Buffer.from(alias.value).toString("hex")}.link`);
35
- try {
36
- const secret_id = await fs.readFile(aliasPath, "utf-8");
37
- const recordPath = path.join(this._baseDir, v, `${secret_id}.json`);
38
- const content = await fs.readFile(recordPath, "utf-8");
39
- return JSON.parse(content);
40
- }
41
- catch {
42
- continue;
43
- }
44
- }
45
- }
46
- catch {
47
- return null;
48
- }
49
- return null;
50
- }
51
- async getById(secret_id) {
52
- try {
53
- const vaultDirs = await fs.readdir(this._baseDir);
54
- for (const v of vaultDirs) {
55
- const recordPath = path.join(this._baseDir, v, `${secret_id.value}.json`);
56
- try {
57
- const content = await fs.readFile(recordPath, "utf-8");
58
- return JSON.parse(content);
59
- }
60
- catch {
61
- continue;
62
- }
63
- }
64
- }
65
- catch {
66
- return null;
67
- }
68
- return null;
24
+ const row = this.db.prepare(`SELECT record FROM secrets WHERE alias = ?`).get(alias.value);
25
+ return row ? JSON.parse(row.record) : null;
69
26
  }
70
27
  async list(vault_id) {
71
- try {
72
- const dir = path.join(this._baseDir, vault_id.value);
73
- const files = await fs.readdir(dir);
74
- const results = [];
75
- for (const f of files) {
76
- if (f.endsWith(".json")) {
77
- const content = await fs.readFile(path.join(dir, f), "utf-8");
78
- results.push(JSON.parse(content));
79
- }
80
- }
81
- return results;
82
- }
83
- catch {
84
- return [];
85
- }
28
+ const rows = this.db.prepare(`SELECT record FROM secrets WHERE vault_id = ?`).all(vault_id.value);
29
+ return rows.map(r => JSON.parse(r.record));
86
30
  }
87
- }
88
- export class FileSecretCustody {
89
- _baseDir;
90
- _workingKey;
91
- constructor(baseDir, workingKey) {
92
- this._baseDir = path.join(baseDir, "custody");
93
- this._workingKey = workingKey;
31
+ async getById(secret_id) {
32
+ const row = this.db.prepare(`SELECT record FROM secrets WHERE secret_id = ?`).get(secret_id.value);
33
+ return row ? JSON.parse(row.record) : null;
94
34
  }
95
- _getPath(secret_id) {
96
- return path.join(this._baseDir, `${secret_id.value}.sealed`);
35
+ }
36
+ // AES-GCM Implementation
37
+ const ALGORITHM = "aes-256-gcm";
38
+ export class SqliteSecretCustody {
39
+ db;
40
+ keyBuffer;
41
+ constructor(db, workingKey) {
42
+ this.db = db;
43
+ this.keyBuffer = Buffer.from(workingKey, 'base64url');
97
44
  }
98
45
  async store(secret_id, plaintext) {
99
- const filePath = this._getPath(secret_id);
100
- await ensureDir(path.dirname(filePath));
101
- await fs.writeFile(filePath, plaintext);
46
+ const iv = crypto.randomBytes(12);
47
+ const cipher = crypto.createCipheriv(ALGORITHM, this.keyBuffer, iv);
48
+ const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
49
+ const tag = cipher.getAuthTag();
50
+ const payload = JSON.stringify({
51
+ iv: iv.toString('hex'),
52
+ tag: tag.toString('hex'),
53
+ ciphertext: encrypted.toString('hex')
54
+ });
55
+ this.db.prepare(`INSERT INTO custody (secret_id, encrypted_payload) VALUES (?, ?) ON CONFLICT(secret_id) DO UPDATE SET encrypted_payload = excluded.encrypted_payload`).run(secret_id.value, payload);
102
56
  }
103
57
  async load(secret_id) {
58
+ const row = this.db.prepare(`SELECT encrypted_payload FROM custody WHERE secret_id = ?`).get(secret_id.value);
59
+ if (!row)
60
+ return null;
104
61
  try {
105
- return await fs.readFile(this._getPath(secret_id), "utf-8");
62
+ const data = JSON.parse(row.encrypted_payload);
63
+ const iv = Buffer.from(data.iv, 'hex');
64
+ const tag = Buffer.from(data.tag, 'hex');
65
+ const encrypted = Buffer.from(data.ciphertext, 'hex');
66
+ const decipher = crypto.createDecipheriv(ALGORITHM, this.keyBuffer, iv);
67
+ decipher.setAuthTag(tag);
68
+ return decipher.update(encrypted, undefined, 'utf8') + decipher.final('utf8');
106
69
  }
107
70
  catch {
108
71
  return null;
109
72
  }
110
73
  }
111
74
  async delete(secret_id) {
112
- try {
113
- await fs.unlink(this._getPath(secret_id));
114
- }
115
- catch { }
75
+ this.db.prepare(`DELETE FROM custody WHERE secret_id = ?`).run(secret_id.value);
116
76
  }
117
77
  }
118
- export class FileAuditLog {
119
- _baseDir;
120
- constructor(baseDir) {
121
- this._baseDir = path.join(baseDir, "audit");
122
- }
123
- _getPath(vault_id) {
124
- return path.join(this._baseDir, vault_id.value, "log.jsonl");
78
+ export class SqliteAuditLog {
79
+ db;
80
+ static subscribers = new Map();
81
+ get subscribers() { return SqliteAuditLog.subscribers; }
82
+ constructor(db) {
83
+ this.db = db;
125
84
  }
126
85
  async append(entry) {
127
- const filePath = this._getPath({ value: entry.vault_id });
128
- await ensureDir(path.dirname(filePath));
129
- await fs.appendFile(filePath, JSON.stringify(entry) + "\n");
86
+ this.db.prepare(`INSERT INTO audit_logs (event_id, vault_id, root_agent_id, request_id, secret_alias, ts, entry) VALUES (?, ?, ?, ?, ?, ?, ?)`)
87
+ .run(entry.event_id, entry.vault_id, entry.root_agent_id || null, entry.request_id || null, entry.secret_alias || null, entry.ts, JSON.stringify(entry));
88
+ const subs = this.subscribers.get(entry.vault_id);
89
+ if (subs) {
90
+ for (const cb of subs)
91
+ cb(entry);
92
+ }
130
93
  }
131
94
  async query(query) {
132
- const filePath = this._getPath({ value: query.vault_id });
133
- try {
134
- const content = await fs.readFile(filePath, "utf-8");
135
- const lines = content.split("\n").filter(l => !!l);
136
- const entries = lines.map(l => JSON.parse(l));
137
- return entries.filter(e => {
138
- if (query.actor_id && e.actor?.id !== query.actor_id)
139
- return false;
140
- if (query.root_agent_id && e.root_agent_id !== query.root_agent_id)
141
- return false;
142
- if (query.secret_alias && e.secret_alias !== query.secret_alias)
143
- return false;
144
- if (query.request_id && e.request_id !== query.request_id)
145
- return false;
146
- if (query.since && e.ts < query.since)
147
- return false;
148
- return true;
149
- });
150
- }
151
- catch {
152
- return [];
153
- }
95
+ let sql = `SELECT entry FROM audit_logs WHERE vault_id = ?`;
96
+ const params = [query.vault_id];
97
+ if (query.actor_id) {
98
+ sql += ` AND entry LIKE ?`;
99
+ params.push(`%"id":"${query.actor_id}"%`);
100
+ }
101
+ if (query.root_agent_id) {
102
+ sql += ` AND root_agent_id = ?`;
103
+ params.push(query.root_agent_id);
104
+ }
105
+ if (query.secret_alias) {
106
+ sql += ` AND secret_alias = ?`;
107
+ params.push(query.secret_alias);
108
+ }
109
+ if (query.request_id) {
110
+ sql += ` AND request_id = ?`;
111
+ params.push(query.request_id);
112
+ }
113
+ if (query.since) {
114
+ sql += ` AND ts >= ?`;
115
+ params.push(query.since);
116
+ }
117
+ sql += ` ORDER BY ts ASC LIMIT 1000`; // Limit to avoid massive memory issues conceptually
118
+ const rows = this.db.prepare(sql).all(...params);
119
+ return rows.map(r => JSON.parse(r.entry));
120
+ }
121
+ subscribe(vault_id, subscription) {
122
+ let subs = this.subscribers.get(vault_id.value);
123
+ if (!subs) {
124
+ subs = new Set();
125
+ this.subscribers.set(vault_id.value, subs);
126
+ }
127
+ const callback = (entry) => {
128
+ if (subscription.afterEventId && entry.event_id <= subscription.afterEventId)
129
+ return;
130
+ if (subscription.operations && !subscription.operations.includes(entry.operation))
131
+ return;
132
+ if (subscription.root_agent_id && entry.root_agent_id !== subscription.root_agent_id)
133
+ return;
134
+ if (subscription.request_id && entry.request_id !== subscription.request_id)
135
+ return;
136
+ subscription.onEvent(entry);
137
+ };
138
+ subs.add(callback);
139
+ return () => {
140
+ const current = this.subscribers.get(vault_id.value);
141
+ if (current) {
142
+ current.delete(callback);
143
+ if (current.size === 0)
144
+ this.subscribers.delete(vault_id.value);
145
+ }
146
+ };
154
147
  }
155
148
  }
156
- export class FileAgentIdentityRegistry {
157
- _baseDir;
158
- constructor(baseDir) {
159
- this._baseDir = path.join(baseDir, "agents");
160
- }
161
- _getPath(vault_id, root_agent_id) {
162
- return path.join(this._baseDir, vault_id.value, `${root_agent_id}.json`);
149
+ export class SqliteAgentIdentityRegistry {
150
+ db;
151
+ constructor(db) {
152
+ this.db = db;
163
153
  }
164
154
  async register(identity) {
165
- const filePath = this._getPath(identity.vault_id, identity.root_agent_id);
166
- await ensureDir(path.dirname(filePath));
167
- await fs.writeFile(filePath, JSON.stringify(identity, null, 2));
155
+ this.db.prepare(`INSERT INTO agents (vault_id, root_agent_id, record) VALUES (?, ?, ?) ON CONFLICT(vault_id, root_agent_id) DO UPDATE SET record = excluded.record`).run(identity.vault_id.value, identity.root_agent_id, JSON.stringify(identity));
168
156
  }
169
157
  async get(vault_id, root_agent_id) {
170
- try {
171
- const content = await fs.readFile(this._getPath(vault_id, root_agent_id), "utf-8");
172
- return JSON.parse(content);
173
- }
174
- catch {
175
- return null;
176
- }
158
+ const row = this.db.prepare(`SELECT record FROM agents WHERE vault_id = ? AND root_agent_id = ?`).get(vault_id.value, root_agent_id);
159
+ return row ? JSON.parse(row.record) : null;
177
160
  }
178
161
  async list(vault_id) {
179
- const dir = path.join(this._baseDir, vault_id.value);
180
- try {
181
- const files = await fs.readdir(dir);
182
- return await Promise.all(files.filter(f => f.endsWith(".json")).map(async (f) => {
183
- const content = await fs.readFile(path.join(dir, f), "utf-8");
184
- return JSON.parse(content);
185
- }));
186
- }
187
- catch {
188
- return [];
189
- }
162
+ const rows = this.db.prepare(`SELECT record FROM agents WHERE vault_id = ?`).all(vault_id.value);
163
+ return rows.map(r => JSON.parse(r.record));
190
164
  }
191
165
  }
192
- export class FileAgentSecretGrantRegistry {
193
- _baseDir;
194
- constructor(baseDir) {
195
- this._baseDir = path.join(baseDir, "grants", "agent_secrets");
196
- }
197
- _getPath(vault_id, root_agent_id, secret_alias) {
198
- return path.join(this._baseDir, vault_id.value, root_agent_id, `${Buffer.from(secret_alias).toString("hex")}.json`);
166
+ export class SqliteAgentSecretGrantRegistry {
167
+ db;
168
+ constructor(db) {
169
+ this.db = db;
199
170
  }
200
171
  async upsert(grant) {
201
- const filePath = this._getPath(grant.vault_id, grant.root_agent_id, grant.secret_alias);
202
- await ensureDir(path.dirname(filePath));
203
- await fs.writeFile(filePath, JSON.stringify(grant, null, 2));
172
+ this.db.prepare(`INSERT INTO grants (vault_id, root_agent_id, secret_alias, record) VALUES (?, ?, ?, ?) ON CONFLICT(vault_id, root_agent_id, secret_alias) DO UPDATE SET record = excluded.record`).run(grant.vault_id.value, grant.root_agent_id, grant.secret_alias, JSON.stringify(grant));
204
173
  }
205
174
  async get(vault_id, root_agent_id, secret_alias) {
206
- try {
207
- const content = await fs.readFile(this._getPath(vault_id, root_agent_id, secret_alias), "utf-8");
208
- return JSON.parse(content);
209
- }
210
- catch {
211
- return null;
212
- }
175
+ const row = this.db.prepare(`SELECT record FROM grants WHERE vault_id = ? AND root_agent_id = ? AND secret_alias = ?`).get(vault_id.value, root_agent_id, secret_alias);
176
+ return row ? JSON.parse(row.record) : null;
213
177
  }
214
178
  async list(vault_id, root_agent_id) {
215
- try {
216
- const results = [];
217
- const vaultDir = path.join(this._baseDir, vault_id.value);
218
- const agentDirs = root_agent_id ? [root_agent_id] : await fs.readdir(vaultDir);
219
- for (const aid of agentDirs) {
220
- const agentDir = path.join(vaultDir, aid);
221
- try {
222
- const files = await fs.readdir(agentDir);
223
- for (const f of files) {
224
- if (f.endsWith(".json")) {
225
- const content = await fs.readFile(path.join(agentDir, f), "utf-8");
226
- results.push(JSON.parse(content));
227
- }
228
- }
229
- }
230
- catch {
231
- continue;
232
- }
233
- }
234
- return results;
235
- }
236
- catch {
237
- return [];
179
+ let sql = `SELECT record FROM grants WHERE vault_id = ?`;
180
+ const params = [vault_id.value];
181
+ if (root_agent_id) {
182
+ sql += ` AND root_agent_id = ?`;
183
+ params.push(root_agent_id);
238
184
  }
185
+ const rows = this.db.prepare(sql).all(...params);
186
+ return rows.map(r => JSON.parse(r.record));
239
187
  }
240
188
  async delete(vault_id, root_agent_id, secret_alias) {
241
- try {
242
- await fs.unlink(this._getPath(vault_id, root_agent_id, secret_alias));
243
- }
244
- catch { }
189
+ this.db.prepare(`DELETE FROM grants WHERE vault_id = ? AND root_agent_id = ? AND secret_alias = ?`).run(vault_id.value, root_agent_id, secret_alias);
245
190
  }
246
191
  }
247
- export class FileSecretDestinationGrantRegistry {
248
- _baseDir;
249
- constructor(baseDir) {
250
- this._baseDir = path.join(baseDir, "grants", "secret_destinations");
251
- }
252
- _getPath(vault_id, secret_alias, site_id) {
253
- return path.join(this._baseDir, vault_id.value, Buffer.from(secret_alias).toString("hex"), `${Buffer.from(site_id).toString("hex")}.json`);
192
+ export class SqliteSecretDestinationGrantRegistry {
193
+ db;
194
+ constructor(db) {
195
+ this.db = db;
254
196
  }
255
197
  async upsert(grant) {
256
- const filePath = this._getPath(grant.vault_id, grant.secret_alias, grant.site_id);
257
- await ensureDir(path.dirname(filePath));
258
- await fs.writeFile(filePath, JSON.stringify(grant, null, 2));
198
+ this.db.prepare(`INSERT INTO destination_grants (vault_id, secret_alias, site_id, record) VALUES (?, ?, ?, ?) ON CONFLICT(vault_id, secret_alias, site_id) DO UPDATE SET record = excluded.record`).run(grant.vault_id.value, grant.secret_alias, grant.site_id, JSON.stringify(grant));
259
199
  }
260
200
  async get(vault_id, secret_alias, site_id) {
261
- try {
262
- const content = await fs.readFile(this._getPath(vault_id, secret_alias, site_id), "utf-8");
263
- return JSON.parse(content);
264
- }
265
- catch {
266
- return null;
267
- }
201
+ const row = this.db.prepare(`SELECT record FROM destination_grants WHERE vault_id = ? AND secret_alias = ? AND site_id = ?`).get(vault_id.value, secret_alias, site_id);
202
+ return row ? JSON.parse(row.record) : null;
268
203
  }
269
204
  async list(vault_id, secret_alias) {
270
- try {
271
- const results = [];
272
- const vaultDir = path.join(this._baseDir, vault_id.value);
273
- const aliasDirs = secret_alias ? [Buffer.from(secret_alias).toString("hex")] : await fs.readdir(vaultDir);
274
- for (const aid of aliasDirs) {
275
- const aliasDir = path.join(vaultDir, aid);
276
- try {
277
- const files = await fs.readdir(aliasDir);
278
- for (const f of files) {
279
- if (f.endsWith(".json")) {
280
- const content = await fs.readFile(path.join(aliasDir, f), "utf-8");
281
- results.push(JSON.parse(content));
282
- }
283
- }
284
- }
285
- catch {
286
- continue;
287
- }
288
- }
289
- return results;
290
- }
291
- catch {
292
- return [];
205
+ let sql = `SELECT record FROM destination_grants WHERE vault_id = ?`;
206
+ const params = [vault_id.value];
207
+ if (secret_alias) {
208
+ sql += ` AND secret_alias = ?`;
209
+ params.push(secret_alias);
293
210
  }
211
+ const rows = this.db.prepare(sql).all(...params);
212
+ return rows.map(r => JSON.parse(r.record));
294
213
  }
295
214
  async delete(vault_id, secret_alias, site_id) {
296
- try {
297
- await fs.unlink(this._getPath(vault_id, secret_alias, site_id));
298
- }
299
- catch { }
215
+ this.db.prepare(`DELETE FROM destination_grants WHERE vault_id = ? AND secret_alias = ? AND site_id = ?`).run(vault_id.value, secret_alias, site_id);
300
216
  }
301
217
  }
302
- export class FileRequestRecordRegistry {
303
- _baseDir;
304
- constructor(baseDir) {
305
- this._baseDir = path.join(baseDir, "requests");
306
- }
307
- _getPath(vault_id, request_id) {
308
- return path.join(this._baseDir, vault_id.value, `${request_id}.json`);
218
+ export class SqliteRequestRecordRegistry {
219
+ db;
220
+ static subscribers = new Map();
221
+ get subscribers() { return SqliteRequestRecordRegistry.subscribers; }
222
+ constructor(db) {
223
+ this.db = db;
309
224
  }
310
225
  async save(record) {
311
- const filePath = this._getPath(record.vault_id, record.request_id);
312
- await ensureDir(path.dirname(filePath));
313
- await fs.writeFile(filePath, JSON.stringify(record, null, 2));
226
+ this.db.prepare(`INSERT INTO requests (vault_id, request_id, root_agent_id, record) VALUES (?, ?, ?, ?) ON CONFLICT(request_id) DO UPDATE SET record = excluded.record`).run(record.vault_id.value, record.request_id, record.root_agent_id, JSON.stringify(record));
227
+ const subs = this.subscribers.get(record.vault_id.value);
228
+ if (subs) {
229
+ for (const cb of subs)
230
+ cb(record);
231
+ }
314
232
  }
315
233
  async get(vault_id, request_id) {
316
- try {
317
- const content = await fs.readFile(this._getPath(vault_id, request_id), "utf-8");
318
- return JSON.parse(content);
319
- }
320
- catch {
321
- return null;
322
- }
234
+ const row = this.db.prepare(`SELECT record FROM requests WHERE vault_id = ? AND request_id = ?`).get(vault_id.value, request_id);
235
+ return row ? JSON.parse(row.record) : null;
323
236
  }
324
237
  async list(vault_id, root_agent_id) {
325
- const dir = path.join(this._baseDir, vault_id.value);
326
- try {
327
- const files = await fs.readdir(dir);
328
- const records = await Promise.all(files.filter(f => f.endsWith(".json")).map(async (f) => {
329
- const content = await fs.readFile(path.join(dir, f), "utf-8");
330
- return JSON.parse(content);
331
- }));
332
- return root_agent_id ? records.filter(r => r.root_agent_id === root_agent_id) : records;
333
- }
334
- catch {
335
- return [];
238
+ let sql = `SELECT record FROM requests WHERE vault_id = ?`;
239
+ const params = [vault_id.value];
240
+ if (root_agent_id) {
241
+ sql += ` AND root_agent_id = ?`;
242
+ params.push(root_agent_id);
336
243
  }
244
+ const rows = this.db.prepare(sql).all(...params);
245
+ return rows.map(r => JSON.parse(r.record));
337
246
  }
338
247
  subscribePending(vault_id, subscription) {
339
- let closed = false;
340
- let scanInFlight = null;
341
- const seen = new Set();
342
- const scan = async () => {
343
- if (closed)
248
+ const rows = this.db.prepare(`SELECT record FROM requests WHERE vault_id = ?`).all(vault_id.value);
249
+ const replay = rows.map(r => JSON.parse(r.record))
250
+ .filter((record) => record.execution.status === DispatchStatus.AWAITING_APPROVAL && !!record.pending_dispatch_event)
251
+ .map((record) => ({
252
+ event_id: record.pending_dispatch_event.event_id,
253
+ emitted_at: record.pending_dispatch_event.emitted_at,
254
+ record
255
+ }))
256
+ .filter((event) => !subscription.afterEventId || event.event_id > subscription.afterEventId)
257
+ .sort((a, b) => a.event_id.localeCompare(b.event_id));
258
+ for (const event of replay)
259
+ subscription.onEvent(event);
260
+ let subs = this.subscribers.get(vault_id.value);
261
+ if (!subs) {
262
+ subs = new Set();
263
+ this.subscribers.set(vault_id.value, subs);
264
+ }
265
+ const callback = (record) => {
266
+ if (record.execution.status !== DispatchStatus.AWAITING_APPROVAL || !record.pending_dispatch_event)
344
267
  return;
345
- const pending = (await this.list(vault_id))
346
- .map((record) => this._toPendingDispatchEvent(record))
347
- .filter((event) => !!event)
348
- .filter((event) => !subscription.afterEventId || event.event_id > subscription.afterEventId)
349
- .sort((a, b) => a.event_id.localeCompare(b.event_id));
350
- for (const event of pending) {
351
- if (seen.has(event.event_id))
352
- continue;
353
- seen.add(event.event_id);
354
- subscription.onEvent(event);
355
- }
356
- };
357
- const tick = () => {
358
- if (closed || scanInFlight)
268
+ const event = {
269
+ event_id: record.pending_dispatch_event.event_id,
270
+ emitted_at: record.pending_dispatch_event.emitted_at,
271
+ record,
272
+ };
273
+ if (subscription.afterEventId && event.event_id <= subscription.afterEventId)
359
274
  return;
360
- scanInFlight = scan().finally(() => {
361
- scanInFlight = null;
362
- });
275
+ subscription.onEvent(event);
363
276
  };
364
- tick();
365
- const interval = setInterval(tick, 250);
366
- interval.unref?.();
277
+ subs.add(callback);
367
278
  return () => {
368
- closed = true;
369
- clearInterval(interval);
370
- };
371
- }
372
- _toPendingDispatchEvent(record) {
373
- if (record.execution.status !== DispatchStatus.AWAITING_APPROVAL || !record.pending_dispatch_event) {
374
- return null;
375
- }
376
- return {
377
- event_id: record.pending_dispatch_event.event_id,
378
- emitted_at: record.pending_dispatch_event.emitted_at,
379
- record,
279
+ const current = this.subscribers.get(vault_id.value);
280
+ if (current) {
281
+ current.delete(callback);
282
+ if (current.size === 0)
283
+ this.subscribers.delete(vault_id.value);
284
+ }
380
285
  };
381
286
  }
382
287
  }
383
- export class FileSessionTokenRegistry {
384
- _baseDir;
385
- constructor(baseDir) {
386
- this._baseDir = path.join(baseDir, "session_tokens");
387
- }
388
- _getPath(root_agent_id) {
389
- return path.join(this._baseDir, `${root_agent_id}.json`);
288
+ export class SqliteSessionTokenRegistry {
289
+ db;
290
+ constructor(db) {
291
+ this.db = db;
390
292
  }
391
293
  async issue(root_agent_id) {
392
294
  const token = `sat_${crypto.randomBytes(16).toString("hex")}`;
393
- const stored = {
394
- token,
395
- root_agent_id,
396
- issued_at: new Date().toISOString(),
397
- };
398
- const filePath = this._getPath(root_agent_id);
399
- await ensureDir(path.dirname(filePath));
400
- await fs.writeFile(filePath, JSON.stringify(stored, null, 2));
295
+ const stored = { token, root_agent_id, issued_at: new Date().toISOString() };
296
+ this.db.prepare(`INSERT INTO session_tokens (root_agent_id, token, record) VALUES (?, ?, ?) ON CONFLICT(root_agent_id) DO UPDATE SET record = excluded.record, token = excluded.token`).run(root_agent_id, token, JSON.stringify(stored));
401
297
  return token;
402
298
  }
403
299
  async inspect(token, root_agent_id) {
404
- try {
405
- const content = await fs.readFile(this._getPath(root_agent_id), "utf-8");
406
- const stored = JSON.parse(content);
407
- if (stored.token === token) {
408
- return { ok: true, token: stored };
409
- }
410
- const tokens = await this.list();
411
- if (tokens.some((entry) => entry.token === token)) {
412
- return { ok: false, reason: "agent_mismatch" };
413
- }
300
+ const row = this.db.prepare(`SELECT record, root_agent_id FROM session_tokens WHERE token = ?`).get(token);
301
+ if (!row)
414
302
  return { ok: false, reason: "token_not_found" };
415
- }
416
- catch {
417
- const tokens = await this.list();
418
- if (tokens.some((entry) => entry.token === token)) {
419
- return { ok: false, reason: "agent_mismatch" };
420
- }
421
- return { ok: false, reason: "token_not_found" };
422
- }
303
+ if (row.root_agent_id !== root_agent_id)
304
+ return { ok: false, reason: "agent_mismatch" };
305
+ return { ok: true, token: JSON.parse(row.record) };
423
306
  }
424
307
  async revoke(token) {
425
- const tokens = await this.list();
426
- const stored = tokens.find((entry) => entry.token === token);
427
- if (!stored)
428
- return;
429
- try {
430
- await fs.unlink(this._getPath(stored.root_agent_id));
431
- }
432
- catch { }
308
+ this.db.prepare(`DELETE FROM session_tokens WHERE token = ?`).run(token);
433
309
  }
434
310
  async list(root_agent_id) {
311
+ let sql = `SELECT record FROM session_tokens`;
312
+ const params = [];
435
313
  if (root_agent_id) {
436
- try {
437
- const content = await fs.readFile(this._getPath(root_agent_id), "utf-8");
438
- return [JSON.parse(content)];
439
- }
440
- catch {
441
- return [];
442
- }
443
- }
444
- try {
445
- const files = await fs.readdir(this._baseDir);
446
- return await Promise.all(files.filter((f) => f.endsWith(".json")).map(async (f) => {
447
- const content = await fs.readFile(path.join(this._baseDir, f), "utf-8");
448
- return JSON.parse(content);
449
- }));
450
- }
451
- catch {
452
- return [];
314
+ sql += ` WHERE root_agent_id = ?`;
315
+ params.push(root_agent_id);
453
316
  }
317
+ const rows = this.db.prepare(sql).all(...params);
318
+ return rows.map(r => JSON.parse(r.record));
454
319
  }
455
320
  }
456
321
  export const DEFAULT_VAULT_KEY_CUSTODY_BLOB_KEY = "master_key.sealed";
@@ -468,21 +333,46 @@ export async function recoverVaultWorkingKey(storage, recoveryKey) {
468
333
  const { workingKey } = JSON.parse(data.toString());
469
334
  return workingKey;
470
335
  }
336
+ const dbCache = new Map();
337
+ function initDb(baseDir) {
338
+ if (dbCache.has(baseDir))
339
+ return dbCache.get(baseDir);
340
+ const dbPath = path.join(baseDir, "vault.sqlite");
341
+ const db = new Database(dbPath);
342
+ db.pragma('journal_mode = WAL');
343
+ db.exec(`
344
+ CREATE TABLE IF NOT EXISTS secrets (secret_id TEXT PRIMARY KEY, vault_id TEXT, alias TEXT UNIQUE, record TEXT);
345
+ CREATE TABLE IF NOT EXISTS custody (secret_id TEXT PRIMARY KEY, encrypted_payload TEXT);
346
+ CREATE TABLE IF NOT EXISTS audit_logs (event_id TEXT PRIMARY KEY, vault_id TEXT, root_agent_id TEXT, request_id TEXT, secret_alias TEXT, ts INTEGER, entry TEXT);
347
+ CREATE TABLE IF NOT EXISTS agents (vault_id TEXT, root_agent_id TEXT, record TEXT, PRIMARY KEY(vault_id, root_agent_id));
348
+ CREATE TABLE IF NOT EXISTS grants (vault_id TEXT, root_agent_id TEXT, secret_alias TEXT, record TEXT, PRIMARY KEY(vault_id, root_agent_id, secret_alias));
349
+ CREATE TABLE IF NOT EXISTS destination_grants (vault_id TEXT, secret_alias TEXT, site_id TEXT, record TEXT, PRIMARY KEY(vault_id, secret_alias, site_id));
350
+ CREATE TABLE IF NOT EXISTS requests (vault_id TEXT, request_id TEXT PRIMARY KEY, root_agent_id TEXT, record TEXT);
351
+ CREATE TABLE IF NOT EXISTS session_tokens (root_agent_id TEXT PRIMARY KEY, token TEXT UNIQUE, record TEXT);
352
+ `);
353
+ dbCache.set(baseDir, db);
354
+ return db;
355
+ }
471
356
  export function createPersistentVaultCoreDependencies(storage, options) {
472
357
  const baseDir = storage.getBaseDir();
473
- const agentRecords = new FileAgentIdentityRegistry(baseDir);
474
- const sessionTokenRegistry = new FileSessionTokenRegistry(baseDir);
358
+ // Ensure black-box environment directory exists synchronously.
359
+ if (!fs.existsSync(baseDir)) {
360
+ fs.mkdirSync(baseDir, { recursive: true });
361
+ }
362
+ const db = initDb(baseDir);
363
+ const agentRecords = new SqliteAgentIdentityRegistry(db);
364
+ const sessionTokenRegistry = new SqliteSessionTokenRegistry(db);
475
365
  return {
476
366
  vault_id: { value: options.vault_id },
477
367
  ids: new RandomIdGenerator(),
478
368
  clock: new SystemClock(),
479
369
  agentRecords,
480
- agent_secretGrants: new FileAgentSecretGrantRegistry(baseDir),
481
- secret_destinationGrants: new FileSecretDestinationGrantRegistry(baseDir),
482
- audit: new FileAuditLog(baseDir),
483
- requests: new FileRequestRecordRegistry(baseDir),
484
- custody: new FileSecretCustody(baseDir, options.vaultWorkingKey),
485
- secrets: new FileSecretRepository(baseDir),
370
+ agent_secretGrants: new SqliteAgentSecretGrantRegistry(db),
371
+ secret_destinationGrants: new SqliteSecretDestinationGrantRegistry(db),
372
+ audit: new SqliteAuditLog(db),
373
+ requests: new SqliteRequestRecordRegistry(db),
374
+ custody: new SqliteSecretCustody(db, options.vaultWorkingKey),
375
+ secrets: new SqliteSecretRepository(db),
486
376
  policy: new DefaultPolicyEngine(),
487
377
  replayGuard: options.replayGuard ?? new InMemoryReplayGuard(options.proofVerifier),
488
378
  agentProofVerifier: new SignatureAgentProofVerifier(agentRecords, sessionTokenRegistry, options.proofVerifier),