learning_ai_notes/docs/COSMOS_QUERY_REVIEW.md

41 lines
3.7 KiB
Markdown

# Cosmos Query Review
Date: 2026-05-05
This review covers current NoteLett repository access patterns against the Cosmos container registrations in `backend/src/lib/cosmos-init.ts`. All documents retain `productId: "notelett"` and all list routes include product/user/workspace scope filters before returning user data.
## Container Partitions
| Container | Partition key | Primary access pattern |
| --- | --- | --- |
| `notes`, `note_relationships`, `note_tasks`, `note_artifacts`, `note_agent_actions`, `note_shares`, `note_versions` | `/workspaceId` | Workspace-scoped note surfaces and agent write audit trail |
| `workspaces`, `saved_views`, `note_prompts`, `note_prompt_schedules`, `note_prompt_webhooks`, `note_intake_rules`, `note_intake_jobs` | `/userId` | User-owned configuration, templates, and intake state |
| `note_collaborators` | `/sharedWithUserId` | Shared-with-me lookup; owner-side collaborator listing is note-scoped |
| `palace_*` | `/userId` | Per-user memory palace data and maintenance operations |
## Cross-Partition Or Count-Heavy Operations
| Operation | Why it can cross partitions | Guardrails |
| --- | --- | --- |
| `notes.listNotes()` without `workspaceId` | `notes` is partitioned by `/workspaceId`, but dashboard/search surfaces can list across a user's workspaces. | Always filters by `userId` and `productId`; paginated to caller limit. Workspace-specific calls include `workspaceId`. |
| `note_agent_actions.listPendingActions()` | Pending review queue intentionally spans workspaces in the `/workspaceId` container. | Filters by `userId`, `productId`, and state; performs two count queries plus two bounded reads. |
| `note_shares.findShareByToken()` | Public share-token resolution starts from token/product instead of workspace. | Filters by `shareToken` and `productId`, returns one record, and rejects expired shares before public note lookup revalidates note/user/product scope. |
| `note_collaborators.listCollaboratorsForNote()` | The container is optimized for `/sharedWithUserId`; owner-side note collaborator listing starts from `noteId`. | Filters by `noteId` and `productId`, limits to 100; route verifies the caller can access the note before listing. |
| `palace` search/maintenance scans | Palace data is `/userId` partitioned, but some relevance, duplicate, KG, and maintenance jobs scan hundreds to thousands of user memories/triples. | All scans include `userId` and `productId`; limits are explicit (`500`, `1000`, or `5000`) and remain single-user partition reads. |
| `palace.getPalaceStats()` | Counts six Palace containers for a user. | Counts are all scoped by `userId` and `productId`; suitable for diagnostics/dashboard use. |
| Prompt template builtin merge | User templates and builtins live in separate `/userId` partitions. | User partition and `__builtin__` partition are queried separately, then merged in memory with limit bounds. |
## Regression Coverage
`backend/src/modules/repository-scope.test.ts` verifies scope isolation for:
- Notes list and workspace counts across user, product, and workspace boundaries.
- Agent action workspace lists and cross-workspace pending queues across user and product boundaries.
- Share token, share list, note collaborator, and shared-with-me lookups across product, user, note, and workspace filters.
## Follow-Up Notes
- If dashboard global note search becomes high traffic, prefer a user-partitioned materialized search/index container or a dedicated search service over broad `/workspaceId` fan-out.
- If pending review volume grows, consider a user-partitioned projection for `draft`/`proposed` agent actions to avoid two cross-workspace counts per queue load.
- Public share tokens should remain high-entropy and globally unique; token lookup is intentionally cross-partition but bounded to one item.