@the-ai-company/cbio-node-runtime 1.69.0 → 1.71.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.
- package/README.md +25 -0
- package/dist/clients/owner/client.js +39 -4
- package/dist/clients/owner/client.js.map +1 -1
- package/dist/clients/owner/contracts.d.ts +3 -1
- package/dist/public-types.d.ts +2 -1
- package/dist/public-types.js.map +1 -1
- package/dist/runtime/index.d.ts +4 -2
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/vault-core/contracts.d.ts +26 -28
- package/dist/vault-core/contracts.js +0 -6
- package/dist/vault-core/contracts.js.map +1 -1
- package/dist/vault-core/core.d.ts +8 -6
- package/dist/vault-core/core.js +125 -52
- package/dist/vault-core/core.js.map +1 -1
- package/dist/vault-core/defaults.d.ts +11 -11
- package/dist/vault-core/defaults.js +75 -35
- package/dist/vault-core/defaults.js.map +1 -1
- package/dist/vault-core/index.d.ts +3 -3
- package/dist/vault-core/index.js +1 -1
- package/dist/vault-core/index.js.map +1 -1
- package/dist/vault-core/persistence.d.ts +39 -42
- package/dist/vault-core/persistence.js +332 -372
- package/dist/vault-core/persistence.js.map +1 -1
- package/dist/vault-core/ports.d.ts +9 -8
- package/dist/vault-ingress/index.d.ts +22 -0
- package/dist/vault-ingress/index.js +40 -14
- package/dist/vault-ingress/index.js.map +1 -1
- package/dist/vault-ingress/server-utils.d.ts +30 -0
- package/dist/vault-ingress/server-utils.js +151 -0
- package/dist/vault-ingress/server-utils.js.map +1 -1
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/REFERENCE.md +7 -3
- package/docs/api/README.md +5 -2
- package/docs/api/classes/IdentityError.md +1 -1
- package/docs/api/classes/OwnerClientError.md +1 -1
- package/docs/api/classes/PersistentVaultAgentIdentityRegistry.md +9 -5
- package/docs/api/classes/PersistentVaultAgentSecretGrantRegistry.md +11 -11
- package/docs/api/classes/PersistentVaultAuditLog.md +29 -5
- package/docs/api/classes/PersistentVaultSecretCustody.md +5 -5
- package/docs/api/classes/PersistentVaultSecretDestinationGrantRegistry.md +14 -14
- package/docs/api/classes/PersistentVaultSecretRepository.md +5 -5
- package/docs/api/classes/VaultCore.md +32 -16
- package/docs/api/classes/VaultCoreError.md +1 -1
- package/docs/api/enumerations/AuditOperation.md +1 -37
- package/docs/api/enumerations/DispatchStatus.md +1 -1
- package/docs/api/enumerations/IdentityErrorCode.md +1 -1
- package/docs/api/enumerations/OwnerClientErrorCode.md +1 -1
- package/docs/api/functions/createAgentClient.md +1 -1
- package/docs/api/functions/createIdentity.md +1 -1
- package/docs/api/functions/createOwnerClient.md +1 -1
- package/docs/api/functions/createPersistentVaultCoreDependencies.md +1 -1
- package/docs/api/functions/createVault.md +1 -1
- package/docs/api/functions/createVaultCore.md +1 -1
- package/docs/api/functions/createVaultCoreDependencies.md +1 -1
- package/docs/api/functions/createVaultService.md +1 -1
- package/docs/api/functions/createWorkspaceStorage.md +1 -1
- package/docs/api/functions/deriveRootAgentId.md +1 -1
- package/docs/api/functions/deriveVaultWorkingKeyFromPassword.md +1 -1
- package/docs/api/functions/getDefaultWorkspaceDir.md +1 -1
- package/docs/api/functions/handleVaultAgentControlHttp.md +1 -1
- package/docs/api/functions/handleVaultAuditSse.md +30 -0
- package/docs/api/functions/handleVaultHttpDispatch.md +1 -1
- package/docs/api/functions/handleVaultPendingDispatchSse.md +30 -0
- package/docs/api/functions/initializeVaultCustody.md +1 -1
- package/docs/api/functions/listVaults.md +1 -1
- package/docs/api/functions/openOwnerSession.md +1 -1
- package/docs/api/functions/readVaultProfile.md +1 -1
- package/docs/api/functions/recoverVault.md +1 -1
- package/docs/api/functions/recoverVaultWorkingKey.md +1 -1
- package/docs/api/functions/restoreIdentity.md +1 -1
- package/docs/api/functions/updateVaultMetadata.md +1 -1
- package/docs/api/functions/writeVaultProfile.md +1 -1
- package/docs/api/interfaces/AgentClient.md +1 -1
- package/docs/api/interfaces/AgentDispatchIntent.md +1 -1
- package/docs/api/interfaces/AgentDispatchTransport.md +1 -1
- package/docs/api/interfaces/AgentIdentity.md +1 -1
- package/docs/api/interfaces/AgentIdentityRecord.md +1 -1
- package/docs/api/interfaces/AgentRequestRecord.md +11 -1
- package/docs/api/interfaces/AgentRuntimeManifest.md +1 -1
- package/docs/api/interfaces/AgentSecretGrant.md +3 -3
- package/docs/api/interfaces/AgentSigner.md +1 -1
- package/docs/api/interfaces/AgentVisibleRequestRecord.md +7 -1
- package/docs/api/interfaces/AgentVisibleSecretRecord.md +1 -1
- package/docs/api/interfaces/AuditEntry.md +1 -1
- package/docs/api/interfaces/CbioRuntime.md +59 -1
- package/docs/api/interfaces/CreateAgentClientOptions.md +1 -1
- package/docs/api/interfaces/CreateIdentityOptions.md +1 -1
- package/docs/api/interfaces/CreateOwnerClientOptions.md +1 -1
- package/docs/api/interfaces/CreatePersistentVaultCoreDependenciesOptions.md +1 -1
- package/docs/api/interfaces/CreateVaultOptions.md +1 -1
- package/docs/api/interfaces/CreatedVault.md +1 -1
- package/docs/api/interfaces/DefaultPolicyEngineOptions.md +1 -23
- package/docs/api/interfaces/DispatchAuthorization.md +1 -1
- package/docs/api/interfaces/DispatchInstruction.md +1 -1
- package/docs/api/interfaces/DispatchRequest.md +1 -1
- package/docs/api/interfaces/DispatchResult.md +1 -1
- package/docs/api/interfaces/IStorageProvider.md +1 -1
- package/docs/api/interfaces/InitializeVaultCustodyOptions.md +1 -1
- package/docs/api/interfaces/InitializedVaultCustody.md +1 -1
- package/docs/api/interfaces/OpenOwnerSessionOptions.md +1 -1
- package/docs/api/interfaces/OwnerAgentProvisionResult.md +1 -1
- package/docs/api/interfaces/OwnerAuditSubscription.md +45 -0
- package/docs/api/interfaces/OwnerClient.md +17 -1
- package/docs/api/interfaces/OwnerCreateSecretInput.md +1 -1
- package/docs/api/interfaces/OwnerPendingDispatchSubscription.md +1 -1
- package/docs/api/interfaces/OwnerRemoveSecretInput.md +1 -1
- package/docs/api/interfaces/OwnerRequestRecord.md +11 -1
- package/docs/api/interfaces/OwnerSensitiveActionConfirmation.md +1 -1
- package/docs/api/interfaces/OwnerSensitiveActionContext.md +1 -1
- package/docs/api/interfaces/OwnerSession.md +1 -1
- package/docs/api/interfaces/OwnerUpdateSecretInput.md +9 -3
- package/docs/api/interfaces/OwnerVisibleRequestRecord.md +7 -1
- package/docs/api/interfaces/PendingDispatchEvent.md +1 -1
- package/docs/api/interfaces/RecoverVaultOptions.md +1 -1
- package/docs/api/interfaces/RecoveredVault.md +1 -1
- package/docs/api/interfaces/RequestRecord.md +5 -1
- package/docs/api/interfaces/RestoreIdentityOptions.md +1 -1
- package/docs/api/interfaces/SecretAlias.md +1 -1
- package/docs/api/interfaces/SecretDestinationGrant.md +3 -3
- package/docs/api/interfaces/SecretId.md +1 -1
- package/docs/api/interfaces/SecretRecord.md +1 -1
- package/docs/api/interfaces/Signer.md +1 -1
- package/docs/api/interfaces/VaultApproveDispatchInput.md +1 -1
- package/docs/api/interfaces/VaultAuditQueryInput.md +1 -1
- package/docs/api/interfaces/VaultCoreDependenciesOptions.md +1 -1
- package/docs/api/interfaces/VaultCreateAgentInput.md +1 -1
- package/docs/api/interfaces/VaultExportSecretInput.md +1 -1
- package/docs/api/interfaces/VaultGetRequestInput.md +1 -1
- package/docs/api/interfaces/VaultGrantAgentSecretInput.md +1 -1
- package/docs/api/interfaces/VaultGrantSecretDestinationInput.md +1 -1
- package/docs/api/interfaces/VaultId.md +1 -1
- package/docs/api/interfaces/VaultImportAgentInput.md +1 -1
- package/docs/api/interfaces/VaultIssueSessionTokenInput.md +1 -1
- package/docs/api/interfaces/VaultListAgentsInput.md +1 -1
- package/docs/api/interfaces/VaultListGrantsInput.md +1 -1
- package/docs/api/interfaces/VaultListRequestsInput.md +1 -1
- package/docs/api/interfaces/VaultListSecretsInput.md +1 -1
- package/docs/api/interfaces/VaultMetadata.md +1 -1
- package/docs/api/interfaces/VaultObject.md +1 -1
- package/docs/api/interfaces/VaultPrincipal.md +1 -1
- package/docs/api/interfaces/VaultProfile.md +1 -1
- package/docs/api/interfaces/VaultReadAgentPrivateKeyInput.md +1 -1
- package/docs/api/interfaces/VaultReadSecretPlaintextInput.md +1 -1
- package/docs/api/interfaces/VaultRevokeAgentSecretInput.md +1 -1
- package/docs/api/interfaces/VaultRevokeSecretDestinationInput.md +1 -1
- package/docs/api/interfaces/VaultRevokeSessionTokenInput.md +1 -1
- package/docs/api/interfaces/VaultService.md +17 -1
- package/docs/api/interfaces/VaultUpdateAgentInput.md +1 -1
- package/docs/api/type-aliases/AgentId.md +1 -1
- package/docs/api/type-aliases/AgentRequestResult.md +1 -1
- package/docs/api/type-aliases/CbioRuntimeModule.md +1 -1
- package/docs/api/type-aliases/DispatchApprovalDecision.md +1 -1
- package/docs/api/type-aliases/GrantStatus.md +1 -1
- package/docs/api/type-aliases/SecretLifecycleStatus.md +1 -1
- package/docs/api/type-aliases/VaultPrincipalKind.md +2 -2
- package/docs/api/variables/DEFAULT_VAULT_KEY_CUSTODY_BLOB_KEY.md +1 -1
- package/package.json +10 -3
|
@@ -1,456 +1,360 @@
|
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
21
|
+
this.db.prepare(`DELETE FROM secrets WHERE secret_id = ?`).run(secret_id.value);
|
|
29
22
|
}
|
|
30
23
|
async getByAlias(alias) {
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
86
|
+
this.db.prepare(`INSERT INTO audit_logs (event_id, vault_id, root_agent_id, request_id, secret_alias, secret_id, 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.secret_id || 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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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.secret_id) {
|
|
110
|
+
sql += ` AND secret_id = ?`;
|
|
111
|
+
params.push(query.secret_id);
|
|
112
|
+
}
|
|
113
|
+
if (query.request_id) {
|
|
114
|
+
sql += ` AND request_id = ?`;
|
|
115
|
+
params.push(query.request_id);
|
|
116
|
+
}
|
|
117
|
+
if (query.since) {
|
|
118
|
+
sql += ` AND ts >= ?`;
|
|
119
|
+
params.push(query.since);
|
|
120
|
+
}
|
|
121
|
+
sql += ` ORDER BY ts ASC LIMIT 1000`; // Limit to avoid massive memory issues conceptually
|
|
122
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
123
|
+
return rows.map(r => JSON.parse(r.entry));
|
|
124
|
+
}
|
|
125
|
+
subscribe(vault_id, subscription) {
|
|
126
|
+
let subs = this.subscribers.get(vault_id.value);
|
|
127
|
+
if (!subs) {
|
|
128
|
+
subs = new Set();
|
|
129
|
+
this.subscribers.set(vault_id.value, subs);
|
|
130
|
+
}
|
|
131
|
+
const callback = (entry) => {
|
|
132
|
+
if (subscription.afterEventId && entry.event_id <= subscription.afterEventId)
|
|
133
|
+
return;
|
|
134
|
+
if (subscription.operations && !subscription.operations.includes(entry.operation))
|
|
135
|
+
return;
|
|
136
|
+
if (subscription.root_agent_id && entry.root_agent_id !== subscription.root_agent_id)
|
|
137
|
+
return;
|
|
138
|
+
if (subscription.request_id && entry.request_id !== subscription.request_id)
|
|
139
|
+
return;
|
|
140
|
+
subscription.onEvent(entry);
|
|
141
|
+
};
|
|
142
|
+
subs.add(callback);
|
|
143
|
+
return () => {
|
|
144
|
+
const current = this.subscribers.get(vault_id.value);
|
|
145
|
+
if (current) {
|
|
146
|
+
current.delete(callback);
|
|
147
|
+
if (current.size === 0)
|
|
148
|
+
this.subscribers.delete(vault_id.value);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
154
151
|
}
|
|
155
152
|
}
|
|
156
|
-
export class
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
return path.join(this._baseDir, vault_id.value, `${root_agent_id}.json`);
|
|
153
|
+
export class SqliteAgentIdentityRegistry {
|
|
154
|
+
db;
|
|
155
|
+
custody;
|
|
156
|
+
constructor(db, custody) {
|
|
157
|
+
this.db = db;
|
|
158
|
+
this.custody = custody;
|
|
163
159
|
}
|
|
164
160
|
async register(identity) {
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
161
|
+
const { private_key, ...metadata } = identity;
|
|
162
|
+
if (private_key) {
|
|
163
|
+
await this.custody.store({ value: identity.root_agent_id }, private_key);
|
|
164
|
+
}
|
|
165
|
+
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(metadata));
|
|
168
166
|
}
|
|
169
167
|
async get(vault_id, root_agent_id) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return JSON.parse(content);
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
168
|
+
const row = this.db.prepare(`SELECT record FROM agents WHERE vault_id = ? AND root_agent_id = ?`).get(vault_id.value, root_agent_id);
|
|
169
|
+
if (!row)
|
|
175
170
|
return null;
|
|
171
|
+
let record = JSON.parse(row.record);
|
|
172
|
+
// Lazy migration: if private key is found in plain text in the record, move it to custody
|
|
173
|
+
if (record.private_key) {
|
|
174
|
+
const pk = record.private_key;
|
|
175
|
+
delete record.private_key;
|
|
176
|
+
await this.custody.store({ value: root_agent_id }, pk);
|
|
177
|
+
this.db.prepare(`UPDATE agents SET record = ? WHERE vault_id = ? AND root_agent_id = ?`).run(JSON.stringify(record), vault_id.value, root_agent_id);
|
|
176
178
|
}
|
|
179
|
+
else {
|
|
180
|
+
const private_key = await this.custody.load({ value: root_agent_id });
|
|
181
|
+
if (private_key)
|
|
182
|
+
record.private_key = private_key;
|
|
183
|
+
}
|
|
184
|
+
return record;
|
|
177
185
|
}
|
|
178
186
|
async list(vault_id) {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
const rows = this.db.prepare(`SELECT record FROM agents WHERE vault_id = ?`).all(vault_id.value);
|
|
188
|
+
return Promise.all(rows.map(async (r) => {
|
|
189
|
+
let record = JSON.parse(r.record);
|
|
190
|
+
if (record.private_key) {
|
|
191
|
+
const pk = record.private_key;
|
|
192
|
+
delete record.private_key;
|
|
193
|
+
await this.custody.store({ value: record.root_agent_id }, pk);
|
|
194
|
+
this.db.prepare(`UPDATE agents SET record = ? WHERE vault_id = ? AND root_agent_id = ?`).run(JSON.stringify(record), vault_id.value, record.root_agent_id);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const private_key = await this.custody.load({ value: record.root_agent_id });
|
|
198
|
+
if (private_key)
|
|
199
|
+
record.private_key = private_key;
|
|
200
|
+
}
|
|
201
|
+
return record;
|
|
202
|
+
}));
|
|
190
203
|
}
|
|
191
204
|
}
|
|
192
|
-
export class
|
|
193
|
-
|
|
194
|
-
constructor(
|
|
195
|
-
this.
|
|
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`);
|
|
205
|
+
export class SqliteAgentSecretGrantRegistry {
|
|
206
|
+
db;
|
|
207
|
+
constructor(db) {
|
|
208
|
+
this.db = db;
|
|
199
209
|
}
|
|
200
210
|
async upsert(grant) {
|
|
201
|
-
|
|
202
|
-
await ensureDir(path.dirname(filePath));
|
|
203
|
-
await fs.writeFile(filePath, JSON.stringify(grant, null, 2));
|
|
211
|
+
this.db.prepare(`INSERT INTO grants (vault_id, root_agent_id, secret_id, record) VALUES (?, ?, ?, ?) ON CONFLICT(vault_id, root_agent_id, secret_id) DO UPDATE SET record = excluded.record`).run(grant.vault_id.value, grant.root_agent_id, grant.secret_id.value, JSON.stringify(grant));
|
|
204
212
|
}
|
|
205
|
-
async get(vault_id, root_agent_id,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
return JSON.parse(content);
|
|
209
|
-
}
|
|
210
|
-
catch {
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
+
async get(vault_id, root_agent_id, secret_id) {
|
|
214
|
+
const row = this.db.prepare(`SELECT record FROM grants WHERE vault_id = ? AND root_agent_id = ? AND secret_id = ?`).get(vault_id.value, root_agent_id, secret_id.value);
|
|
215
|
+
return row ? JSON.parse(row.record) : null;
|
|
213
216
|
}
|
|
214
217
|
async list(vault_id, root_agent_id) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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 [];
|
|
218
|
+
let sql = `SELECT record FROM grants WHERE vault_id = ?`;
|
|
219
|
+
const params = [vault_id.value];
|
|
220
|
+
if (root_agent_id) {
|
|
221
|
+
sql += ` AND root_agent_id = ?`;
|
|
222
|
+
params.push(root_agent_id);
|
|
238
223
|
}
|
|
224
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
225
|
+
return rows.map(r => JSON.parse(r.record));
|
|
239
226
|
}
|
|
240
|
-
async delete(vault_id, root_agent_id,
|
|
241
|
-
|
|
242
|
-
await fs.unlink(this._getPath(vault_id, root_agent_id, secret_alias));
|
|
243
|
-
}
|
|
244
|
-
catch { }
|
|
227
|
+
async delete(vault_id, root_agent_id, secret_id) {
|
|
228
|
+
this.db.prepare(`DELETE FROM grants WHERE vault_id = ? AND root_agent_id = ? AND secret_id = ?`).run(vault_id.value, root_agent_id, secret_id.value);
|
|
245
229
|
}
|
|
246
230
|
}
|
|
247
|
-
export class
|
|
248
|
-
|
|
249
|
-
constructor(
|
|
250
|
-
this.
|
|
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`);
|
|
231
|
+
export class SqliteSecretDestinationGrantRegistry {
|
|
232
|
+
db;
|
|
233
|
+
constructor(db) {
|
|
234
|
+
this.db = db;
|
|
254
235
|
}
|
|
255
236
|
async upsert(grant) {
|
|
256
|
-
|
|
257
|
-
await ensureDir(path.dirname(filePath));
|
|
258
|
-
await fs.writeFile(filePath, JSON.stringify(grant, null, 2));
|
|
237
|
+
this.db.prepare(`INSERT INTO destination_grants (vault_id, secret_id, site_id, record) VALUES (?, ?, ?, ?) ON CONFLICT(vault_id, secret_id, site_id) DO UPDATE SET record = excluded.record`).run(grant.vault_id.value, grant.secret_id.value, grant.site_id, JSON.stringify(grant));
|
|
259
238
|
}
|
|
260
|
-
async get(vault_id,
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return JSON.parse(content);
|
|
264
|
-
}
|
|
265
|
-
catch {
|
|
266
|
-
return null;
|
|
267
|
-
}
|
|
239
|
+
async get(vault_id, secret_id, site_id) {
|
|
240
|
+
const row = this.db.prepare(`SELECT record FROM destination_grants WHERE vault_id = ? AND secret_id = ? AND site_id = ?`).get(vault_id.value, secret_id.value, site_id);
|
|
241
|
+
return row ? JSON.parse(row.record) : null;
|
|
268
242
|
}
|
|
269
|
-
async list(vault_id,
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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 [];
|
|
243
|
+
async list(vault_id, secret_id) {
|
|
244
|
+
let sql = `SELECT record FROM destination_grants WHERE vault_id = ?`;
|
|
245
|
+
const params = [vault_id.value];
|
|
246
|
+
if (secret_id) {
|
|
247
|
+
sql += ` AND secret_id = ?`;
|
|
248
|
+
params.push(secret_id.value);
|
|
293
249
|
}
|
|
250
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
251
|
+
return rows.map(r => JSON.parse(r.record));
|
|
294
252
|
}
|
|
295
|
-
async delete(vault_id,
|
|
296
|
-
|
|
297
|
-
await fs.unlink(this._getPath(vault_id, secret_alias, site_id));
|
|
298
|
-
}
|
|
299
|
-
catch { }
|
|
253
|
+
async delete(vault_id, secret_id, site_id) {
|
|
254
|
+
this.db.prepare(`DELETE FROM destination_grants WHERE vault_id = ? AND secret_id = ? AND site_id = ?`).run(vault_id.value, secret_id.value, site_id);
|
|
300
255
|
}
|
|
301
256
|
}
|
|
302
|
-
export class
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return path.join(this._baseDir, vault_id.value, `${request_id}.json`);
|
|
257
|
+
export class SqliteRequestRecordRegistry {
|
|
258
|
+
db;
|
|
259
|
+
static subscribers = new Map();
|
|
260
|
+
get subscribers() { return SqliteRequestRecordRegistry.subscribers; }
|
|
261
|
+
constructor(db) {
|
|
262
|
+
this.db = db;
|
|
309
263
|
}
|
|
310
264
|
async save(record) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
265
|
+
this.db.prepare(`INSERT INTO requests (vault_id, request_id, root_agent_id, secret_id, record) VALUES (?, ?, ?, ?, ?) ON CONFLICT(request_id) DO UPDATE SET record = excluded.record, secret_id = excluded.secret_id`).run(record.vault_id.value, record.request_id, record.root_agent_id, record.request.secret_id?.value ?? null, JSON.stringify(record));
|
|
266
|
+
const subs = this.subscribers.get(record.vault_id.value);
|
|
267
|
+
if (subs) {
|
|
268
|
+
for (const cb of subs)
|
|
269
|
+
cb(record);
|
|
270
|
+
}
|
|
314
271
|
}
|
|
315
272
|
async get(vault_id, request_id) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
return JSON.parse(content);
|
|
319
|
-
}
|
|
320
|
-
catch {
|
|
321
|
-
return null;
|
|
322
|
-
}
|
|
273
|
+
const row = this.db.prepare(`SELECT record FROM requests WHERE vault_id = ? AND request_id = ?`).get(vault_id.value, request_id);
|
|
274
|
+
return row ? JSON.parse(row.record) : null;
|
|
323
275
|
}
|
|
324
276
|
async list(vault_id, root_agent_id) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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 [];
|
|
277
|
+
let sql = `SELECT record FROM requests WHERE vault_id = ?`;
|
|
278
|
+
const params = [vault_id.value];
|
|
279
|
+
if (root_agent_id) {
|
|
280
|
+
sql += ` AND root_agent_id = ?`;
|
|
281
|
+
params.push(root_agent_id);
|
|
336
282
|
}
|
|
283
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
284
|
+
return rows.map(r => JSON.parse(r.record));
|
|
337
285
|
}
|
|
338
286
|
subscribePending(vault_id, subscription) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
287
|
+
const rows = this.db.prepare(`SELECT record FROM requests WHERE vault_id = ?`).all(vault_id.value);
|
|
288
|
+
const replay = rows.map(r => JSON.parse(r.record))
|
|
289
|
+
.filter((record) => record.execution.status === DispatchStatus.AWAITING_APPROVAL && !!record.pending_dispatch_event)
|
|
290
|
+
.map((record) => ({
|
|
291
|
+
event_id: record.pending_dispatch_event.event_id,
|
|
292
|
+
emitted_at: record.pending_dispatch_event.emitted_at,
|
|
293
|
+
record
|
|
294
|
+
}))
|
|
295
|
+
.filter((event) => !subscription.afterEventId || event.event_id > subscription.afterEventId)
|
|
296
|
+
.sort((a, b) => a.event_id.localeCompare(b.event_id));
|
|
297
|
+
for (const event of replay)
|
|
298
|
+
subscription.onEvent(event);
|
|
299
|
+
let subs = this.subscribers.get(vault_id.value);
|
|
300
|
+
if (!subs) {
|
|
301
|
+
subs = new Set();
|
|
302
|
+
this.subscribers.set(vault_id.value, subs);
|
|
303
|
+
}
|
|
304
|
+
const callback = (record) => {
|
|
305
|
+
if (record.execution.status !== DispatchStatus.AWAITING_APPROVAL || !record.pending_dispatch_event)
|
|
344
306
|
return;
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
.
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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)
|
|
307
|
+
const event = {
|
|
308
|
+
event_id: record.pending_dispatch_event.event_id,
|
|
309
|
+
emitted_at: record.pending_dispatch_event.emitted_at,
|
|
310
|
+
record,
|
|
311
|
+
};
|
|
312
|
+
if (subscription.afterEventId && event.event_id <= subscription.afterEventId)
|
|
359
313
|
return;
|
|
360
|
-
|
|
361
|
-
scanInFlight = null;
|
|
362
|
-
});
|
|
314
|
+
subscription.onEvent(event);
|
|
363
315
|
};
|
|
364
|
-
|
|
365
|
-
const interval = setInterval(tick, 250);
|
|
366
|
-
interval.unref?.();
|
|
316
|
+
subs.add(callback);
|
|
367
317
|
return () => {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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,
|
|
318
|
+
const current = this.subscribers.get(vault_id.value);
|
|
319
|
+
if (current) {
|
|
320
|
+
current.delete(callback);
|
|
321
|
+
if (current.size === 0)
|
|
322
|
+
this.subscribers.delete(vault_id.value);
|
|
323
|
+
}
|
|
380
324
|
};
|
|
381
325
|
}
|
|
382
326
|
}
|
|
383
|
-
export class
|
|
384
|
-
|
|
385
|
-
constructor(
|
|
386
|
-
this.
|
|
387
|
-
}
|
|
388
|
-
_getPath(root_agent_id) {
|
|
389
|
-
return path.join(this._baseDir, `${root_agent_id}.json`);
|
|
327
|
+
export class SqliteSessionTokenRegistry {
|
|
328
|
+
db;
|
|
329
|
+
constructor(db) {
|
|
330
|
+
this.db = db;
|
|
390
331
|
}
|
|
391
332
|
async issue(root_agent_id) {
|
|
392
333
|
const token = `sat_${crypto.randomBytes(16).toString("hex")}`;
|
|
393
|
-
const stored = {
|
|
394
|
-
|
|
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));
|
|
334
|
+
const stored = { token, root_agent_id, issued_at: new Date().toISOString() };
|
|
335
|
+
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
336
|
return token;
|
|
402
337
|
}
|
|
403
338
|
async inspect(token, root_agent_id) {
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
}
|
|
414
|
-
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
|
-
}
|
|
339
|
+
const row = this.db.prepare(`SELECT record, root_agent_id FROM session_tokens WHERE token = ?`).get(token);
|
|
340
|
+
if (!row)
|
|
421
341
|
return { ok: false, reason: "token_not_found" };
|
|
422
|
-
|
|
342
|
+
if (row.root_agent_id !== root_agent_id)
|
|
343
|
+
return { ok: false, reason: "agent_mismatch" };
|
|
344
|
+
return { ok: true, token: JSON.parse(row.record) };
|
|
423
345
|
}
|
|
424
346
|
async revoke(token) {
|
|
425
|
-
|
|
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 { }
|
|
347
|
+
this.db.prepare(`DELETE FROM session_tokens WHERE token = ?`).run(token);
|
|
433
348
|
}
|
|
434
349
|
async list(root_agent_id) {
|
|
350
|
+
let sql = `SELECT record FROM session_tokens`;
|
|
351
|
+
const params = [];
|
|
435
352
|
if (root_agent_id) {
|
|
436
|
-
|
|
437
|
-
|
|
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 [];
|
|
353
|
+
sql += ` WHERE root_agent_id = ?`;
|
|
354
|
+
params.push(root_agent_id);
|
|
453
355
|
}
|
|
356
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
357
|
+
return rows.map(r => JSON.parse(r.record));
|
|
454
358
|
}
|
|
455
359
|
}
|
|
456
360
|
export const DEFAULT_VAULT_KEY_CUSTODY_BLOB_KEY = "master_key.sealed";
|
|
@@ -468,21 +372,77 @@ export async function recoverVaultWorkingKey(storage, recoveryKey) {
|
|
|
468
372
|
const { workingKey } = JSON.parse(data.toString());
|
|
469
373
|
return workingKey;
|
|
470
374
|
}
|
|
375
|
+
const dbCache = new Map();
|
|
376
|
+
function initDb(baseDir) {
|
|
377
|
+
if (dbCache.has(baseDir))
|
|
378
|
+
return dbCache.get(baseDir);
|
|
379
|
+
const dbPath = path.join(baseDir, "vault.sqlite");
|
|
380
|
+
const db = new Database(dbPath);
|
|
381
|
+
db.pragma('journal_mode = WAL');
|
|
382
|
+
// Check if we need to migrate from alias to id
|
|
383
|
+
const hasOldGrants = db.prepare("SELECT count(*) as count FROM sqlite_master WHERE type='table' AND name='grants' AND sql LIKE '%secret_alias%'").get();
|
|
384
|
+
if (hasOldGrants.count > 0) {
|
|
385
|
+
console.log("🛠️ Migrating Vault database from secret_alias to secret_id...");
|
|
386
|
+
db.transaction(() => {
|
|
387
|
+
// 1. Rename old tables
|
|
388
|
+
db.exec("ALTER TABLE grants RENAME TO grants_old");
|
|
389
|
+
db.exec("ALTER TABLE destination_grants RENAME TO destination_grants_old");
|
|
390
|
+
// 2. Create new tables
|
|
391
|
+
db.exec("CREATE TABLE grants (vault_id TEXT, root_agent_id TEXT, secret_id TEXT, record TEXT, PRIMARY KEY(vault_id, root_agent_id, secret_id))");
|
|
392
|
+
db.exec("CREATE TABLE destination_grants (vault_id TEXT, secret_id TEXT, site_id TEXT, record TEXT, PRIMARY KEY(vault_id, secret_id, site_id))");
|
|
393
|
+
// 3. Migrate data by joining with secrets table
|
|
394
|
+
db.exec(`
|
|
395
|
+
INSERT INTO grants (vault_id, root_agent_id, secret_id, record)
|
|
396
|
+
SELECT g.vault_id, g.root_agent_id, s.secret_id, g.record
|
|
397
|
+
FROM grants_old g
|
|
398
|
+
JOIN secrets s ON g.secret_alias = s.alias
|
|
399
|
+
`);
|
|
400
|
+
db.exec(`
|
|
401
|
+
INSERT INTO destination_grants (vault_id, secret_id, site_id, record)
|
|
402
|
+
SELECT dg.vault_id, s.secret_id, dg.site_id, dg.record
|
|
403
|
+
FROM destination_grants_old dg
|
|
404
|
+
JOIN secrets s ON dg.secret_alias = s.alias
|
|
405
|
+
`);
|
|
406
|
+
// 4. Drop old tables
|
|
407
|
+
db.exec("DROP TABLE grants_old");
|
|
408
|
+
db.exec("DROP TABLE destination_grants_old");
|
|
409
|
+
})();
|
|
410
|
+
console.log("✅ Migration complete.");
|
|
411
|
+
}
|
|
412
|
+
db.exec(`
|
|
413
|
+
CREATE TABLE IF NOT EXISTS secrets (secret_id TEXT PRIMARY KEY, vault_id TEXT, alias TEXT UNIQUE, record TEXT);
|
|
414
|
+
CREATE TABLE IF NOT EXISTS custody (secret_id TEXT PRIMARY KEY, encrypted_payload TEXT);
|
|
415
|
+
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, secret_id TEXT, ts INTEGER, entry TEXT);
|
|
416
|
+
CREATE TABLE IF NOT EXISTS agents (vault_id TEXT, root_agent_id TEXT, record TEXT, PRIMARY KEY(vault_id, root_agent_id));
|
|
417
|
+
CREATE TABLE IF NOT EXISTS grants (vault_id TEXT, root_agent_id TEXT, secret_id TEXT, record TEXT, PRIMARY KEY(vault_id, root_agent_id, secret_id));
|
|
418
|
+
CREATE TABLE IF NOT EXISTS destination_grants (vault_id TEXT, secret_id TEXT, site_id TEXT, record TEXT, PRIMARY KEY(vault_id, secret_id, site_id));
|
|
419
|
+
CREATE TABLE IF NOT EXISTS requests (vault_id TEXT, request_id TEXT PRIMARY KEY, root_agent_id TEXT, secret_id TEXT, record TEXT);
|
|
420
|
+
CREATE TABLE IF NOT EXISTS session_tokens (root_agent_id TEXT PRIMARY KEY, token TEXT UNIQUE, record TEXT);
|
|
421
|
+
`);
|
|
422
|
+
dbCache.set(baseDir, db);
|
|
423
|
+
return db;
|
|
424
|
+
}
|
|
471
425
|
export function createPersistentVaultCoreDependencies(storage, options) {
|
|
472
426
|
const baseDir = storage.getBaseDir();
|
|
473
|
-
|
|
474
|
-
|
|
427
|
+
// Ensure black-box environment directory exists synchronously.
|
|
428
|
+
if (!fs.existsSync(baseDir)) {
|
|
429
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
430
|
+
}
|
|
431
|
+
const db = initDb(baseDir);
|
|
432
|
+
const custody = new SqliteSecretCustody(db, options.vaultWorkingKey);
|
|
433
|
+
const agentRecords = new SqliteAgentIdentityRegistry(db, custody);
|
|
434
|
+
const sessionTokenRegistry = new SqliteSessionTokenRegistry(db);
|
|
475
435
|
return {
|
|
476
436
|
vault_id: { value: options.vault_id },
|
|
477
437
|
ids: new RandomIdGenerator(),
|
|
478
438
|
clock: new SystemClock(),
|
|
479
439
|
agentRecords,
|
|
480
|
-
agent_secretGrants: new
|
|
481
|
-
secret_destinationGrants: new
|
|
482
|
-
audit: new
|
|
483
|
-
requests: new
|
|
484
|
-
custody
|
|
485
|
-
secrets: new
|
|
440
|
+
agent_secretGrants: new SqliteAgentSecretGrantRegistry(db),
|
|
441
|
+
secret_destinationGrants: new SqliteSecretDestinationGrantRegistry(db),
|
|
442
|
+
audit: new SqliteAuditLog(db),
|
|
443
|
+
requests: new SqliteRequestRecordRegistry(db),
|
|
444
|
+
custody,
|
|
445
|
+
secrets: new SqliteSecretRepository(db),
|
|
486
446
|
policy: new DefaultPolicyEngine(),
|
|
487
447
|
replayGuard: options.replayGuard ?? new InMemoryReplayGuard(options.proofVerifier),
|
|
488
448
|
agentProofVerifier: new SignatureAgentProofVerifier(agentRecords, sessionTokenRegistry, options.proofVerifier),
|