Ainsworth Program — Roadmap

Cross-project status for the website, merchant portal, and ticketing system
Updated 2026-10-06 (three ships landed on 10-05 — 31 portal commits in the last 36 hours; two new Done cards and one new Mark action. Signer status on the E-signature card + Send reminder (810f7eb, no migration) — an MPA request that is out for signature now shows who has signed and who has not, read live from Dropbox Sign, with an audited Send reminder button; the package card says when the MPA is still out for signature; the half-signed label reads "Signed — not yet executed" (a1050a8). Inspection signer (5c0dd93, migration applied) — the inspection block on TRX / iBill paper is signed by the agent who brought the deal in (sourcing agent, else the partner's designated MPA signer, else Ainsworth's MPA signers), never whoever clicks Send; the sender can switch to someone at Ainsworth; Reassign moves an outstanding inspection signature; a same-day fix (5f11ec5) makes the card say why Reassign is missing. New for Mark, and the top action item: the signer lists shipped empty — set Settings → MPA signers (Mark + Jeff, default Mark) and each partner's MPA signer (PayByCard first), then Reassign the live merchant's inspection signature off the wrong inspector; until the lists are set, a TRX / iBill send with no sourcing agent is refused by design. First live send under the new rule, first live Reassign, and the live reminder are all still unproven. Still open for Mark: Dropbox Sign's cancel call is asynchronous in both the MPA and Add Location lanes — the cancel probe scheduled for 19:45 ET on 10-02 has no result recorded in PROJECT_STATE as of this refresh. The slice Mark named on 09-30 — a declined or errored re-send keeps the earlier executed MPA — is still built on a branch and unmerged (worktree present, migration filename collision to fix), review next. Still open for Mark: decide whether to rotate the Dropbox Sign API key. The placement-driven MPA form's server-route follow-up remains deferred as Mark's call. The KYC vendor-spend decision with Jeff was due Friday 09-18 and gates the first Plaid tracer — seventeen days past due, still no outcome recorded. The payout ledger's stage-2 re-verify on a real period was due around late September and has no recorded result. Still open for Mark from 09-30: one live merchant's hand-uploaded signed application needs converting through the wet-sign lane. Also still open for Mark: the privacy policy is draft v0.1 with no counsel review, and whether its info@ address is a monitored mailbox is unconfirmed; a removed or converted merchant user's cookie keeps working for up to 7 days (session-revocation slice 2); his reply on the Canadian intake forms (v0.3, EN + FR) plus a French proofread and a named Canadian merchant; the empty production SCRAPINGBEE_API_KEY; the PII key rotation (grilled, GO, needs a morning); and the VPS backfills’ EIN + banking numbers, which only the admin UI can write)
Phases 1-9 + Residuals + Intake API + pricing + CRM P1–P5 + the wet-sign lane complete (both slices + notifications) + the Executive Summary + the sign-in-link invalidation reason + admin collaborator management + the privacy-policy link + the merchant-to-partner conversion scripts + the TRX all-banks paper fix + multi-bank placements + the placement-driven MPA form + the pre-signature bank package + the MPA re-send chain + the Executed MPA view with a system-filled I.1 + Add Location automation with its send reservation and signed-paper integrity + the customer service email + live signer status with reminders + the inspection-signer rule with Reassign shipped · the compliance rubric is now in flight rather than pending — schema + the complete pharmacy vertical live (batches 1 and 2, 09-05/09-06), thirteen verticals to go · the P4 exit gate is still down to one Mark action (resolve the six open auth-decline alerts) and /bank to one (the bank-auth survey) · the Fiserv Canada referral lane has approved forms (v0.3 — English + French, Fiserv co-branded) and is waiting on Mark’s reply, a French proofread, plus a named Canadian merchant · the next engineering work is reviewing and merging the standing-MPA branch (a declined re-send keeps the earlier executed MPA), then the peptide B2B vertical, alongside the PII key rotation (grilled, GO, slipped from 08-22 — needs a morning with Mark) and the unblocked Fiserv Marketplace boarding build, then the P6 grill · two items still carry a hard pre-North ordering: the MPA-queue entry gate and the abandon/create race residual
✓Delivered / Live
▶In Progress
○Ready / No Dependencies — can start now
⏸Deferred / Blocked
Public Website LIVE ✓
✓ Done

Agent-focused site built (Next.js 16)

Four pages live: /, /agents, /merchants, /about. Positioning locked, compliance pre-flight integrated.

✓ Done

Launch polish

Favicon, OG card, Vercel Analytics, Resend-backed apply forms, /thank-you confirmation. Forms verified delivering to mark@.

✓ Done

Jeff's review — approved

Positioning + content signed off 2026-05-27.

✓ Done

Vercel project moved out of QuickRefund

Project relocated to Mark's personal Vercel ownership before the domain swap.

✓ Done

ainsworthpayments.com is LIVE

Shipped 2026-06-06. Domain now points at the new Next.js site; old static page retired. Closed workstream.

Merchant Portal Phases 1-9 + Intake API + pricing shipped

✓ Shipped — production

✓ Done

Phases 1–4 — foundation

Auth (magic-link, AES-256-GCM sessions, 5 roles), Postgres + PII encryption, Tier 1/2/3 intake, dashboard, uploads, admin flow. In production at merchants.ainsworthpayments.com since 2026-05-17.

✓ Done

Phase 5 — canonical-schema MPA generation

Full loop verified end-to-end in prod: signup → 9-step canonical wizard → admin generates filled MPA → send-for-signature → embedded Dropbox Sign → webhook finalize → signed PDF auto-links to I.1 checklist. Banks wired: Merrick, Maverick/TSYS, Synovus, PB&T.

✓ Done

Phase 6 — partner financial reporting

ISO residual portal shipped 2026-05-30. Single + bulk entry, month/range/CSV views, edit + list pages, atomic CTEs with diff-aware audit. Dogfooded in prod against ATR + QuickRefund books.

✓ Done

Phase 7 — Utah automation tracer + docs-ready

Tracer shipped: extraction → 7 deterministic rules → readiness verdict (GO/HOLD/NO-GO). Docs-ready workflow trigger shipped 2026-06-04 (email Mark+Jeff inline + iMessage to Mark via local launchd poller every 15 min). 57 fixture tests.

✓ Done

Synovus + PB&T MPA generators

Corduro/Priority template family — 497-of-499 fields shared via corduro-base.ts. Full e-sign loop verified in prod 2026-06-03. Iron Peak's path to bank submission once he finishes the wizard.

✓ Done

Residual Automation tracer

Schema + entry pivot + agent CSV + colorblind-safe PDF shipped 2026-05-30. Agent statement = net interchange + net fee × split %. Full vertical dogfooded in prod.

✓ Done

Admin intake card + canonical migration (this week)

"Application intake" status card on admin merchant page. Migration tool moved 4 merchants from legacy → canonical (Iron Peak, Deep Water, Alpha Peptides, test). 2 legacy remaining by design (Enhanced Wellness, Paragon — already transcribed).

✓ Done

Partner Intake API v1 — all 6 slices

Shipped + live 2026-07-05. Full /api/v1/merchants surface for partner-programmatic intake. Open follow-ups: webhooks, spec v0.2, integration guide.

✓ Done

Partner org structures — master merchants, agent hierarchy, referrer visibility

Live 2026-07-06 → 07-10. Master-merchant orgs with commission-attribution invariants; sub-agents under partners with per-category splits (Model 1); referrers see their referred partner's in-progress pipeline read-only (status-only, no bank identity).

✓ Done

Merchant abandon/restore + Statement Analyzer

Both live 2026-07-07. Admin abandon/restore archives merchants out of every active + partner surface, restores to prior status. Partner-facing statement→savings analyzer at /partner/analyzer (PDF upload, interchange audit, flat-rate/tiered estimates).

✓ Done

Phase 9 — placement consolidation

Merged + live 2026-07-08. Placements now own the merchant-placement lifecycle end to end; the legacy bank_submissions path is retired/archived. Deferred list closed empty.

✓ Done

TRX MPA generation — trx-v2 paper + full fill fidelity

Live 2026-07-10 → 07-13. Remapped to the 6.2026 5-page TRX paper (stale 4-page blobs 409 on send); radio groups, checkboxes, and deal pricing now fill; Acrobat "error 18" fixed for good (MuPDF bake instead of pdf-lib flatten); generated MPAs ship clean (empty-field highlighting opt-in); ISO-level template resolution defaults the picker.

✓ Done

E-sign hardening — two-signer TRX + party-signer resolution

Live 2026-07-13, closed 07-15. TRX packages now sign in parallel: merchant (3 signature lines) + Ainsworth inspector (survey line); Personal Guarantee dropped per TRX paper. Signer resolves from the application's primary-contact party (all banks), with login fallback. First three TRX MPAs fully executed by both signers.

✓ Done

Pricing models — partner defaults + per-merchant editor + cost-plus / flat / tiered

Live 2026-07-12 → 07-15. Partner-level default deal pricing auto-seeds new merchants (trigger, all create paths); admin per-merchant Pricing card overrides it; three models flow into the trx-v2 MPA pricing sections. Shared fieldset + save hook — both editors can't drift. Surcharge / cash-discount stay manual on the MPA.

✓ Done

Post-mortem patterns 1/3/4/5 — fully remediated

Merged + live 2026-07-12. Bank-paper mapping fixes, 17 DB CHECK constraints + pricing trigger, load-bearing verify scripts, dual-flow (legacy vs canonical) owner surfaces. Every stored MPA now requires regenerate-before-send (canonical shape versioning).

✓ Done

Underwriting completeness — sponsor-bank fields, Section B KYC fix, post-submit resurface

Live 2026-07-11. Statement phone + card-present% + refund stats collected self-service in the portal and hard-gate TRX MPA generation; owner photo-ID/KYC checklist items now generate for canonical merchants (backfilled); pre-gate submitters see "Update needed" instead of a false "Complete ✓".

✓ Done

Admin ops surface — signature status, "Mark application submitted", internal resources

Live 2026-07-12 → 07-15. Merchants list gains a Signature column + Awaiting/Executed filters (third status axis); admin-only button catches up act-as-stranded merchants behind a two-layer current-executed-MPA gate; staff-only internal resource audience (also closed a download-route leak).

✓ Done

Ops tracker boards — MPA Tracker + URL Tracker

Live 2026-07-16 → 07-24. /admin/mpa-tracker began as the per-merchant 8-step checklist from intake review → executed MPA → bank submission (steps 1–5 derived live, 6–8 manual, every mutation a single audited CTE, generation-token guard against stale re-adds). Superseded 2026-08-05 — see the re-key card above; the manual steps are gone and the board is derived from placements. /admin/url-tracker is the matching ops board for the Add Location lifecycle.

✓ Done

Secure file delivery — bank packages + Secure Send

Live 2026-07-17, deep-link 07-27. Password-gated download links at /secure-files/[token] (7-day expiry, download receipts) replace ad-hoc encrypted-zip email. /admin/secure-send handles one-off sends to bank contacts. The download-receipt email now deep-links back to its own row (?send= highlight + anchor scroll) instead of the bare creation form.

✓ Done

Add Location workflow — MCC capture, batched e-sign, admin-API domain gate

