# Comment Visibility Architecture **Date:** 2026-03-30 **Status:** Phase 4 mention audience aligned with entity visibility ## Problem The original comment router accepted arbitrary `entityType` and `entityId` pairs behind `protectedProcedure`. That was too broad: - comment visibility depends on the backing entity, not on the comment record alone - generic strings allowed clients and assistant tools to imply support for entity types that had no explicit policy - author/admin checks on `resolve` and `delete` were not enough, because list/create access was still effectively "any authenticated user" ## Current Product Reality There are now two real first-party consumers: - web UI estimate workspace comments via `entityType: "estimate"` - web UI resource detail comments via `entityType: "resource"` The older examples for `scope_item`, `estimate_version`, and `demand_line` remain aspirational, not backed by an explicit visibility model or active UI. ## Architecture Decision Comments now use an explicit entity registry. - supported entity types are allowlisted, not free-form - each entity type owns: - its audience rule - its existence check - its deep link builder for notifications - every comment route calls the entity access layer before touching comment data ## Current Policy Supported entity types: - `estimate` - `resource` Audience: - same audience as the estimate workspace - controller, manager, or admin only - `resource` comments inherit the exact resource detail audience: - self-service for the caller's own linked resource - broad visibility for users who already have resource overview access Route effects: - `list`, `count`, `create`, `resolve`, and `delete` all require estimate visibility first - `listMentionCandidates` uses the same entity visibility gate before returning mention candidates - the same routes require resource visibility first for `entityType: "resource"` - `resolve` and `delete` still require comment author or admin after entity visibility is granted - replies are only allowed when the parent comment belongs to the same entity tuple - mention notifications use the entity policy link builder instead of hardcoded route assumptions scattered through the router ## Why This Shape - It closes the real security gap now without pretending a generic multi-entity policy already exists. - It keeps future comment expansion additive: a new entity type must be onboarded deliberately. - It gives the assistant and UI one source of truth for what is actually supported today. ## Phase 2 Foundation Phase 2 does not add a fake second entity type. Instead, it removes duplication around the real registry: - supported comment entity types now live in shared constants instead of being re-declared in multiple layers - the API owns a dedicated comment entity registry module for: - access checks - notification link building - assistant-facing supported-entity metadata - assistant tool schemas and descriptions derive their comment entity metadata from that registry layer - the web comment thread now receives an explicit `{ entityType, entityId }` target instead of hardcoding `estimate` internally This keeps the product honest today while making a future second consumer additive rather than copy-paste driven. ## Phase 3 Resource Onboarding `resource` is now the second deliberate commentable entity because it already had: - a real product detail surface - an explicit access model in the API - a coherent notification deep link target Implementation shape: - shared constants now expose both supported entity types - the API registry now maps `resource` to: - existence checks via the backing resource record - audience checks via the same resource-read ownership rules as `resource.getById` - notification links via `/resources/:id#comments` - the resource detail page now renders a first-party comment thread instead of leaving the second consumer theoretical - assistant comment tools now remain entity-scoped instead of pretending every comment operation is controller-only ## Phase 4 Mention Audience Alignment Mention autocomplete no longer depends on `user.listAssignable`. Instead, comments own a dedicated mention-candidate route scoped by the same entity tuple as comment read/write access. Current shape: - `comment.listMentionCandidates` first enforces the entity access policy - estimate mention candidates are limited to `ADMIN`, `MANAGER`, and `CONTROLLER` users, matching estimate comment visibility - resource mention candidates include: - the linked owner of that resource - users who already have broad resource visibility through effective permissions - `user.listAssignable` remains a separate operational lookup for assignment flows and is not widened as a side effect of comment support ## Extension Rules For Future Entity Types To add another commentable entity: 1. Add the entity type to the registry, not just to input examples. 2. Define the backing audience source of truth. 3. Add an existence check for that entity. 4. Add a notification link builder for that entity. 5. Update assistant tool metadata and assistant visibility gates in the same change. 6. Add router auth tests for unauthenticated, plain authenticated, and elevated callers. 7. Update `docs/route-access-matrix.md`. ## Non-Goals - generic comment support for arbitrary entities - row-level polymorphic authorization based only on `entityType` strings - automatic inheritance for future entities without explicit onboarding