hub-delete-vault.mjs
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