Sloth-ninja puts firm libraries behind real access controls

This fork turns shared firm knowledge into a governed workspace, with visible ownership and tighter protection around deleted material.

knowledge-managementsecurity

Sloth-ninja adds a firm-level library alongside the personal workspace, then gives people a clearer way to see who can access what.

  • A firm library for material shared across the organisation.
  • A people view and visibility badges that show the sharing context.
  • Server-side permission checks that enforce those visibility rules.
  • Protection for content beneath a deleted parent item, closing a route that could otherwise expose it to its owner.

The work was activated in production, and the fork's own notes make the point plainly: the interface only makes sense alongside the access controls behind it.

So what Firms building shared precedent or knowledge stores should care because usable collaboration depends on permissions that hold up when records are deleted or reorganised.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

13 commits from Sloth-ninja/JessicaOSS, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
a5da048f WS9 PR 1: firm-visibility migration + spec sloth-ninja 2026-07-28 ↗ GitHub
commit body
Owner-authorised 28/07/2026 (allowlist entry added by the owner).
projects.organisation_id (dead visibility column becomes live:
private|firm); tabular_reviews gains visibility + organisation_id;
partial firm-visible indexes; deletion_audit_logs action check extends
with firm_shared/firm_reverted; both overview functions recreated with
p_user_org_id (old signatures dropped) + firm predicate + visibility in
returns. No behaviour change until a caller passes the org id (PR 2).
Spec: docs/FIRM_LIBRARY_SPEC.md; approved mock-ups linked in BUILD_LOG.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
47bf2e90 Merge pull request #54 from Sloth-ninja/ws9-firm-visibility-migration Ezana.H 2026-07-28 ↗ GitHub
WS9 PR 1: firm-visibility migration + spec
2cc393d1 WS9 PR 2: firm-visibility backend (access branches, visibility routes, firm library) sloth-ninja 2026-07-28 ↗ GitHub
commit body
Enforce and expose the firm library on top of migration 20260728_02:

- lib/access.ts: firm branch added to checkProjectAccess, ensureReviewAccess,
  ensureDocAccess, listAccessibleProjectIds, filterAccessibleDocumentIds - an
  item is accessible when visibility='firm' AND organisation_id = the caller's
  org (resolved once via getUserOrganisationId, threadable, 42703-tolerant).
  Firm viewers are non-owners; project-scoped reviews inherit their matter's
  visibility; missing columns => firm branch silently absent (legacy fallback).
- projects/tabular overview RPC calls pass p_user_org_id; GET /projects/:id and
  /people route access through checkProjectAccess so firm viewers reach matters.
- lib/firmVisibility.ts (self-contained seam): owner flip (predicate-encoded,
  owner-only), admin revert (org-scoped cross-org exclusion), firm-library list
  (reuses the overview RPCs filtered to visibility='firm').
- Routes: PATCH /projects/:id/visibility, PATCH /tabular-review/:id/visibility
  (owner-only, orgless 403, project-scoped review rejected 400), admin
  GET /admin/firm-library + POST /admin/firm-library/:type/:id/revert (MFA),
  member GET /firm-library, GET /user/firm-members (displayName+email only).
  All flips best-effort audited firm_shared/firm_reverted.

Tests: 500 passed (442 baseline + 58 new: access matrix, firmVisibility predicate
atomicity + cross-org, and the four route suites). tsc clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aed5f1d6 Merge pull request #55 from Sloth-ninja/ws9-firm-visibility-backend Ezana.H 2026-07-28 ↗ GitHub
WS9 PR 2: firm-visibility backend (access branches, visibility routes, firm library)
913897ac WS9 PR 3: firm-visibility frontend (PeopleModal, Firm library, badges) sloth-ninja 2026-07-28 ↗ GitHub
commit body
Build the firm-library UI on top of WS9 PR 2's backend (#55). Frontend only;
no backend/migration/.env touched. Built to the approved mock-ups.

- mikeApi: getFirmMemberSuggestions, updateProjectVisibility,
  updateTabularReviewVisibility, getFirmLibrary, getAdminFirmLibrary,
  revertFirmLibraryItem; visibility field on the Project/TabularReview types.
- PeopleModal: firm-member suggestion pills (owner + firm only; external
  emails still free-typed) and a "Visible to everyone at <firm>" toggle
  (owner-only, optimistic + rollback, confirm-before-ON, audit info strip);
  project-scoped reviews show an inherited-visibility note instead of the
  toggle. AccountToggle gains an ariaLabel prop.
- Firm library page ((pages)/firm-library) + a firm-gated sidebar item.
- Admin Firm settings: a "Firm library" card with MFA-guarded per-item
  Revert to private (confirm + optimistic removal + rollback).
