Skip to content

Nk/edit permissions - #28

Merged
nadeem-fileverse merged 11 commits into
mainfrom
nk/edit-permissions
Jul 23, 2026
Merged

Nk/edit permissions#28
nadeem-fileverse merged 11 commits into
mainfrom
nk/edit-permissions

Conversation

@nadeem-fileverse

Copy link
Copy Markdown
Contributor

No description provided.

nadeem-fileverse and others added 11 commits July 15, 2026 05:24
  Add server-side enforcement for the GP/private edit rail and a fileKey-
  encrypted view-plane mirror, so the collab-server (not just the room key)
  governs who may write and lets viewers load the latest editor state.

  Edit admission & revocation:
  - verifyEditUcan: verify a gate-minted collab/EDIT UCAN against the pinned
    GATE_DID and extract its signed facts (editGrantEpoch, nullifier); the
    epoch comes from the token, never a client arg (auth.ts).
  - GateEpochCache: passive, single-flight, per-doc editGrantEpoch cache with
    expiry-on-read and monotonic writes — no timer, no background poll
    (gate-epoch.ts).
  - Rail-exclusive admission at /auth: an editUcan admits on the GP rail only
    (fresh epoch or 403, no fall-through); the public arm admits only when
    collabJoinEnabled === true; workspace via workspaceEditEnabled. Per-actor
    identity (nullifier / address) carried on the connection (socket-handlers).
  - Per-write chokepoint: reject ddoc writes with EDIT_REVOKED when a GP
    editor's admitted epoch is stale or the rail flag is off (owner bypass).
  - Owner ops (HTTP): POST /documents/:id/refresh-edit-grant (cache-bypass
    refetch + synchronous force-drop of stale GP sockets), workspace-edit-tier,
    and collab-join-enabled (owner-op-routes.ts).
  - Session flags workspaceEditEnabled / collabJoinEnabled default off
    (fail-closed); startup refuses to boot if GATE_DID is set without GATE_URL
    (config/index.ts, index.ts).

  View-plane mirror (latest-state for viewers):
  - document-mirror model + store: upsert, getLatest (ordered by server-stamped
    createdAt), purge (mongodb-store.ts).
  - handleMirrorSnapshot write handler at POST /documents/mirror-snapshot — any
    admitted editor (by rail), never broadcast; the fileKey ciphertext is the
    only boundary.
  - Open read GET /documents/:documentId/mirror (no auth — the ciphertext IS
    the boundary; the server never holds the fileKey).

  Config: adds GATE_URL, GATE_DID, COLLAB_SERVER_DID.
  Tests: vitest suite updated across handlers + new gate-epoch and
  handleMirrorSnapshot suites.

  Deploy: land AFTER the gate exposes the 0x03 edit role / editGrantEpoch;
  capabilities here stay inert (flags off, no editUcans minted) until the
  gate and client cutover ship.
- verifyEditUcan returns a discriminated actor|legacy union
- EditBoundCache: fail-closed per-actor (editHandle) admission poll
  against the gate /edit-bound endpoint, with TTL and stale-bound
  degrade while the gate is unreachable
- enforce admission at the join, write, and mirror chokepoints
- POST /documents/:id/evict-edit-actors for targeted cache eviction
…ing-DID workspace arm, {ok,updated} tier route

Phase 1 server side of whole-team PrivateEdit:
- joinOnly connections never create/bind a session (404 ROOM_NOT_ESTABLISHED),
  role capped at editor, null-fill heal skipped
- identity-based role determination on bound ddoc sessions: proven identityToken
  DID vs bound ownerIdentityDid decides owner/editor; invalid token = 401;
  token-less joins keep legacy compare behind LEGACY_ROLE_FALLBACK (default on)
- workspace arm admission replaced: requires ownerToken DID MATCHING the
  session's ownerDid + workspaceEditEnabled === true (closes cross-portal hole)
- workspace-edit-tier route returns {ok, updated} row count
- test-mock repairs for the pre-existing editBoundCache/kind stale-mock class

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…actor revocation

Remove the legacy gp editGrantEpoch admission rail (GateEpochCache and the
/refresh-edit-grant owner op) and enforce edit revocation deterministically:

- evict-edit-actors hard-kicks matching gp-actor sockets across all of a
  document's session rooms (was cache-bust only)
- the write and mirror chokepoints disconnect the socket on a failed
  admission re-check instead of only returning 403
- a throttled awareness re-check drops revoked-but-idle actors

Only the targeted actor is affected: every disconnect traces to an exact
owner-supplied editHandle or that socket's own failed re-check, and a gate
error never kicks (stale-bound grace).
GET /documents/:documentId/share-context -> { exists, isPublished } lets
the app distinguish "created, first publish pending" from a real 404 (the
meta row is upserted on owner connect). Open read — same trust model as
the mirror GET; the handler try/catches to 500 since Express 4 does not
catch async rejections.
…dit eviction

- rotate-route + rotation-coordinator: old->new session cutover with opaque
  relay payload, liveEditors snapshot, minEditEpoch stamped via $max
- documentEditEpoch store; auth rejects editUcan below the doc's minEditEpoch
- owner-op routes: evict-edit-actors with epoch stamp; socket handlers gate
  admission on epoch load
… diagnostics

share-context GET accepts ?sessionDid= and answers sessionExists through the
same getSession lookup the owner-ops open with, so a client can confirm the
durable session exists before firing an op that would 404. The owner-op
session-not-found branches now log the supplied sessionDid against the doc's
non-terminated sessions (absence vs stale-key skew evidence).
@nadeem-fileverse
nadeem-fileverse merged commit 4b5e8a3 into main Jul 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant