use-share-state.tsfix(sharing): close the gates a review found open, and make one predicate mean one thing
Six holes, and they fall into two shapes.
SURFACES NEVER BROUGHT ONTO THE GATE. `documents.createShareLink` and its four
siblings were written before the access model and check tenancy alone, so any
member of an org could mint a permanent anonymous public link to any document in
it — a teammate's file, a deal they are not on, a folder explicitly made private
— and `/share/:token` honours those tokens. `agentSharing.list`/`resend` asked
only whether the caller could RESOLVE the agent, which for an org-published one
is true of everybody, so an agent's whole recipient list (names and addresses)
was readable by the tenant and its invite notifications were a primitive anyone
could aim at anyone. And `getFolderVisibility` took an id and no viewer at all,
which made `visibilityPreview` an existence oracle over every organisation.
TWO RENDERINGS THAT HAD DRIFTED. The floor was a chain guarded on `floor ===
null` where `accessibleFilter` renders independent OR arms — so the first arm to
match suppressed the rest, and a read-only seat on a deal capped you on your own
file inside it. `agentIdFromPath` scanned every segment while the SQL tests two,
so a folder named `agent-{id}` four deep was reachable by id and invisible in
every listing. Both are the same failure: a listing that admits a row the
mutation then refuses, which is the split this subsystem exists to prevent.
Also: `grantAccess` took a client-supplied user id with no membership check, and
`namesFor` looks users up by id alone — so the share control doubled as a
cross-tenant directory. `viewerFor` trusted its `orgId`, which the filter renders
and the resolver does not. Private→public silently dropped the role you picked,
because it read the org's id off an entry a barrier had cut. Plus a capped
password on the unauthenticated unlock route (it reaches a blocking scryptSync),
beacons bound to their token, two disjoint cache invalidations, a hardcoded
"Owner" on every viewer of an agent panel, and a deterministic grant tie-break.
`refusals.test.ts` pins all six; the two floor cases were checked against the old
chain to be sure they fail for the right reason.
Rides along: the `autoExpandDepth` work already in the tree. Two of its five
files carry review fixes as well and cannot be split, so the test and doc that
prove it come with them rather than landing a behaviour change without either.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 10, 2026, 8:14 PM