Skip to content

The release gate that turned a green pipeline into a real promise

A layered automated test suite and a CI release gate replaced a shipping process built on crossed fingers, and a flake-cleanup pass made a red result mean something again.

SievemarkFMFernwick Mutual/ policy-platform
mainAP
release-gate on #2291

feat: multi-vehicle discount on renewal quotes · r.lindqvist · merged to main 09/17/2026, 10:42 AM

Run logDeploy #2291

Gate passed · deploy allowed

Every criterion was evaluated on this commit. Nothing re-run; no quarantined test counted.

Required check · no override3fa92c7 · run 1 of 1

Criteria the gate checked

All must pass · evaluated in order
Unit suitesVitest · premium edge cases, rating, instalments1,284 passed · 0 failedPass
Integration, real PostgresTest containers · Postgres 16 started per run212 passed · 0 failedPass
Flows: quote → bind → invoicePlaywright · full stack, seeded38 passed · 0 failedPass
Flake quarantine2 quarantined tests excluded · none new on this buildNo new intermittentsPass
Risk-tier coverageChanged files: quoting/premium-calc, quoting/rating-factorsAt or over tier-1 targetPass

Run for #2291

GitHub Actions
Build image3fa92c7 · policy-platform
Unit1,284 tests
Integration212 tests · Postgres container
Flows38 tests · Playwright
release-gateRequired check · 5 of 5 criteria
Deploy to productionUnblocked by release-gateReady

Since the gate went in

Recorded against the months before it
Regressions caught pre-release

0 → 14

First month · held by week
408/10308/17508/24208/31
Flaky test rate

31% → <2%

Runs failing with no code change · rolling two weeks
Manual pre-release click-through

Retired

The gate is the release decision now

What the engagement involved

Industry
Professional Services
Duration
6 weeks
Cooperation model
Fixed price
Services
Test suite designRelease gatesFlake management
Integrations
HubSpotDocuSignXeroGoogle Workspace
Technologies
PlaywrightVitestTypeScriptGitHub ActionsTest containersCoverage reporting
Team
1 Project lead2 Frontend engineers1 Backend engineer

Client name withheld under NDA. Engagement details are shown to the extent our agreement permits.

The problem

What went wrong, and when

The team had tried automated tests twice before, and both suites had rotted into a wall of flaky failures everyone learned to ignore. By the time we arrived, "run the tests" had stopped meaning anything.

Both previous suites had chased a coverage percentage, which spreads effort evenly across code whose failure costs are anything but even. They were also slow and intermittent, so a red build got re-run instead of investigated. By the time we arrived, "run the tests" had stopped carrying information, and the release decision had quietly reverted to whoever was on call.

We mapped the quoting and billing paths that would hurt most if they broke, built a layered suite of unit, integration, and full-flow tests around them, wired a CI release gate that blocks a red build from shipping, and quarantined or fixed every flaky test until green reliably meant safe.

Process

Phase by phase

  1. Phase 1: Map risk

    What hurts if it breaks

    Mapped the quoting and billing flows carrying the most regression risk, and deliberately left coverage elsewhere thin.

    • Risk map
    • Coverage targets
  2. Phase 2: Build layers

    Unit to full flow

    Built layered unit, integration, and end-to-end tests aimed at those specific flows.

    • Test suite
    • Fixtures & factories
  3. Phase 3: Gate

    Blocking, not advisory

    Wired a CI release gate that blocks a failing build from deploying at all.

    • Release gate
    • CI configuration
  4. Phase 4: De-flake

    Make red mean red

    Audited the existing suite, quarantined flaky tests, and fixed their root causes instead of retrying them.

    • Flake audit
    • Quarantine list
    • Root-cause fixes
SievemarkFMFernwick Mutual/ policy-platform
mainAP
Suites

Every suite, its layer, where it runs and what it costs to run · wall time drawn relative to the slowest suite

Runner config
UnitVitest · In processThe premium calculation's edge cases1,284tests
IntegrationVitest + Test containers · Real Postgres 16 per runWrites against a real database, with no agreeable mock212tests
FlowPlaywright · Full stack, seededQuote, bind and invoice, end to end38tests

All suites

