Skip to content

Trust boundaries ​

Status: slice 34 · October 2026.

Who is allowed to do what, where that is enforced, and which browser cookies and sessions belong to which product. Security controls themselves are catalogued on Security; this page defines the boundaries they protect.

1. Tenant vs platform perimeter ​

text
┌─ Tenant perimeter ─────────────────────────────┐
│  Dossiers, Documents, cells, contradictions,     │
│  market book, portfolio, Q&A, alerts             │
│  Isolation: EF query filters + FORCE RLS GUCs    │
└──────────────────────────────────────────────────┘

┌─ Platform perimeter ───────────────────────────┐
│  LegalInstruments / Editions / Provisions /      │
│  SourceArtifacts / ReviewItems / Applicability   │
│  Writes: PLATFORM_OPERATOR (system:manage)       │
│  Tenant reads: resolve + open-statute (hash OK)  │
└──────────────────────────────────────────────────┘
Tenant perimeterPlatform perimeter
OwnerOne institution (OrganizationId)Locus platform operators
TablesDossiers + children, market/portfolio/alerts/search, AuthSessions, AuditEvents, …RegulatorySources, LegalInstruments, LegalEditions, LegalProvisions, LegalSourceArtifacts, LegalReviewItems, LegalIngestionLogs, LegalAmendments, LegalProvisionApplicability (no OrganizationId)
DocumentsDeal-room PDFs in R2 under tenants/{tenantId}/…, AES-256-GCMStatutory PDFs in R2 or the local vault dir, SHA-256 verified on read
Written byTenant roles per the matrix belowPLATFORM_OPERATOR through locus-ops only
Read by tenantsOwn rows onlyregulatory:read → resolve + open-statute

Tenant uploads never become legal authority for other tenants. Deal-room PDFs and the platform vault are two separate libraries (What Locus is).

Cross-perimeter rules:

  • PLATFORM_OPERATOR has no dossier-origination or committee rights, and cannot be invited into a tenant through the Members API.
  • A tenant admin cannot write platform legal tables or ingest market data for another org; the SoR rejects it regardless of what a client sends.
  • The one deliberate cross-tenant write path is operator market ingest with an explicit organizationId (Market ingest (ops)); it is audit-logged as MARKET_INGESTED.

2. RBAC matrix ​

Six-tier institutional matrix ​

The permission matrix spans institutional roles at the API and service layer:

RoleScopePermitted operationsRestricted / prohibited actions
PLATFORM_OPERATORGlobal platformSystem health, tenant provisioning, global catalogs (legal vault), cross-tenant audit maintenanceDirect client deal origination, internal credit committee votes
INSTITUTION_ADMINTenant organizationUser lifecycle, seat allocation, edition customization, module toggling, audit trailsCross-tenant queries, unilateral regulatory visa issuance
ARRANGER_LEADDeal mandateDossier creation, term sheet editing, placement memo sign-off, external sharingGlobal platform administration, bypass of accounting checks
CREDIT_ANALYSTFinancial & quantSYSCOHADA table reconciliation, covenant stress tests, cell overrides with rationaleMemo sign-off (403), user management (403)
COMPLIANCE_OFFICERRegulatory & legalCOSUMAF pre-visa audit, legal gazette cross-checks, quitus verification, committee reviewFinancial cell override (403), covenant margin alteration
SYNDICATE_GUESTRead-only deal roomApproved document inspection, sanitized Q&A submissionMemo generation (403), internal credit committee deliberations (403), exports

Roles beyond the six tiers ​

The shipped matrix in locus-backend/Services/InstitutionalAuthorization.cs (mirrored in locus-web/src/domains/auth/types.ts) also defines seven specialist tenant roles that map onto the same permission vocabulary: ORIGINATION_BANKER, RISK_MANAGER, LEGAL_COUNSEL, RESEARCH_ANALYST, PORTFOLIO_MANAGER, COMMITTEE_VIEWER, plus the soft aliases administrator and investment_banker used by organization profile data. Treat the table above as the conceptual model and the C# dictionary as the exact source.

Permission vocabulary (selected) ​

PermissionTypical holdersGuards
system:managePLATFORM_OPERATORops regulatory admin, access-request review/provision, cross-org market ingest
tenant:manage, tenant:view_auditINSTITUTION_ADMIN, PLATFORM_OPERATORmembers, org settings/entitlements, audit view
dossier:create / read / write / archiveadmin, arranger, banker, analysts per roledossier lifecycle; archive needs rationale ≥ 5 chars
financial:read / write / reconcile / overrideanalysts, arranger, risk, portfoliomarket overview/ingest, portfolio, reconciliation, cell override (financial:override, justification ≥ 5 chars)
covenant:read / editarranger, analyst, risk, legalcovenant evaluation
regulatory:read / auditmost roles; audit for arranger, compliance, legallegal-vault resolve, open-statute, pre-visa audit
memo:generate, memo:signoffgenerate: most; signoff: ARRANGER_LEADmemo drafter
dealroom:upload / read_approved / manage_watermarkupload: admin, arranger, bankerdocument upload; approved-only reads
qa:read / submitmost; guests submit sanitized questionsQ&A governance
committee_notes:view / writenot SYNDICATE_GUESTcommittee deliberations (Chinese wall)

