hub-delete-vault.mjs
122 lines 4.1 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 /**
2 * Self-hosted Hub: delete a non-default vault (filesystem + hub_vaults.yaml + access/scope/proposals + vectors).
3 */
4
5 import fs from 'fs';
6 import path from 'path';
7 import { readHubVaults, writeHubVaults } from '../lib/hub-vaults.mjs';
8 import { createVectorStore } from '../lib/vector-store.mjs';
9 import { readVaultAccess, writeVaultAccess } from './hub_vault_access.mjs';
10 import { readScope, writeScope } from './hub_scope.mjs';
11 import { removeProposalsForVault } from './proposals-store.mjs';
12
13 function httpError(message, code) {
14 const e = new Error(message);
15 e.code = code;
16 return e;
17 }
18
19 /**
20 * @param {{ dataDir: string, projectRoot: string, vaultId: string, config: object }} opts
21 * @returns {Promise<{ ok: true, deleted_vault_id: string, proposals_removed: number, vectors_purged: boolean }>}
22 */
23 export async function deleteSelfHostedVault(opts) {
24 const { dataDir, projectRoot, config } = opts;
25 const id = String(opts.vaultId || '').trim();
26 if (!id) throw httpError('vault id required', 'BAD_REQUEST');
27 if (id === 'default') throw httpError('Cannot delete the default vault', 'BAD_REQUEST');
28
29 const list = readHubVaults(dataDir, projectRoot);
30 const vaultsEffective = Array.isArray(list) ? list : [];
31 const target = vaultsEffective.find((v) => v && String(v.id).trim() === id);
32 if (!target) {
33 throw httpError(`Unknown vault id: ${id}`, 'BAD_REQUEST');
34 }
35
36 const pathToIds = new Map();
37 for (const v of vaultsEffective) {
38 if (!v || !v.path) continue;
39 const p = v.path;
40 if (!pathToIds.has(p)) pathToIds.set(p, []);
41 pathToIds.get(p).push(String(v.id).trim());
42 }
43 const idsAtPath = pathToIds.get(target.path) || [];
44 if (idsAtPath.length > 1) {
45 throw httpError(
46 'Multiple vault ids share the same directory; fix hub_vaults.yaml before deleting a vault.',
47 'BAD_REQUEST',
48 );
49 }
50
51 const rootResolved = path.resolve(projectRoot);
52 let rootReal;
53 try {
54 rootReal = fs.realpathSync(rootResolved);
55 } catch {
56 throw httpError('Could not resolve project root path', 'RUNTIME_ERROR');
57 }
58
59 let vaultReal = null;
60 try {
61 vaultReal = fs.realpathSync(target.path);
62 } catch {
63 /* directory already removed — still drop from registry and vectors */
64 }
65
66 if (vaultReal) {
67 if (vaultReal === rootReal) {
68 throw httpError('Refusing to delete the project root as a vault', 'FORBIDDEN');
69 }
70 const rel = path.relative(rootReal, vaultReal);
71 if (rel.startsWith('..') || path.isAbsolute(rel)) {
72 throw httpError('Vault directory must resolve inside the project root', 'FORBIDDEN');
73 }
74 fs.rmSync(vaultReal, { recursive: true, force: true });
75 }
76
77 const remaining = vaultsEffective.filter((v) => v && String(v.id).trim() !== id);
78 writeHubVaults(dataDir, remaining, projectRoot);
79
80 const access = readVaultAccess(dataDir);
81 const nextAccess = {};
82 for (const [uid, arr] of Object.entries(access)) {
83 if (typeof uid !== 'string' || !uid.trim() || !Array.isArray(arr)) continue;
84 const filtered = arr.filter((x) => String(x).trim() !== id);
85 if (filtered.length > 0) nextAccess[uid.trim()] = filtered;
86 }
87 writeVaultAccess(dataDir, nextAccess);
88
89 const scopeRaw = readScope(dataDir);
90 const nextScope = {};
91 for (const [uid, vmap] of Object.entries(scopeRaw)) {
92 if (typeof uid !== 'string' || !uid.trim() || !vmap || typeof vmap !== 'object') continue;
93 const inner = {};
94 for (const [vid, rules] of Object.entries(vmap)) {
95 if (String(vid).trim() === id) continue;
96 inner[vid] = rules;
97 }
98 if (Object.keys(inner).length > 0) nextScope[uid.trim()] = inner;
99 }
100 writeScope(dataDir, nextScope);
101
102 const proposalsRemoved = removeProposalsForVault(dataDir, id);
103
104 let vectorsPurged = false;
105 try {
106 const store = await createVectorStore(config);
107 if (typeof store.deleteByVaultId === 'function') {
108 await store.deleteByVaultId(id);
109 vectorsPurged = true;
110 }
111 } catch {
112 /* optional vector backend misconfigured or delete unsupported — vault data already removed */
113 vectorsPurged = false;
114 }
115
116 return {
117 ok: true,
118 deleted_vault_id: id,
119 proposals_removed: proposalsRemoved,
120 vectors_purged: vectorsPurged,
121 };
122 }
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 1 day ago