Skip to content

Regulatory analyze ​

The workbench is a CEMAC due-diligence engine, not a single-country tool. Congo (CG) is the default jurisdiction; SEEG (Gabon) is a showcase dataset. All checks are deterministic (no LLM).

Two layers must not be confused:

LayerOwnerWhat it is
Catalog (regulatory_engine/rules/catalog.ts)code (versioned Regulation corpus)Rules evaluated by analyzeDeal; always available, offline-safe
Vault (LegalInstruments / Editions / Provisions / SourceArtifacts)platform (locus-ops)Reviewed statute PDFs + provision text; overlaid onto evidence, never a replacement for catalog evaluation yet
text
analyzeDeal(project)                  ← catalog + deal evidence → findings/checks/coverage
        │
        ▼ (server only, with session)
analyzeDealWithVault(project, ctx)
   1. report = analyzeDeal(project)
   2. enrichRegulatoryEvidenceWithVault → GET /api/legal-vault/resolve per evidence item
   3. replaceRegulatoryFindingsInSor → PUT /api/deals/{dealId}/regulatory-findings
                                        → DossierRegulatoryFindings (Vault* ids)

Jurisdiction layer (technical documentation §6) ​

ModuleRole
src/jurisdiction/countries.ts (CEMAC registry, default CG), framework-map.ts (authorities, transaction types), resolver.ts (resolveDealProfile)
src/regulatory_engine/rules/catalog.ts (versioned Regulation corpus), versioning/ (effective-date logic), applicability/ (which rules apply), citations/, analyze.ts (typed Findings, checks, coverage), memo.ts (regulatory-basis memo section)
src/types/{jurisdiction,regulation,finding}.tsCEMACCountry, DealJurisdictionProfile, Regulation, Finding / FindingType

Resolution flow: deal profile (primary + involved jurisdictions, transaction type, public offering / cross-border / government-related) → regional layer (OHADA, SYSCOHADA, CEMAC, COSUMAF, BEAC, COBAC) + national layer of every involved country → filtered by the transaction date (effectiveFrom / effectiveTo) and applicability conditions (temporal applicability engine: excludes repealed articles and cites gazette volume, article and SHA-256 checksum).

Findings are classified and always carry jurisdiction, framework, source, evidence and confidence:

FindingType
document_contradiction, financial_inconsistency, contractual_breach, regulatory_issue, regulatory_gap, missing_document, legal_risk, calculation_issue

Mapping from contradiction alerts: topic INTEREST_RATE|MATURITY_DATE|COVENANT → document_contradiction, EBITDA_DEFINITION → financial_inconsistency, NEGATIVE_PLEDGE → contractual_breach; severity CRITICAL → HIGH, MAJOR → MEDIUM, MINOR → LOW.

Known limits: regional entries are catalogued by institutional reference only (no article text loaded); national corpora are pending placeholders reported as regulatory_gap. All references must be validated by counsel before use in a credit committee.

Client path ​

analyzeDeal(project) in regulatory_engine/analyze.ts (domain twin in domains/regulatory/analyze.ts kept in sync) — findings, checks, curation stats from catalog + deal evidence.

RegulatoryReport:

FieldMeaning
regulatoryresolved RegulatoryProfile
findingsFinding[] with regulatoryEvidence[] and dealEvidence[]
checksDealCheck[]
coverageround(catalogued / applicable × 100) (0 when no rule applies)
frameworkCurationper-authority FrameworkCurationStat (human_verified / machine_validated / source_verified / pending)
incompleteNationalWarningnational corpus pending
counts{ issues, questions, verified, highSeverity }

Server path ​

analyzeDealWithVault(project, sessionCtx):

  1. Run analyzeDeal
  2. Overlay vault IDs via enrichRegulatoryEvidenceWithVault
  3. Optionally replaceRegulatoryFindingsInSor → DossierRegulatoryFindings
mermaid
sequenceDiagram
    autonumber
    participant S as Server (BFF / locus-server)
    participant E as analyzeDeal
    participant V as /api/legal-vault/resolve
    participant R as PUT /api/deals/{dealId}/regulatory-findings
    S->>E: project
    E-->>S: report{findings, checks, coverage}
    loop each regulatoryEvidence item
        S->>V: instrumentId=item.regulationId, provisionNumber=item.article
        V-->>S: editionId, provisionId, artifactId, sha256, provisionText, pageStart
    end
    S->>S: item += vaultEditionId, vaultProvisionId, vaultArtifactId, vaultSha256, page, statutoryExcerpt, openStatuteHref
    S->>R: replace-all {findings[]} (findingKey = f.id)

