Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
196 commits
Select commit Hold shift + click to select a range
ae513f8
Add standalone authoritative payment callback resolution
stefan-burke Jul 23, 2026
48c90d0
Harden webhook handler tests to 100% mutation kill rate
stefan-burke Jul 24, 2026
d29e711
Address PR review: partial refunds, fetch-before-retry, shared helper
stefan-burke Jul 24, 2026
d4d3391
Record webhook retry alerting follow-up in TODO.md
stefan-burke Jul 24, 2026
253596c
Resolve Codex review threads: split payment states, fix test coverage
stefan-burke Jul 24, 2026
485b73f
Fix Square tests after rebase
stefan-burke Jul 25, 2026
00e067b
Close payment mutation coverage gaps
stefan-burke Jul 25, 2026
a2d9bd4
Pin callback thank-you links
stefan-burke Jul 25, 2026
f6cfe45
Resolve payment callback review feedback
stefan-burke Jul 25, 2026
24bebea
Restore Square signature test vector
stefan-burke Jul 25, 2026
0f6ebe4
Replace payment callbacks with durable aggregate
stefan-burke Jul 27, 2026
cfe462d
Handle zero-value payment callbacks
stefan-burke Jul 27, 2026
eb469d1
Merge main into durable payment aggregate
stefan-burke Jul 27, 2026
068e986
Merge latest main into payment aggregate
stefan-burke Jul 27, 2026
83a6654
Make the payment aggregate branch typecheck its tests again
claude Jul 27, 2026
99decd6
Sort the imports Biome moved after the refund-mock change
claude Jul 27, 2026
73bf6fc
Say registration closed when registration closed, and settle refunds …
claude Jul 27, 2026
2495de0
Point the payment test doubles at the reads the provider actually makes
claude Jul 27, 2026
e359018
Drop the error spy the redirect test no longer reads
claude Jul 27, 2026
bff95df
Settle deferred refunds before checking them, and read error copy fro…
claude Jul 27, 2026
3b4cbd6
Replay a finished payment from the payment record, not the retired table
claude Jul 27, 2026
0ad1090
Let a processed callback finish its follow-up work before tests read it
claude Jul 27, 2026
ffbba3e
Read refund records where the rewrite now writes them
claude Jul 27, 2026
be7a625
Read webhook replies and notes where the rewrite puts them
claude Jul 27, 2026
db5177d
Keep a paid booking when one free-text answer loses its string id
claude Jul 27, 2026
da9b7da
Say what a callback does when the charge or the refund is missing
claude Jul 27, 2026
e9c1bd4
Finish the webhook suites: settle late work and follow the new read o…
claude Jul 27, 2026
78163dd
Give the booking-preparation tests a real payment to work from
claude Jul 27, 2026
5d45e6e
Check item validation for what it decides, not for a refund it no lon…
claude Jul 27, 2026
14bf142
Make a cancelled checkout recognisably ours, and read its errors from…
claude Jul 27, 2026
bbfb7fd
Check the page a broken payment link shows, not a log line nothing wr…
claude Jul 27, 2026
c106733
Show a message when the payment key no longer works, instead of crashing
claude Jul 27, 2026
8dfc307
Check a new checkout for what it actually has: an id and a link
claude Jul 27, 2026
dc7c776
Drop a round trip stripe-mock cannot make, and follow the JSON error'…
claude Jul 27, 2026
5cf28bd
Follow refunds and replays through to where they now finish
claude Jul 27, 2026
491aed1
Fold the repeated payment-page checks into shared ones
claude Jul 27, 2026
3ebe7cc
Tell the operator payments are not set up, instead of blaming the pay…
claude Jul 27, 2026
3c39c72
Refund on the account that took the money, in tests as on the site
claude Jul 27, 2026
4170f9b
Set up the payment provider before the ledger-failure refund test
claude Jul 27, 2026
1ee0f70
Merge remote-tracking branch 'origin/main' into base/payment-aggregate
claude Jul 27, 2026
2cc8b5c
Clear the house-style breaches the payment rewrite left behind
claude Jul 27, 2026
2ebdb97
Give every payment helper a caller on the site, or a home in the tests
claude Jul 27, 2026
1d17f9f
Bring back the two things the attendee payment panel lost
claude Jul 27, 2026
c2739de
Point the payment tests at the two reads a paid session now needs
claude Jul 27, 2026
b1b819f
Match the payment tests to what a refunded booking now shows
claude Jul 27, 2026
9ff4851
Replaying a payment link now hands back the same ticket link
claude Jul 27, 2026
f71b182
Stand in for both balance-session reads, and reuse the success-page h…
claude Jul 27, 2026
cdfc59c
A charge that disagrees with the checkout goes to the owner, not a re…
claude Jul 27, 2026
313bbd2
Say something when a signed payment message cannot be read
claude Jul 27, 2026
96d064d
Set up three payment tests the way the site now records a payment
claude Jul 27, 2026
ec89e46
Stand in for both reads in the package-and-child payment tests
claude Jul 27, 2026
8bf7a04
Write down the payment problems the review found
claude Jul 27, 2026
0d9a0a2
Clear a wrong "could not be refunded" note even when nothing new come…
claude Jul 27, 2026
0a7b24b
Make the reservation and dual-path refunds match their charge
claude Jul 27, 2026
84c61be
A charge that disagrees with its signed total goes to the owner
claude Jul 27, 2026
e0b49ca
Write down the one upgrade step that now needs foreign keys off
claude Jul 27, 2026
509482f
Let an old site upgrade without switching foreign keys off
claude Jul 27, 2026
d304a47
Rebuild every old payment table, not just the ones pointing at attendees
claude Jul 27, 2026
23ab2ef
Write down the three newest payment problems the review found
claude Jul 27, 2026
9252094
Point the three payment stories at how a payment is recorded now
claude Jul 27, 2026
11884cd
Merge remote-tracking branch 'origin/main' into base/payment-aggregate
claude Jul 27, 2026
29b32e1
Write down the three newest payment problems the review found
claude Jul 27, 2026
e71a42f
Cover handing out a site when someone pays for one
claude Jul 27, 2026
d792826
Merge remote-tracking branch 'origin/main' into base/payment-aggregate
claude Jul 27, 2026
de1ab2c
Cover the SumUp answers the tests never asked for
claude Jul 27, 2026
181077d
Write down how the branch coverage work is being finished
claude Jul 27, 2026
eb0e682
Let an upgrade finish inside the calls one request is allowed
claude Jul 27, 2026
532a409
Write down the upgrade call-budget trap and five more review findings
claude Jul 27, 2026
761faa1
Refuse a webhook address that points back inside the network, provably
claude Jul 27, 2026
c2f3b88
Test how far the site will look for a Square payment
claude Jul 27, 2026
3e950d2
Write down six more payment problems the review found
claude Jul 27, 2026
1a40b5e
Write down four more upgrade problems the review found
claude Jul 27, 2026
7af8d9e
Write down three more findings, including one this branch introduced
claude Jul 27, 2026
5cc6c2a
Merge remote-tracking branch 'origin/main' into base/payment-aggregate
claude Jul 27, 2026
d0bef49
Cover redirect payment outcomes and share the fixture guard
claude Jul 27, 2026
3400468
Cover the check that a saved owner decision still fits the payment
claude Jul 27, 2026
de2341f
Cover the paid-completion email outbox and share its setup
claude Jul 27, 2026
ba68dca
Fix the type of the email addresses in the new delivery tests
claude Jul 27, 2026
aa82285
Cover finding a payment from a charge and from an old SumUp reference
claude Jul 27, 2026
91bbc28
Record what is left of the saved-decision snapshot check
claude Jul 27, 2026
16bc4b8
Leave a decision another worker is running to them
claude Jul 27, 2026
df64463
Cover the SumUp refund answers we do not act on
claude Jul 27, 2026
04828f0
Reserve a site through the delivery outbox
claude Jul 27, 2026
4db0c74
Share the renewal tier setup the site tests both need
claude Jul 27, 2026
cc2eb00
Push a site's read-only date out when a renewal is paid for
claude Jul 27, 2026
668659c
Cover every way a paid renewal can no longer fit its site
claude Jul 27, 2026
d0dc7a8
Send a decision back when a refund moved before it first ran
claude Jul 27, 2026
c5a6d84
Cover what a payment webhook does before it trusts a notice
claude Jul 27, 2026
b212d9d
Refuse renewals we cannot honour, and buy nothing for no months
claude Jul 27, 2026
5be934b
Say so when a cancelled payment is one we do not know
claude Jul 27, 2026
cfc6323
Merge remote-tracking branch 'origin/main' into base/payment-aggregate
claude Jul 27, 2026
2efd2c1
Record four more payment findings from the review
claude Jul 27, 2026
a93dab2
Give the fixtures one guard for a missing value, and one for a charge
claude Jul 27, 2026
9b36cfd
Cover what SumUp does when it cannot answer
claude Jul 27, 2026
cb61d84
Delete Square read states that can never arrive, and cover the rest
claude Jul 27, 2026
bd4f9c7
Cover a zero-value Stripe checkout that does not add up
claude Jul 27, 2026
584cad9
A webhook always names its listing, and a finished decision shows no …
claude Jul 27, 2026
f820ae5
Explain why the decision snapshot check cannot be covered
claude Jul 27, 2026
2e12179
Test the owner keeping an old payment as it is
claude Jul 27, 2026
feb13ad
Cover an old SumUp payment that was already paid for
claude Jul 27, 2026
6950ea9
Refuse a queued bulk refund whose payment came back empty
claude Jul 27, 2026
81e1c06
Test which payment accounts a site offers
claude Jul 27, 2026
0e606d5
Keep the time SumUp gives a refund
claude Jul 27, 2026
9cdf292
Cover a refund with no name yet, and a notice about someone else
claude Jul 27, 2026
17149b3
Test what a repeated callback makes of a finished payment
claude Jul 27, 2026
25b2f40
Keep the claim when the money was already all sent back
claude Jul 27, 2026
e433e54
Refuse a price proof naming no payment, and a free checkout that charged
claude Jul 27, 2026
eaf7bb5
Refuse to rewrite a note that is not this attendee's
claude Jul 27, 2026
2f50b44
Record three more payment findings from the review
claude Jul 27, 2026
3cd52c0
Carry the checkout out of an old SumUp promotion
claude Jul 27, 2026
c1961c8
Remove a local coverage run committed by mistake
claude Jul 27, 2026
46ede43
Test the three ways a queued bulk refund can end
claude Jul 27, 2026
6c77113
Test the three answers we refuse to adopt a payment from
claude Jul 27, 2026
4bba12d
Test what an old payment says it already did
claude Jul 27, 2026
334be2a
Test what stops an old SumUp checkout being brought forward
claude Jul 27, 2026
b3dba82
Test the two ways an old payment names its provider choices
claude Jul 27, 2026
2bcc2df
Let a site upgrade when a paid booking has no quantity left
claude Jul 27, 2026
c75bf8e
Fold the repeated upgrade finding back into its existing entry
claude Jul 27, 2026
7fb81e0
Test that a site renewed by someone else after payment is refused
claude Jul 27, 2026
8bb7d04
Test how scheduled owner decisions stop and how they start
claude Jul 27, 2026
8760c94
Make the payment test helpers say what they mean
claude Jul 27, 2026
4c57e0d
Record an unwanted refund request instead of refusing it
claude Jul 27, 2026
37b85b3
Test an old record that never finished going to the owner
claude Jul 27, 2026
839f4a2
Trust the stored shape for a legacy review's charge
claude Jul 27, 2026
89584c7
Test the buyer paying for a site there is none of
claude Jul 28, 2026
bae0eb2
Test two endings a refund can reach that nothing checked
claude Jul 28, 2026
82214b6
Test the alert task's wiring and two refusals to rebuild an answer
claude Jul 28, 2026
216b21d
Test what a broken checkout tells the buyer, and unreadable times
claude Jul 28, 2026
4a650d6
Write down why two owner-decision refusals cannot happen
claude Jul 28, 2026
70c8512
Note that running a decision re-checks what offering it settled
claude Jul 28, 2026
f69034a
Stop the no-site test from depending on the network
claude Jul 28, 2026
f10931f
Test both ways a site can slip away mid-assignment
claude Jul 28, 2026
f88841c
Drop four unused options from the webhook test helpers
claude Jul 28, 2026
ea03d51
Test the refusal to guess, and which refunds raise an alert
claude Jul 28, 2026
0b1d65e
Share the claim-a-paid-payment setup between two suites
claude Jul 28, 2026
0669564
Test four refusals guarding money and bookings
claude Jul 28, 2026
2a733a0
Test three refusals to act on a record that contradicts itself
claude Jul 28, 2026
80e6106
Test four more refusals around finishing a payment
claude Jul 28, 2026
b1a07f2
Test that a long SumUp migration pauses and picks up again
claude Jul 28, 2026
6ce0645
Test the migration pausing on old attendee payments too
claude Jul 28, 2026
cd01809
Fold a SumUp checkout found two ways onto one payment
claude Jul 28, 2026
bb9ef98
Test a payment reading landing on a lease that has moved on
claude Jul 28, 2026
11e6c58
Test reading a decision the owner accepted but has not settled
claude Jul 28, 2026
4eee08a
Write down the charge review index that cannot be missing
claude Jul 28, 2026
e4765a7
Test that queueing no refunds costs no database call
claude Jul 28, 2026
0a42010
Merge remote-tracking branch 'origin/main' into base/payment-aggregate
claude Jul 28, 2026
e9aeec7
WIP: merge main into the payment aggregate — NOT GREEN, ~40 type erro…
claude Jul 29, 2026
1468e5c
WIP: cut the merge's type errors from ~40 to 18 — still NOT GREEN
claude Jul 29, 2026
b0730de
Point the notes work at the module both branches ended up with
claude Jul 29, 2026
2c9261f
Ask the cancel page the question this branch actually asks it
claude Jul 29, 2026
cdac331
Point the last stale tests at the shapes this branch actually has
claude Jul 29, 2026
d59b1ea
Delete an attendee's notes the way notes are now found
claude Jul 29, 2026
b3c3f85
Format and tidy the notes work
claude Jul 29, 2026
a968f50
Give the spec helpers one way to make a listing and one way to refund
claude Jul 29, 2026
88adcb7
Record the ten new review findings on the payment branch
claude Jul 29, 2026
bc4c2c5
Update the migration snapshots this branch's own changes moved
claude Jul 29, 2026
cd1838e
Ask for an attendee's notes the way notes are now found
claude Jul 29, 2026
57673ee
Merge remote-tracking branch 'origin/main' into base/payment-aggregate
claude Jul 31, 2026
170406c
Merge remote-tracking branch 'origin/main' into base/payment-aggregate
claude Jul 31, 2026
e6a3ed1
WIP: merge main into payment aggregate base — NOT GREEN, 13 typecheck…
claude Jul 31, 2026
59230d7
WIP: merge main — NOT GREEN, 10 typecheck errors in 2 files
claude Jul 31, 2026
f435918
WIP: merge main — NOT GREEN, 11 typecheck errors
claude Jul 31, 2026
0be85f8
Merge main into the payment aggregate base
claude Jul 31, 2026
30c9941
Merge main into the payment aggregate base
claude Jul 31, 2026
3e4b561
Correct the payment aggregate error count to ten
claude Jul 31, 2026
86a2cd2
Let a paid checkout finish in the payment stories again
claude Jul 31, 2026
484b278
Name the sign that the last failing payment story is fixed
claude Jul 31, 2026
39d7de7
Write down the eight Codex findings on the payment base
claude Jul 31, 2026
6f96a8c
Keep this branch's payment APIs and fix the story helpers to match
claude Jul 31, 2026
98863cd
Finish a booking against the whole payment, not loose arguments
claude Jul 31, 2026
cca20e0
WIP: build a real paid payment for the refund-placeholder stories
claude Jul 31, 2026
ae87515
WIP: stand in for the refund in the refund-placeholder stories
claude Jul 31, 2026
9ed18b9
Stand in for the refund when settling a balance too
claude Jul 31, 2026
8df6e67
Stand in for the refund in the capacity story too
claude Jul 31, 2026
95c4367
Ask for a refused booking the one way, and use the shared refund stan…
claude Jul 31, 2026
e75c7e8
Give the payment tables one place in the schema, not two
claude Jul 31, 2026
bd012b2
Match the payment migration tests to the migrations that survived the…
claude Jul 31, 2026
8cd1fe3
Make the tests around payments say what the merged code actually does
claude Jul 31, 2026
3ec039a
Write down the findings from the latest reviews of this branch
claude Jul 31, 2026
3a5815e
Spell every hidden value in the tests the way the merged schema wants
claude Jul 31, 2026
9c25147
Give the refund-everyone story payments it can actually refund
claude Jul 31, 2026
f0c519c
Sell a thing and book people onto it the one way
claude Jul 31, 2026
b1529e0
Drop the payment fixture nobody asks for
claude Jul 31, 2026
81982cb
Write down where the payment rewrite still needs tests
claude Jul 31, 2026
c67f041
Write down the Square event that carries no order
claude Jul 31, 2026
f0eeab8
Name every reason a payment case can carry, in one place
claude Jul 31, 2026
d585a9a
Merge remote-tracking branch 'origin/main' into claude/great-fermi-l2…
claude Jul 31, 2026
7951afe
Write down the discount-code story the merge left failing
claude Jul 31, 2026
9f36267
Read a payment case reason instead of casting it
claude Jul 31, 2026
c16a7c0
Record the remaining Codex findings on the payment branch
claude Jul 31, 2026
99d0801
Let the discount-code story wait for the work done after payment
claude Jul 31, 2026
3b255f2
Say what the two worst payment findings really cost
claude Jul 31, 2026
32e0293
Write down what the payment branch still has left to do
claude Jul 31, 2026
0f7b8b8
Say what the paging and currency findings really cost
claude Jul 31, 2026
1587775
Record three payment findings nobody had written down
claude Jul 31, 2026
3cf6929
Point the two findings at the file the code is really in
claude Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,7 @@ example, `storage.bunnycdn.com` or `uk.storage.bunnycdn.com`).
- `STORAGE_ZONE_NAME` - Bunny CDN storage zone name (required for image uploads)
- `STORAGE_ZONE_KEY` - Bunny CDN storage zone access key (required for image uploads)
- `BACKUP_PAGE_SIZE` - Rows read per keyset page when dumping a table for backup (default 500). Each page is one libsql response, so this bounds the response size to stay under libsqld's "Response is too large" payload cap. Used by `deno task backup` and the admin Backups page; migrations no longer back up inline (the edge subrequest budget can't fit a full dump), so backups are taken out-of-band.
- `PAYMENT_HISTORY_REDACTION_DAYS` - Days to retain PII-heavy terminal payment evidence before redacting it (default 90, minimum 3). Replay identities, provider resources, exact charge and refund money, attendee links, and decision audit rows remain.
- `MAIN_INSTANCE_KEY` - Shared secret authorizing the inter-instance site-credentials endpoint (`POST /instance/site-credentials`). When set on a builder/main instance, that endpoint returns built sites' DB URL + token to a caller presenting this key as a bearer token, so the upgrade workflow can back each site up to the builder's storage before deploying. The returned token is each site's own full-access credential (the same one the site runs with) — callers only read, but must treat the response as write-capable production secrets. The caller passes the release tier it is publishing as `?tier=alpha|beta|release` (a tier-less call defaults to `release` ⇒ the whole fleet, which is what the single-site `backup-site` action relies on); each site carries an `updates` channel and only the sites at that tier or more eager are returned (a `release` deploy reaches every site, `beta` reaches beta + alpha sites, `alpha` only alpha sites — an unknown tier is a 400). The response echoes the applied `tier` so a caller can confirm the server actually filtered: a pre-tier build ignores the query string and omits it, letting the canary workflow fail closed instead of fanning a non-release deploy out to the whole fleet. Unset `MAIN_INSTANCE_KEY` ⇒ the endpoint is disabled (404). The upgrade workflow receives the key as a run-time input, not a stored GitHub secret.
- `DENO_DEPLOY_TOKEN` - Deno Deploy organization access token. Required with `DENO_DEPLOY_ORG_ID` and `DENO_DEPLOY_ORG_SLUG` to build sites on Deno Deploy.
- `DENO_DEPLOY_ORG_ID` - Deno Deploy organization ID used by the app creation API.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ Prices are in the smallest currency unit (e.g. pence, cents). For multi-listing
### Concurrency

