129 lines
5.4 KiB
Markdown
129 lines
5.4 KiB
Markdown
# 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
|