Overlay rules (vault_enrich.ts):

RuleDetail
Fail-softresolveVaultProvision returns null when the SoR is unreachable or !res.ok → catalog text kept
Fields addedvaultEditionId, vaultProvisionId, vaultArtifactId, vaultSha256, page (from pageStart), statutoryExcerpt (from provisionText)
Open statuteopenStatuteHref = /api/locus/legal-vault/artifacts/{artifactId}/content when an artifact exists
Evaluation sourceCatalog text remains the evaluation source until vault text is legal_reviewed
PersistencePUT is replace-all and idempotent per run; findingKey = finding id; primary evidence supplies regulationId, articleNumber, statutoryExcerpt, Vault* ids; full evidence in EvidenceJson

Persisted columns: Legal vault & Watchtower → DossierRegulatoryFindings. Read back with GET /api/deals/{dealId}/regulatory-findings (regulatory:read), BFF /api/locus/deals/[id]/regulatory-findings.

Heuristics (slice 34) ​

  • BEAC Art. 42: non-XAF currency or data-room wording (EUR/USD, emprunt extérieur, devise).
  • COSUMAF APE: deal type + title tags + investor/listing keywords vs private-placement language.

Implementation notes (analyze.ts):

HeuristicSignals
BEAC Règlementation des Changes Art. 42 (beac-reg-changes-2018, "Article 42")FOREIGN_LOAN_SIGNAL: EUR/USD/GBP/CHF, euros, dollars US, emprunt extérieur / en devises, prêt/financement/dette extérieure, devise(s), foreign-currency, external loan/borrowing/debt in deal-room page titles/snippets/summaries — triggers finding F-fx (regulatory_issue, severity LOW, status question) when the profile currency ≠ XAF or a document mentions foreign/external borrowing, even if the declared profile currency is XAF
COSUMAF APE vs private placementPRIVATE_PLACEMENT language (investisseurs qualifiés/institutionnels, placement privé, information memorandum) and title tags vs PUBLIC_OFFER_SIGNAL (appel public, offre au public, prospectus, note d'information, visa COSUMAF, admission à la cote, BVMAC, listing, retail); conflict = private language alongside a declared APE, or — for bond_issuance — alongside public/listing vocabulary even when no APE is declared

Evidence helper findDocEvidence is fail-soft: malformed documents yield null, never an exception; citations are cite_reg_{docId}_{page} and reuse the page bbox from the deal room (no invented coordinates).

Applicability ​

LegalProvisionApplicability CRUD in ops (GET/POST /api/admin/regulatory/applicability, PUT/DELETE …/{id}); bootstrap seeds imported rows. Not yet consumed inside analyzeDeal (deferred).

Bootstrap seeds (LegalVaultBootstrap.EnsureApplicabilityAsync, id app_{provisionId}):

Provision idTransactionsFlagsNote
cosumaf-rg-2014-art-4bond_issuance, equity_issuancepublicOfferingAPE qualification
cosumaf-rg-2014-art-28bond_issuance, equity_issuancepublicOfferingVisa / note d'information
cosumaf-inst-ape-2019-art-3bond_issuanceprivatePlacementPrivate-placement carve-out
beac-reg-changes-2018-art-42—foreignCurrency, externalBorrowingPrior declaration of external / foreign-currency borrowing
syscohada-audcif-2017-art-17——Balance sheet equilibrium (every SYSCOHADA statement)
ohada-aus-2010-art-138 / -140secured_loan, syndicated_loan, project_financeaccountPledgeAccount pledge enforceability / effects

Rows stay imported until ops verify; the seed is idempotent and never overwrites ops edits.

  • Prospectus pre-visa fee audit: 3.50 % statutory fee ceiling (> 3.50 % → VISA_PRE_REJECTED, ≤ 3.50 % → VISA_PRE_APPROVED), cryptographically signed pre-visa attestation (REGULATORY_ATTESTATION_HMAC_SECRET).
  • Regulatory module cannot be disabled in PUT /api/organizations/current/entitlements.
  • Copilot check_regulatory_compliance evaluates catalog + deal payload; regulatory_corpus scope blocks financial calcs.
  • Workspace routes: regulatory, cosumaf-auditor (Analysis zone).

Tests: regulatory_engine.test.ts, regulatory_domain.test.ts, regulatory_and_precedents_persistence.test.ts, regulatory_admin_vault.test.ts, locus-workers/tests/test_regulatory_engine.py; ops: vault_publisher_extract_findings.test.ts.

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