16 suites · 1,534 tests
SuiteLayerRisk tierTestsRuns onWall time (relative)In gate
quoting/premium-calcUnit1412Every pushRequired
quoting/rating-factorsUnit1286Every pushRequired
billing/instalmentsUnit1174Every pushRequired
billing/refundsUnit196Every pushRequired
policy/endorsementsUnit288Every pushRequired
quoting/quote-documentsUnit264Every pushRequired
admin + reporting (peripheral)Unit3164Every pushRequired
policy/bind-transactionIntegration158Every PRRequired
billing/ledgerIntegration164Every PRRequired
billing/instalmentsIntegration136Every PRRequired
renewals/noticesIntegration228Every PRRequired
broker/quote-apiIntegration226Every PRRequired
flow/quote-bind-invoiceFlow114Every PRRequired
flow/mid-term-endorsementFlow19Every PRRequired
flow/renewalFlow28Every PRRequired
flow/broker-quoteFlow27Every PRRequired
On screen

Every suite, its layer and what it costs to run: Vitest units, integration against Postgres in test containers, and Playwright flows through quote, bind and invoice.

The numbers, before and after

0 → 14 in first month

Regressions caught pre-release

31% → under 2%

Flaky test rate

Retired

Manual pre-release click-through

Regressions caught pre-release is the count in the first month after the gate went live, against zero before, because there was no mechanism to catch them. Flaky rate is the share of runs failing without a code change, measured over rolling two-week windows. The manual click-through is retired, which is a state, not a measurement.

Client name withheld under NDA. Figures are approximate, drawn from the engagement’s own reporting.

Introduction

The engagement

Quote and billing changes shipped with no automated coverage at all. Every release rode on a manual click-through and whichever bugs the on-call engineer happened to notice first.

An insurance platform where quoting and billing changes shipped on a manual click-through, and where two previous automated suites had already rotted into walls of flaky failures the team had learned to ignore. So the engagement had two jobs, and the second (making green mean something again) was the harder and the more important.

Quality Engineering

The solution

How it was handled

  1. 01

    Mapped the quoting and billing flows carrying the most regression risk

    The business signed off on the risk map, since what a failure costs is a commercial call first and a technical one second.

  2. 02

    Built layered unit, integration, and end-to-end tests targeted at those flows

    Three layers, each doing what it's good at: units for the premium edge cases, integration against a real database, and full flows through quote, bind and invoice.

  3. 03

    Wired a CI release gate that blocks a failing build from deploying

    The gate went live only after the flaky rate was under control, because a blocking check on an unreliable suite would have been overridden within a week.

  4. 04

    Audited the existing suite, quarantined flaky tests, and fixed their root causes

    Every intermittent test was quarantined out of the gate on first sighting and root-caused, which is what made the pass informative again.

Risk-targeted coverage

Tests concentrated on the quoting and billing paths that would hurt most if they broke.

Coverage was aimed, not maximized. The quoting and billing paths were mapped for what a failure would actually cost (a wrong premium, a double charge, a policy issued without payment), and tests were concentrated there. Peripheral code has thin coverage on purpose: a suite that treats every line as equally important takes hours to run and tells you nothing about risk.

What shipped
  • Failure cost mapped before a single test was written
  • Depth concentrated on quoting and billing
  • Peripheral code deliberately thin, so the suite stays fast
SievemarkFMFernwick Mutual/ policy-platform
mainAP
Coverage by risk

Targets set per tier from the risk map · peripheral code has no floor on purpose

Risk mapExport report

Risk map

Agreed with the business
1Moves moneyTarget 90%

If it breaks: a wrong premium, a double charge, a policy issued without payment.

6 modules1 under target
2Customer-visibleTarget 70%

If it breaks: quote documents, endorsements, renewal notices, the broker quote api.

5 modules2 under target
3PeripheralNo floor

If it breaks: admin settings, reporting exports, the feature-flag console.

3 modulesThin by design

Depth goes where a failure costs money. A suite that treats every line as equally important takes hours and says nothing about risk.

Coverage by module

Line coverage · main @ 3fa92c7
3 modules under targetbilling/refundsrenewals/noticespolicy/endorsements
ModuleTierLayersCoverage vs targetLineStatus
quoting/premium-calc1UF96%At target
billing/instalments1UIF94%At target
quoting/rating-factors1U93%At target
policy/bind1IF92%At target
billing/payment-capture1IF91%At target
billing/refunds1U84%Under 90%
quoting/quote-documents2U78%At target
billing/dunning-notices2UI72%At target
broker/quote-api2IF71%At target
renewals/notices2I66%Under 70%
policy/endorsements2UF64%Under 70%
admin/user-settings3U31%No floor
reporting/exports3U22%No floor
flags/console3—12%No floor
On screen

Coverage by module against a risk map agreed with the business: modules that move money held to the deepest target, peripheral code deliberately thin, and the three modules sitting under their threshold.

