Skip to content

Access requests (ops) ​

Status: slice 34 · October 2026.

UI: locus-ops /access-requests (operator queue). Public submit and e-mail verification stay on the bank app; review, rejection, and provisioning are platform-operator actions on ops.

This page documents the onboarding state machine persisted in PostgreSQL by AccessRequestsController (/api/access-requests), the guards on submit, and what provisioning creates.

State machine ​

text
 public (bank)                                   operator (ops, system:manage)
┌───────────┐  verify link   ┌────────────────────┐  review   ┌──────────┐  provision  ┌─────────────┐
│ SUBMITTED │ ─────────────▶ │ COMPLIANCE_REVIEW  │ ────────▶ │ APPROVED │ ──────────▶ │ PROVISIONED │
└───────────┘  (≤ 24 h)      │ (EMAIL_VERIFIED →  │           └──────────┘             └─────────────┘
                              │  auto-advanced)    │  review
                              └────────────────────┘ ────────▶ ┌──────────┐
                                                                │ REJECTED │
                                                                └──────────┘
StateMeaningEntered by
SUBMITTEDForm accepted; verification e-mail sent; token valid 24 hPOST /api/access-requests (public)
EMAIL_VERIFIEDRequester proved control of the corporate mailboxTransient: verify immediately auto-advances to COMPLIANCE_REVIEW; still accepted as input to review
COMPLIANCE_REVIEWWaiting in the operator queueGET/POST /api/access-requests/verify
APPROVEDOperator accepted; edition fixed; tenant not yet createdPOST /{id}/review decision=APPROVE
REJECTEDOperator declined; reason storedPOST /{id}/review decision=REJECT
PROVISIONEDTenant, edition modules, and invited admin existPOST /{id}/provision

In short: pending (SUBMITTED → EMAIL_VERIFIED → COMPLIANCE_REVIEW) → APPROVED / REJECTED, then PROVISIONED for approved requests. For duplicate detection, "pending" means SUBMITTED, EMAIL_VERIFIED, COMPLIANCE_REVIEW, or APPROVED (an approved but unprovisioned request still blocks a new one for the same domain).

Illegal transitions return 409: reviewing a request that is not EMAIL_VERIFIED/COMPLIANCE_REVIEW → INVALID_STATUS_TRANSITION; provisioning anything other than APPROVED → NOT_APPROVED.

Submit guards (public, no session) ​

POST /api/access-requests (bank BFF /api/locus/access-requests) applies these checks in order:

GuardRuleError
Valid e-mailMust contain @400 INVALID_EMAIL
Webmail rejectionConsumer domains are refused: gmail.com, yahoo.com, hotmail.com, outlook.com, icloud.com, protonmail.com, mail.com, aol.com, gmx.com, yandex.com400 CONSUMER_EMAIL_REJECTED
TermstermsAccepted and advisoryDisclaimerAccepted both true400 TERMS_NOT_ACCEPTED
Rate limit5 submissions per 15 minutes per e-mail address (in-process sliding window)429 RATE_LIMIT_EXCEEDED
Active tenant on domainAn active Organization already has this CorporateDomain409 TENANT_ALREADY_PROVISIONED
Pending request on domainAnother request in a pending state for the same domain409 PENDING_REQUEST_EXISTS

On success the SoR stores the request (ar_… id, raw body in PayloadJson, RequestedEdition), generates a one-time token, stores only its SHA-256 hash with a 24-hour expiry, and sends the verification e-mail through the outbox/SMTP (/request-access/verify?token=… on App__PublicWebBaseUrl). When SMTP is not configured the response carries verificationToken as a development sink only.

Verify (public) ​

/request-access/verify calls GET or POST /api/access-requests/verify with the token. The SoR hashes it, rejects unknown (INVALID_TOKEN) or expired (TOKEN_EXPIRED) tokens, then sets the request to COMPLIANCE_REVIEW, records VerifiedAt, and clears the token. Re-verifying an already verified request returns alreadyVerified.

Operator review (ops) ​

Every review and provision call requires system:manage (PLATFORM_OPERATOR). Ops BFF routes: GET /api/access-requests, POST /api/access-requests/{id}/review, POST /api/access-requests/{id}/provision.

  1. Open /access-requests, select a request in COMPLIANCE_REVIEW.
  2. Check the institution against its COSUMAF/COBAC licence and national registry (the reviewer notes field is for this).
  3. Approve — optional approvedEdition (defaults to the requested edition, normalized). Audit event ACCESS_REQUEST_APPROVE.
  4. Reject — rejectionReason is stored; if blank the default is Institutional criteria not met.. Audit event ACCESS_REQUEST_REJECT.
  5. Reviewer id, timestamp, and notes are persisted on the row.

Provisioning ​

For an APPROVED request, Provisionner le tenant calls POST /{id}/provision, which:

  • refuses if an active organization already exists for the domain (409 TENANT_ALREADY_PROVISIONED);
  • creates the Organization (org_…): name from the institution, CorporateDomain, edition preset, PrimaryCountry, LicensedJurisdictions, EnabledModulesJson from EditionModuleCatalog for the approved edition, and a ProfileJson containing the requester as the first team member;
  • finds or creates the User for the requester e-mail;
  • creates a Membership with role INSTITUTION_ADMIN and status Invited, with an invite token (SHA-256 hashed, 72 h TTL);
  • sets the request to PROVISIONED and records the new OrganizationId;
  • e-mails the invite (/auth/accept-invite?token=…) and writes ACCESS_REQUEST_PROVISION to the audit chain.

The new tenant starts with an empty workspace. The admin sets a password and OTP through accept-invite, then creates dossiers. Edition presets: Securities, Credit, Asset Management, DFI, Sovereign, Legal, Enterprise.

Where each piece lives ​

StepAppRoute
Submit formbank locus-web/request-access → /api/locus/access-requests (public)
Verify linkbank locus-web/request-access/verify → /api/locus/access-requests/verify (public)
Queue, approve/reject, provisionlocus-ops/access-requests
Legacy bank pagebank locus-web/workspace/access-requests redirects to ops via NEXT_PUBLIC_OPS_APP_URL

Tests: onboarding_lifecycle.test.ts, access_request_production_api.test.ts.

Related: Ops overview · Auth & sessions · Trust boundaries · Security.

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