- FirmBadge (indigo pill) on firm-visible rows in the matters and
  tabular-reviews lists, via a new TablePrimaryCell adornment prop.

Deviations (honesty-driven, noted in BUILD_LOG): admin card shows "updated"
not "firm-visible since" (payload has only updatedAt); review rows show
document count without a column count (not in the firm-library payload).

Verification: frontend tsc clean; eslint introduces zero new problems (3
pre-existing set-state-in-effect errors on untouched lines). No prettier on
frontend files. Backend tsc + vitest re-run green (500/500) to prove it was
untouched. Screenshots pending (headless worktree; mock-ups are the reference).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
c59adce3 Merge pull request #56 from Sloth-ninja/ws9-firm-visibility-frontend Ezana.H 2026-07-28 ↗ GitHub
WS9 PR 3: firm-visibility frontend (Firm library, PeopleModal, badges)
cee6bc61 WS9 fix wave: compose firm-visibility with deletion governance (WS8×WS9) sloth-ninja 2026-07-28 ↗ GitHub
commit body
Composed-range re-review of the WS9 train surfaced five fixes, all in the
WS8×WS9 interaction class (firm visibility reads through rows deletion
governance tombstones; the two seams were never composed):

1. Parent-tombstone gap (important): tombstoning sets only deleted_at, so a
   tombstoned firm-visible matter/review still passed checkProjectAccess /
   ensureReviewAccess, and the content sub-routes (never re-checking the
   tombstone) let any firm member read - and via /generate, /chat WRITE - a
   soft-deleted item for the whole retention window. Folded the tombstone
   check into both helpers (choke point): tombstoned parent => not-ok for
   everyone incl. the owner. 42703-tolerant. Detail-route guards left as
   defence-in-depth. Restore/expedite/list paths verified unaffected.
2. setResourceVisibility now refuses a tombstoned item (.is deleted_at null
   in the predicate => not_found).
3. Audit resource_type unified to hyphenated "tabular-review" for both firm
   actions (flip route + admin revert param mapping); URL/API keep underscore.
4. PeopleModal add-member placeholder gains firm copy when firm surfaces show.
5. Firm-library rows: role=button + tabIndex + Enter/Space + aria-label.

Tests: +11 (backend 511/511). tsc/eslint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
d21d6d7a Review fix: parent-tombstone gate in ensureDocAccess owner path; tr role=link sloth-ninja 2026-07-28 ↗ GitHub
commit body
Independent review of PR #57 (REQUEST_CHANGES, one should-fix):

- ensureDocAccess returned {ok:true} for the document owner BEFORE reaching
  checkProjectAccess, so the folded-in parent-tombstone gate only fired on the
  non-owner path. A doc owner (or a firm colleague who uploaded a doc into
  someone else's tombstoned firm matter) could still GET/download it for the
  retention window. Added the parent-matter tombstone check before the owner
  short-circuit (mirrors ensureReviewAccess); standalone docs (project_id null)
  unaffected. 42703-tolerant. +2 tests.

- NIT: firm-library <tr> role="button" -> role="link" (navigation semantics;
  keeps tabIndex/handlers/focus style).

Backend 513/513, tsc clean. Frontend tsc/eslint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
e3c82310 Merge pull request #57 from Sloth-ninja/ws9-train-fixes Ezana.H 2026-07-28 ↗ GitHub
WS9 fix wave: compose firm-visibility with deletion governance (WS8×WS9)
d0071663 WS9 close-out: status refresh + cwd-drift durable lesson sloth-ninja 2026-07-28 ↗ GitHub
commit body
CLAUDE.md Current status: WS9 complete (#54-#57) and deployed; 20260728_01
run in prod (saves active); 20260728_02 owner-pending; Quill re-registration
state. DURABLE_LESSONS: shell cwd drift into a worktree silently updated the
wrong refs and invalidated a composed-range review - git -C absolute paths
for ref-affecting commands, SHA-pinned review ranges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
71bf18bd Merge pull request #58 from Sloth-ninja/ws9-closeout Ezana.H 2026-07-28 ↗ GitHub
WS9 close-out: status refresh + cwd-drift durable lesson
6c0eee88 Status: 20260728_02 run in production - WS9 firm library active sloth-ninja 2026-08-03 ↗ GitHub
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
319969ff Merge pull request #60 from Sloth-ninja/ws9-activation-note Ezana.H 2026-08-03 ↗ GitHub
Status: WS9 firm library active in production

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-1002.md from inside the repo you want the changes in.

⬇ Download capture-thread-1002.md