5.4 KiB
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
resolveanddeletewere 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:
estimateresource
Audience:
- same audience as the estimate workspace
- controller, manager, or admin only
resourcecomments 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, anddeleteall require estimate visibility firstlistMentionCandidatesuses the same entity visibility gate before returning mention candidates- the same routes require resource visibility first for
entityType: "resource" resolveanddeletestill 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 hardcodingestimateinternally
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
resourceto:- 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.listMentionCandidatesfirst enforces the entity access policy- estimate mention candidates are limited to
ADMIN,MANAGER, andCONTROLLERusers, 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.listAssignableremains 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:
- Add the entity type to the registry, not just to input examples.
- Define the backing audience source of truth.
- Add an existence check for that entity.
- Add a notification link builder for that entity.
- Update assistant tool metadata and assistant visibility gates in the same change.
- Add router auth tests for unauthenticated, plain authenticated, and elevated callers.
- Update
docs/route-access-matrix.md.
Non-Goals
- generic comment support for arbitrary entities
- row-level polymorphic authorization based only on
entityTypestrings - automatic inheritance for future entities without explicit onboarding