Live 2026-07-23 → 07-24. Existing merchants can add locations without a fresh full application: MCC captured at intake (trx-v2 has no MCC field — the bank assigns it), locations modeled first-class, and all location addenda go out as ONE multi-page Dropbox Sign request rather than N separate ones. Same pass closed the repo-wide gap where /api/admin/* enforced role but not the proxy's admin-domain gate.

✓ Done

Wet Signature Sent — first-class status for paper-signed MPAs

Live 2026-07-27. New wet_signed e-sign status for MPAs signed on paper outside the portal and delivered to the bank by hand — its own admin badge + filter chip, executed-PDF download, bank-package attach, and signed-equivalent treatment in the MPA tracker. Driver was the Venture Payment Solutions TRX portfolio — now 12 entities / 35 DBAs after Flask Creative and Hillside Drift were added 07-28, Brave Fenn LLC on 09-11 and Marixor Holdings Corp on 09-15 — all backfilled with their real executed applications and placed TRX → Esquire.

Brave Fenn is the first backfill to land on two channels at once, and it stretched the shape in three places. Its three applications were signed outside the portal on 09-10 and went straight to Corduro, while MPAs for the same entity also went to TRX — so the record carries a Hagen Pay → Chesapeake placement stamped at signature and a TRX → Esquire placement stamped 09-11. It got three wet-signed rows rather than the usual one, so every signed application downloads from the merchant page under its own DBA name — and the titles name the merchant and its DBA only, never the ISO or the bank, because placement secrecy holds inside document titles too. Its location rows carry the channel directly so the URL Tracker labels them instead of falling back to a generic "Bank"; only one channel fits on a location row, so TRX's coverage of the same three DBAs lives on the placement. And the real pricing — customer-paid 4.99% + $0.35 — cannot be expressed by any of the three pricing models, so rather than force it into one that would misreport it, the schedule sits in the placement notes and the audit trail while the partner's cost-plus default stays on the record untouched. A document decision followed the next day. Corduro returned three application copies; only one was executed, the other two being five-page pre-signature copies — character-identical to the signed apps but carrying none of the signature graphics and no audit page. They went in as reference documents and then came straight back out, because the same flag governs both the merchant's document list and the bank-package zip: a document cannot be visible in the portal and absent from the bank's copy. Hiding them somewhere nobody would look was the worse option, so they were removed and the executed PDFs remain the MPA of record. Marixor Holdings Corp followed on 09-15 as the simplified version of the same script — VPS's twelfth entity, two applications (e-signed outside the portal via Adobe rather than on paper) on a single TRX → Esquire channel, so both location rows point at the one placement with none of Brave Fenn's single-value contention. It was the first VPS backfill with nothing in the portal to build on, so the script created the whole record from the applications: merchant, party, locations, placement, checklist regenerated and approved, one wet-signed row per signed app. Three details came from reading the paper rather than copying the previous entity — the corporate and operating addresses genuinely differ, the business structure on file is a C-corp where both applications check LLC, and the refund window is the policy's 30 days. Pricing again exceeds what the three models can express (4.99% + $0.35 plus a $200 monthly fee), so it lives in the placement notes. Applied to prod 09-15 and verified.
✓ Done

Left-sidebar navigation — admin + partner shells

Live 2026-07-27. The overflowing 11-link top bar is replaced by a layout-level left rail on both /admin (24 pages) and /partner, sharing one SidebarShell with the rail picked by real session role. Six previously URL-only surfaces gained nav entries. The mobile drawer is a real modal dialog (focus trap, Escape, focus return, breakpoint reset). Colorblind rule honored — every item is icon + label, never color alone.

✓ Done

Merchant Oversight bank demo

Live 2026-07-21. Admin-only sample-data demo at /admin/oversight-demo for walking sponsor banks through the monitoring story. Source of truth is the standalone HTML under Merchant Monitoring/ — re-copy and redeploy to update it.

✓ Done

Multi-channel placements — a merchant on two bank channels at once

Live 2026-07-31. The VPS portfolio entities were also submitted through a second channel on signed paper applications while still holding in-flight TRX placements — and the old one-in-flight-per-merchant index made that unrepresentable. Migration 2026-07-30-01 (applied to prod before the deploy, since the widened index is what the new conflict arbiter targets) moves it to one in-flight per (merchant, ISO); the placement panel renders every in-flight card and offers a second channel with already-in-flight ISOs disabled; executed e-sign rows displaced by a newer channel keep their download link. Review caught a real leak — the backfill's e-sign title named the ISO and bank, and titles are merchant-visible, so wet-sign titles are now generic and channel identity lives only in placement notes + audit rows. Backfill ran post-deploy and re-verified idempotent: three second-channel placements, three added DBA locations, three paper-signed applications attached.

✓ Done

Pend Tracker — bank underwriting conditions as tracked state (CRM P1)

Live 2026-08-01. First slice of the CRM portal-extension plan. Bank pends stop living in email threads: placement_conditions makes each one placement-scoped state (open → answered → cleared/waived, optionally tagged to a single location), with response threads that carry an author, a timestamp, and an optional document. Open or answered pends now block both approve and activate — a friendly pre-check first, then the same predicate inside the atomic write so a race can't slip a placement past a live condition. /admin/pend-tracker is the work queue, sorted by due date with Eastern business-day overdue math; partners see the bank's verbatim ask on their merchant page and can respond in text (clearing stays admin-only). Placements also gained decision reason codes, reserve %, and an approved monthly cap — soft flags for now, wired to enforcement later. Fixture was the real VPS pend batch; Mark logged the first live pend on Hillside Drift.

Policy: bank/ISO identity disclosure relaxed the same day (v2) — once a placement exists, the bank may be named to partner, agent, and merchant; only pre-pick deliberation stays admin-only. New surfaces disclose from day one; retrofitting the older scrubbed paths is a later pass.
✓ Done

MID registry + Accounts board (CRM P2)

Live 2026-08-02. A live merchant account is now a first-class row rather than an implied consequence of an approved placement: merchant_accounts holds one row per merchant account at a processor, unique on (ISO, MID), with at most one live account per merchant/DBA/processor. It carries the processor ids parsed off the VAR sheet, and informal monthly caps that require a provenance note — a cap with no stated source isn't accepted. The registry seeded from the seven Highwire TRX VAR sheets, which also flipped those seven queued TRX → Esquire placements active with their real MIDs; Mark then set all seven caps to $500k/mo. /admin/accounts is the board — live-MID tiles per processor, per-row cap editor, and an automatic flag when a statement descriptor doesn't match the DBA (the Zen Oil lesson, now caught by the tool). The Active book on /admin/placements collapsed to headline counts that click through, and the partner merchants list now shows DBA — four "Members America LLC" rows were otherwise indistinguishable.

Scope note: the registry is forward-only — the two legacy Corduro/Hagen statement-history merchants deliberately get no rows.
✓ Done

Payout ledger — recording what we actually paid (CRM P3)

Live 2026-08-02. The residual side of the CRM plan: runs, per-payee items, statement-level slices, payability gates, a carry chain, and post-close adjustments over frozen statements. It records payments only and never initiates one — Mark executes the transfer at the bank and keys the reference in. Every amount is integer cents, no floats anywhere. Gate order is agreement → W-9 → floor (a $100 org default, editable in settings); held items wait inside the run, and sub-floor items carry forward visibly to the payee rather than vanishing. A statement side can only be claimed once, which is the structural guard against double-paying. Surfaces: an /admin/payouts board and run detail with a reconciliation strip, W-9 receipt and encrypted payee ACH capture, and partner-visible payout lines that state a floor rollover explicitly.

Verification: a 49-check rollback battery plus a read-only trace against real historical months, which reconciled to the cent. Exit gate still open: stage 2 is a re-verify on a real period around late September — before any real payment reference gets entered.
✓ Done

Ingestion + monitoring (CRM P4)

Live 2026-08-03. The machinery that turns processor files into watched numbers. /admin/import takes a normalized CSV all-or-nothing — any bad row or a control total that doesn't match refuses the whole file with every problem listed — archives the original, and applies it in one transaction. Metrics land as revisioned snapshots that are never edited in place, with the grain (daily vs monthly) part of the row's identity so the two can't be silently mixed in a view; a feed that doesn't report a number leaves it null rather than faking a zero. Thresholds are sourced data, not constants in code — the TRX 1%-or-50 line is seeded with its page reference, and the Esquire program row ships deliberately empty because no bank has stated numbers in writing. Five alert kinds (cap utilization, volume swing, chargeback/refund acceleration, ratio headroom, auth-decline spike) feed an /admin/alerts queue with frozen evidence and a required note to call anything a false positive; critical ones email Mark and Jeff. The Accounts board now prefers per-MID snapshot volume over statement-derived figures.

Built generic-first on purpose — and the bet paid off on 2026-08-10, when real gateway data arrived and the machinery took it unchanged: the NMI pull emits the same normalized CSV the console already accepted, so no processor-specific parser was needed at all. Exit gate now down to one step: both accounts are ingested and reconciled to the cent and the nightly feed is live; what remains is Mark resolving the six open auth-decline alerts on /admin/alerts with the MCC-5411 note — resolve, not false-positive, because they were true positives against the old 50% line. The retune to warn-80 / crit-90 stops them re-firing.
✓ Done

Bank surface — /bank (CRM P5)

Live 2026-08-03. The portal's fifth audience: sponsor-bank risk officers get read-only, program-scoped visibility instead of an email thread. A bank_viewer role plus per-program grants resolved fresh on every request, so revoking access beats a live cookie. The surface is a portfolio view (volume against cap, headroom, a standing chip), a merchant deep-dive with trailing-six-month metrics and the resolution history of closed alerts, and frozen monthly standing reports with a PDF. Admins get a thresholds editor, grant management, and a banner-disclosed single-program preview that audits as an admin action. What a bank cannot see is enforced by tests, not habit: economics, partner identity, other-processor volume, and the free-text internal resolution notes are all denied at the SQL level. Standing says "no data" rather than "good" when a number is missing, and an interim watch rule is labeled as Ainsworth's own, never as the bank's.

Demo-grade until one gate clears — down from two as of 2026-08-10. The data gate is closed: real gateway volume is ingested, reconciled, and refreshing nightly, so /bank would now show real numbers. What still holds it at internal demo grants is the bank-auth expectations survey — the draft is written and Mark sends it, because handing a sponsor bank a login before knowing their MFA/SSO/revocation requirements is the one thing here that can't be undone quietly.
✓ Done

Fiserv Bank Credit App — a fourth artifact type

Live 2026-08-04. Admin-only surface that assembles Fiserv's New Account Credit Package Transmittal from Ainsworth intake and renders it for Fiserv's credit desk. It is deliberately not an MPA, not a bank package, and not a secure send: never signed, never merchant-facing, keyed to the placement rather than the merchant, and regeneration replaces in place. The merchant never sees Fiserv paperwork — Fiserv issues its own application separately, which is why the new fiserv ISO row carries no MPA template at all. The two gates differ on purpose: completeness is soft (Fiserv's own form allows a stated-gaps package, so it generates with an INCOMPLETE banner printed on the PDF — a screen-only marker would vanish the moment the file left the portal — and names the file -INCOMPLETE), while the SSN guard is hard (422): an incomplete package may be sent knowingly, a Social Security number may not be sent at all. Built from two real hand-filled transmittals rather than a spec.

Open: what Fiserv means by "Clearing Bank" (their two real transmittals disagree), whether they require the SSN at all, and a phase-2 conditional attachment-rules engine — both real packages had gaps those rules would have caught.
✓ Done

Self-hosted PDF rendering — no portal PDF touches a third party

Live 2026-08-04. Headless Chromium in-process replaced the external PDF vendor behind the identical conversion seam, so all four PDF surfaces (Fiserv credit app, bank standing reports, statement analyzer, website screening) changed one import line each. There is no external fallback by design — a render failure returns an error, and the HTML is never shipped off-box. The trigger was the Fiserv credit app: it is the first portal PDF carrying principals' dates of birth and home addresses, and the vendor's data-processing agreement covered only account-level data, not people named inside submitted documents. Page geometry parity is pinned by a script that renders through the real module and asserts the actual page size.

Same-evening prod incident, fixed: the Chromium binary didn't ship in the first deploy — the bundler can't follow a runtime filesystem read — so the four functions had no browser. A hotfix that explicitly includes the binaries went out, was proven by a real 2-page PDF rendered on the live function, and the vendor API key has since been removed from the environment. Lesson: verifying locally against installed Chrome cannot see how the deployed function is packaged.
✓ Done

VAR sheet import — the processor's own boarding document takes a placement live

Live 2026-08-05. The MID registry had no creation path since it shipped three days earlier — every row came from a one-time seed, and activating a placement stamped a MID while creating nothing. Importing a bank's VAR sheet now does the whole motion at once: it creates the merchant account with its descriptor and processor ids, seeds the cap, files the PDF, and flips the placement live. That makes it the real replacement for CRM P2 slice 2 rather than a companion to it, and it arrives from the direction the work actually does. Mark's corrections shaped the scope: every channel issues VARs (not just TRX), caps are seeded per MID at the full requested amount (multi-MID exists precisely to obtain higher aggregate capacity), and pends neither block an import nor auto-resolve — a VAR is the bank's own proof the account is boarded, and conditions legitimately outlive activation. The sheet is downloadable by admin, the merchant, and the merchant's own partner; referrers are excluded by construction.

Five adversarial review rounds, all fixed — a placement could go live while the registry write was refused, revoked act-as sessions outlived revocation on the download route, and a re-issued MID could be relocated off another DBA. Deferred: parsing the TRX PDF to pre-fill the form (the same extraction question behind the Chromium packaging incident); manual confirm covers every layout indefinitely. The same session finally merged the domain glossary onto main, where the SOP has been telling everyone to read it since 08-01.
✓ Done

Pends at book scale — surfaced where the work actually happens

Live 2026-08-05. The Corduro/Chesapeake batch (86 pends across nine lists) proved the P1 board doesn't scale on its own: a pend is only useful where someone is already looking. The list was sorted — on legal name, while rendering "DBA (Legal Name)" — so one string had two writers and merchants filed under the wrong letter; one function now owns the label and the sort that matches it. Search and paging landed with a tiebreak on row id, because a single bank email produces a dozen rows sharing a due date, and without a total order paging repeats one row while skipping another. Pends now appear on the admin merchant page and read-only on the merchant's own dashboard. Two governance calls shaped it: merchant-facing pend text is authored, never the bank's verbatim ask — no summary means the pend is never published, enforced so that the raw ask is a compile error on that surface rather than a convention — and the merchant card shows exactly one placement, since merging across ISOs would let a merchant infer they're being shopped to several banks at once.

Ops decision: Mark is not loading the 86 Corduro pends — "better to start with anything new." The tracker begins from new pends going forward. Lesson worth keeping: the second test lane had been silently skipped repo-wide since a Node upgrade; both lanes are green again under the corrected runner. Follow-on 2026-08-10: the merchant picker now matches on location DBAs and URLs too, not just the legal name — the same "one string, one writer" problem showing up on the search side.
✓ Done

MPA tracker re-keyed onto the placement — a derived queue with zero manual steps

Live 2026-08-05, both slices. The 8-step per-merchant checklist is gone; the board is now a fully derived work queue where a row is a placement — the same merchant at TRX and at Corduro are separate chases — and nothing is ticked off by hand. A deal enters when its latest real MPA signature is executed (merchant-scoped by necessity: one signed paper serves every concurrent channel), sits in Awaiting send / Awaiting decision / Awaiting VAR buckets read straight off placement status, and exits when the VAR import takes it live. Day one it surfaced three signed-and-packaged deals that had never been sent — exactly the failure mode the board exists to catch. Slice 2 added notes and dismiss/restore, written by one planned statement shared by the route and its verifier, and removed the hand-typed Activate control entirely: VAR import is now structurally the only way a placement goes live. The legacy tracker's stubs and vocabulary were deleted later the same day; its table survives as history, read and written by nothing.

Same-day companion: the merchants board now defaults to In flight (21 today) with live merchants one chip away on Accounts, plus a derived "Dead (banks declined)" view — deliberately not a status value, since banks saying no everywhere is the fact, and revival means routing a new placement. One open item on the whole arc: North API placements can't enter the queue (they create a placement with no signature row). Moot while North is uncredentialed — but it must be resolved before North is activated.
✓ Done

iBill → Esquire channel — a sixth MPA paper, e-signable the same day

Live 2026-08-06, both slices. iBill is a new placement channel that fronts TRX's Esquire rails — deals actually run Ainsworth → iBill → TRX → Esquire, modeled as iBill → Esquire with the processor in the middle deliberately invisible. New Highwire deals now default to it; existing TRX placements stay put. The channel brings the portal's sixth MPA paper (a 4-page, 165-field application, SHA-pinned) and, hours later, guided e-sign for it: two signers like TRX — the merchant primary contact on pages 2–4, the sending admin on the page-3 survey inspector line — so iBill deals never needed the wet-sign path the first slice shipped behind. Every print name for the signer of record resolves strictly to the primary-contact party with no owner fallback, which is the first deliberate divergence from the TRX gate. Two traps are worth remembering: the paper's "Return Policy" radio options are misnamed in the form itself (the widget called Home prints NONE, Office prints EXCHANGE) so the mapper selects by render-verified position and must never be "corrected" to match the names; and the send-for-signature allowlist is now derived from the bank registry rather than hand-copied, killing a whole class of skew.

New shared mechanism: mapper revisions are now paper-scoped — bumping one paper's revision invalidates only that paper's stored hashes, so completed signatures on the other five stay valid. The first attempt bumped the global canonical version and would have forced re-signing already-executed non-iBill MPAs; it was reverted in-branch. Eleven adversarial rounds across the two slices (5 + 6) caught, among others, a paper that would have asserted office numbers as home phones and a card-present-dominant merchant being asked to sign a card-not-present claim. Ops: any iBill MPA generated before the e-sign deploy 409s at send — regenerate first; other papers unaffected.
✓ Done

Abandoning a merchant now closes its in-flight placements

Merged 2026-08-06, backfill applied. Found by a memory sweep rather than a bug report: Enhanced Wellness sat at placement submitted for two months after the bank rejected it and the merchant was abandoned, so the portal believed a live submission existed. Twelve of sixteen abandoned merchants had the same shape. Grilling the real rows changed the design — all twelve were queued, meaning the bank had never seen those deals, so the honest close is withdrawn, not declined. That one word deleted an entire subsystem: the notification outbox fires on submitted/approved/declined/active, and withdrawn sits outside it, so the "suppress the merchant email" machinery the feature was originally framed around was never needed — and a test now counter-proves it by failing if declined ever rejoins the notify list. One writer, three callers: both abandon routes had carried separate inline SQL, so a rule implemented in one was a rule the bulk bar silently broke. Live accounts are never touched — a real MID has a registry row behind it.

Four adversarial rounds, and rounds 2–4 each bled from the previous fix — ending with the round-3 database trigger being cut: it never actually closed the race it was written for, and it made the North submission path worse, 500ing after the application had already left the building. Accepted residual risk, and the second item that must be revisited before North activation: a concurrent create in a millisecond window can still strand one placement. Never observed, three-person admin team, and the backfill script re-runs safely at any time to sweep one up. Lesson worth keeping: a two-connection test covering one ordering does not license a claim about both.
✓ Done

Partner activation, W-9 + payout account name, and the executed agreement download

Live 2026-08-06. Mark hit an "Activate partner" button on a partner that already existed, and it errored. The grill contradicted all three premises behind the obvious fix: activation had never once succeeded in production (all ten partners were hand-created), the payout engine has never run a single row, and the W-9 field everyone assumed existed was a dead foreign key pointing at a merchant-hardwired table. So activation now resolves three ways — create, link to an existing partner (no second org, no second invite), or blocked outright — and the W-9 is the actual PDF held on the partner record. A payout account name is always required with banking and never defaulted from the org name, because ACH returns on a mismatch. Nothing gates payouts yet: capture first, gate when the first real run happens. The same pass closed an orphan class where a refused activation stranded a partners row — two had reached production and were deleted here. A follow-on shipped hours later: Phase 6.5 had been storing the countersigned agreement PDF since July but nothing ever served it back, so a copy had to be pulled from Dropbox Sign by hand. It is now an audited admin-only download — admin-only because the agreement carries Schedule A, i.e. the partner's whole commission structure.

Two lessons with teeth. BEGIN/ROLLBACK is inert over the HTTP database driver — it is stateless and every statement autocommits, so a verify script that believed it was rolling back wrote straight to production (artifacts cleaned up; rollback verification now uses a real client). And a verify harness must execute the production statement rather than a paraphrase: writing a literal where the route binds a parameter hid a bug that 500'd every banking save. Also: the blob library's get() throws on a missing object instead of returning a status, so status-only checks leave an unhandled 500 — the same shape still exists in the VAR-sheet route and should be fixed next time that file is open.
✓ Done

A declined or errored signature now emails somebody

Live 2026-08-09. Until this shipped, a merchant declining to sign — or a signature request erroring out at Dropbox Sign — was silent. The row changed status and nothing else happened; someone had to notice a request had stopped moving. All three webhook write sites (decline, file error, and the catch around finalize) now queue a coalescing digest to the admin distro through the notification outbox that already existed, with a toggle on /admin/settings. Two gates keep it honest: it only fires when the guarded update actually transitioned the row, so Dropbox's redeliveries can't re-send, and it never fires for test-mode rows. Queueing is best-effort — a failure there never costs the webhook its acknowledgement. This is slice 1 of the usability review: Mark cut the original four-family scope down to e-sign alone, and the other three (pends, account pause/terminate, payout hold/reverse) are parked with explicit triggers — each waits until he has worked a real one of that thing by hand, rather than being designed against an imagined workflow.

The toggle genuinely mutes. Every other notification type has fallback recipients, which would have made "off" mean "email someone else instead" — this one ships with the fallback list deliberately empty and its real recipients seeded by migration. The dispatcher hardening is the part that outlives the feature and applies to all outbox types: sent-stamps are now compare-and-swapped against a claim-time token, so a digest merging mid-dispatch leaves the row unsent for a superset re-send rather than vanishing (duplicate over loss, deliberately); and an empty recipient list is only treated as "nobody subscribed" when the lookup actually succeeded — a transient failure now defers instead of burning the row. Three adversarial rounds ending in a zero-finding approve, plus a bug review that caught the walkthrough script exiting past its own teardown against production. Accepted residual: if the queue write fails after the webhook is acknowledged, the notification is lost with no redelivery repair — repairing it means queueing without a transition, which reopens replay amplification. Revisit trigger is the first Sentry event, not a date.
✓ Done

Real processor data — the NMI pull, then the nightly automation

Live 2026-08-10, both halves — this closes the item that sat at the top of this roadmap for a week. The Highwire book runs on an NMI white-label gateway, and the read-only Query API keys landed on 08-10. A pull script turns the gateway's XML into exactly the import console's CSV contract, and both accounts reconciled to the cent against the gateway's own Transaction Snapshot before a single row was written — then Mark landed and applied both batches through /admin/import. Hours later the same seam went nightly: a cron pulls both accounts concurrently and lands → applies → evaluates headlessly, with the manual console kept as the correction path. The first automated run independently reproduced the manual import byte-for-byte (both accounts unchanged, every snapshot still at revision 1, zero alert churn), which is the strongest evidence available that the two paths agree. The live data also forced three fixes no synthetic file would have surfaced: the two gateway accounts sit in different timezones so a hardcoded one would have corrupted the other; the API stamps in UTC and has to be converted before day-bucketing; and a narrow date window silently fragments a transaction's action history, orphaning settlements from their sales and reporting $0 settled — the pull now fetches wide and converges on output rows across two sweeps.

Two design choices worth keeping. The keys are scoped to a per-account user rather than the account owner, so the pull sees only the processors Ainsworth actually placed — the merchants' own other-processor relationships stay invisible, which is data minimization and a smaller sweep; a preflight coverage guard fails closed if one of ours ever stops being visible, since the new risk of a scoped key is silently missing something. And which feed owns a lane is now an explicit record rather than an inference: the gateway adapter succeeds the manual CSV lane, never the reverse, and a console upload into a gateway-owned lane quarantines. Review record: a 3-finder round caught a critical that would have quarantined 100% of automated rows while reporting green, plus 3 Codex adversarial rounds to approve and a 52-check rollback battery. Deferred by name: superseded-row retention sweep, the deviation-based decline arm (needs 2–3 real months), and per-account cron fan-out once the book passes roughly 1.4k gateway transactions a day.
✓ Done

The URL Tracker learns which bank a URL was actually sent to

Live 2026-08-12. Mark found three of Zen Oil Atelier's wet-signed Hagen Pay URLs displaying as TRX on the board. The cause was structural rather than a typo: merchant_locations was bank-agnostic — Add Location was built when TRX was the only channel — so both the board and the bank-response chip simply printed "TRX" because there was nothing else to print. A location row now records which placement (which ISO/bank channel) it rode, plus whether it was signed on paper. Step 5 names the channel ("Hagen Pay · Chesapeake Bank"), the response chip reads <channel> confirmed and falls back to a generic "Bank" for legacy unlinked rows rather than guessing, and paper-lane rows say "Wet signature" at the signing step. Capture closes the loop: marking a URL sent records the channel silently when the merchant has one open placement and asks with a picker when there's more than one. The three Zen Oil rows were backfilled to the Hagen Pay · Chesapeake placement with audit rows written.

The honest limit: wet rows are annotate-and-backfill only. No admin action advances a paper-signed location from approved to signed, because designing that lane is a product call Mark hasn't made yet — which channels get a wet lane, and what gates it. That was logged as a deferred finding rather than guessed at. Two smaller ones logged with it: the route validates that a linked placement belongs to the same merchant but not that it's still alive (the UI only offers live ones), and an open tab's picker can hold a stale id if the options change under it. Closed 2026-08-13: Zen Oil's three remaining sent URLs turned out to have ridden TRX · Esquire, and were linked through the same update with audit rows written — all six locations now carry a named channel. Codex zero critical; 4 new deriver tests on top of the 885-check battery.
✓ Done

Fiserv placement channel — a seventh MPA paper, and the first one Ainsworth wrote itself

Live 2026-08-15. Fiserv is now selectable as a placement channel at merchant-invite time, with the placement row created in the same atomic statement as the merchant and the user. The wizard grew a conditional step (delivery timing / order mix) whose visibility is derived from the merchant's live placements rather than from a column on the merchant — deliberately, because a merchant can sit on two bank channels at once and a single "channel" field would have to lie about one of them. Every other paper in the repo came from the bank as a fillable PDF; Fiserv had none, so this one was built from their Marketplace sample and is a DRAFT until Fiserv approves it. A revision they ask for ships as a new template plus a mapper-revision bump, never as an edit to blobs merchants have already signed.

The reusable mechanism this ship produced: paper-scoped canonical keys. The first cut bumped the global shape version because the canonical record gained an order profile — which would have invalidated every other bank's stored MPA and stranded merchants who had already executed a signature on unrelated paper. Now a key only one paper reads is projected out of every other paper's hash, so new fields stay local to the bank that needs them. Nine adversarial rounds, ending zero critical / zero high; round one alone caught a 12× volume understatement, silently dropped principals, a sole proprietor's EIN labelled as an SSN, and a blank certified filing name. Deferred by name for Mark: the bank-package hash bypass, the exactly-100 order-mix contract, and a repo-wide font-encoding limit.
✓ Done

Tier 0 + Tier 1 backups — the portal can now be restored

Shipped and running 2026-08-16. Until this landed, the portal's database and document store had no independent copy: the only thing standing between Ainsworth and total loss was the hosting provider's own retention. A nightly job at 02:30 now takes an integrity-verified database dump (30 daily, 12 monthly), mirrors the blob store (602 objects / 939 MB, append-only — it never deletes, so a bad sync can't propagate a deletion), and encrypts an offsite copy. A restore drill runs as part of the system rather than living in a runbook nobody executes, and it earned its keep immediately: running it for real surfaced the first genuine finding, which is exactly the point of a drill.

⚠ Two gaps that make this partially, not fully, restorable — both need Mark, both are on the action list. First: the encrypted-data key that protects merchant PII lives only in the hosting provider's runtime and cannot be exported. A restored dump therefore gives back the structure and the non-sensitive columns but cannot decrypt the PII — the local key is a different key and a candidate from the password manager decrypted 0 of 21 test rows. Closing this is a key-rotation project, and it is grilled and GO as of 2026-08-21, scheduled for the morning of Saturday 2026-08-22 with Mark supervising — the second key gets minted and escrowed to a shared password-manager vault before anything is encrypted under it, and the old key stays in place until November so a late-discovered straggler is still readable. Second: the offsite leg is a no-op until Mark picks a destination, and the offsite passphrase needs to go into the password manager. Until both close, treat the backups as "restores the system, not the secrets."
✓ Done

Session revocation — removing someone now actually removes them

Live 2026-08-16 (slice 1). Portal sessions are stateless seven-day cookies, which means that before this shipped, deactivating a user did not end their session — a departed employee's or partner's browser kept working until the cookie expired on its own. Every request carrying an admin, bank-viewer, partner or act-as session is now checked against the account's live state. The design that survived review is a monotonic counter, not a timestamp: cookies record the generation they were minted under, deactivating increments it, and reinstating never rewinds it — so a deactivate-then-reinstate cycle permanently strands every cookie issued before it, including one stolen during the window. Offboarding is a single audited command (plus closing the mailbox), reversible, with a dry run by default.

The load-bearing lesson, learned twice: the check is tied to who the session belongs to, not to which URLs look sensitive — a URL list is a thing you forget to update. Two clock-based designs fell in review before the counter replaced them, because the database's default "now" is transaction-start time, so a deactivation that begins before a sign-in but commits after it stamps an earlier fence than the cookie it was supposed to kill. Seven adversarial rounds ending safe-to-merge, and the verification harness caught two real bugs during the build. Note for whoever ships next: rolling the portal back below this commit is a security rollback, and it breaks sign-in outright. Slice 2 (decided, not built): organization-level partner flags and plain merchant sessions.
✓ Done

Partners get the Fiserv channel picker too

Live 2026-08-16. A partner reported not seeing the Fiserv option that admin had gotten the day before. The cause wasn't a bug so much as an omission: the picker was only ever built for the admin invite flow. It now exists on the partner side as well, behind a per-partner permission that defaults to off — admin flips "Fiserv at invite" on a partner's page and the flip is recorded in the audit diff. Channel vocabulary and labels are single-sourced, so a channel added without copy fails to compile rather than rendering a blank card, and the authorization check lives inside the shared invite function rather than in the route — which means the partner API inherits the same gate for free when its spec adds the field.

Four ways an invite can now be refused, each deliberate: the partner isn't flagged (or their organization is deactivated), the channel is paused on the network page (invites could previously bypass a pause — both paths are fixed), the channel has no bank configured, or the bank record vanished mid-invite. That last one was caught in review as a real ordering defect: the first build sent the invite email and then failed, which would have left a merchant holding a link to a placement that didn't exist. ⚠ Carried forward: the admin invite route still holds a hand-maintained copy of the same creation logic — mirror comments point both ways, and any change to the shape has to be made twice until they're unified. Nobody was enabled at ship by design; the requesting partner gets flipped on. Superseded 2026-08-20: Mark's call flipped the policy — every partner is enabled and new partners start enabled, so the checkbox is now the opt-out rather than the opt-in.
✓ Done

Admin can invite a merchant on behalf of a partner

Live 2026-08-20. A partner's merchant could only be invited by the partner logging in and doing it themselves. Admin now invites into a partner's book straight from the partner's page — carrying the partner's attribution, the sourcing agent, and the channel choice, while the audit trail records the admin who actually acted. The deliberate call: the partner's channel permission binds the admin too. There is no bypass parameter, because one would have made the permission decorative — if admin needs a channel the partner isn't allowed, they flip the partner's flag first, and that flip is itself audited. Same day, a second decision retired the flag's default: the Fiserv channel is now on for every partner, so the checkbox reads as an opt-out.

The seam got deeper on the way through — the partner record is now read first on every invite path, which closed a pre-existing hole where an invite without a channel could still fill a deactivated organization's book from the partner portal or the API. Adversarial review found one high: a gap between "check that they're allowed" and "create the merchant" where a permission revoked in between still let the write through. The authorization now rides inside the creating statement, so a mid-flight revocation creates nothing at all and returns a safe-to-retry conflict rather than a half-made merchant. ⚠ Carried forward: that in-statement check hardcodes the one gated channel — a second gated channel has to extend both it and the permission helper together (tests fail if only one moves). No database migration. Gates all green: review 10/10, security clean, live walkthrough on synthetic data, 822 tests.
✓ Done

Applications signed on paper finally have a real way in

Live 2026-08-22. Some merchant applications get signed on paper, outside the portal entirely. Until this shipped there was no lane for them — every wet-signed deal on the books got there through a backfill script run by hand, while the send-for-signature screen promised an "upload the executed copy" path that had never been built. Admin now uploads the executed PDF from the same panel that generates the application: the portal files it as that merchant's signed application document, records who confirmed it and when, and the deal becomes eligible to be marked submitted on the strength of it. The confirmation is the attestation — a human vouching that the paper matches what the portal holds. If the underlying data has drifted since the document was generated, the upload warns and requires an explicit accept rather than refusing: the deal is already signed, and blocking the record of it would help nobody.

The invariant that took four adversarial rounds to get right: a paper copy must never discard a document the merchant already signed electronically. Uploading now cancels any outstanding electronic request before writing anything, because a cancelled signing link is recoverable and a discarded executed document is not. If the signing service answers "that one is already fully executed," the lane refuses to overwrite it and repairs the portal's own record instead. If the cancel result is genuinely uncertain, the request is left open in both systems and the screen names the two real remedies rather than guessing. Re-uploading the same file is treated as the same event, not a second one, and a failed filing can be retried without creating a duplicate. Gates: nine review findings fixed, four adversarial rounds each returning fixes, a fifth returning approve.
✓ Done

Partners can upload their own wet-signed applications

Live 2026-08-22. The second half of the same lane, and the one with an actual customer behind it: Venture Payment Solutions runs their own signature process, so their merchants only ever see VPS's brand. Because the portal has never generated an application for a VPS merchant, the path built is the blank-template one — the partner downloads the bank's blank paper, gets it signed their way, and uploads the executed copy from the merchant's page. An upload is a queue item, never a status. It lands as pending review, and pending is deliberately invisible to every surface that asks "is this application signed" — so an unreviewed file cannot masquerade as an executed one, and it cannot hide an executed one that already exists. Any admin reviews it: confirm turns it into the executed signature, or reject returns it with a note the partner reads on their own page ("Returned by Ainsworth: … — please re-upload").

Access is a per-partner permission, off by default, flipped by admin and recorded in the audit diff — and the permission is re-checked inside the write, not just before it, so a partner deactivated mid-upload gets a clean refusal rather than a half-made record. Only one upload can be pending per merchant at a time, enforced by the database rather than by hope. One deliberate secrecy carve-out: the blank bank template names the bank on its face and in its filename, which is accepted for a permissioned partner; everywhere else the bank stays unnamed. ⚠ Nobody is switched on at ship — VPS gets flipped on once TRX confirms that VPS may sign the inspector block, which is a condition to confirm rather than code to write. Next in this lane, and now shipped 08-23: the notification step — admin emailed on a new pending upload, the partner on confirm or reject. Gates: ten review findings fixed, one security finding fixed (a filename that leaked the bank), 857 tests, three adversarial rounds converging clean.
✓ Done

Every generated bank package now opens with a written case for the merchant

Live 2026-08-24. A bank package used to be documents and nothing else — the underwriter got the application, the statements and the corporate papers, and had to infer the story from them. An admin can now write a short bank-facing narrative per merchant in three parts — the opportunity, the business model, and document notes — and it renders as a branded PDF into every generated package, immediately after the executed application. Document notes is the part that earns its place: it is where "this company is six months old, so there are no two-year financials" gets said by us, in writing, instead of arriving at the bank as an unexplained gap. Writing one is never required — a merchant without a summary produces a soft warning at package time, never a block — and partners and merchants never see it. Ad-hoc Secure Send deliberately does not attach one, and the panel says so, because that lane sends a file somebody picked rather than a package the portal built.

The concurrency rule was made structural rather than careful. The path that updates a summary has no ability to create one — so an editor left open on a deleted summary can never resurrect it, which is exactly the case a conditional upsert quietly gets wrong. Deleting is likewise a single guarded statement that reports back which of "deleted", "already gone" or "someone else changed it" actually happened; the nine-case matrix was walked through live rather than reasoned about. One further discipline came out of adversarial review: a single reading of the merchant's name rules the whole package — the zip filenames, the summary PDF and the final commit check all anchor to it, so a rename mid-build aborts the package instead of shipping one with two different names inside it. The narrative is rendered before anything is uploaded, so a render failure can never leave a half-built package behind, and it reports as a retryable infrastructure error rather than a conflict. Gates: ten review findings fixed, three security findings fixed, four adversarial rounds to ship, 900 tests, three live walkthroughs with zero residue. Not incidental: the load-and-render step is deliberately the one seam the Fiserv boarding build will call when it uploads documents — written once, not copied.
✓ Done

A dead sign-in link now says why it died — and stops telling people to kill their own link

Live 2026-08-26. A sign-in link can die three ways — it was used, it was superseded by a newer one, or the account behind it was revoked — and the database recorded only that it had died. All three therefore rendered the same sentence: "This sign-in link has already been used." For supersession that sentence is false, and worse than false, it is self-feeding: it tells the reader to request a new link, and requesting a new link kills the one they were about to click. This was found on a real merchant. EB Trading, on 08-25, never clicked their invite link at all — they burned three tokens going around that loop before getting in. The fix records which death happened (consumed | superseded | revoked) and says so plainly: a superseded link is now a calm blue notice rather than a red error, because nothing went wrong and red framing on a no-fault message is exactly what makes a bounced user start over.

The diagnostic worth keeping is how the false report was proven: a token whose used_at matched the next token's created_at to the millisecond was killed by a reissue, not by a click — the signature of a frozen clock inside the request-link transaction. Identical means reissue; different means a real click. Three design calls carry weight beyond this feature. The reason rides alongside the existing "used" mark rather than in a new timestamp column, so the partial unique index that enforces at most one live link per user is left untouched — that index is the invariant, and it does not get widened. A revoked link now defers to the account reason, so a deactivated user finally reads about their account instead of a lie about a link. And an existing session outranks a dead link — a stale click while already signed in lands in the portal — but an unmatched token earns no such override, because a malformed or expired-out token is no evidence of whose link it is. Adversarial review then caught the cost of that refusal: it stranded a valid session on a page offering only "send me a link", so /login now offers an explicit — never automatic — way back in. Copy stays deliberately silent about email, because supersession proves a link was issued and never that it was delivered; a test now rejects any wording that promises otherwise. Gates: three adversarial rounds to approve, 925 tests, an eight-case live walkthrough on a synthetic login. Known adjacent gap, untouched: a collaborator unlinked from a merchant can still request a link and sign in to nowhere — the same deleted-user session gap already tracked elsewhere.
✓ Done

Admins can add and remove a merchant's teammates on the merchant's behalf

Live 2026-09-18. Until now only a merchant's primary contact could add teammates, from their own Team page and behind the legal collaborator-access acknowledgment — and merchants kept asking Ainsworth to do it for them. The admin merchant page now carries a Team card: primary contact, collaborators, invite and remove. An admin never accepts the primary's acknowledgment for them. Instead the admin gives an attestation — a checkbox plus a required note saying who at the merchant asked and how — and the primary contact is emailed who was added. The same five-collaborator cap applies, and remove is there as the undo for a mistyped invite. No migration.

One team-management seam sits behind both the merchant and the admin routes, so the collision, cap and removal rules are shared rather than hand-copied; the caller supplies only who stands behind the grant. Standing is judged by the actor's own live account row, never by their sign-in cookie, and the grant and its proof are written in one statement — the proof exists if and only if the grant landed. Moving a merchant's primary contact is a guarded script, not UI (used once, for one merchant). Adversarial review: approve with fixes, no critical or high findings; two medium and two low deferred. Known gap made more visible: merchant traffic is not re-checked per request, so a removed or demoted merchant user's cookie keeps working for up to 7 days — the Remove dialog says so, and the real fix is session-revocation slice 2.
✓ Done

The portal's two public pages now link the privacy policy

Live 2026-09-19. The privacy policy went up on the marketing site the day before; the portal's own sign-in and signup pages — the two places it collects a person's details before they have an account — now point at it. On sign-in it is a small centered link under the form, placed so it renders in every state the page can be in: the form itself, "check your email", rate-limited, and each error notice. On signup it reads as a sentence under the consent text — we handle your information as described in our privacy policy — with the link deliberately placed outside the consent label, so clicking it can never toggle the checkbox. Consent enforcement is unchanged: the form blocks submission without it and the server refuses anything else. Three files, no migration.

Both links open in a new tab, so a half-filled signup form is never lost, and they carry the no-referrer flag — which means the marketing site learns nothing about who came from where. The sign-in email hint already travels in a locked cookie rather than the address bar, so there is no path by which a person's details reach the link. The address itself is one constant in a single file, which is now the home for any future public legal link such as terms. Per Mark's colorblind rule the links are underlined rather than distinguished by color alone, and the gray passes contrast on both page backgrounds. Adversarial review: approve, nothing critical, high or medium; two low findings deferred — the new tab is not announced to screen-reader users, and four other surfaces that collect details still carry no link (the collaborator acknowledgment, both invite forms, and the invitation email). Not a code issue but worth naming: the policy text is still draft v0.1 with no counsel review, and the signup notice points merchants at that text as the governing description.
✓ Done

A merchant's contact can become a partner — and a merchant can change partner of record

Live 2026-09-22. A person on a merchant's team was becoming a referral partner in their own right, bringing that merchant with them. The portal allows one login per email address, and every partner-invite path refuses an address already held by a merchant login — so there was no way to do it without making them a second email. Two guarded admin scripts now cover it: one converts a merchant teammate's login into a partner login (same email, sessions revoked, optionally attached to the new partner org in the same write), and one re-homes a merchant into another partner's book, re-notifying the primary contact on the new partner's first Act-as. Both show everything they will touch, dry-run first, and require a written note on the audit record. No app-route change, no migration; used once the same day, with each production write run by Mark.

Both writes re-assert what the operator was shown. A re-home refuses a master-merchant target, a merchant with a sourcing agent still attached, or an abandoned merchant, and the single update re-checks the displayed book, split values and target liveness so nothing changed between look and write slips through; pricing is deliberately not re-seeded. Adversarial review: request changes, then approve on the third round. Known gap, pre-existing: the converted user's old merchant cookie keeps working for up to 7 days — the same session-revocation slice 2 item already tracked.
✓ Done

TRX's one application now covers all three of its banks

Live 2026-09-23. TRX sent a single merchant application that serves Chesapeake, Esquire and Metropolitan Commercial Bank, with TRX deciding which bank gets the deal. Recon showed it is the same paper the portal already fills — identical fields, text and render on all five pages — so no new template, mapper, e-sign or hash change was needed. The one real change: when choosing which paper a placement uses, the ISO's paper now wins over the bank's, and that rule is one shared expression read by both the admin picker and the wet-sign gate (the wet-sign copy had been a hand-kept twin). Across every ISO × bank pair in production exactly one result changes — TRX → Chesapeake now gets the TRX paper — and no existing placement, generated MPA or signature is touched. No migration.

Ops: create TRX placements with the bank blank until TRX names it, then set it on the placement. Deferred (medium, pre-existing, Mark to decide): admin generate, send-for-signature, admin wet upload and bank packages accept any registered paper — the picker only defaults — so a deal can still be hand-picked onto the wrong paper; the fix is a server-side check with an explicit admin override. Follow-on shipped 09-24: multi-bank TRX placements — one placement per bank, with the bank disclosed once the application is sent (next card).
✓ Done

One merchant, several banks at one ISO — multi-bank placements + disclosure policy v3

Live 2026-09-24 (e002c23). TRX may send one merchant to two or three of its banks (Chesapeake, Esquire, Metropolitan) on its one application. A placement is now one per merchant, ISO and bank, each with its own status, pends, VAR sheet and merchant account. An admin Add bank action creates the sibling placement; a live placement's bank can be named or corrected (its MIDs move with it) but never cleared. Disclosure policy v3: partners see the ISO from creation and the bank once the application is sent; merchants see ISO and bank only once sent, enforced in SQL. Partner decline emails name "ISO · bank"; merchant emails stay bank-agnostic, one per ISO per status. Merchant pends now show from every sent, still-open placement. Two migrations applied to prod by Mark before the code.

Reviewed hard: two live walkthroughs across three banks, /review (7 fixed), security review clean, adversarial review converged over three rounds — the last closed by locking the placement row before every write. A live walkthrough caught a comparison bug that would have failed every placement update. Deferred (Mark's call, pre-existing class): concurrent sibling approvals/declines can duplicate or drop a merchant email; add-bank vs abandon race (same gap as create); the North submit path silently no-ops when a North placement is already queued (North inert).
✓ Done

The MPA application form now follows the placement — no silent Merrick default

Live 2026-09-25 (434992e). With no placement saved, the admin Generate-MPA panel quietly defaulted to the Merrick form — an admin set TRX on an unsaved placement dropdown and generated two Merrick MPAs for a TRX deal (the same trap caught a merchant in July). The panel now reads the form from the placement: one form-bearing placement → that form, shown fixed, with a deliberate "use a different form" override that can be reverted; parallel channels on different forms → choose among only those, ISO named; no placement → nothing preselected and Generate / Send / Upload stay disabled until a form is chosen. At ship, 25 live merchants classified as 18 fixed, 6 none (previously silent Merrick) and 1 choice. The affected TRX MPA was regenerated on the right form, with Amex OptBlue added. No migration.

Reviewed: tests first (12 new), live admin walkthrough of every mode, /review (6 of 8 fixed), security review clean. Deferred (Mark's call): the guard is UI-only — the generate, send-for-signature and wet-upload routes still accept any registered form (the server-side check flagged 09-23 is still open); and the partner wet-sign lane still resolves only the newest placement's form, so with parallel channels a partner's wet upload of the older form is refused.
✓ Done

Banks can get a merchant's documents before the application is signed — and every merchant is now asked for processing statements

Live 2026-09-30 (67459a1). Banks — Fiserv asked first — want to see a merchant's documents before the application is signed, but a bank package required an executed MPA. An admin can now opt into a pre-signature package on the Secure bank package card: current documents plus the Executive Summary and no application of any vintage, with the file named as pre-signature. It is offered only while no executed MPA is on file; once one executes the server refuses it and the card says, with a warning mark and words, to regenerate the full package. Separately, checklist item C.5 is now "3 Months of Processing Statements (for merchants with prior processing)" and applies to every merchant. It stays conditional — it never counts toward progress or docs-ready — and a stale statement on it is an advisory warning rather than a block. One migration, applied before the code; the backfill added or renamed the item on 27 live merchants and fired no docs-ready notices.

Reviewed: tests first, a live admin walkthrough (pre-signature zip → simulated execution → regenerate prompt → full zip), code review (7 of 9 fixed), security review clean, adversarial review approved on the fourth round. Deferred (Mark's call, pre-existing class): the package's final check before commit is lock-free, so a document or signature change in the milliseconds between check and commit could activate a stale package. The signature half of that race was closed the same day by the e-sign version (two cards down); the document-set half remains.
✓ Done

Re-sending an application for signature can no longer lose, mask or duplicate a signed one

Live 2026-09-30, four ships in one day (2c41b0b, 7581faf, d538c9b, 4e12bc2). An architecture review found that re-sending an MPA for signature treated any failed cancel of the earlier request as "superseded" — including Dropbox Sign's refusal because the earlier request was already fully signed — and the completion webhook then deleted that superseded request's PDF. A legally signed application could be lost. The fix came in four steps. (1) A re-send only supersedes on a successful cancel; an already-executed prior is saved immediately, and neither finalizer ever deletes an executed PDF. (2) The re-send now reconciles prior requests before creating the new one, and the database allows only one live MPA request per merchant. (3) A send reservation is written before Dropbox Sign is called, so a request orphaned by a crash still blocks the next send and is adopted or cancelled afterwards — an executed orphan is never dropped. (4) Any executed MPA on file stops a new send unless the admin deliberately clicks Send anyway (merchant signs again), and a paper-signature filing and an e-sign send now exclude each other. Three migrations plus one post-deploy index, all applied by Mark.

Reviewed hard: tests first throughout, every changed statement rollback-verified against production, and the reservation slice alone took sixteen adversarial rounds to reach approve. The governing rule is fail closed: an uncertain lookup blocks the send rather than guessing. One deliberate exception (Mark): the paper lane still files a signed paper even when an older signing link's cancel is uncertain — a legally signed paper must always be fileable. Deferred: the Add Location lane still supersedes on a refused cancel (its PDFs are now kept); a request stuck at "signed" whose webhook never arrived is only finalized by a re-send.
✓ Done

One definition of "the executed MPA" — and the signed-application checklist item now follows it

Live 2026-09-30, five ships (5100439, 5a5f17d, c28cbbb, a524c2c, edbbc4c). "Which application is this merchant's signed one?" was answered by a hand-written rule copied into each place that asked. It is now one database view — the latest real MPA request per merchant, with test-mode signatures invisible everywhere — read by bank packages, the mark-submitted gate, the partner status API, the MPA Tracker, the local poller and the step that files the signed PDF onto the checklist. A per-merchant e-sign version, bumped by a database trigger, makes a package activation and a concurrent signature change collide instead of slipping past each other. Checklist item I.1 (signed MPA) is now system-filled: only the executed application fills it, every upload door refuses it, and delete, N/A and needs-revision are refused on it. And I.1's status follows the view: a re-send un-satisfies it and re-arms the docs-ready notice, a cancelled re-send restores it, and restoring an abandoned merchant re-syncs it. Four migrations, applied by Mark before each deploy.

The most serious finding was pre-existing since 08-22: deleting an I.1 document from the admin page hard-deleted the stored file — and that file is the executed MPA. The delete route now refuses system-filled items and never deletes a file an e-sign record references. Adversarial review also caught a half-transition that could leave I.1 with no current document, a deadlock, and a lost docs-ready re-fire; all fixed, with I.1's status now written by one function under one lock. A CI check fails the build if any consumer hand-writes the old rule again. A read-only audit of every I.1 row found three live records out of step; two had their executed applications re-filed by Mark the same day. Open for Mark: the third — one live merchant has a hand-uploaded signed application with no executed MPA behind it and needs converting through the wet-sign lane. Deferred: the same commit-window race for a package's document set, Executive Summary and merchant identity; an "executed" column on the view; driving system-filled from data rather than a constant; one accepted low risk on hand-edited request IDs.
✓ Done

The admin Merchants list shows each merchant's partner

Live 2026-09-30 (4c0c92f). A Partner column now sits between Merchant and Vertical on the admin Merchants list, showing the partner of record, or "Direct" when there is none — the same label the Placements page uses. A master merchant's sub-merchant shows the master merchant; the referrer is deliberately not shown. The table also scrolls sideways now: its old wrapper clipped the right-hand columns on narrower screens, and the extra column would have made that worse. One file, no migration.

Verified read-only against production — the new column matches the partner of record on all 27 live merchants (24 with a partner, 3 direct). Code, security and adversarial review: no findings.
✓ Done

A merchant's additional websites now get their own bank forms automatically — sent for signature with the MPA

Live 2026-10-01, three ships (bb15b29, 5763ee1, ecfe141). A merchant needed a URL statement descriptor, its own consumer phone and a support email on the bank paper, and nothing in the portal could carry them. Now, on TRX and iBill paper: the application has a statement descriptor separate from the DBA; any merchant listing more than one website is asked for per-website details (brand name required, phone and support email optional); each described additional website becomes a Location created from the application; and the Add Location forms go out for signature automatically with the MPA — the lead website rides the MPA itself. Long DBAs, URLs and emails now print in full on the form instead of being clipped, and the fill refuses a value that still cannot fit. The same evening the lane got the MPA lane's send reservation: every batch send reserves before Dropbox Sign is called, the webhook adopts a request the portal never recorded, and the next send settles a stale reservation first — never on a guess. A signed form that no longer matches its Locations is kept and listed on the admin Locations panel with its download; executed paper is never dropped. Four migrations, applied before each deploy.

Reviewed: adversarial review approved at round five on the automation and round three on the reservation; the automation was verified live against real Dropbox Sign in test mode with zero residue. A pre-existing defect surfaced while verifying: PDF bytes baked by MuPDF were a view into memory that could be overwritten before use — worst for multi-form batches, which now go out automatically. Fixed the same day; no evidence a stored or sent document was affected. Neither new field made any stored or signed MPA stale. Owed: the reservation's live verify must be re-run — all six scenarios — once Dropbox Sign's test-mode cap of ten emails a day has reset. It was scheduled for 19:45 ET on 10-02; no result is recorded as of 10-03. Until then the failure mode is fail-closed: an Add Location send errors for the admin; MPA sends are unaffected. Deferred: signed-paper integrity (shipped 10-02, its own card below); a website added after admin review gets its form sent on the next MPA send with no separate approval (by design); no automated test executes the batch send (the live script does).
✓ Done

Every application now states a customer service email — and TRX's paper prints it

Live 2026-10-01 (0e4a4fb). A bank ask turned into a feature the same day. TRX's application has a contact email line and no customer service email line; TRX confirmed the customer service email belongs in that spot. The portal now always asks for both a contact email and a customer service email on the Processing step — they may be the same address, but the merchant states it — because banks ask for it on paper or in their boarding systems. The TRX MPA prints it on the contact email line, the Add Location form falls back to it, and the admin merchant header shows it. Forward only: no backfill and nobody blocked. One migration, applied before the deploy.

No stored or signed MPA went stale — an unset value hashes as if the field did not exist. Code review (3 fixed), security pass clean, adversarial review: no findings. Deferred (Mark): re-saving the Processing step after signing with a customer service email equal to the contact email makes an executed TRX MPA read stale on byte-identical paper.
✓ Done

Security hotfix — a logged Dropbox Sign error can no longer carry the API key

Live 2026-10-01 (c21a394). Found in the security review of the Add Location reservation branch, and live on main until this merge: the Dropbox Sign SDK's error objects hold the whole request, so logging a caught error could write the authorization header — and for network errors the key itself — into the runtime logs. Several call sites logged errors whole. Every SDK call now runs through one wrapper that rethrows an error holding only the message, status and the API's three error fields, each bounded and scrubbed of the key. The short-lived signed-PDF download URL is kept out of errors too. A build-time check fails if any other module imports the SDK or any call skips the wrapper.

Open — Mark's call: rotate the Dropbox Sign API key. Whether the key actually reached the hosting provider's runtime logs was not checked — the build session had no log access. Deferred (low): several callers read the API's rejection reason from the wrong field name, so the reason is lost in logs and admin messages — pre-existing, separate task.
✓ Done

Add Location signed-paper integrity — a re-send can no longer leave two signable forms for one website

Live 2026-10-02 (2497e1b), no migration. Named by Mark on 10-01 from findings the adversarial review raised on the reservation branch, two of them pre-existing since 07-24. Re-sending Add Location forms from the admin panel is now cancel-first, the MPA lane's rule: an earlier batch is closed only when Dropbox Sign proves it can no longer be signed, a batch that turns out to be signed is finalized with its PDF saved, and when the answer is uncertain nothing new is sent. Two new admin actions: Cancel signing request (the way to correct a locked detail, with no automatic recall on edit) and Check earlier send (settles a stale reservation without sending). Printed details are locked while a form is out — brand name, phone and support email from the moment it is sent; the MCC only while it is out for signature, so the bank-assigned code can still be recorded afterwards. Dropbox Sign's cancel event now closes an Add Location request, and signed paper that arrives after a cancel is still adopted, never dropped.

Reviewed: code review (7 fixed), adversarial review approved at round three. Not verified live: a successful cancel and a real cancel event — the Dropbox Sign test-email cap was exhausted; the live check and a cancel probe were scheduled for 19:45 ET on 10-02 and no result is recorded as of 10-03. Open — Mark's call after the probe: Dropbox Sign's cancel call is asynchronous, in both the MPA and Add Location lanes, so a success answer is not proof the request is dead; for a short window two forms can be live, and "the old signing link no longer works" is said slightly early. Pre-existing and not made worse by this slice. Deferred (logged): a missed decline closed during a re-send is recorded as superseded with no problem notification; a request in error that Dropbox Sign cannot be asked about keeps blocking panel re-sends (fail closed, as in the MPA lane); merchant-level details printed on the form can still change while it is out; no admin screen to change the MCC of a Location that already has one.
✓ Done

The E-signature card now says who has signed and who has not — with a Send reminder button

Live 2026-10-05 (810f7eb), no migration; the status label fix followed the same day (a1050a8). Found on a live merchant: the Secure bank package card refused with "Needs an executed MPA" although the merchant had signed. The portal keeps one status per signing request and stamps it "signed" on the first signature — so a two-signer TRX or iBill application reads "Signed" while it still waits on the Ainsworth inspector, and nothing on the page said so. Now an application that is out for signature shows each signer — the merchant and the inspector — as Signed or Awaiting signature (icon and words, with the signer's name and the signed, opened and reminded times), read live from Dropbox Sign and never stored; the page never waits on the lookup, and after five seconds the card says the status is unavailable rather than stalling. A Send reminder button re-emails one signer; who may be reminded is decided from Dropbox Sign's live status (awaiting, not reminded inside the hour, request not declined or fully signed, merchant not abandoned), and the reminder is on record before it is sent, so a reminder can never go out unaudited. The package card's hint now reads "the MPA is still out for signature" instead of demanding an executed one, and the admin label for the half-signed state reads "Signed — not yet executed" on the merchant page and the Merchants list.

Admin-only, MPA lane only — Add Location requests are out of scope. Verified read-only against the live merchant after deploy: merchant signed, inspector never opened, Dropbox Sign's own automatic reminder three days after the send. The reminder was not exercised against live Dropbox Sign — Mark's first use is the proof. Deferred: a request fully signed at Dropbox Sign but stuck at "signed" (a missed completion callback) is now shown but still has no one-click finalize; three older sites read the API's rejection reason from the wrong field, so the reason is lost there.
✓ Done

The inspection block is signed by the agent who brought the deal in — never whoever clicks Send

Live 2026-10-05 (5c0dd93), migration applied before the merge. TRX and iBill paper carry an inspection block (the survey section, "inspector's signature"). Until now its signer was whichever admin pressed Send, which is how a live application went out with the wrong Ainsworth name on it and sat waiting on him. Mark's rule changed twice in one day; the first version ("Mark or Jeff only") was built, reviewed and never released. The rule that shipped: the merchant's sourcing agent signs by default; with no agent named, the partner's designated MPA signer (a new per-partner setting on the admin partner page, one of that partner's own people); for a direct merchant, one of Ainsworth's MPA signers (a new Settings card listing which admins may sign, and the default). The sender can always switch a partner deal to someone at Ainsworth — offered, never preselected while an agent or partner signer exists. A deactivated partner's people sign nothing; API service accounts are never signers. The signer is validated against that merchant's live candidates before any work, again before the earlier request is cancelled, and again right before Dropbox Sign is called — a signer who is gone stops the send with the earlier request intact. The signing email now addresses both people by role ("Sponsor agent: please review and sign the survey section"). Reassign hands an outstanding inspection signature to another candidate without re-sending to the merchant; a follow-up the same day makes the card say why Reassign is missing when nobody can be reassigned to, and what to set.

Mark accepted that the inspection signer — an outside agent or partner admin — sees the whole application on the signing page. Not verified: a real send end to end, and whether Dropbox Sign allows swapping a signer on a half-signed request — the live merchant is the first use. Until Mark sets the Settings list and each partner's signer, a TRX / iBill send with no sourcing agent is refused by design — see the action items. Deferred: a signer who stops qualifying after a request went out keeps it until an admin uses Reassign; requests already out keep the old email wording; no lock in the ~1 s between the last signer check and Dropbox Sign's answer.

▶ In flight

▶ In Progress

Fiserv Marketplace SMB boarding API — board a merchant by API instead of by paper

Plan written 2026-08-18, kickoff call held 2026-08-19 (Acquirers/Fiserv/APIs MarketPlace/INTEGRATION_PLAN.md, outcomes in §8a). US merchants only. This is Fiserv's own boarding platform: create an order, pull their agreement, upload documents, submit to underwriting, then poll — and on approval it hands back the MID and the TID, which is the step that today waits on a VAR sheet arriving by email. The portal turns out to be well placed for it by accident of timing: the Fiserv channel shipped 08-15, and the order-profile fields it added (delivery timing, the volume and acceptance mixes) are exactly what the API asks for. The kickoff settled the open questions, and two of the answers retire other work. Build against V3 boarding, not V1. Fiserv returns the merchant-populated agreement as HTML and sends no emails itself, so we render it and capture the signature — which makes the fillable MPA we authored a paper fallback at best on this lane, not something worth chasing approval on. The NAU credit check is replaced by the boarding flow entirely. Cost-plus at 900–1,000 bps over interchange is acceptable to them. Still open on Fiserv's side: a high-risk routing identifier for the credit team, and the certified gateway list.

2026-08-24 was the day this item stopped being blocked and started being built. The authentication wall that had held it since 08-19 turned out never to have been a signing problem at all — the recipe matched Fiserv's own sample exactly; the stored secret was simply a stale, truncated value, and the real linked pair arrived inside the collection Fiserv sent. With that corrected the sandbox answered on the first try. The exploratory run that followed exercised the whole reachable surface: the category and product lookups return, a full boarding payload validates, and — after the second false alarm of the day — an order boards, and Fiserv returns the agreement. The pricing failure that looked like a missing price list on our profile was actually a product combination nobody had priced; the priced combination works, on cost-plus as well as tiered. That is a build constraint, not a footnote: the portal has to keep merchants inside priced combinations rather than letting an admin assemble one that fails at the bank with an unreadable system error.

The agreement response turns out to be the entire application, not just the terms — business and owner detail, locations, entitlements and the fee schedule that was just boarded, and it arrives with the sensitive fields already masked, so the screen a merchant signs on carries no raw identifiers. It also states exactly which signature blocks it expects and confirms remote signing is enabled on the Ainsworth profile, which is precisely the two-mode design Mark locked the same day: signature captured in the portal by default, and partners who run their own signing process download and sign remotely. A 41-page sample was assembled from a real sandbox order to prove it end to end. The portal build itself has been grilled and is a conditional go.

What's left is two switches on Fiserv's side and one answer from us. Document upload and order submission still need enabling before the last two steps of the flow can be exercised — asked, and we've confirmed we want both. The answer we owe is commercial rather than technical: Fiserv prices per element as a band — a floor, a default and a ceiling — and the per-deal number is chosen inside that band at boarding, which means the floor Ainsworth sets is the discipline no admin can price below. A draft schedule has gone over for format confirmation, and the real numbers wait on our own cost side — the per-chargeback, retrieval and ACH-return rates we're charged — which isn't written down anywhere we hold. Both temporary watchers are green and silent, and come off the machine when this phase lands.
▶ In Progress

Fiserv Canada — the paper lane for Canadian merchants, approved by the bank on first read

Drafted 2026-09-09, reviewed by Fiserv Canada 2026-09-10, v0.2 built the same day, v0.3 — Fiserv co-brand plus French — built 2026-09-11. This is a second, entirely separate Fiserv relationship from the boarding API above — a different legal entity with its own underwriting, and a referral lane rather than an integration: Ainsworth fills an intake per merchant, Fiserv Canada drafts the merchant agreement in their own portal, the merchant e-signs from inside Canada, and their credit team reviews. Ten approved accounts come before a residual agreement. The reason it exists at all is a gap in our own product: the portal cannot board a Canadian merchant — employer ID, state, routing number and social security number are all US-shaped gates — so until a Canadian lane is grilled into the portal, this paper lane is the Canadian process, not a workaround around it.

What was built is two fillable, Ainsworth-branded intake forms generated from Fiserv Canada's own spreadsheet — a storefront / multi-location form carrying a column per location, and an e-commerce form covering the online sales profile and the website behind it — plus a machine-readable map from every field back to the row it came from on their sheet. That map is the deliberate seam: it is what a later script would read to turn a filled form straight into their spreadsheet, so the manual lane can become an automated one without redrawing the forms.

v0.3, on 09-11, made the forms bilingual and co-branded — and kept the seam intact while doing it. Fiserv's own wordmark now sits beside Ainsworth's on the masthead, which is not a liberty taken: the logo was requested by Fiserv's senior contact on the thread, so co-branding is authorized rather than assumed. Both forms also exist in Canadian French, four PDFs out of one generator driven by a two-language string table. The part that matters structurally is that field names and export values are identical in both languages, asserted at build time — the French entity-type dropdown shows French labels but exports the English values — so a French return maps onto Fiserv's spreadsheet exactly like an English one and the single field map still covers everything. Translating the forms did not fork the lane. The French is machine-drafted, though, and needs a native proofread before it reaches a merchant; that is the one new item this version put on Mark's list.

The review came back clean with a single addition — the forms "look great", and the one missing item was Articles of Incorporation, a Canadian requirement with no exact US counterpart on our existing document lists. It was added and v0.2 replaced v0.1. Two of the four process questions closed themselves in v0.3 — Quebec's French is built, and the co-branding permission turned out to have come from Fiserv in the first place. What's left is Mark's, not engineering's: send v0.3 and settle how filled forms come back (the PDF, their spreadsheet, or both), close the two questions still standing — a secure channel for bank details, and which entity's mailbox sends a form branded Ainsworth on a thread running from QuickRefund addresses — get the French proofread by a Canadian reader, and name the actual Canadian merchant the lane was opened for, which is still unidentified. Nothing has been sent to a merchant yet.
▶ In Progress

Phase 7 — remaining slices

Shipped since: docs-ready choke point + MPA data-staleness gate (2026-07-11); local poller now also detects + texts act-as strands (2026-07-16, runs 3×/day). Still specced, not built: external re-upload nudges, Utah LLM judgment pass + standards.ts, scheduled sweep-job backstop, daily digest, bank-match sheet, auto-MPA trigger.

Dependencies: auto-MPA trigger slice waits on nothing (Phase 5 is shipped)

○ Ready — no dependencies

▶ In Progress

A declined or errored re-send should keep the earlier executed MPA

Named by Mark on 2026-09-30 as the next slice; built 2026-10-01 on a branch (60406b2), not merged. Today, if a merchant with a signed application is sent a new one and declines it — or the request errors — the declined request becomes the latest row, so the checklist's signed-application item drops to not started and a full bank package is refused, even though the earlier signed application is still valid. The fix is an executed-pick view that skips declined and errored requests and falls back to the earlier executed MPA, with every executed consumer moved onto it: the I.1 sync, bank packages and their activation guard, the mark-submitted gate, the MPA Tracker, the poller and the filing guard. The partner status API keeps the current view, so partners still see "declined".

Dependencies: none — the Executed MPA view and its consumers shipped 09-30. Until it lands the behaviour is consistent rather than wrong: the checklist and the package agree with each other. Before it merges: the review gates have not run yet, its migration is unapplied, and the migration's filename now collides with one that shipped on 10-01 — it must be renumbered first.
✓ Done

Phase 6.5 — agent agreements

Merged + live 2026-07-04. Editable Schedule A splits, ordered Dropbox Sign agreement flow, manual activation gate. Closes the agent onboarding + payment loop. Remaining: a real signed completion + prod Dropbox Sign plan follow-ups.

✓ Done

Wet-sign step 2 — the review notifications

Live 2026-08-23. The lane worked end to end, but both sides had to go looking: a partner uploaded and nothing told an admin there was something in the review queue; an admin confirmed or rejected and nothing told the partner. On a lane whose entire premise is that a human confirmation is the attestation, a confirmation sitting unseen was the weak point. Three emails close it — a new pending upload alerts the internal distro (Mark and Jeff), and confirm or reject reaches the sourcing partner's own admins. The confirmation email says the application was accepted and never promises it is on file with anyone, because filing can still be mid-repair and the portal should not claim a bank send it has not made. A rejection carries Ainsworth's note verbatim plus a re-upload prompt, and the same email covers the case where an admin's own upload replaces a partner's pending copy. Mark approved all three partner-facing bodies word for word before ship.

Two disciplines worth keeping. The per-type mute toggle genuinely mutes — the recipients are the subscription rows themselves, so switching it off sends nothing rather than sending to a filter — and every send fires only on the branch that actually changed something, so a retry, a repair re-post or a duplicate submit never emails anyone twice (proven on the live walkthrough, not assumed). Mail is best-effort and can never block or fail the write it reports on. Three defects came out of the work, all pre-existing: the field reporting how many pending uploads an admin's upload replaced had been structurally zero since the partner slice shipped; the note explaining a replacement was being spliced into the database statement as text, where one apostrophe would have broken every admin upload (now bound as a parameter and stamped inside the superseding statement); and the test file pinning the mute behaviour had never run in the suite — now included and passing. Recipients exclude deactivated logins and abandoned merchants. Gates: ten review findings fixed, one security finding fixed, 892 tests, the lane verifier extended and green, two live walkthroughs with zero residue, adversarial review returning one high that was verified and rebutted, then approve.
▶ In Progress

Encode the compliance rubric — the pharmacy vertical is complete; thirteen remain

Research delivered 2026-09-04; the code half started and shipped twice on 2026-09-05. The portal already screened merchant websites, but the rules it screened against were assembled ad hoc. The new rubric replaces that with a written standard: a universal baseline plus fourteen high-risk verticals — supplements, nutra and nutra affiliate funnels, gaming and gambling, MLM, crypto, dating, digital services, firearms, health and medical, subscriptions and continuity, research-use-only peptides sold business-to-business, pharmacy and telehealth including GLP-1, and legal services — 271 checks, each carrying its severity, the condition that makes it apply, the on-page signals that trigger it, the rule behind it, why a sponsor bank cares, and the remediation to hand a merchant. Two slices are now live. The schema eb24f21 gave the engine the shape the rubric needs: three severities (stop / flag / informational, with the old telehealth and required labels demoted to display tags), a manual mode for checks a machine cannot judge — those render as a reviewer checklist whose answers are deliberately never stored — six business-context toggles that set themselves from the detected vertical and can be overridden by the agent, and a new pharmacy vertical. It also closed a quieter hole: a thin crawl can no longer return a clean verdict, because absence of evidence on a page nobody could read is not evidence of compliance. Then the first vertical went in 5a5c0c4 — fourteen pharmacy / telehealth / GLP-1 rules, eleven of them automated — and batch 2 completed it on 09-06 3063715 with six more. A merchant screened today is genuinely and fully screened against the new standard for that vertical; thirteen verticals remain.

The hard problem turned out to be telling a sale from a warning. Roughly half the review effort went into a single mechanism: a page that says "we will never sell you semaglutide without a prescription" must not be flagged as selling it, while a page that says "no prescription needed" must be. What shipped is an offer-governance model — a negation, a warning verb, a warning-shaped heading standing over a section, or an FAQ question all suppress the offer beneath them, and nothing written after an offer can retract it. The ordering rules matter commercially too: real pharmacy credentials now outrank cannabis vocabulary in vertical detection, but a telehealth prescriber alone does not, so a medical-marijuana telehealth site stays cannabis. Mark made one calibration call by hand — compounded GLP-1 sold from a price list is a flag with a reviewer follow-up, not a stop — which is exactly the kind of judgment the bank calibration ask is meant to settle at scale. Gates: 925 tests plus the screening suites, fifteen review findings fixed, six adversarial rounds fixed, and live passes against seven real pharmacy sites. That open thread is now closed: the credits were refilled during batch 2, the owed post-merge pass ran and fixed six reproduced defects, and the final round returned approve. Batch 2 then finished the vertical on 2026-09-06 3063715 — six more checks, all flags, all of them firing on what a page leaves out: no checkable accreditation, a compounded drug with no named compounder, a drug sold beside a benefit claim with no risk language, a dispensing site with no pharmacist and no reachable line, animal prescription drugs with no vet requirement, and an "FDA-registered" badge with no non-approval disclosure. Each fires with the reviewer’s question attached. The lesson from its sixteen adversarial rounds is the transferable part: three exemptions added after batch 1 merged were cut back out, because each one turned out to be a route an evasive page could take — so the posture is now that a stop fails toward the reviewer, accepting a handful of human-resolved false stops rather than a carve-out an evader can drive through. The crawler was hardened with it: every redirect hop is checked and the checked address is the one fetched, and treatment / medication pages are now read as product pages. Eight live pharmacy sites calibrated it. Next: the peptide business-to-business vertical, then the rest in the written risk order. Scoring — a 0–100 risk number in four bands with a categorical stop overriding the arithmetic — is deliberately deferred until two verticals are encoded, so the numbers get calibrated against real bank decisions rather than guessed. Carried caveats: a handful of existing coded rules still need citation corrections, and the Mastercard citations rest on secondary sources because the rulebooks were bot-walled.

⏸ Blocked / held

⏸ Blocked

Esquire MPA mapper

Schema reconciliation can't start without a fillable Esquire PDF. Need to chase Esquire for one. Less urgent since 2026-08-06: the iBill paper now covers the iBill → Esquire channel end to end, including e-sign, so the deals actually flowing to Esquire today do get a portal-generated MPA. This card is for Esquire's own paper on a direct channel.

Blocker: Mark/Jeff to request fillable PDF from Esquire
✓ Done

Guided e-sign — verified end-to-end in production

Shipped + verified 2026-06-10. Hosted signing (Dropbox Sign emails the merchant a secure link; API Essentials plan, $75/mo, 50 docs/mo — embedded-in-portal signing is gated to the $250/mo tier, deliberately skipped). Synovus + PB&T merchants are walked through 8 guided fields — 4 signatures (officer, personal guarantee, Program Guide confirmation, FinCEN cert) + 4 auto-dates — with print names / title / legal name pre-stamped on the PDF. Mark signed the ZZ TEST package live: all 4 signatures on their lines. Merrick + Maverick keep freeform signing until they get their own placement pass.

⏸ Held

Residual: CSV ingest connector

Next residual-automation slice — input automation (currently admin enters by hand). Framework first, then per-bank mapping. Note: CRM P4 shipped a generic CSV landing/apply console for metrics, which is a template for this one but not a substitute — residual statements are a different shape.

Blocker: needs a real upstream CSV/Excel export sample
⏸ Held

IRIS v1.5b — push side

Three numeric IDs still needed (Tab ID, Label IDs, owners catId). All API-discoverable during v1.5a build — Aaron email is fallback only.

Dependencies: v1.5a built first (gives auth to self-serve discovery)
⏸ Held

Phase 6 deferred: PDF statement parsers

Corduro / TRX parsers — highest-leverage Phase 6 next step. Reuses Phase 7's lib/utah/extract.ts primitive.

Blocker: sample PDFs from Jeff
Ticketing System Deferred by design
▶ In Progress

Planning in Co-Work

Persistent project home in Co-Work. Reads PORTAL_CONTEXT_FOR_TICKETING_PROJECT.md. Resolves 9 open questions → produces PLAN.md.

Dependencies: none for planning
✓ Done

Handoff doc prepared

Context dump at Ticketing System/PORTAL_CONTEXT_FOR_TICKETING_PROJECT.md for the Co-Work session to read cold.

⏸ Build Deferred

Ticketing v1 build

Merchant + partner support tickets, role-isolated views, reuses portal's notification + UI primitives.

Trigger gates (all must be true):
1. Phase 7 notification slice fully shipped — still open
2. All active phases at clean committed state
3. At most 1 other portal build session active
Forcing date LAPSED: the June 15, 2026 reopen date passed without a decision. Gate 1 is still the real blocker — needs an explicit build-or-defer call from Mark.
✓ Done

Discoverability nudge (related work)

"Invite a teammate" inline nudge on /dashboard for primary contacts with no collaborators. Retires once they invite anyone.

⚠ Action items requiring Mark (not coding)

These are blockers that no amount of engineering will clear — Mark needs to chase the artifact or sign a contract.

Dependency map — what's free vs. what's blocked

Website + Phases 1-9 + Intake API + pricing models + the 2026-07 admin-ops layer (tracker boards, secure delivery, Add Location, sidebar nav) all shipped. The CRM portal-extension sequence ran five slices in three days (P1 08-01, P2 + P3 08-02, P4 + P5 08-03); 2026-08-05 closed the whole tracker arc in a single day; and 2026-08-06 landed five more — the iBill → Esquire channel and its guided e-sign, abandon closing in-flight placements, the partner activation fix with W-9 capture, and the executed agreement download. 2026-08-09 added the first usability slice — declined and errored signature requests now email the admin distro. 2026-08-10 changed the shape of this map for the first time in weeks: the data gate that both P4 and P5 were parked behind is closed — real gateway data is ingested, reconciled to the cent, and refreshing nightly on its own. What's left of those two gates is one alert-resolution pass and one email, both Mark's, neither engineering. 2026-08-12 taught the URL Tracker which bank channel a location was actually sent to, retiring the hardcoded "TRX" that had been quietly mislabelling Hagen Pay sends. 2026-08-22 closed the last structural gap in the signature workflow — both slices of the wet-sign lane shipped the same day, so an application signed on paper now enters through a real, reviewed path instead of a hand-run backfill, and partners who sign under their own brand have a permissioned upload lane of their own; 2026-08-23 finished the lane with the review notifications, so neither side of that human confirmation has to go looking any more. 2026-08-24 moved the map in both directions at once — the Executive Summary shipped, putting a written bank-facing case at the front of every generated package, and the Fiserv Marketplace boarding API left the blocked column entirely after both of its blockers turned out to be misdiagnosed on the same day. 2026-08-26 closed a small but corrosive one — a dead sign-in link now says which of the three deaths it suffered, ending the loop where asking for a new link was what killed the old one. Nothing is sitting finished-but-unshipped. Two pieces of engineering still carry a hard ordering rather than a priority, and both point at the same event: the MPA-queue entry gate and the abandon/create race residual must precede North activation.

No dependencies — can start anytime

  • Phase 7 remaining slices (LLM judgment, nudges, scheduled sweep, daily digest, bank-match, auto-MPA trigger). → Phase 5 stable, so auto-MPA-trigger slice is now also unblocked.
  • Phase 6.5 (agent agreements). → SHIPPED 2026-07-04 — see Done cards above.
  • VAR sheet import. → MERGED + LIVE 2026-08-05. Superseded CRM P2 slice 2 — importing the sheet creates the registry row and takes the placement live in one motion.
  • Pends at book scale. → MERGED + LIVE 2026-08-05. Board search/paging/A–Z plus pends on the admin and merchant records.
  • MPA tracker re-key (both slices) + merchants-in-flight cleanup. → MERGED + LIVE 2026-08-05. The tracker-grill arc is fully closed; the legacy stubs are deleted.
  • iBill → Esquire channel + guided e-sign. → MERGED + LIVE 2026-08-06, both slices. Sixth MPA paper; Highwire's new default channel.
  • Abandon closes in-flight placements. → MERGED 2026-08-06, backfill applied (12 stale placements withdrawn, 0 notifications queued).
  • Partner activation + W-9 / payout account name + agreement download. → MERGED + LIVE 2026-08-06. Activation now links to an existing partner instead of minting a duplicate.
  • E-sign problem notifications (usability slice 1). → MERGED + LIVE 2026-08-09. Declined/errored signature requests email the admin distro; the dispatcher hardening it brought applies to every outbox type. The other three notification families are cut, not dropped — each has a written trigger.
  • NMI gateway pull + nightly automated ingestion. → MERGED + LIVE 2026-08-10, both halves. Real data reconciled to the cent, applied through the console, and now pulling itself every night at 10:00 UTC — the first automated run reproduced the manual import byte-for-byte.
  • URL Tracker bank channel + wet signature. → MERGED + LIVE 2026-08-12. A location row records which bank channel it was sent to and whether it was signed on paper; the board stops printing "TRX" at everything. The wet-signature lifecycle lane was deferred pending Mark's product call — that call was made 2026-08-22 and the lane shipped as its own two slices.
  • Fiserv placement channel + Fiserv MPA paper. → MERGED + LIVE 2026-08-15. Seventh paper, and the only one Ainsworth authored — still a DRAFT until Fiserv approves it, which is their call, not a build dependency.
  • Tier 0 + Tier 1 backups. → SHIPPED + RUNNING 2026-08-16, nightly. Restores the system; does NOT yet restore encrypted PII — the key rotation that closes that is grilled and GO but slipped its 08-22 slot and still needs a morning with Mark. The offsite leg still waits on a destination.
  • Session revocation (slice 1). → MERGED + LIVE 2026-08-16. Admin, bank, partner and act-as sessions die on deactivation. Slice 2 (partner org flags + merchant sessions) is decided and unblocked.
  • Partner parity for the Fiserv invite picker. → MERGED + LIVE 2026-08-16. Policy reversed 2026-08-20: the permission now defaults ON for every partner, so the checkbox is the opt-out rather than the opt-in.
  • Admin-on-behalf partner invite. → MERGED + LIVE 2026-08-20. Admin invites into a partner's book with the partner's attribution; the partner's channel permission binds the admin, and the authorization now rides inside the creating statement so a mid-flight revocation creates nothing.
  • Wet-sign lane — admin tracer (slice 1). → MERGED + LIVE 2026-08-22. Applications signed on paper have a real upload path; the confirmation is the attestation, and a paper copy can never discard an electronically executed document.
  • Wet-sign lane — partner path (slice 2). → MERGED + LIVE 2026-08-22. Permissioned partners upload their own executed applications as pending review; any admin confirms or returns them with a note. Nobody is switched on yet — VPS waits on TRX's answer about the inspector block.
  • Wet-sign step 2 — review notifications. → MERGED + LIVE 2026-08-23. Three emails: pending upload to the internal distro, confirm and reject to the sourcing partner’s admins with the rejection note verbatim. The per-type toggle genuinely mutes, and sends fire only on the branch that changed something, so repairs and retries never re-notify.
  • Executive Summary in the bank package. → MERGED + LIVE 2026-08-24. An admin-written opportunity / business-model / document-notes narrative renders into every generated package; absent is a soft warning, never a block. Its load-and-render step is deliberately the seam the Fiserv document upload will call.
  • Pre-signature bank package + processing statements (C.5) for every merchant. → MERGED + LIVE 2026-09-30. Documents plus the Executive Summary can go to a bank before the MPA is signed; the server refuses a pre-signature package once an MPA executes.
  • MPA re-send chain. → MERGED + LIVE 2026-09-30, four ships. An executed MPA is never superseded or deleted; one live MPA request per merchant; a send reservation before Dropbox Sign is called; any executed MPA on file requires an explicit Send anyway.
  • Executed MPA view + system-filled I.1. → MERGED + LIVE 2026-09-30, five ships. One view answers "which MPA is the signed one" for every consumer; test rows are invisible; I.1 is filled only by the executed MPA and its status follows the view.
  • Admin Merchants list — Partner column. → MERGED + LIVE 2026-09-30. Partner of record, or "Direct".
  • TRX / iBill Add Location automation + send reservation. → MERGED + LIVE 2026-10-01, three ships. Statement descriptor, per-website details, Locations created from the application, forms sent with the MPA; every batch send reserves before Dropbox Sign is called. The reservation's live verify was scheduled for 19:45 ET 10-02; no result recorded as of 10-03.
  • Customer service email. → MERGED + LIVE 2026-10-01. Collected on the Processing step, printed on TRX's contact email line.
  • Dropbox Sign error redaction (security hotfix). → MERGED + LIVE 2026-10-01. Errors no longer carry the request or the API key. Key rotation is Mark's call.
  • Declined or errored re-send keeps the earlier executed MPA. → IN FLIGHT — built 2026-10-01 on a branch, unmerged. Review gates next; its migration must be renumbered (filename collision with a 10-01 ship) and applied before merge.
  • Add Location signed-paper integrity. → MERGED + LIVE 2026-10-02. Cancel-first panel re-send, Cancel signing request and Check earlier send, printed details locked while a form is out, cancel event closes the request. Open: Dropbox Sign's cancel is asynchronous in both lanes — Mark's call after the cancel probe.
  • Signer status on the E-signature card + Send reminder. → MERGED + LIVE 2026-10-05. Who has signed and who has not, live from Dropbox Sign; an audited reminder; the half-signed label reads "Signed — not yet executed".
  • Inspection signer = the agent who brought the deal in. → MERGED + LIVE 2026-10-05. Sourcing agent, else the partner's designated signer, else Ainsworth's MPA signers; Reassign. Mark still owes the Settings list, each partner's signer, and the reassign on the live merchant.
  • Pre-North checklist (two items). → Nothing blocks building either, but both must land BEFORE North activation — North placements carry no signature row so they can't enter the re-keyed queue, and the abandon/create race residual is worst on the one path with an irreversible external call.
  • Encode the compliance rubric (271 checks, 14 verticals). → IN FLIGHT — three slices MERGED + LIVE, the pharmacy vertical COMPLETE 2026-09-06. The schema eb24f21, batch 1 5a5c0c4 and batch 2 3063715 are all running against real merchants; thirteen verticals remain and peptide business-to-business is next, then the rest in the written risk order. Still no dependency on anything, and the bank calibration ask runs alongside it rather than ahead of it. The owed adversarial pass is done (credits refilled, six reproduced defects fixed, final round approve). One new operational gap instead: the managed-crawler key in production is empty, so bot-walled sites cannot be fetched in prod until Mark sets it.
  • Next.js 16.3.4 security bump. → MERGED + LIVE 2026-09-05. The screening security review found the portal's framework sitting inside six open advisories — an auth-gate bypass among them, which matters here because the auth gate is a proxy file. Bumped and audited to zero known vulnerabilities, dependency-only, with the shipped PDF templates and the headless browser proven still bundled after the change.
  • The P6 grill. → P1–P5 all shipped 2026-08-01→03. Largest remaining CRM phase; grilled before anything is built.
  • Oakville MPA mapper. → Form on hand. ~1-2 hours.
  • IRIS v1.5a (webhook listener). → Aaron's answers cleared the path.
  • Maverick API integration. → Account activated, research done.
  • Ticketing planning in Co-Work. → Planning has no build dependencies.

Has dependencies — needs a gate to open first

  • P4 exit gate. → Data half CLEARED 2026-08-10 (NMI ingested + reconciled + nightly). Remaining: Mark resolves the six open auth-decline alerts. Processor-specific parsers turned out to be unnecessary — the gateway pull emits the console's own CSV contract.
  • Real bank logins on /bank. → Down to ONE gate: the bank-auth expectations survey answered (Mark sends). The data gate closed 08-10. Internal demo grants only until it clears.
  • Full restore-from-backup (PII included). → Decision gate cleared 2026-08-21 — the PII key rotation is grilled and locked — but the execution gate is still shut: the 08-22 slot passed unused. Until it runs and the drill decrypts, a restore still returns the system without the protected columns.
  • Fiserv Marketplace boarding API. → Gate OPENED 2026-08-24 — moved to in-flight. Authentication, pricing, boarding, the agreement and order status all return against the sandbox; a real order was boarded and its 41-page agreement assembled end to end, and the portal build is grilled to a conditional go. What remains isn't a wall: document upload and order submission need switching on at Fiserv (asked, both confirmed wanted), and the rate schedule needs Ainsworth's own buy rates before the real pricing program can be configured. Constraint carried into the build: product selection must stay inside priced combinations, because an unpriced one fails at the bank as an unreadable system error.
  • Esquire MPA mapper. → Needs fillable AcroForm PDF from Esquire.
  • Phase 6 PDF parsers. → Needs Corduro / TRX sample PDFs from Jeff.
  • Residual CSV ingest. → Needs upstream CSV/Excel sample.
  • IRIS v1.5b (push). → Needs v1.5a built first (provides API auth for self-serve ID discovery).
  • Ticketing build. → Needs Phase 7 notification slice shipped + clean shared-file state + lower concurrent build pressure. June 15 forcing date has LAPSED — needs a fresh build-or-defer call.

Recommended next — prioritized

Ordered by leverage × ease. Each item explains why it earns its rank.
1
Run the PII key rotation — it lost its slot, not its case a supervised morning It was booked for Saturday 08-22 and two days went to the wet-sign lane instead — both slices on 08-22, the review notifications on 08-23. That was a defensible trade on the days it happened — the lane is now complete — but it changes none of the arithmetic underneath: until the rotation runs, every nightly backup keeps restoring the system without the columns that actually matter, and the only key that could read them lives in one runtime with no copy anywhere else. The plan is grilled, locked, and escrow-first; the vault exists and Jeff is in it; the proof runs smallest-first. It returns to number one because it is the one open item whose downside is measured in "if this goes wrong once, it is unrecoverable" rather than in effort.
2
Close out the two data gates — six alerts and one email minutes, plus a send The item that held this slot for a week is done — real gateway data landed 2026-08-10, reconciled to the cent, and now pulls itself nightly. What inherits the slot is the small remainder, and it stays at number one because two fully built surfaces are still being held back by it rather than by any code. First: resolve the six open auth-decline alerts with the MCC-5411 note (Resolve, not False positive — they were true positives under the old threshold), which closes P4's exit gate outright. Second: send the bank-auth expectations survey, now the only thing standing between /bank and real sponsor-bank logins. Neither is engineering; both are the last inch of work already paid for.
3
The Fiserv Marketplace boarding build — newly unblocked, and the largest strategic lane open a grilled build, phased It enters the list at this rank because on 2026-08-24 it stopped being a waiting item. Both blockers dissolved the same day, the sandbox now boards an order and returns its agreement, and the build has already been grilled to a conditional go. What it replaces is the slowest part of the current process: today a merchant's identifiers arrive when a VAR sheet is emailed by a human, and on this lane the platform hands them back. Two conditions shape the sequencing rather than delay it — the pricing bands need Ainsworth's own buy rates before real numbers can be configured, and document upload and submission need switching on at Fiserv, so the build can proceed on the proven half while those land. It ranks below the rotation and the two data gates only because those are measured in a morning and in minutes.
4
Continue Phase 7 active slices ongoing Notification + nudge slice also unblocks the Ticketing gate. LLM judgment + scheduled sweep are the highest-leverage automation gains. Already running in its own session.
5
The P6 grill a grill session, then a build Now the largest remaining CRM phase, and it inherits this slot because the 08-05 arc emptied the queue ahead of it — the MPA-regeneration item that sat here is effectively closed, and both branches that were unmerged are live. Grilled before anything is built, per the SOP.
6
Oakville MPA mapper 1-2 hrs Quick win. Form on hand. Adds the 5th bank template and removes "Oakville-placed merchants need manual MPA" from the operations list.
7
The pre-North checklist (before North is switched on) small, but ordered Ranks here on size, not urgency — both items are inert while North is uncredentialed. What earns the slot is the ordering. North submissions create a placement with no signature row, so those deals would be invisible on the re-keyed MPA queue from the moment North goes live; and the abandon/create race residual accepted on 08-06 is worst on exactly this path, where the placement write sits downstream of an irreversible external call. Cheap to fix now, silent gaps if they're fixed after.
8
IRIS v1.5a — webhook listener ~1 week Buildable. Validates the full integration stack (API key, IP allowlist, signing). Sets up v1.5b push-side. Hagen is the primary API-banked path; this opens the lane.
9
Maverick API integration ~1-2 weeks Second of two API banks. Replaces the PDF pipeline for Maverick-placed merchants. Account active, research done — just needs a focused build slice.
10
Chase the Mark-action blockers phone calls / a credit card TRX portal access, the bank-auth survey, Esquire fillable PDF, Dropbox Sign account email, Corduro/TRX statement samples, residual export sample. Each one is a short decision or email that unblocks ~1-2 weeks of work later.
11
Ticketing build (gate needs a decision) ~1-2 weeks Trigger fires when Phase 7 notification ships + workload allows. The June 15 forcing date lapsed unactioned — either ship the notification slice or set a new dated decision point.
12
QBO payout integration ~1 week The natural follow-on to Residual Automation. Approval flow → QBO export. Waits on the CSV ingest connector for the input half to be automated.

Engineering hygiene — known deferred issues

Items that should land eventually but aren't blocking anything. Tracked so they don't get forgotten.

Auth / sessions

  • Session revocation on user removal Pre-existing app-wide gap. Removed users keep API access until 7-day cookie expires. Fix is per-request DB revalidation. Covers merchant_collaborator and partner_admin removals.
  • Email-change after sign-in Same root cause — gated to never-signed-in users until session revocation lands.

Repo / infra

  • Repo on iCloud Desktop — RESOLVED 2026-06-15 iCloud sync was creating * 2.tsx / * 2.ts duplicates and corrupting .git. Repo moved to ~/dev/ainsworth-merchant-portal, with feature worktrees as siblings under ~/dev/. Closed.
  • VAR-sheet route still has the throwing-blob shape (2026-08-06) The blob library's get() throws on a missing object rather than returning a non-200, so a status-code-only check leaves an unhandled 500 where a typed error was intended. Fixed in the two routes shipped 08-06; api/merchant-accounts/[id]/var-sheet was deliberately not touched. Fix it next time that file is open.
  • Cross-surface dedup Date utils ↔ statements.ts, checklist-def ↔ nudge route. Deferred as a coordinated pass post Phase 7 review.

Wizard / data

  • Wizard step 2.6 — id_date_issued not collected in UI Field is in the canonical schema + DB, preserved on edit, but no UI input. Add when a sponsor bank flags it as required.
  • Free-text rendering safety card_types_not_accepted / refund_policy_description / product_storage_location in PDF fill + admin views must use safe text APIs (not raw HTML). Flagged at every commit.

Ingestion / monitoring

  • Superseded batch + record retention (2026-08-10) Nothing prunes superseded ingestion batches or their rows, so a nightly feed accumulates history forever. Harmless at today's volume; wants a retention sweep before it isn't.
  • Deviation-based auth-decline arm (2026-08-10) The retune to warn-80 / crit-90 is an absolute line, chosen because the MCC-5411 book genuinely declines that often. The better rule is deviation from each account's own baseline — deliberately deferred until 2–3 real months of history exist to compute one from.
  • Per-account cron fan-out (2026-08-10) One nightly job pulls both gateway accounts concurrently inside a single timeout budget. The efficiency review put the ceiling around 1.4k gateway transactions a day; past that, split it per account rather than raising the timeout.

E-sign / placements

  • Dropbox Sign finalize webhook error (2026-05-24) One ALL_SIGNED webhook 500'd during dogfood. Status stuck at 'signed', no Download button. Likely test-mode PDF retrieval or Blob put. Next-session triage: re-run the loop, capture full stack. The affected row can be manually fetched from Dropbox Sign UI.
  • Webhook terminality is enforced in TS, not in the DB predicate The DECLINED / FILE_ERROR / finalize write predicates don't list wet_signed in their terminal exclusions. Unreachable today (synthetic wet-sign IDs never match a Dropbox event) but the DB, not the pre-read, should own it. Still open after the 08-09 webhook touch — that pass added 'error' and 'superseded' to the predicates it needed, but wet_signed is still guarded only in TypeScript. Next webhook touch, as one transition-policy/CAS helper.
  • Notification lost if the queue write fails after ACK (2026-08-09) A transient failure queueing the e-sign problem digest is swallowed so the webhook still acknowledges — and there's no repair on redelivery, because queueing without a transition is exactly the replay amplification the transition gate exists to prevent. Accepted at ship: near-zero volume, Sentry catches it, the admin badge still shows the row. Revisit on the first Sentry esign-problem-queue event.
  • Webhook event_hash has no freshness window (surfaced 2026-08-09) Pre-existing, not introduced by the notification slice: event_time is never checked, so a captured hash replays forever. LOW — the transition guards mean a replay can't change state — but a bounded window is the standard fix. Mark's call.
  • Multi-channel edges left open at ship (2026-07-31) A bank package attaches the newest executed application regardless of channel; a merchant stays at ready_for_review while any channel is in flight; each channel emails its own submitted/approved notice. All logged as decide-later, none regressions. See the deferred list in PROJECT_STATE's multi-channel section.
  • E-sign / Executed MPA deferrals (2026-09-30) Logged across the 09-30 ships, none blocking: a bank package's document set, Executive Summary and merchant identity are still revalidated lock-free before commit (the signature half is closed by the e-sign version; the general fix is a per-merchant "package inputs" version); the view has no executed column, so the executed-status test is still hand-written in several readers; system-filled is a code constant rather than a checklist input type, and the file doors still accept files onto URL-type items; the Add Location lane still supersedes on a refused cancel; a row stuck at signed with no webhook is finalized only by a re-send; the wet-sign verify script's fixture seeds two live requests, which the one-live index now forbids; the generate, send and wet-upload routes still accept any registered form (UI-only guard, open since 09-23). Update 10-01: the Add Location refused-cancel item is now part of the named next slice (signed-paper integrity).
  • Add Location automation deferrals (2026-10-01) Logged across the 10-01 ships, none blocking: the lead website the MPA prints is chosen two different ways across the papers — they disagree only when the primary website is outside the listed sites, which no production merchant has today (a mapper change, Mark's call); a batch in error is not mentioned after an MPA send; two storefronts differing only by port or query string collapse to one; a URL too long to print can only be fixed by withdraw and re-add; a stale reservation's age is computed from two different clocks; the stale-reservation banner points at a Send button that is hidden when nothing is sendable; no email for a signed form kept without a Location (panel card and audit only); several callers read Dropbox Sign's rejection reason from the wrong field name, so it never reaches the admin.
  • Pend Tracker deferrals (2026-08-01) None load-bearing: row-creating pend POSTs have no idempotency key, so a client retry can duplicate a pend (the CAS routes are already replay-safe); reopen should be tightened to in-flight placements; the location tag can't be lane-aware until the P2 MID registry carries both foreign keys; and the edit UI for decision fields / caps plus pend email notifications are still unbuilt.

Out of scope (v1) — deliberate decisions

Items deliberately excluded from v1. Documented so future-Mark knows this was a choice, not an oversight.

Product surface

  • Mobile apps Web-only for v1. Revisit if/when usage patterns demand it.
  • Multi-language English-only v1.
  • Card-present / physical merchant intake v1 wizard is e-commerce only. Card-present columns + intake reserved for Phase 2.

Integration

  • Direct submission to sponsor bank portals (PDF banks) Out of v1 — admin submits to the bank manually. API banks (Hagen via IRIS, Maverick via onboarding API) auto-submit.
  • KYB lookup provider call Data model wired, provider call deferred to v2.

External

  • Fintech-compliance attorney review Revisit before scaling beyond v1 volumes (10-20 apps/month → bigger).

Killed (not deferred)

  • Merchant-facing statement view Killed during Phase 6. The data is agent-economics (net→split→residual), not a merchant statement. Merchants get the real thing (volume/rate/chargebacks/reserves) from the processor.

Living docs — where the canonical content lives

Source-of-truth documents for each surface area. Refresh these, not the roadmap, when implementing a change.

Notion-synced handbooks

  • PARTNER_HANDBOOK_v1.mdAinsworth Payments root
  • PORTAL_HANDBOOK_v1.mdAinsworth Payments root
  • TECHNICAL_DOC_v1.mdAinsworth Payments root

In-repo operating docs

  • docs/PROJECT_STATE.mdcanonical project state
  • docs/POSITIONING_STRATEGY.mdaudience + pillars
  • docs/AGENT_DASHBOARD_SPEC.mdUX spec
  • docs/BANK_MPA_OPERATIONS.mdrunbook for new banks
  • docs/RESIDUAL_AUTOMATION_PLAN.mdPhase 8 plan
  • docs/PHASE_7_BUILD_SPEC.mdUtah automation
  • docs/PHASE_6_5_AGENT_AGREEMENTS_PLAN.mdnext workstream

Planning docs (Merchant Onboarding/)

  • CANONICAL_APPLICATION_SCHEMA_v2.md7-bank reconciled superset
  • STEP_B_PLAN.mdPhase 5 sequence
  • IRIS_INTEGRATION_v1.5.mdHagen API spec
  • MAVERICK_INTEGRATION_v1.mdMaverick API research
  • ESIGN_OPTIONS_v1.mdDropbox Sign decision
  • PRD_Ainsworth_MerchantPortal_v1.mdv1 PRD
  • DEVILS_ADVOCATE_v1.mdstress-test of v1.0