Sloth-ninja puts firms in charge of JessicaOSS's AI choices

JessicaOSS now lets a firm standardise the models its members use, without taking that choice away from self-hosted teams.

workflowsecurity

Firm owners can set a default AI model and limit the cloud providers available to their members. Individual preferences remain saved, but sit dormant when the firm chooses central control, so a later policy change does not erase anyone's setup.

  • Firm defaults can govern both everyday chat and document-review work.
  • Provider limits can keep cloud-model choices within an approved catalogue.
  • Locally run models remain available, preserving an on-premise route for sensitive work.
  • The controls are enforced at the service level, not just in the interface, so users cannot sidestep a firm's policy with a modified request.

The move gives firms a more credible way to balance model quality, spending and information governance while retaining flexibility for less managed deployments.

So what Worth a look for legal teams piloting AI who need a consistent approved-model policy without turning every workflow into an IT ticket.

View this fork on GitHub →

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

Commits in this thread

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

SHA Subject Author Date
e504926a WS8 PR F: firm model preferences sloth-ninja 2026-07-27 ↗ GitHub
commit body
Add a third firm policy (memberModelPrefs) and firm model configuration
(default model + offered providers) on top of the #44 migration columns.

When a firm turns the policy OFF: members' personal model-preference writes
are gated (fixed 403), their stored prefs become inert (never deleted) and the
firm default governs server-side resolution, and the model pickers are filtered
to the firm's offered providers. Orgless self-hosters and policy-ON firms are
unchanged everywhere. All new columns read 42703-tolerantly; the gate and
resolver fail open on lookup error. No migration, no new env vars, no new deps.

Backend: organisations.ts (policy + modelConfig on membership,
getUserOrganisationModelContext, get/setOrganisationModelConfig,
normaliseModelConfig); llm/models.ts (MODEL_PROVIDERS, isSelectableModelId);
userSettings.ts + serializeProfile firm-aware resolution; requireMemberPolicy
gate on PATCH /user/profile (model fields only, admins not exempt); admin
PATCH /policies memberModelPrefs + GET/PATCH /admin/model-config with 400 on
unknown model/provider.

Frontend: mikeApi types + get/updateFirmModelConfig; firmPolicy helpers;
account/models neutral card under policy-off + offered-provider filtering;
ModelToggle offeredProviders prop (chat + tabular); firm-settings third policy
toggle + Model configuration card.

Tests: 295 backend vitest pass (24 files) incl. new userSettings.test.ts and
extended auth.policy/organisations/user.serialize/admin suites. Frontend tsc
clean; eslint no new problems on changed files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3fa1d9eb WS8 PR F review fix: enforce firm model policy on the chat routes sloth-ninja 2026-07-27 ↗ GitHub
commit body
Blocker from independent review: firm model config was filtered client-side
only. POST /chat and POST /projects/:id/chat accepted a raw body.model, so a
policy-OFF member (or a policy-ON member under a provider restriction) could
bypass the policy with a crafted request ("gate the routes, not the tabs").

Fix: new resolveOrgChatModel(userId, requested, db) in userSettings.ts, applied
right after model resolution on both streaming chat routes. Rules: local ids
pass through untouched; fail-open on org-lookup error / orgless; policy-OFF +
firm default -> forced to firm default; requested provider outside a non-empty
offeredProviders -> substituted (firm default -> default main model -> first
offered provider's default). models.ts gains safeProviderForModel +
defaultMainModelForProvider.

Coherence: PATCH /admin/model-config now rejects (400) a MERGED config whose
cloud default model's provider sits outside a non-empty offeredProviders, so the
clamp can treat a set firm default as an in-set substitute; a local default is
exempt.

Routes checked for raw client model ids: tabular resolves via getUserModelSettings
(already firm-aware); workflows accepts no client model - neither needs a clamp.

Tests: +14 (309 total, 24 files) - resolveOrgChatModel clamp matrix and the
model-config coherence 400s. Backend tsc + vitest green.

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

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-1142.md from inside the repo you want the changes in.

⬇ Download capture-thread-1142.md