Enforcement layers ​

LayerComponentFailure
EdgeMiddleware ticket checkredirect /login or 401
BFFrequireBffSession + enforceRbacGuard (src/domains/auth/guard.ts, src/lib/api/locus-bff.ts)401 UNAUTHENTICATED / 401 SESSION_EXPIRED / 403
SoRInstitutionalAuthMiddleware → claims; InstitutionalAuthorization.RequirePermission403 PERMISSION_DENIED, 403 TENANT_REQUIRED
DataEF query filters + FORCE RLSzero rows / not found

Cross-tenant dossier access is blocked with 403 TENANT_FORBIDDEN or not-found under the caller's OrganizationId. The matrix applies to both BFF /api/locus/* and every SoR controller (Deals, Documents, Members, Organizations, Exports, Market, Portfolio, Search, Alerts, Precedents, Blockers, AnalysisJobs, AccessRequests, Q&A, RegulatoryFindings, LegalVault). PLATFORM_OPERATOR additionally uses RegulatoryAdminController.

Verification suites: rbac_and_tenancy, auth_gate_and_api_rbac, dossier_persistence_rbac, market_portfolio_search_rbac, storage_health_and_r2.

3. Cookies and sessions: bank vs ops ​

CookieAppRole allowedSigningSession guard
locus_sessionlocus-web (bank, :3000)any tenant roleSESSION_SIGNING_KEY (HMAC ticket, v1. prefix, expiry)requireBffSession
locus_ops_sessionlocus-ops (:3001)PLATFORM_OPERATOR onlySESSION_SIGNING_KEY — the same key is recommended, but cookies are never sharedrequireOpsSession

Boundaries to remember:

  • The two cookies are independent: a bank ticket does not authenticate ops, and an ops ticket does not authenticate /workspace.
  • Both tickets are only the edge proof (signature + expiry). Real authority is the Postgres AuthSessions row; logout revokes it server-side, so a stolen but revoked ticket stops working at the BFF/SoR.
  • Bank login: credentials → mandatory OTP. Ops login: POST /api/auth/platform-login rejects non-PLATFORM_OPERATOR accounts before the OTP step.
  • Bank BFF forwards Authorization: Bearer <sessionId> and X-Organization-Id derived from the session. X-Organization-Id / X-User-* from a browser are never trusted in production (Auth__AllowDevHeaders=false).
  • Public bank routes (no cookie): landing, /login, /register, /request-access*, /auth/accept-invite, /legal/*, and part of /api/auth. Everything else under /workspace/** redirects to /login; protected /api/** returns 401 JSON.
  • Legacy bank paths /workspace/regulatory-admin and /workspace/access-requests redirect to ops via NEXT_PUBLIC_OPS_APP_URL.

Session lifetimes on the SoR: login 8 h, accept-invite 72 h. See Auth & sessions.

4. Identity chain ​

  1. Edge middleware verifies the signed ticket (presence + signature + expiry).
  2. BFF requireBffSession (bank) / requireOpsSession (ops) loads the SoR session and rejects the wrong role for the app.
  3. C# InstitutionalAuthMiddleware validates AuthSessions (TokenHash = SHA-256(sessionId)), loads the Active membership, and sets claims sub, tenant_id, role, session_id.
  4. HttpContextTenantProvider prefers claims over client headers.
  5. TenantRlsMiddleware sets app.current_tenant_id / app.is_platform_operator GUCs; TenantRlsConnectionInterceptor applies them per connection for FORCE RLS.
  6. Controllers call InstitutionalAuthorization.RequirePermission(...).

5. Other trust edges ​

EdgeRule
Browser → NextNext never opens Postgres or spawns OS subprocesses; product writes go through the SoR
Next → SoRBearer session; org and role from the session, not from request headers
SoR → WorkersInternal HTTP (WorkerSettings__PythonWorkerUrl); typed AnalysisResult JSON; no LLM for numbers
Next → Workers (OCR)LOCUS_WORKER_URL, POST /api/v1/ocr when native/harvest extraction is insufficient
Web/Ops → R2Private bucket; tenant PDFs encrypted at rest; vault PDFs hash-verified on read
Copilot → LLMOptional prose polish only (COPILOT_LLM_PROVIDER, default off); numbers are untouchable; regulatory_corpus scope blocks dossier financial computation
Watchtower → vaultDiscovery only; human APPROVE is the sole path to in_force

Related: Security · Data stores · Ops overview · locus-backend.

Source of trust for Locus. If this conflicts with code, code + IMPLEMENTATION_TRACKER.md win.