Skip to main content

ADR-0181: Navin Enterprise Insurance Broker and Distribution Agent, brokerage product layer on top of the Control Plane

  • Status: proposed (2026-11-04)
  • Deciders: Navin Enterprise AI platform team, mission UC-05 (agent_uses_cases/05_Assurance_Courtier_Distribution.md)
  • References: decision of 2026-11-04 "five insurance sector agents built in parallel" (.navin/continuity/DECISIONS.md), ADR-0175 (RAG product), ADR-0176 (Banking Back-Office, pattern of copied generic modules), ADR-0178 and ADR-0179 (sibling insurance agents), audit navin-enterprise-assurance-courtier-distribution/docs/en/NAVIN_INTEGRATION_AUDIT.md, contract contracts/v1/agent-product-manifest.schema.json, OPA package eai.assurance_courtier (deploy/policies/opa/assurance_courtier.rego), contracts contracts/v1/assurance-courtier-distribution-copilot-event.schema.json and contracts/v1/assurance-courtier-distribution-action-request.schema.json, Manager template navin-assurance-courtier-distribution-agent (published: false).

Context​

A broker or a distribution network wants to compare offers from several insurers against the declared needs of a customer, prepare a documented advice file, pass the application to the chosen insurer and follow the commissions, without ever letting a language model choose, bind, place or advise instead of an authorised human. The brief requires seven modules (needs and mandate, panel and authorities, quote requests, offer normalisation, explainable comparison and ranking, advice and traceability, application file and follow-up), an explicit server-side state machine with 409 on every forbidden transition, Decimal amounts per currency without ever summing two currencies, and an absolute prohibition: no computed or invented premium, no assumed issuance status, no commission paid from this application.

The platform already provides identity, OpenFGA, OPA, the Action Gateway with maker-checker, durable jobs, the document pipeline (ClamAV, YARA-X, Docling behind the Control Plane), RAG, memory and observability (ADR-0176). No real insurer connector is wired in the repository: connectors are exercised in a deterministic sandbox.

Decision​

1. The model proposes, the human decides, the source confirms​

The comparison (offer_comparisons) is a deterministic versioned engine (engine_version, ruleset_version, input_snapshot_hash) over the quotes that are valid for one needs version and one explicit priority scenario; a coverage absent from the source document is unknown, never covered. The advice record (advice_records) stays draft until a competent human other than its preparer validates it (database constraint validated_by_other_human, OPA rule advice_not_validated_by_other_human, OpenFGA relation can_validate ... but not preparer). The client choice, the insurer agreement and the effective date are three distinct dated events; the issuance status (broker_submissions.issuance_status) is what the insurer answered, unknown_pending_confirmation until it did.

2. Generic modules copied, business modules written​

As in ADR-0178 and ADR-0179: the generic modules of the UC-00 backend (config, errors, i18n, authz, audit, jobs, worker, controlplane, db/session, Action Gateway, connectors, observability, document pipeline, copilot, ops, api/common, app) are copied into navin_courtier with no change other than the package name, the schema (courtier), the prefix (/api/v1/assurance-courtier-distribution) and the OPA package. The seventeen business tables of section 18.1 are written in migration 0001_foundation with tenant-then-project RLS, composite (tenant_id, parent_id) foreign keys and CHECK constraints on statuses, ISO currencies and amounts carrying their currency.

3. Prices and statuses: only what a source answered​

insurance_quotes.premium_amount is the insurer's value (source_kind = insurer_api) or a human's with evidence (manual_with_evidence, document required), dated (priced_at, validity_end) and bound to the terms hash (terms_hash). broker_profiles.licence_status is the answer of the official registry through a licence_registry connector; not_checked and unknown never mean valid, and the constraint active_requires_valid_licence forbids activation without a valid licence. Commission statements are reconciled per currency; expected and received are kept apart, a difference opens a dispute, never a payment.

4. Delegation matrix and separation of duties in OPA​

The package eai.assurance_courtier follows the fail-closed pattern of the sibling products: sixteen operation types, preparing and approving roles as data (eai.config.assurance_courtier), a mandatory second human for broker_activate, broker_suspend, manual_quote_record, advice_validate, application_submit, commission_dispute_open, portfolio_transfer and rule_change, minimal authority per operation and urgency, dated revocable delegations, rail always none (a broker never moves money), applies true for the courtier.* and document.* actions only. The obligations announce proposal_not_advice and unknown_pending_confirmation.

5. Personal data and minimisation​

Quote requests carry a minimised payload (minimised_payload) and its hash; the customer identity is sent to an insurer only when the mandate allows it (broker_mandates.allows_data_sharing). The copilot receives sourced facts only; memory keeps no health or personal situation data.

Consequences​

  • Positive: six acceptance scenarios runnable through the real HTTP API, tested policies (opa test, fga model test), indexed and validated contracts, per-currency indicators honestly empty without data.
  • Negative: the Manager template stays an unpublished draft; the insurer, registry, CRM and e-signature connectors are exercised only in a deterministic sandbox until a source system is wired; the pdf, docx and xlsx report formats stay unavailable (csv and json only); no frontend is delivered by this mission (ADR-0180).