Skip to content

locus-ops (platform) ​

Next.js 15 (App Router) · React 19 · Bun · port 3001 (next dev --port 3001) · cookie locus_ops_session.

Responsibilities ​

  • Platform-only UI for PLATFORM_OPERATOR — separated from the bank workspace so tenant users never see platform surfaces.
  • Regulatory admin (sources, vault, review, applicability, logs).
  • Access-request approve / provision.
  • Market ingest on behalf of a tenant org.
  • Legal vault PDF upload (R2 or LEGAL_VAULT_LOCAL_DIR).
text
 PLATFORM_OPERATOR browser
      │ cookie locus_ops_session (HMAC v1 ticket, NOT shared with bank)
      ▼
 locus-ops middleware ─► (ops)/ pages ─► /api/* BFF (requireOpsSession)
      │                                     │ Bearer <sessionId>
      ▼                                     ▼
 PUBLIC: /login, /login/otp, /api/auth     locus-backend :5000
                                           /api/admin/regulatory/*  (system:manage)
                                           /api/access-requests/*   (system:manage)
                                           /api/market/ingest       (org override)

Stack ​

ConcernChoice
FrameworkNext.js ^15.1.7, React ^19
StylingTailwind 3 + shared institutional look (AdminShell, OpsFrame)
Storage@aws-sdk/client-s3 for legal vault objects
Testsbun test

Identity: PLATFORM_OPS vs bank ​

Aspectlocus-opslocus-web
Role allowedPLATFORM_OPERATOR onlyin-tenant roles (never PLATFORM_OPERATOR)
Cookielocus_ops_sessionlocus_session (+ __Secure-locus-session)
LoginPOST /api/auth/platform-login (SoR)POST /api/auth/login (SoR)
Mismatch responseNOT_PLATFORM_OPERATOR before OTP is issuedUSE_PLATFORM_LOGIN if an operator tries the bank form
Public paths/login, /login/otp, /api/authlanding, register, request-access, legal, …
Unauthenticatedpages → redirect /login?redirect=…; /api/** → 401 { error: "UNAUTHENTICATED", message: "Ops session ticket required." }same pattern

Cookies are separate from the bank. The signing key may be the same SESSION_SIGNING_KEY (recommended if rotating tickets across apps), but the cookie names differ so a bank session can never authenticate in ops and vice versa. Ops ticket and bank ticket share the v1.<payload>.<sig> format — see Auth & sessions.

The ops /api/auth route (src/app/api/auth/route.ts) supports:

Method / actionBehaviour
GETResolve cookie → SoR session; 401 unless role is PLATFORM_OPERATOR
POST { action: "platform_login", email, password, rememberDevice }SoR platform-login; returns mfaRequired: true (+ otpCode outside production)
POST { action: "otp", email, code }SoR OTP verify; 403 NOT_PLATFORM_OPERATOR if the session role differs; mints ticket and sets cookie
POST { action: "logout" }Revokes SoR session and deletes cookie

Key routes ​

RoutePagePurpose
/login, /login/otppublicPlatform password + OTP
/(ops)/page.tsx"Console opérateur" — tiles to Regulatory Admin / Market ingest / Demandes d'accès
/regulatoryRegulatoryAdminViewSource Registry · Legal Vault · Review · Applicability · Logs
/access-requestsAccessRequestsAdminViewOnboarding queue (review / provision)
/marketMarketIngestAdminViewTenant-targeted market ingest JSON

BFF routes (src/app/api/) ​

GroupRoutes
Auth/api/auth
Access requests/api/access-requests, [id]/review, [id]/provision
Market/api/market/ingest
Regulatoryoverview, sources, sources/[id] (+ check, fetch-documents), sources/check-all, instruments, instruments/[id], editions/[editionId]/{artifact,provisions,amendments}, artifacts/[id]/{content,extract}, amendments, applicability (+ [id]), review (+ [id]/decide), logs

Each regulatory route proxies 1:1 to api/admin/regulatory/* on the SoR (regulatory-proxy.ts), which enforces system:manage.

src/lib/storage/legal-vault.ts computes SHA-256 of the uploaded PDF and stores it at legal-vault/{sha256}/{safe-filename}:

BackendWhenstorageKey
R2R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET all setlegal-vault/{sha256}/{file}
Localotherwiselocal:<abs path under LEGAL_VAULT_LOCAL_DIR/{sha256}/{file}>

The BFF then registers metadata with POST /api/admin/regulatory/editions/{editionId}/artifact (SoR stores sha256, storageKey, filename, size; duplicate SHA reuses the artifact row). See Legal vault & Watchtower.

Env (locus-ops/.env.example) ​

VariableRole
LOCUS_API_BASE_URLSoR (default http://localhost:5000)
SESSION_SIGNING_KEYHMAC key for locus_ops_session; required in production (openssl rand -hex 32)
NEXT_PUBLIC_APP_URLOptional public URL of the ops app (the bank app uses NEXT_PUBLIC_OPS_APP_URL to redirect)
R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET, R2_ENDPOINTLegal vault object storage
LEGAL_VAULT_LOCAL_DIRFallback dir (default ./data/legal-vault, gitignored) when R2 is not configured

Seeded operator (dev only) ​

When the SoR seeder runs (Seed:RunOnStartup=true): ops@locus.platform / Locus@2026!Seed. Off in production by default (Seed__RunOnStartup=false). Do not use this credential outside a throwaway environment.

Tests ​

locus-ops/tests/ (bun test): ops_app_scaffold, legal_vault_watchtower, document_fetcher_and_vault_resolve, legal_vault_amendments_applicability_fixtures, vault_publisher_extract_findings, market_ingest_and_change_detector.

UI tours ​

Ops overview · Regulatory admin · Access requests · Market ingest UI.

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