Matthew Meszaros and Devin AI
44bb752482
feat: keep campaign mailboxes sending around durable recovery holds and reconcile uncertain outcomes
...
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-10 04:20:12 +00:00
Matthew Meszaros
b66c98a4ca
feat: let one campaign fill a large sender pool by keeping sub-minute pacing in finalSlot and dividing the pool send interval in floating point
2026-10-09 05:58:14 +02:00
Devin AI
b1198cde97
feat: preserve known warmup history and conservative campaign execution projections and mailbox explanations on missing feedback with reply-backed ramp regressions
2026-10-07 15:37:33 +00:00
Devin AI
d090b8e021
feat: complete native and Cloud diagnostic consent immutable shared provider attempt accounting authorized bounded IMAP timing retries conservative feedback pacing and released-schema correction fixtures
2026-10-07 15:12:45 +00:00
Devin AI
8a45508e79
feat: enforce shared outbound capacity and directional diagnostic consent across campaign manual warmup and seed dispatch with transactional result outbox bounded Gmail Graph DKIM audited repair holds and legacy upgrade gates
2026-10-07 14:02:11 +00:00
Devin AI
8e53fc7027
feat: integrate warmup observation evidence canonical diagnostics send-result recovery cloud trust exact-parent lineage durable dispatch portability and legacy compatibility
2026-10-07 12:38:58 +00:00
Matthew Meszaros
a844c506a0
feat: make Warmbly Cloud warmup start and explain itself for linked mailboxes: apply the instance's warmup settings, start warmup on adopted mailboxes, read the warmup day mask Monday-first, report the last refused warmup send and the partner cap to the instance and dashboard, hand changed SMTP credentials to the cloud, refuse cleartext mailboxes at enrollment, and show cloud warmup with the warming animation and a clear Cloud marker
2026-10-01 03:01:36 -07:00
Matthew Meszaros
f88ecd7b4c
feat: rebuild the new campaign flow as Leads, Emails, Schedule and Review with a lead-list picker, the full step editor and a live launch plan, keep unlaunched campaigns as server drafts that reopen in the flow from the campaigns list and a Continue setup button, simulate POST /campaigns-estimate day by day under warmup graduation, warmup mail sharing the send spacing, other campaigns, health holds and follow-up waits, retire one-time emails (migration 000229 drops campaigns.kind, the list type filter and badge go), enlarge the step subject field, and lift the editor popovers above dialogs
2026-09-29 01:37:07 -07:00
Matthew Meszaros
8812cba439
feat: detect each contact's inbox provider from its domain's MX and SPF in a backend sweep, show it as a sortable Email provider column with the provider logo, filter and segment on it across contacts, campaign leads and segments, and use it for campaign ESP matching including Workspace and Microsoft 365 custom domains and the coverage panel's per-provider lead counts
2026-09-26 06:33:44 -07:00
Matthew Meszaros
de7a7c9748
feat: take back a replayed campaign pass the task queue refused and keep its dead letter, seed the next pass on a fresh context when the one after a failed hand-off could not be written, resume a campaign whose mailboxes lack a worker at the earliest reopening, and log that wait under its own daily line
2026-09-24 20:09:55 -07:00
Matthew Meszaros
a54e9a3a2f
feat: keep a campaign's pass chain running through every failure (a pass whose hand-off to a worker fails or that errors is followed by another a minute later instead of waiting on the reconciler, retries move their slot, dead-lettered passes replay through the campaign lock), pass over mailboxes no heartbeating worker holds (worker_id now loaded with the sender pool, shown as Reconnecting in the send plan), count a hand-off failure against the lead only when it is the lead's, and try up to 200 due leads per pass with a daily activity line when all are waiting
2026-09-24 19:56:14 -07:00
Matthew Meszaros
87a346f4ff
feat: wake a freshly seeded campaign chain (start, resume, schedule edit, reconciler re-seed) as soon as a lead is due instead of at the paced slot of the send after it, and let leads added to a waiting campaign pull its deferral recheck forward while leaving a sending tick's paced successor alone (scheduler.WakeSlot, CampaignRepository.LastTickSent)
2026-09-24 01:43:58 -07:00
Matthew Meszaros
145e9c45d7
feat: refresh the advisor on a status-only campaign PATCH, fall back to the whole pool under ESP prefer when no matching mailbox is past its min gap, and draw each candidate's behaviour gap once so the selection filter and the send enforce the same gap
2026-09-23 09:06:28 -07:00
Matthew Meszaros
d474fdc4b2
feat: count the all-active-mailboxes fallback in the advisor's campaign sender count, re-run the advisor when a campaign's senders, tags or status change, start a new lead from a mailbox whose min gap (warmup included) has elapsed instead of deferring the campaign, and scope the pre-start mailbox check to the organization
2026-09-23 08:43:06 -07:00
Matthew Meszaros
45c045a5bb
feat: give each workspace a timezone that mailbox warmup and campaign sending windows follow by default (organizations.timezone, migration 000198), let campaigns and mailboxes follow it or pin their own, add a Timezones control centre on Settings > Profile with inline per-campaign and per-mailbox zones, default new workspaces and the campaign wizard to the browser zone, expose effective_timezone on campaigns, and move the first-email delay from the Schedule tab and wizard into campaign Settings > First email
2026-09-22 20:13:55 -07:00
Matthew Meszaros
d667c1dacd
feat: keep the send plan honest at the edges: a behaviour mailbox reopening after the window closes is closed for the day, only a mailbox coming back on its own (spent, closed, spaced out) holds its bound leads as waiting, the min gap is charged from when the mailbox is next open, an end date later today ends the day there, explainCap is stagedCap's last stage so there is one clamp chain, last-send times and warmup health are read for the pool in one query each, the plan cache is keyed on the campaign's status and updated_at so a start or an edit is answered fresh, and the workspace posture row links to /app/deliverability
2026-09-19 09:58:52 -07:00
Matthew Meszaros
89daae3f29
feat: make the send plan count a lead bound to a spent mailbox as waiting for it (leads.waiting_on_sender), charge a behaviour profile's spent budget and hourly ceiling to the plan rather than to hours or spacing, fold a foreign-timezone mailbox's 8pm close into its pacing, report the UTC budget day and keep the waterfall adding up when a cap was lowered after sends, read the pool's sends today in one query and cache the plan and workspace capacity for a few seconds, share one cold-ramp notice builder between the drawer and the plan, let the wizard estimate survive a counter miss, and stop a malformed plan payload from taking the campaign overview down
2026-09-19 09:45:03 -07:00
Matthew Meszaros
150dc7df6e
feat: add a Today's sending plan to the campaign overview (GET /campaigns/:id/send-plan, derived through the scheduler's own gates: per-mailbox cap clamps, warmup graduation, health bands, other campaigns on the same mailbox, hours, spacing, window, plan allowance, new-lead cap and leads due, as a waterfall that adds up), feed the sidebar meter and the wizard estimate from the same clamps instead of summing configured caps, floor the campaign chain's next tick at the pool's spacing rather than one mailbox's whole gap, fold the compact Advisor strip to one line, add warmbly campaign plan and warmblyctl campaign plan, and document it (issue #606 )
2026-09-19 09:31:46 -07:00
Matthew Meszaros
e668a2a36b
feat: complete the ADA CASA v2.1.1 AL1 control set across authentication, sessions, access control, cryptography, input validation and configuration, adding a breached-password denylist and per-account login throttling, enforced multi-factor authentication on the admin panel, step-up confirmation before an action that mints a lasting credential, purpose-scoped session tokens, single-use TOTP steps, tenant verification on every cross-referenced identifier, security headers on every surface, encrypted webhook signing secrets, per-organization idempotency, PKCE and a minimal two-scope Gmail consent on the mailbox OAuth flow, bounded spreadsheet and archive decoding, a patched Go toolchain with govulncheck in CI, and the evidence pack under compliance/casa
2026-09-19 08:18:35 +02:00
Matthew Meszaros
d46cfad597
feat: check the warmup daily target again at the moment a send executes rather than only when the next one is placed, so a spam placement, health band or partner loss that cuts the target while a send is pending holds it as skipped_daily_limit and parks the chain at the next opening with a reply-back's aim intact, fail closed when that count cannot be read, share one target resolver between the placer and the send-time gate, add the skipped_org_suspended task status the suspended-workspace hold has written since #233 without a migration so its write stops failing and leaving the task pending for the dispatcher to re-fire, and anchor the ramp live fixture in UTC off the day boundary so its assertions no longer depend on the host timezone
2026-09-18 22:39:52 -07:00
Matthew Meszaros
7e6cbd6b1f
feat: count the send in flight on the last-email-of-the-day feed line so a cap-one mailbox is shown at its cap with a budget gate rather than at zero, and put live campaign progress in emails (contacts times steps) with a total_emails field so a six-step campaign no longer reads 100% once every contact has had its first email
2026-09-18 13:54:12 +02:00
Matthew Meszaros
52cdf829d6
feat: say a campaign is sending its last email of the day only when the whole mailbox pool has one send left, not just the provider-matched subset ESP matching narrowed it to
2026-09-18 13:18:40 +02:00
Matthew Meszaros
5b96ce903b
feat: count campaign progress from each table on its own so one sent email is no longer multiplied by leads times steps into 378 of 63 contacts at 100%, show the live Sending card only while a named contact's email is in flight and close it on EMAIL_SENT instead of leaving Sending Unknown contact up all day, and write the day's last send and every budget-spent line to the campaign feed with a per-mailbox breakdown of the cap, the clamp that set it, sends today, the gate and warmup health band so a campaign sending one email a morning says why
2026-09-18 13:12:07 +02:00
Matthew Meszaros
195f6bf2cd
feat: add a live scheduler test proving an instant branch's target is sendable through CalculateNextCampaignTime and the contact preview without its step wait_after, and that opting the branch out restores the wait
2026-09-18 02:23:25 -07:00
tunglambk
bae46914d5
feat: stop applying a target step's wait_after when an instant conditional branch routes to it ( #583 )
2026-09-18 08:26:18 +00:00
Matthew Meszaros
13e9ce8e10
feat: hold a lead whose mailbox answers out of office until they are back, resuming at the return date it names, plus a manual per-contact pause in one campaign that unsubscribing and the suppression list were the only stand-ins for
2026-09-14 09:26:06 -07:00
Matthew Meszaros
2bbd72e758
fix: make cross-tier warmup borrowing real and one-directional: a thin premium tier borrows proven free mailboxes through one repository rule, gates each drawn partner in its own pool, and only reply-backs cross tiers ( #496 )
...
* fix: gate a warmup partner borrowed from the other tier against the pool it is in rather than the sender's, since the thin-tier fallback had rejected every borrowed candidate and a thin tier failed instead of borrowing
* fix: make cross-tier warmup borrowing one-directional and gate borrowed partners in their own pool, so a thin premium tier can actually borrow proven free mailboxes (#495 )
* fix: pin the borrow floor at the exact boundary so a premium tier at the floor including its sender still borrows (#495 )
* fix: put the warmup borrowing rule in one repository method (direction, floor, proven age, workspace standing) that the selector and scheduler both read, pin every drawn partner's gate to its own pool, fall through buckets when a stale row fails the gate, and allow only reply-backs across tiers (#495 )
* fix: end the warmup partner draw by candidate exhaustion instead of a fixed attempt cap, and fail closed when a free mailbox's workspace standing cannot be read before it answers into a paid inbox (#495 )
* fix: end the warmup partner draw by candidate exhaustion instead of a fixed attempt cap, and fail closed when a free mailbox's workspace standing cannot be read before it answers into a paid inbox (#495 )
2026-09-14 02:51:02 -07:00
Matthew Meszaros
e49a7c4c3a
feat: count only dispatched sends against a mailbox's sending profile, so deferral wake-ups and the campaign chain's next queued run stop spending the rolled daily plan (issue #469 ) ( #475 )
2026-09-13 06:47:42 -07:00
Matthew Meszaros
06b8db5529
feat: report the one silent state a campaign had no words for, a mailbox pool that is part out of budget and part outside its own sending hours, which fell between the 'every mailbox is capped, sending resumes tomorrow' line and the deliberately unlogged closed-hours band and so left an active campaign sending nothing with an empty activity feed; give it its own line under the mailboxes_unavailable event the pool's other refusals already use, naming how many mailboxes are in each state and carrying the moment the pool comes back in metadata rather than promising a day, while leaving daily_cap_reached to mean exactly what it meant before, every usable mailbox spent and nothing coming back until tomorrow
2026-09-12 03:47:36 -07:00
Matthew Meszaros
c4aece241b
feat: scope the tag, category and folder registries and unibox conversation labels to the organization instead of the creating user, so a teammate sees and can edit the labels the owner made, splitting a label two workspaces shared into one copy each and guarding every label write against ids from another workspace ( #457 )
2026-09-12 03:37:31 -07:00
Matthew Meszaros
5869a2148f
feat: stop a campaign sending outside its sending window, and past its end date, when a follow-up is overdue: the placer's schedule gates are asked about the moment a step became due, and nextScheduleSlot deliberately returns an instant that was already inside a window unchanged, so a step that came due at 2pm still read as due at 11pm and the task sent it there, while the end-date comparison found a candidate predating the end date; floor an overdue candidate at now so the window, the weekday mask and the end date are all asked about the send that is actually about to happen, with live tests for a closed window, a passed end date and the overdue step that must still go out promptly while the window is open
2026-09-12 03:29:33 -07:00
Matthew Meszaros
dc9ce403de
feat: stop one un-sendable lead parking a whole campaign and stop the contact drawer's next-action time walking forward on every refresh (issue #437 ): route up to config.CampaignPlacementCandidates due leads per pass instead of one, classify a placement refusal that belongs to a single lead (ESP-strict finding no mailbox for that recipient's provider, a bound lead inside its own mailbox's minimum gap or waiting for it to reopen, a recipient's send-time-optimized hours) as the new ErrLeadDeferred so the pass moves to the lead behind them and only defers the campaign when every candidate is refused, log the ESP-strict deferral once a day rather than once per refused lead per tick, and make PreviewContactSend a pure read that answers unchanged state identically on every call by running placement with the even-distribution, jitter, conflict-resolution, distribution-curve and sub-minute layers off, taking a behaviour profile's gap at its floor instead of drawing it, picking the mailbox deterministically instead of re-rolling rotation, reporting the next sending day's first open minute instead of a jittered twenty-four-hours-from-now, and reporting a due step's time as the campaign chain's own stored wakeup
2026-09-12 02:58:48 -07:00
Matthew Meszaros
68b5d8f358
feat: bind a campaign lead to the mailbox that sends its first email so every follow-up leaves from the same address, holding a lead back while its mailbox is merely out of budget or outside its hours and moving it to another mailbox only when that one can no longer send for the campaign at all
2026-09-09 08:51:00 -07:00
Matthew Meszaros
8b1ceccb05
feat: carry the unexcluded routing pass's own re-check time back when the daily new-lead cap is spent, so a campaign whose only remaining leads are still inside the entry delay defers to their due time instead of reporting itself completed, with a live test that fails without it
2026-09-08 05:13:13 -07:00
Matthew Meszaros
846d691768
feat: write the campaign's 'no step is due yet' activity line once a day instead of on every deferred wake-up, and name the entry delay in it when the campaign holds its first emails, so launching a delayed campaign explains itself instead of filling the feed with two hundred identical lines
2026-09-08 05:09:37 -07:00
Matthew Meszaros
ed50ad9f2f
feat: add a campaign entry delay so a contact's first email can wait a set time after they enter the campaign, with campaigns.entry_delay_minutes and a campaign_leads.added_at anchor (migration 000136), the delay applied in the router's per-lead due check and floored into the placer through ContactSequencePair.NotBefore, a distinct entry_delay constraint in the contact next-action preview, and the control surfaced on the Schedule tab, a new Trigger card at the top of the Steps canvas, the campaign wizard's Schedule step, the launch dialog, the update_campaign AI tool and the iOS schedule page, plus guides, API reference and live scheduler and repository tests
2026-09-08 05:06:04 -07:00
Matthew Meszaros
5d60fe9a21
feat: fix campaign restart dead end (issue #340 ): resolve preflight tracking senders through the same explicit/tags/all pool the scheduler uses so a connected mailbox is never reported missing, fetch the launch dialog's step count from the steps endpoint instead of a field the campaign API never returns, turn on Keep running for new leads when a form or an automation feeds a campaign (migration 000131 backfills existing ones) and when a member starts a campaign whose every lead has finished so it goes active and waits for leads instead of answering 400, return waiting_for_leads from the start endpoint for the dialog's success screen, and document the no_leads and no_remaining_leads codes and the new behaviour in the campaigns, forms, automations and API docs
2026-09-07 02:26:50 -07:00
Matthew Meszaros
5c9d365163
feat: pick a closed-hours mailbox that stays closed for the next ten minutes so the scheduling pass cannot run after it opens, and assert the mixed-pool deferral lands on the reopening from both sides instead of accepting any earlier time
2026-09-04 02:59:00 -07:00
Matthew Meszaros
4bc971eecb
feat: resume a campaign whose pool mixes capped and hours-closed mailboxes at the earlier of tomorrow and the closed mailbox's reopening, log the daily cap only when every usable mailbox is capped, and add the mixed-pool live regression
2026-09-03 20:26:21 -07:00
Matthew Meszaros
1c88c2196b
feat: add live tests proving wake-ups leave the daily budget and min-gap untouched, real sends still count, and a capped campaign parks a wake-up with a single daily log line instead of pausing
2026-09-03 20:18:43 -07:00
Matthew Meszaros
711be1f8c2
feat: defer a campaign whose mailboxes are all at their daily cap, outside their own hours, resting, or held by warmup health instead of auto-pausing it, replace the unreachable push-to-tomorrow recompute, and log the daily-cap and unavailable-pool decisions once per UTC day
2026-09-03 20:18:43 -07:00
Matthew Meszaros
3bdb0fb82d
feat: address the Greptile review on the pool link: require https for the cloud URL (loopback exempt for local development) since the instance token and mailbox passwords travel on it, remove the cloud copy when the local enrollment row cannot be written so a mailbox never warms in both places, delete the local enrollment row before the cloud one and restore it if the cloud call fails so a mailbox is never left with no warmup anywhere, and trim the new multi-line comments to the one-line style
2026-08-29 07:17:10 -07:00
Matthew Meszaros
b75fdcf86c
Merge remote-tracking branch 'origin/main' into feature/self-hosted-warmup-pool-access
...
# Conflicts:
# internal/scheduler/warmup_scheduler.go
2026-08-29 07:10:38 -07:00
Matthew Meszaros
37b60b59d3
feat: let a self-hosted instance warm its mailboxes in the hosted pool: device-code link approved at /connect, instance-token API that enrolls SMTP/IMAP mailboxes as warmup-only accounts (no history import, non-warmup mail dropped), free for 10 mailboxes and unlimited on the seeded $15 pool plan, tier fallback to proven healthy mailboxes when a pool runs thin, local warmup stands down for enrolled mailboxes, Settings > Warmbly Cloud step flow and linked-instances page, docs guide, marketing copy, and fix SetWarmupLifecycle re-reading the row with an org-scoped lookup so every warmup start/pause returned 404
2026-08-29 07:09:04 -07:00
Matthew Meszaros
9fd9e082ae
feat: address Greptile on PR #260 by only linking meeting join_url values whose scheme is http or https in the contact timeline (both the meta Join link and the expanded detail), projecting the campaign's daily ramp advance in memory before the read-only next-send preview so a preview on a new UTC day budgets with the level the next scheduler pass will persist (with a unit test), and trimming the campaign-state model, preview and service comments to one-line invariants
2026-08-29 03:51:24 -07:00
Matthew Meszaros
a4739b63ec
feat: add first-touch contact source attribution (migration 000106: contacts.source with a CHECK, source_detail, first_seen_at, existing rows stamped unknown) stamped at every creation site (dashboard manual/campaign, file import, Google Sheets sync, API key, AI assistant), write contact_created / campaign_added / campaign_removed / category_added / category_removed lifecycle events into contact_activities inside the same transactions as the links and merge them into the contact timeline, refactor FindNextRoutedPair's routing into a campaignRouter shared with a per-contact RouteContact and split CalculateNextCampaignTime into campaignSenders + placeCampaignSend so a read-only PreviewContactSend derives a contact's next step through the scheduler's own constraints, expose it as GET /contacts/:id/campaigns behind the contact service, render a per-campaign state panel, Campaigns/Lifecycle filter chips and expandable event rows in the Activity tab plus a Source section on Overview, cover it with TestLive* for the timeline events and the next-action preview (step wait, sending window, paused), and document it in the contacts, campaigns and API reference docs (issue #255 )
2026-08-29 03:28:16 -07:00
Matthew Meszaros
230d80db64
feat: condense the new warmup pool comments in email_task.go, handler.go, service.go, pg_worker.go and warmup_scheduler.go to the one-line form the repo convention asks for, keeping the non-obvious constraints (the stored tier is never empty, the tier column records what is paid for rather than where the mailbox warms) and dropping the narration
2026-08-28 21:31:06 -07:00
Matthew Meszaros
c28ef3e82a
feat: keep a restricted or suspended workspace out of the premium warmup pool by checking the organization's risk posture before the stored warmup_pool_type in resolveWarmupPoolType (tasks and email services), which is never empty so the restricted branch below it was dead code, make the scheduler's recipient-capacity count risk-aware the same way, wire the org risk repository into the email service, and refuse the premium membership move in UpdateEmailAccountWarmupPoolType while the owning organization is restricted so a worker rebalance cannot readmit a risky tenant
2026-08-28 21:26:57 -07:00
Matthew Meszaros
9f23c663c4
feat: give a cold mailbox a rotation lifecycle so a tired one can rest ( #237 )
...
* feat: give a cold mailbox a rotation lifecycle so a tired one can rest and come back, instead of running at full volume until a hard band trips: send_lifecycle is warming, active, resting or reserve and decides whether cold sender resolution offers the mailbox at all, which is a different axis from risk_band deciding which worker and IP host it, so a resting mailbox is still a clean-band mailbox that keeps its warmup traffic and its reputation; the hourly rebalancer rests a mailbox at throttled and worse but never at watch, since watch is defined as the band that changes nothing a customer can feel and leaving cold rotation is very much something they feel, and a rested mailbox returns only after three clean days so one good hour cannot bounce it back to full volume; reserve is the owner's hold and is never overridden, the default is active so no existing mailbox changes on deploy, and the state never travels in a workspace archive because it is this instance's decision about sending it watched
* feat: stop a query error re-admitting rested mailboxes, make probation measure healthy time, and rotate the candidate window so no mailbox starves: sendLifecycles returned a nil map on failure and an unresolved state reads as active, so one bad query quietly put every resting and reserved mailbox back into cold rotation, and the gate is now applied only when the states were actually read, with the skip logged rather than silent; ReadyToResume measured total time resting, so a mailbox that sat unhealthy for three days resumed on its first healthy tick having served no clean time, and an unhealthy evaluation now restarts the streak; and ordering candidates by send_lifecycle_since put every never-moved mailbox equal-first, so on an install with more than one page of them the same page was re-examined forever, which a checked-at stamp and its index fix
2026-08-28 12:08:52 -07:00
Matthew Meszaros
b019174910
feat: give an organization one fused abuse posture ( #233 )
...
* feat: give an organization one fused abuse posture, because every existing control watches a single subject and an actor slightly wrong on several axes sits under all of them: organizations gains risk_state, risk_score, risk_reason and an append-only risk_signals evidence blob, modelled on the warmup participant health machine that already works rather than a second vocabulary for the same idea; restricted cuts per-mailbox cold volume to a quarter and forces the free warmup pool so a risky tenant cannot spend the paid pool's shared reputation, suspended stops sending at the send gate, and watch deliberately changes nothing a customer can feel so evidence accumulates before anything is taken away; an operator's suspension outranks the derived band so a detector clearing cannot release a workspace a human suspended, transitions ride the audit spine to every teammate's dashboard, a banner says which limit is active and why rather than letting volume drop silently, and the posture never travels in a workspace archive since it is one platform's verdict reached from evidence the destination never saw
* feat: make the suspension actually stop sending, and emit the audit transitions the PR claimed: emailsend.SendEmail is only the manual and API path, so campaign and warmup sends went nowhere near the gate and a suspended workspace kept sending on its schedule, while the restricted multiplier floored every mailbox at one a day which quietly turned suspension into a trickle rather than a stop; the campaign scheduler now defers the whole campaign with a reason and the warmup task skips as org-suspended, since warmup is outbound mail from the same domains; separately the band change emitted no audit entry at all despite the entity type and the frontend spine entry both existing, so no banner moved for a teammate and there was no trail of who was restricted when, and only a real transition is logged so a detector re-recording the same finding cannot fill the feed; one of my own live tests also asserted how far out a slot lands, which depends on the hour the suite runs, and now asserts the property it was about
* chore: drop a test that belongs to the signup-risk change and reached this branch by mistake, where the type it exercises does not exist
2026-08-28 11:20:19 -07:00