scope-filter.mjs
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠ breaking
1 day ago
| 1 | /** |
| 2 | * Filter note list/search results by per-user scope (projects + folders). |
| 3 | * Same behavior as applyScopeFilter in hub/server.mjs; used by gateway (hosted) and Node Hub. |
| 4 | * |
| 5 | * Each note: { path, project?, ... } |
| 6 | */ |
| 7 | |
| 8 | /** |
| 9 | * @param {Array<{ path?: string, project?: string | null }>} notes |
| 10 | * @param {{ projects?: string[], folders?: string[] } | null | undefined} scope |
| 11 | * @returns {Array<{ path?: string, project?: string | null }>} |
| 12 | */ |
| 13 | export function applyScopeFilterToNotes(notes, scope) { |
| 14 | if (!scope || (!scope.projects?.length && !scope.folders?.length)) return notes; |
| 15 | const list = Array.isArray(notes) ? notes : []; |
| 16 | return list.filter((n) => { |
| 17 | if (scope.folders?.length) { |
| 18 | const p = n.path && typeof n.path === 'string' ? n.path : ''; |
| 19 | const folder = p.includes('/') ? p.split('/').slice(0, -1).join('/') : ''; |
| 20 | if (scope.folders.some((f) => folder === f || folder.startsWith(f + '/'))) return true; |
| 21 | } |
| 22 | if (scope.projects?.length && n.project && scope.projects.includes(n.project)) return true; |
| 23 | return false; |
| 24 | }); |
| 25 | } |
| 26 | |
| 27 | /** |
| 28 | * Filter proposals the same way as notes for hosted team scope (folder prefix + project slug). |
| 29 | * Full proposal objects from GET /proposals/:id include `path`; `project` may appear in frontmatter. |
| 30 | * |
| 31 | * @param {Array<{ path?: string, project?: string | null, frontmatter?: string | object }>} proposals |
| 32 | * @param {{ projects?: string[], folders?: string[] } | null | undefined} scope |
| 33 | * @returns {typeof proposals} |
| 34 | */ |
| 35 | export function applyScopeFilterToProposals(proposals, scope) { |
| 36 | if (!scope || (!scope.projects?.length && !scope.folders?.length)) return proposals; |
| 37 | const list = Array.isArray(proposals) ? proposals : []; |
| 38 | return list.filter((p) => { |
| 39 | const pathStr = p.path && typeof p.path === 'string' ? p.path : ''; |
| 40 | if (scope.folders?.length) { |
| 41 | const folder = pathStr.includes('/') ? pathStr.split('/').slice(0, -1).join('/') : ''; |
| 42 | if (scope.folders.some((f) => folder === f || folder.startsWith(f + '/'))) return true; |
| 43 | } |
| 44 | if (scope.projects?.length) { |
| 45 | let proj = p.project && typeof p.project === 'string' ? p.project : null; |
| 46 | if (!proj && pathStr.startsWith('projects/')) { |
| 47 | const rest = pathStr.slice('projects/'.length); |
| 48 | proj = rest.split('/')[0] || null; |
| 49 | } |
| 50 | if (!proj && p.frontmatter) { |
| 51 | let fm = p.frontmatter; |
| 52 | if (typeof fm === 'string' && fm.trim()) { |
| 53 | try { |
| 54 | fm = JSON.parse(fm); |
| 55 | } catch { |
| 56 | fm = null; |
| 57 | } |
| 58 | } |
| 59 | if (fm && typeof fm === 'object' && !Array.isArray(fm) && fm.project) { |
| 60 | proj = String(fm.project); |
| 61 | } |
| 62 | } |
| 63 | if (proj && scope.projects.includes(proj)) return true; |
| 64 | } |
| 65 | return false; |
| 66 | }); |
| 67 | } |
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