• contact@verticalserve.com
Home / Engineering / Post 96
Engineering Blog · Post #96

Six Sources, One Gate, One Override Path: How InsightUW Refuses Bind Until Everything Is Resolved — and Records *Exactly* What Was Open When a Manager Said Bind Anyway

From "the UW clicked bind, the policy got created, three weeks later legal flagged that the master nondisclosure was still pending and we'd just bound a $40M D&O without it" to "bind() returns a 409 with six grouped blockers; the manager-override path requires every blocker guid plus a typed reason plus senior_uw role; the bind audit row carries blocking summary json so the next regulator can see the open count at the moment a human decided to override anyway" — through one aggregator function, one state machine, and the deliberate refusal to make is bound derivable from anywhere except Pas Message.send_status='sent' plus the bind transaction.


The Problem

A UW finishes the quote, the broker sends back "bind it." The UW clicks Bind. What should happen between click and policy in PAS?

In real shops, the answer is "depends." Blocked by an open referral? Sometimes. Blocked by a fac placement that hasn't found participants? Sometimes. Blocked by a missing TRIA election on a Property quote in California? Should be — but the UW didn't see the form. Blocked by 3 binding subjectivities? Definitely yes — except the AC swore Stuart said his loss runs were "in the mail." Blocked by an unresolved Legal review on the manuscript wording? Yes — except the manager override has been a verbal Slack approval since 2019.