- Capacity check + insert in a single SQL statement to reduce the window for overbooking
- Payment webhook idempotency via two-phase locking on `processed_payments` table
- Payment callbacks use leased aggregate records, revision fences, and permanent replay identities
- Stale reservation auto-cleanup after 5 minutes

### Security
Expand Down
2,427 changes: 1,965 additions & 462 deletions TODO.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion e2e-payments/src/providers/square.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
import type { Page } from "playwright";
import { log } from "#e2e/log.ts";
import { sleep } from "#e2e/util.ts";
import { squareRequestInit } from "#shared/square.ts";
import { squareRequestInit } from "#shared/square-client.ts";
import {
configureProvider,
exerciseAdminRefund,
Expand Down
24 changes: 24 additions & 0 deletions scripts/mutation/equivalent-mutants/features.txt
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,27 @@ src/features/api/folded-booking.ts:381:17 1 → 0 # base.dayCount is read onl
# placeholder and a throw-only label that can never throw.
src/features/admin/questions/answers.ts:88:45 0 → 1 # the inserted sortOrder is a placeholder: insertScopedOrderedRow runs order.append in the same transaction, and append sets sort_order from MAX over siblings that explicitly exclude the row itself, then overwrites it unconditionally before commit — the placeholder value is never read by anything
src/features/admin/questions/forms.ts:32:9 Display as → "" # requireChoiceOptions reads its label only in its two throw paths: an empty options list, or a value failing an invalid-rule. QUESTION_DISPLAY_TYPES is a non-empty picklist constant and requireChoiceOptions carries no invalid rule, so neither throw is reachable and the label is never read

# Carried over from the payment aggregate base branch.
src/features/public/ticket-payment.ts:173:11 ?? → || # date is nullish or a non-empty ISO date; only the nullish values use the null fallback
src/features/public/ticket-payment.ts:201:42 ?? → || # childrenByParentId.get(): TicketListing[]|undefined, and every present array is truthy
src/features/public/ticket-payment.ts:168:14 1 → 0 # bookingDateFields clamps either default day count to one, so the produced availability rows are identical
src/features/public/ticket-payment.ts:245:30 ?? → || # a package stand-in is a non-empty group name or undefined, so only undefined reaches the listing-name fallback
src/features/public/ticket-payment.ts:317:28 ?? → || # prebuiltTree is a BookingTree object when present, and objects are always truthy
src/features/public/ticket-payment.ts:326:43 ?? → || # base.quantities.get(): number|undefined; the only falsy number is 0, 0??0===0||0
src/features/public/ticket-payment.ts:371:17 0 → 1 # EMPTY_PRICED_ORDER.fullSubtotal is never read: bookingFactsFromOrder derives its facts from total, extras, lines, and modifierApplications
src/features/public/ticket-payment.ts:382:14 1 → 0 # bookingsForOrder normalizes either default day count to one, so the stored booking duration is identical
src/features/public/ticket-payment.ts:419:37 ?? → || # ledgerOrder: PricedOrder|null — an object is always truthy, null→fallback either way
src/features/public/ticket-payment.ts:573:61 ?? → || # childrenByParentId.get(): TicketListing[]|undefined — an array is always truthy, undefined→null either way
src/features/public/ticket-payment.ts:771:17 ?? → || # pagePackages is an array when present, and arrays are always truthy
src/features/admin/groups.ts:182:47 ?? → || # parseMemberDayPrices byListing.get(): DayPrices|undefined — an object is always truthy
src/features/admin/groups.ts:201:51 ?? → || # parsePackageMembers dayPricesByListing.get(): DayPrices|undefined — an object is always truthy
src/features/admin/questions.ts:133:7 questionText → "" # defineForm config.id: only feeds CSRF/flash-form coordination; the page's CsrfForm uses id="new-question", so this value is never rendered observably
src/features/admin/questions.ts:146:7 answerText → "" # answerTextForm config.id: same as questionText — unobservable form-coordination id
src/features/api/payment-processing/index.ts:107:22 → "mutated" # replaySuccess is private and both callers always pass session.paymentReference, so the default is never evaluated
src/features/api/payment-processing/package-pricing.ts:296:51 ?? → || # the allocated quantity is non-negative and the fallback is 0, so both operators agree for 0 and undefined
src/features/api/folded-booking.ts:84:42 ?? → || # childrenByParentId is Map<number, TicketListing[]>; get returns an array (always truthy, even []) or undefined, so ?? and || agree
src/features/api/folded-booking.ts:115:31 ?? → || # qtyByChild accumulates ApiQuantitySchema values (positive integers >= 1) or is unset; it is never 0 or NaN for validated inputs, so ?? 0 and || 0 agree
src/features/api/folded-booking.ts:173:53 1 → 0 # the thankYouUrl guard checks fold.listings.length > 1 only when parentThankYouUrl is truthy (parent flow), which always folds parent + >= 1 child (length >= 2); package callers pass no parentThankYouUrl, so the && short-circuits and the check never runs
src/features/api/folded-booking.ts:297:70 0 → 1 # feeSubtotal flows into priceCheckout → owedOrderForLedger, which drops all extras and sets total to 0; bookingFactsFromOrder reads extras (empty) → bookingFee: 0 regardless. The feeSubtotal value never reaches any observable output.
src/features/api/folded-booking.ts:377:17 1 → 0 # base.dayCount is read only by parentResolvedDuration → childDaysFromParent(parent, dayCount, 1), which uses its first argument (dayCount) only in the parent.customisable_days branch; processParentApiBooking rejects customisable parents at line 335 before reaching the fold, so that branch is unreachable. For daily parents the function returns clampDurationDays(parent.duration_days), and for standard parents it returns 1 (the standardValue) — neither reads dayCount, so 1 and 0 produce the same duration and thus the same fold output for every reachable input.
9 changes: 9 additions & 0 deletions scripts/mutation/equivalent-mutants/shared-a-l.txt
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,12 @@ src/shared/dates.ts:431:30 1 → 0 # bookedRangeLabel's `fallbackDurationDay

# Logger (logger.ts) — a provably-equivalent survivor from the logger suites.
src/shared/logger.ts:300:69 ?? → || # context.listingId is number|undefined and listing ids are positive autoincrement integers, so the LHS is never a falsy non-null value — ?? and || agree on every input

# Carried over from the payment aggregate base branch.
src/shared/accounting/store.ts:202:60 ?? → || # planGroup existingByGroup.get(): Transfer[]|undefined — an array is always truthy
src/shared/accounting/store.ts:225:42 ?? → || # planGroup originalsById.get(): Transfer|undefined — a Transfer object is always truthy
src/shared/accounting/rows.ts:116:21 ?? → || # insertStatement kind: string|undefined, only falsy-non-null is "" and "" ?? "" === "" || ""
src/shared/accounting/rows.ts:117:21 ?? → || # insertStatement memo: string|undefined, only falsy-non-null is "" and "" ?? "" === "" || ""
src/shared/accounting/rows.ts:250:58 ?? → || # selectById (await …)[0]: Transfer|undefined — a Transfer object is always truthy
src/shared/accounting/rows.ts:136:72 ?? → || # renderInsert guard?.args: InValue[]|undefined — an array is always truthy
src/shared/accounting/queries.ts:166:26 || → && # transferActivityBounds: MIN(occurred_at) and MAX(occurred_at) over one table are NULL together (both iff the table is empty), so either-null and both-null coincide
17 changes: 17 additions & 0 deletions scripts/mutation/equivalent-mutants/shared-db.txt
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,20 @@ src/shared/db/processed-payments.ts:287:7 processed_payments.failure_data → "
# resolve.
src/shared/db/news-posts.ts:147:47 news_post → "" # dropping the alias leaves the column names unqualified, and the read selects from one table (news_posts AS news_post) plus scalar subqueries that carry their own qualified id, so every name still resolves to the same column
src/features/admin/api-keys.ts:147:75 ?? → || # entity-page load: find() returns a key row object or undefined — a row object is always truthy, so `?? null` and `|| null` agree on every input

# Carried over from the payment aggregate base branch.
src/shared/db/table.ts:445:28 ?? → || # condition?.args is an array or undefined, and every array is truthy
src/shared/db/table.ts:492:71 ?? → || # a found row is a truthy object, and a miss is undefined, so both operators give the row or null
src/shared/db/table.ts:496:31 ?? → || # findByIds's element is a truthy row, null, or undefined, and all three agree
src/shared/db/table.ts:584:21 ?? → || # dependsOn is a readonly array or undefined, and every array is truthy
src/shared/db/table.ts:617:32 ?? → || # the cached table's dependsOn is the same array-or-undefined as above
src/shared/db/groups.ts:84:58 1 → 0 # the stored group ids come from group_listings.group_id, a foreign key onto groups' INTEGER PRIMARY KEY, so SQLite never issues 0 and no stored value can tell minValue(1) from minValue(0)
src/shared/db/groups.ts:238:7 Missing group listing membership → "" # getListingsByGroupIds builds its result map from the ids it was given, so the single-group lookup always finds its own id and requiredMapValue never raises this message
src/shared/db/groups.ts:504:55 groupRecord → "" # columnsSql only qualifies a column when the alias is truthy, so "" selects the same bare column names, which are unambiguous in this single-table query
src/shared/db/groups.ts:224:35 ?? → || # entriesByGroup.get(id): readonly entry[]|undefined — an array is always truthy
src/shared/db/groups.ts:458:20 ?? → || # blocked[0]: { listing, block }|undefined — the present object is always truthy
src/shared/db/groups.ts:703:66 ?? → || # package rows: GroupListing[]|undefined — an array is always truthy
src/shared/db/groups.ts:836:34 ?? → || # quantity is undefined or at least 1 by PackageMemberInput's contract, so every present value is truthy
src/shared/db/attendees/select.ts:294:47 ?? → || # where.kind: AttendeeKindFilter|undefined — every member ("attendee"/"servicing"/"attendee-or-servicing") is truthy, so `?? "attendee"` and `|| "attendee"` agree on every input
src/shared/db/attendees/select.ts:379:26 ?? → || # query.join: AttendeeJoin|undefined — "inner"/"left" are both truthy, so `?? "inner"` and `|| "inner"` agree on every input
src/shared/db/news-posts.ts:148:47 news_post → "" # dropping the alias leaves the column names unqualified, and the read selects from one table (news_posts AS news_post) plus scalar subqueries that carry their own qualified id, so every name still resolves to the same column
14 changes: 14 additions & 0 deletions scripts/mutation/equivalent-mutants/shared-m-z.txt
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,17 @@ src/shared/rebrand.ts:152:36 + → * # `open + 1` → `open * 1` = open: the
src/shared/rebrand.ts:152:39 1 → 0 # `open + 0` = open: same inert-"<" start, identical result
src/features/admin/attributes.ts:215:46 0 → 1 # option insert's sortOrder is a placeholder: the same transaction's append overwrites it before commit, and a failed append rolls the row back — no committed state can carry the inserted value


# Carried over from the payment aggregate base branch.
src/shared/payment-runtime/current.ts:63:21 ?? → || # getPaymentSessions returns a PaymentSession object or null for each requested id; every PaymentSession object is truthy
src/shared/payment-runtime/process.ts:181:33 ?? → || # a validated createdAt is undefined or a non-empty timestamp, so only undefined reaches the fallback
src/shared/rest/crud-api.ts:342:31 ?? → || # policy is an AuthPolicy object or undefined
src/shared/rest/crud-api.ts:346:18 ?? → || # lookup is a function or undefined
src/shared/rest/crud-api.ts:353:28 ?? → || # lookupAfterWrite is a function or undefined
src/shared/rest/crud-api.ts:363:31 ?? → || # hydrated map values are records or undefined; records are truthy
src/shared/rest/crud-api.ts:573:27 ?? → || # extraRoutes is a route object or undefined
src/shared/rest/resource.ts:157:25 ?? → || # validationError is an ErrorResult object or null; an object is truthy
src/shared/payment-helpers.ts:115:26 true → false # safeAsync's getValue is () => true only to pass the value === null guard; the fn callback ignores the value so false is equivalent
src/shared/payment-helpers.ts:204:24 ?? → || # toBookingItems intent.allocations: ChildAllocation[]|undefined — an array is always truthy, undefined→[] either way
src/shared/payment-helpers.ts:584:16 || → && # parsed JSON null/falsy primitives return {} in both forms; truthy primitives expose none of PACKED_KEYS and also produce {}
src/shared/payment-helpers.ts:643:40 ?? → || # metadata field length is undefined or a non-negative integer; 0 and undefined both use 0
3 changes: 3 additions & 0 deletions scripts/mutation/equivalent-mutants/test-utils.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ src/shared/booking/signed-metadata.ts:120:59 ?? → || # allocations come fr
src/shared/booking/signed-metadata.ts:142:37 ?? → || # foldedQty values are positive allocation totals; absent and present values behave identically with either zero fallback
src/shared/catalog-fields/definition.ts:166:38 1 → 0 # a missing column becomes selected by its non-empty database name, but the columns projection still returns undefined and drops it; present columns are selected either way
src/shared/booking/capacity-tree.ts:38:45 ?? → || # groupIdsByListingId.get(): number[]|undefined — an array is always truthy, undefined→[] either way

# Carried over from the payment aggregate base branch.
test/test-utils/test-browser.ts:460:28 ?? → || # forms[0] is a FormInfo object when present, otherwise undefined, so both operators reach throwNoForm() on the same input
3 changes: 3 additions & 0 deletions scripts/mutation/equivalent-mutants/ui-templates.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ src/ui/templates/admin/questions.tsx:87:50 ?? → || # `listingNames.get(ques
src/ui/templates/admin/questions.tsx:170:14 question → "" # a column `key` is read only by renderTable's columnKeys/hiddenKeys lookups and saved-filter value rendering; reorderableListPage renders the questions table with only reorder options — no columnKeys, hiddenKeys, or filter — so this key is never read
src/ui/templates/admin/questions.tsx:176:14 answers → "" # same as questions.tsx:170:14 — the questions list table is rendered without columnKeys, hiddenKeys, or filters, so its column keys are never read
src/ui/templates/admin/questions.tsx:187:14 listings → "" # same as questions.tsx:170:14 — the questions list table is rendered without columnKeys, hiddenKeys, or filters, so its column keys are never read

# Carried over from the payment aggregate base branch.
src/ui/templates/admin/questions.tsx:267:50 ?? → || # `answerCounts?.get(a.id) ?? 0`: answerCounts is Map<number,number>|undefined, so the left side is a number or undefined. The only falsy-non-null number is 0, and 0 ?? 0 === 0 || 0; undefined maps to 0 under both — no input distinguishes them (same class as attributes.tsx:173:61 and checkout-pricing.ts:85:35)
Loading