SievemarkFMFernwick Mutual/ policy-platform
mainAP
#2294 fix: round instalments per schedule

Pull request by d.okafor · 8c41f0e · 09/16/2026, 3:18 PM

Open PRDeploy held

Gate failed · deploy held

1 integration test failed on a tier-1 module. release-gate is a required check, so this build can't reach production.

Required · admins includedOverride: not available

Failing test

First failure on this commit · not re-run
Integrationsplits an annual premium into 12 installments that sum to itbilling/instalments.int.test.ts · postgres:16 containerFailed
AssertionErrorinstalments.int.test.ts:48
46const policy = await bindPolicy(db, quote({ annualPremium: "1283.99" }))
47const schedule = await db.instalments.forPolicy(policy.id)
48expect(sum(schedule.map((i) => i.amount))).toBe(policy.annualPremium)
Expected "1283.99"the annual premium
Received "1283.88"12 × 106.99 · $0.11 never billed
Change in this PRbilling/instalments/schedule.ts
21export function splitPremium(annual: Money, count: number): Money[] {
22 const each = floorCents(annual.div(count))
23 const parts = Array.from({ length: count }, () => each)
24- const remainder = annual.minus(each.times(count))
25- parts[count - 1] = parts[count - 1].plus(remainder)
24+ // rounding handled per instalment now
25 return parts

Criteria on #2294

release-gate
Unit suites1,284 passed
Integration, real Postgres1 failed · 211 passed
Flows: quote → bind → invoice38 passed
Flake quarantineTest not quarantined · counts
Risk-tier coveragebilling/instalments at target
Deploy to production

Held

Waiting onrelease-gate (required)
Branch protectionAdmins included
OverrideNone — fix and push
QuarantineNot applicable: not intermittent
On screen

A red build: an integration test against real Postgres finds twelve installments that no longer sum to the premium, the change that caused it, and a deploy held by a required check with no override.

A gate that blocks

A red build can't deploy. No noting it and overriding it.

Three layers, each doing what it's good at: Vitest units for the premium calculation's edge cases, integration tests against a real Postgres in a test container (no mock that agrees with itself), and Playwright flows through quote, bind and invoice. The gate is a required check, so a red build can't deploy. Before, it was a status somebody could override, and did.

What shipped
  • Units, container-backed integration, and full Playwright flows
  • Integration tested against real Postgres
  • Required check: a red build can't be overridden
SievemarkFMFernwick Mutual/ policy-platform
mainAP
Flake register

Every intermittent test: quarantined out of the gate on first sighting, ticketed, root-caused, then fixed or deleted

Open tickets
Flaky test rate · rolling two weeks

31% → <2%

Now under 2% of runs
In quarantine now

2

Excluded from the gate · ticketed
Released from the register

23

17 fixed · 6 deleted
Reruns to green

0

The gate never retries a failure

Register

25 entries · most recent first
IDTestLayerFirst seenRoot causeTicketState
FLK-025renewals › notice queued once per termIntegration09/14/2026InvestigatingQA-318Quarantined
FLK-024broker quote › shows referral bannerFlow09/03/2026InvestigatingQA-311Quarantined
FLK-023bind › issues policy after capture settlesFlow08/05/2026Unawaited async workQA-284Fixed
FLK-022ledger › refund posts reversing entryIntegration08/04/2026Shared fixture stateQA-281Fixed
FLK-021instalments › first due date on month endUnit08/03/2026Wall-clock / time zoneQA-279Fixed
FLK-019quote › recalculates on vehicle changeFlow07/30/2026Unawaited async workQA-270Fixed
FLK-017admin › audit table paginatesFlow07/28/2026Unawaited async workQA-266Deleted
FLK-014ledger › daily totals by carrierIntegration07/23/2026Shared fixture stateQA-259Fixed
FLK-012payment capture › retries declined cardIntegration07/21/2026Unawaited async workQA-255Fixed
FLK-009endorsement › prorates mid-term changeUnit07/16/2026Depends on test orderQA-247Fixed
FLK-006reporting › export includes cancelledUnit07/13/2026Shared fixture stateQA-240Deleted
FLK-003quote PDF › renders premium breakdownFlow07/09/2026Unawaited async workQA-233Fixed
FLK-001rating › territory factor for new ZIPUnit07/07/2026Shared fixture stateQA-229Fixed

Root causes

23 released entries
Unawaited async work11
Shared fixture state8
Wall-clock / time zone2
Depends on test order2
Lifecycle of an entry
First sightingFailed with no code change
QuarantinedOut of the gate the same day
TicketedOwned, never left to rerun
Root-causedWhy it was intermittent
Fixed or deletedOnly then back in the gate

Green that means something

On screen

The flake register: each intermittent test quarantined out of the gate on first sighting, ticketed, root-caused (mostly unawaited async and shared fixtures), then fixed or deleted.

Flaky tests quarantined and root-caused, so a pass is informative again.

Flakiness was treated as a defect, never as weather. Every intermittent test was quarantined out of the gate immediately, root-caused, and either fixed or deleted; most were racing on unawaited async work or on shared fixture state. A suite that fails one time in ten teaches a team to re-run it, and a gate nobody believes stops being a gate.

What shipped
  • Flaky tests quarantined out of the gate on first sighting
  • Root-caused to unawaited async and shared fixtures
  • Fixed or deleted, never left rerunning until green

Working inside their operation

  1. 01

    A cross-functional team of 3 worked on a fixed price basis over 6 weeks, covering Test suite design, Release gates, Flake management. We held a standing mid-week checkpoint and wrote decisions down in place of status meetings. Nothing shipped until they'd seen it working.

    Six weeks, fixed price, with the risk map produced first and agreed with the business, because what a failure costs is a commercial question. Flakiness was treated as a defect from the first sighting: quarantined out of the gate immediately, root-caused, then fixed or deleted, never left re-running until green.

What it settled

What changed in the runbook

  • Coverage percentage was the wrong target. The risk map was the right one.

    Coverage percentages treat every line as equally important, which is false and expensive. The risk map put depth where a failure costs money and left the rest deliberately thin.

  • A flaky suite is worse than no suite: it teaches the team to ignore a red build.

    A suite that fails one time in ten teaches a team to re-run instead of investigate, and after that, only removing the flakiness can repair it. More tests won't.

  • The gate only had authority because the tests had stopped lying.

    The gate is a social object as much as a technical one. It only holds because the team believes a red build means something, and that belief is what the flake work bought.

Three builds, one gate

Why one build ships, one is held, and one flaky failure doesn’t count

Pick a build and the gate checks its suites, the flake quarantine and risk-tier coverage, then open any criterion to see why it passed or held. Switch tabs, or use the arrow keys once one is focused.

A build at the gate

#2294 · release-gate · required checkfix: round instalments per schedule Deploy held on suites
1 integration test failedHold

Against a real Postgres, twelve instalments of $106.99 sum to $1,283.88, not the $1,283.99 premium: the PR dropped the remainder. A mock that agreed with itself would have passed. The check is required, so there's no override. Fix and push.

Unit1,284 passed
Integration211 passed · 1 failed
Flow38 passed
Bars on a log scale so the 38 flows stay visible beside 1,284 units. Failing: billing/instalments.int.test.ts
Replay pacing is illustrative, not a measured run. Test counts and coverage readings are drawn for this demo.
Architecture

From a pull request to a deploy the gate has allowed

The quarantine sits in front of the gate on purpose: noise is taken out before the result is read, so what stays red is a real regression and what stays green is safe to ship.

  1. 01 · Trigger
    Pull request on GitHub ActionsEvery quoting and billing change runs the suite; nothing ships on a manual click-through anymore.
  2. 02 · Suites
    Vitest · Test containers · PlaywrightUnits for premium edge cases, integration against a real Postgres, flows through quote, bind and invoice.
  3. 03 · Quarantine
    Flake registerA test failing without a code change leaves the gate on first sighting, is root-caused, then fixed or deleted. It's never re-run until green.
  4. 04 · Gate
    Required release checkA red build can't deploy. The check is required, so nobody can note it and override it.
  5. 05 · Deliver
    Deploy on green onlyThe deploy runs only once the gate passes, and the gate went live only after the flaky rate was under control.

So a billing mistake stays in CI

A gate the team believes

A red build cannot deploy

The gate is a required check, so a failing build is held. The old status let people note a failure and override it, and they did.

Tested against a real database

Integration tests run against a real Postgres in a test container, and Playwright flows go through quote, bind and invoice, so billing is never checked by a mock that agrees with itself.

Nobody learns to re-run

A flaky test is quarantined out of the gate on first sighting, root-caused, then fixed or deleted. With the flaky rate down from 31% to under 2%, a red result gets investigated instead of retried.

Does a green build actually mean your release is safe? Scope your build in 3 minutes.

Scope your build
Have a project?

Let's talk

Running a large platform, shaping a first MVP, or getting a product ready for a funding round? Tell us where you are. We'll shape the process around it, and stay with you after launch.