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), auditnavin-enterprise-assurance-courtier-distribution/docs/en/NAVIN_INTEGRATION_AUDIT.md, contractcontracts/v1/agent-product-manifest.schema.json, OPA packageeai.assurance_courtier(deploy/policies/opa/assurance_courtier.rego), contractscontracts/v1/assurance-courtier-distribution-copilot-event.schema.jsonandcontracts/v1/assurance-courtier-distribution-action-request.schema.json, Manager templatenavin-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).