The blocked-by-X list is a moving target across capability boundaries:

  • Cap #1 raises a severity=required Fac flag → that's a hard blocker until the placement workflow (cap #2) closes.
  • Cap #2's placement is non-terminal → that's a hard blocker until participants accept and Σshare_pct hits 100.
  • Pending referrals on the quote → blocker until cleared, escalated, or overridden (Quote cap #6 already had this gate for send; Bind needs the same for bind).
  • Open Legal review on the manuscript → blocker until approved (Quote cap #10 propagates Legal lifecycle, but bind has to read it).
  • Missing TRIA election on a TRIA-eligible LOB with U.S. exposure → blocker until offered/accepted/rejected/not_offered.
  • Open binding subjectivities → blocker until cleared/waived (subjectivities live on Quote.subjectivities_summary_json.binding_open).

The usual fixes don't fix:

  • Hardcode the gate in Rating Service.bind. Six call-outs into six different services, six places to keep in sync. New blocker source? Edit six files.
  • Read it from a denormalized counter. Each cap maintains its own counter on the quote — fine for the panel chip; not fine for the gate audit ("what was open at 3:47pm when Sarah clicked bind?"). Counters drift.
  • Async background job that "computes binding_open" every 5 minutes. The bind decision has to be transactional. A 5-minute lag means the UW sees "0 open" while a referral fires concurrently. Race condition with regulator-grade audit consequences.
  • Force every cap to call a central record blocker helper. Couples the caps to the gate; new blocker = invasive change to the source cap.

The root cause: the gate has to be synchronous, all-sources, single-decision — and the override has to be per-blocker, role-gated, and forever audit-visible.

The InsightUW Approach

One read-only aggregator function over six existing tables; one bind-request row that owns the lifecycle; one override path that requires every blocker guid by hand.

graph TD subgraph Sources["Six blocker sources (read-only at gate time)"] F1["Fac Tria Flag<br/>severity=required<br/>flag status=open"] F2["Fac Placement<br/>placement status NOT IN<br/>(bound,declined,cancelled)"] F3["Referral Queue<br/>referral status IN<br/>(pending,escalated)<br/>quote guid match"] F4["Legal Review<br/>review status NOT IN<br/>(approved,withdrawn,completed)<br/>entity guid match"] F5["Tria Election<br/>missing where<br/>lob ∈ Tria Eligible Lobs"] F6["Quote.<br/>subjectivities summary json<br/>.binding open > 0"] end subgraph Aggregator["compute blockers(quote guid)"] AGG["Read all six,<br/>build {source, guid,<br/>label, severity,<br/>detail, link path} list"] end subgraph Gate["bind at Allowed Transitions guard"] Check{"len(blockers) == 0?"} OK["Atomic txn:<br/>Policy.create<br/>Bind Request.bound<br/>cap #4/#5/#7/#8 hooks"] Reject["409 Bind blocked<br/>{blockers: [...],<br/>summary: {fac:1, legal:2, ...}}"] AGG --> Check Check -->|yes| OK Check -->|no| Reject end subgraph Override["bind with override — manager path"] Override In["actor role ∈<br/>{manager, senior uw, chief uw}<br/>+ override blocker guids[]<br/>+ override reason"] Verify{"All blocker guids<br/>present in override list?<br/>Legal-review override<br/>requires senior uw+?"} Override Ok["Bind Request stamps<br/>override blocker guids json<br/>override reason / by / role<br/>+ blocking summary json frozen<br/>+ proceed to bind"] Override Reject["409 missing overrides[]<br/>or role-gate failure"] Override In --> Verify Verify -->|yes| Override Ok Verify -->|no| Override Reject end F1 --> AGG F2 --> AGG F3 --> AGG F4 --> AGG F5 --> AGG F6 --> AGG

compute blockers — read once, decide once

The aggregator is one function. It reads the six tables in sequence, returns a flat list of blockers shaped:

Synthetic guids (<quote_guid>:tria_election_missing, <quote_guid>:subj_binding_open) make every blocker overrideable by guid even when the source is "absence of a row" rather than "presence of a row." The override doesn't care about the difference.

The function is read-only. It never writes; it never recomputes counters; it never side-effects. Calling it twice in a millisecond returns the same shape (modulo concurrent inserts in another transaction, which is the desired behavior — bind sees the most-current state).

One active request per quote

Bind Request is the lifecycle owner. State machine: requested → in_progress → ready_to_bind → bound, with on_hold / cancelled / declined branches. Service enforces "at most one ACTIVE request per quote" (active = requested / in_progress / ready_to_bind / on_hold). A second manual create returns 409; cap #9's auto-create-from-quote (broker-authorization-detected entrypoint) is idempotent — it returns the existing active request if one exists.

The transition guard table:

The transition method short-circuits on disallowed targets with the allowed list in the error message — UI never has to know the rules.

bind() refuses; bind with override accepts (with strings attached)

Two paths to bound:

  1. bind — happy path. Calls compute blockers. If len(blockers) == 0, runs the atomic transaction. If not, returns 409 + blockers payload.

  2. bind with override — manager path. Requires:
    - actor_role ∈ {manager, senior_uw, chief_uw} (else 403)
    - override_blocker_guids: List[str] covering every open blocker guid (else 400 with missing_overrides[])
    - override_reason: str non-empty (else 400)
    - Legal-review blockers additionally require senior_uw or chief_uw — manager alone is insufficient (Legal lifecycle is owned by Legal; senior UW + above are the only roles authorized to override Legal's hold)

On success, the bind request stamps:
- override blocker guids json — the JSON-list of blocker guids the override covered
- override reason — the typed text
- override by — the actor user_name
- override role — the role that overrode

Plus the standard blocking summary json (frozen) — {"fac_tria_flag": 1, "legal_review": 2, "subjectivity": 3} — so the audit row says exactly what was open at the moment a human decided to bind anyway.

Atomic bind transaction

Inside _bind, in a single db.commit():

  1. Policy created with source quote guid + source option set guid + source option label.
  2. Bind Request stamped with request_status='bound', bound at, bound by, resulting policy guid.
  3. Audit Entry for bind request bound with the override flag and summary.
  4. Five soft-import hooks fire in sequence; failures log warnings but do not roll back the bind:
    - cap #4 first-bind milestone detection
    - cap #5 broker activity state update
    - cap #7 PAS bound push (always) + conditionally PAS issued (if LOB's issuance_trigger='on_bound')
    - cap #8 validation/booking cadence schedule
    - cap #9 already populated broker authorized at/ref if the request came from authorization-confirm

The hooks are soft because their failure is recoverable (manual retry from the lifecycle panel, the milestone feed, the cadence panel) but the bind commitment itself is not — once the policy exists, downstream caps reconcile state asynchronously.

Worked Example: $40M D&O on Acme Industries, Bound Despite an Open Legal Review

QT-2026-DO-3819 is a primary-and-excess D&O program. Bound by Sarah (UW) at 3:47pm on a Friday because the broker authorization landed at 3:30pm with a "we need this in force by EOD or board reconvenes Monday" subject line.

Step 1 — Sarah clicks Bind

UI calls POST /uw_bind_request/bind-requests/<guid>/bind?actor=sarah.

Backend runs compute blockers:

Two blockers. bind returns:

UI shows a red "Bind blocked by 2 items" banner with each blocker grouped by source and a deep-link.

Step 2 — Sarah resolves what she can

She opens the AC subjectivity panel (cap #10's surface). One subjectivity is "loss runs received but not reviewed" — she opens, marks lifecycle progressed to cleared with notes "reviewed; clean four-year history." The other is "broker confirmation of audit committee composition" — already in evidence, AC marks cleared.

Subjectivity blocker drops to 0. But the Legal review on the manuscript is genuinely still open — Legal is reviewing the punitive-damages carve-back wording and won't clear before Tuesday.

Step 3 — Manager override request

Sarah escalates to her senior UW manager Marcus. Marcus reviews:
- Legal review status: in review since Wednesday; expected clearance Tuesday.
- The carve-back wording is identical to one Legal cleared two months ago on a parallel D&O.
- Broker authorization is real; binding-effective-Monday is a real customer commitment.

Marcus, who has the senior uw role, decides to override. UI calls bind-with-override:

Backend:
1. Verifies actor_role='senior_uw' is in {manager, senior_uw, chief_uw}
2. Verifies override_blocker_guids = ['lr-7a2c'] covers all open blocker guids — but wait, compute blockers is called again at override time. The subjectivity blocker has been resolved between Sarah's earlier call and Marcus's override; only lr-7a2c is still open.
3. Verifies the missing-overrides set is empty.
4. Verifies that since lr-7a2c.source == 'legal_review' and actor_role == 'senior_uw' (not just manager), the legal-override role gate passes.
5. Atomically: creates Policy (P-2026-A8F4C2), transitions Bind Request to bound, stamps override_blocker_guids_json='["lr-7a2c"]', override_reason=..., override_by='marcus', override_role='senior_uw', frozen blocking_summary_json='{"legal_review": 1}'.
6. Five hooks fire — first-bind detection (no, broker's been with us 4 years), activity state update (last_bind_at = now), PAS bound push queued, validation/booking cadence scheduled, audit entry written.
7. db.commit().

UI gets back {request, policy_guid: 'pol-...', policy_number: 'P-2026-A8F4C2', override: true}. Bind-request-detail shows the policy chip; lifecycle panel shows "PAS bound — pending" (sweeper will flip to "PAS bound ✓" within 3 minutes).

Step 4 — Tuesday: Legal formally clears

Legal review lr-7a2c reaches approved Tuesday morning. The bind already happened; the legal-clearance event is logged on Legal Review per its own lifecycle. No retroactive action needed on the bind — the audit row carries forever:

Twelve weeks later when a regulator audits the file, every byte of the override decision is on a single row. There's no Slack thread to find, no email to retrieve, no "what was open at 3:47pm Friday" guesswork. The override is the durable record.

What This Means for Underwriters

  1. The bind gate is one button. UW clicks Bind; the system enumerates blockers from six sources and either binds atomically or refuses with a grouped, deep-linked list.

  2. Manager override is by-design intentional. Three required inputs (role, per-blocker guids, reason) — no shortcuts. The role gate distinguishes manager from senior_uw from chief_uw because Legal-override is a different conversation than referral-override.

  3. At-most-one active request per quote. UI doesn't have to dedupe; service enforces. Cap #9 auto-create from authorization is idempotent — it returns the existing request rather than creating a duplicate.

  4. bound is terminal AND atomic AND policy-creating. Same db.commit() writes Policy, advances the bind request, fires five downstream hooks. There's never a window where the bind request says bound but Policy doesn't exist.

  5. Audit captures override forever. blocking summary json is frozen at gate time. override blocker guids json lists exactly what was waived. override_reason / by / role answer "who, why, with what authority." Twelve weeks later or twelve years later, the answer is on one row.

  6. on_hold has a sweeper. When a UW puts a bind request on hold pending broker confirmation with an on hold until timestamp, the 30-minute sweeper auto-clears expired holds back to in progress. UWs don't have to remember to come back.

  7. Cross-cap hooks are soft-import + try/except. Cap #7 PAS push fails because the connector is misconfigured? Bind still commits; PAS lifecycle panel shows "pending — retry available." Cap #4 milestone detection raises because of a stale broker_id? Bind still commits; warning logs. The bind decision is the durable commitment; downstream reconciliation is recoverable.

What's Next

Next: State Machines and Sticky Owners: How the Account Coordinator Inherits Subjectivities Without Losing Them on Reassign


Want to see how a six-source blocker aggregator, a manager override path, and a frozen audit row close the "what was open when we bound it anyway?" gap in your underwriting workstation? Request a demo.

See InsightUW run on your data

A 45-minute working session with a real broker email and your LOBs.

Request a demo