Sharing
A ResourceShare row records a per-resource share between a
user and either another user or a group. Shares apply to
annotations, summaries, claims, personas, and world states. A
recipient gains either read-only access or the additional ability
to fork the resource into their own ownership.
Endpoints
POST /api/sharing body { resourceType, resourceId,
sharedWithUserId?, sharedWithGroupId?,
permissionLevel? }
GET /api/sharing/received shares pointed at the requester
GET /api/sharing/sent shares the requester created
DELETE /api/sharing/:shareId revoke a share
POST /api/sharing/:shareId/fork fork a forkable share into an owned copy
Exactly one of sharedWithUserId / sharedWithGroupId is set per
row.
Resource types
annotation | summary | claim | persona | world_state
The resource must exist, and the requester must either own it
(via createdByUserId for annotations, createdBy for summaries
and claims, or userId for personas and world states) or hold a
non-expired ResourceShare with permissionLevel = 'forkable'
targeting them directly through sharedWithUserId or via group
membership through sharedWithGroupId. A read_only recipient
cannot re-share at all; only forkable recipients may re-share,
and never above the level they received.
Permission levels
read_only recipient can read the resource only
forkable recipient can additionally call POST /api/sharing/:shareId/fork
to copy the resource into their own ownership
A share cannot escalate. If the requester only has read_only
access (because the resource was shared to them at that level),
they cannot re-share at forkable. The route refuses; this is the
sharing privilege cap.
Forking
POST /api/sharing/:shareId/fork writes a new owned copy of the
resource under the requester. The new row receives a fresh
top-level id and the owner column is rewritten to the requester
(createdByUserId for annotations, createdBy for summaries and
claims, or userId for personas and world states). All
JSON-shaped fields (for example annotation.frames;
summary.summary, keyFrames, and transcriptJson;
claim.gloss, textSpans, claimerGloss, claimRelation,
audio, video, and metadata; persona.ontology.entityTypes
through relationTypes; worldState.entities, events, times,
the *Collections, and relations) and foreign-key columns
(videoId, personaId, summaryId, claimEventId,
claimTimeId, claimLocationId) are copied verbatim from the
source row. The fork does not rewrite internal cross-references
the way cross-user imports do.
Forking is one-way: revoking the original share does not delete the forked copy.
Expiry
The ResourceShare row carries a nullable expiresAt column,
and the re-share permission check honors it (an expired share no
longer authorizes re-sharing). The POST /api/sharing body does
not currently accept expiresAt, so shares created through the
API are open-ended unless the column is set by another path. A
revoked share is hard-deleted.