feat(api): Authorize private transcripts for participants#981
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
dcramer
marked this pull request as ready for review
July 22, 2026 22:09
dcramer
force-pushed
the
feat/private-transcript-owner-access
branch
from
July 22, 2026 23:27
dc1e975 to
dc5b327
Compare
dcramer
force-pushed
the
feat/private-transcript-owner-access
branch
from
July 22, 2026 23:55
dc5b327 to
6904dae
Compare
dcramer
force-pushed
the
feat/private-transcript-owner-access
branch
from
July 23, 2026 02:14
b7bbfda to
6e31343
Compare
Co-Authored-By: David Cramer <david@sentry.io>
Resolve participant access from verified normalized emails on the bounded conversation lineage. Return isParticipant in detail responses and keep malformed lineage fail-closed.
Make isParticipant part of the shared REST conversation schema and derive it for feed, People, and Location summaries with a direct verified identity comparison.
Persist each conversation's privacy root so REST detail reads use direct primary-key joins. Backfill existing trees once during migration and leave malformed lineages fail-closed.
Roll top-level conversation usage and model breakdowns across persisted descendants, including children absent from the current transcript projection. Scope reads through an indexed root relation while direct child resources retain their own usage.
Co-Authored-By: David Cramer <david@sentry.io>
Co-Authored-By: David Cramer <david@sentry.io>
Require top-level conversations to self-reference before participant authorization, and keep child summary privacy rooted in the owning conversation. Auth-disabled dashboard sessions no longer grant participant access. Keep the REST contract documented and cover cross-root, child-actor, migration, and PostgreSQL fixture behavior.
Co-Authored-By: David Cramer <david@sentry.io>
Resolve participant access once for bounded conversation IDs and use the same projection across feed, detail, People, and Location responses. Aggregate descendant usage in PostgreSQL, keep root counts stable, and reject child writes whose parent has no persisted root.
Use component counters before provider totals in SQL aggregates, matching the canonical tree rollup and avoiding inconsistent REST statistics.
Co-Authored-By: David Cramer <david@sentry.io>
Validate every dashboard REST success response at route registration and return expected errors through one strict JSON schema. Centralize resource schemas and use the canonical conversation summary in People profiles so conversation metadata cannot drift across endpoints.
Reuse the profile tree-metric join for directory totals so duration and usage agree without double-counting same-actor descendants.
Exercise the root backfill through the Drizzle migration chain and verify the missing-root guard through the current store API after rebasing.
Exercise every top-level reporting route through the HTTP boundary and parse each response with its exported Zod schema.
dcramer
force-pushed
the
feat/private-transcript-owner-access
branch
from
July 23, 2026 06:21
660ee2a to
59edb90
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 59edb90. Configure here.
Avoid returning descendant-only totals when a conversation has an invalid root reference. List serializers now fall back to the row's own metrics consistently with the detail endpoint.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Private conversation REST resources now consistently return
isParticipantacross feed, detail, People, and Location responses. A participant is a signed-in viewer whose verified email matches the owning root conversation actor's persisted verified email. The same resolved access value controls title, channel, and transcript exposure, so unmatched viewers remain redacted and transcript expiry still takes precedence.Conversation roots are persisted once: roots self-reference and descendants copy their parent's existing root. Request-time access starts from the bounded set of requested conversation IDs and follows indexed conversation, root, destination, and identity keys; it does not recursively walk history or scan conversations for an email. There is no lineage fallback: malformed roots fail closed, and new child writes reject parents without a persisted root. The migration backfills existing trees before adding the root foreign key and index.
Top-level conversation summaries, details, System statistics, People statistics, and Location metrics include usage and duration from all persisted descendants even when the client has not loaded those child resources. Conversation counts and statuses remain resource-level, while a directly requested child reports only its own usage. Child resources remain independently addressable for lazy UI loading.
Junior-owned dashboard REST routes now declare strict Zod success schemas and serialize through one response boundary; expected failures use one strict JSON error contract. Resource contracts live under
api/schema/{conversation,person,location,common}.ts, and embedded conversation objects all use the canonical conversation summary schema so participation and future metadata cannot drift by endpoint. Responses remain resource-native rather than adding a generic envelope, and dashboard reads and mutations validate the same schemas.Requested by David Cramer via Junior.
--
View Junior Session