You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
find the buyer by key or label, and refuse a tie (#10)
Stripe's API requires a key on every checkout custom field; Stripe's
dashboard never asks for one and never shows one, and it will not add a
field to a payment link that already exists. So the setup pages asked a
seller to type a key they cannot set, on a link they cannot edit. Their
field read to fulfillment as no field at all: paid orders skipped, a green
run, and a buyer with nothing. Stores this engine built are unaffected,
because init.js sets the key over the API.
Fulfillment now ranks three signals and never pools them: the exact key, the
key ignoring case and punctuation, then the label. Where several fields tie,
the shape of the value breaks it; where even that cannot, nothing is invited
and both keys are named, because an invitation to a private repo has no undo.
The needs-attention row now carries which of the three reasons applied.
Docs give the API call that adds the field to a link in use, with the URL
unchanged, and say plainly that the dashboard route only exists for a new
link, which gets a new URL.
Co-authored-by: Lucide <279298543+LucideLarp@users.noreply.github.com>
Copy file name to clipboardExpand all lines: docs/failure-catalogue.md
+48-5Lines changed: 48 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -25,8 +25,8 @@ This file is generated from `audit/lib/catalogue.js` (the same data the
25
25
checks run against) by `node audit/audit.js --catalogue`. It is meant to be
26
26
worth reading even if you never run the tool.
27
27
28
-
**13 entries.** 5 are incidents that happened on this store's own
29
-
infrastructure. 4 are real defects we shipped and caught by execution before
28
+
**14 entries.** 5 are incidents that happened on this store's own
29
+
infrastructure. 5 are real defects we shipped and caught by execution before
30
30
a buyer hit them. 4 have never happened here and are labelled as such: they are
31
31
guarded because the first occurrence would be unrecoverable and invisible.
32
32
@@ -72,8 +72,11 @@ GitHub expires an unaccepted repository invitation after 7 days. That is
72
72
documented behaviour, not a secret: GitHub's changelog of 5 February 2020,
73
73
"Self-expiring repository and organization invitations", says invitations to
74
74
become a collaborator on a repository expire seven days after they are created.
75
-
The REST API states no period of its own, so the engine also watches GitHub's
76
-
`expired` flag rather than only the calendar. The non-obvious part is architectural:
75
+
It is the only place GitHub says it. The REST API carries no period at all, so
76
+
this check treats seven days as an assumption: it acts on GitHub's own `expired`
77
+
flag, measures against a shorter window if an invitation on your store has
78
+
actually died sooner, and says which of the two it used rather than presenting a
79
+
deadline. The non-obvious part is architectural:
77
80
NOTHING IN THE PIPELINE EVER REVISITS AN INVITATION IT SENT.
78
81
79
82
The engine invites the buyer and writes the ledger row in the same breath.
@@ -116,6 +119,46 @@ CI.
116
119
**Checked by:**`grant-shape` (static)
117
120
**Not checked:** None. Pure config shape, no credentials, runs on every push.
118
121
122
+
## A checkout that collects nothing you can deliver against
123
+
124
+
`unreadable-checkout` · shipped defect: real bug, reproduced, no victim · 2026-07-24
125
+
· evidence: the exact-key lookup these modules shared (reconcile/lib/reconcile-core.js, ops-bots/bots.js), and the setup pages that asked a seller to type a key the dashboard does not offer (honorbox docs/setup.md, docs/bring-your-own-store.md)
126
+
· cost: none here. Our own links are made over the API, which is the only route that can set a key at all
127
+
128
+
Every module in this suite answers one question first: which GitHub account
129
+
does this payment belong to? It reads that off a custom field on the
130
+
checkout. Get that wrong and nothing downstream can be right, because there
131
+
is no other thread from a payment to a person.
132
+
133
+
The suite used to find that field by one exact key, `github_username`, and
134
+
the setup instructions told you to type that key in when you added the field.
135
+
Two facts make that instruction unfollowable. Stripe's API requires a unique
136
+
`key` on every custom field, so a key always exists on the object. Stripe's
137
+
DASHBOARD never asks for one and never shows one: the Add custom fields panel
138
+
offers a field type, one "Label name" input and a few checkboxes, and no key
139
+
appears afterwards on the field either. A seller who built their link by
140
+
clicking cannot set the key, cannot read it, and cannot be told to use ours.
141
+
142
+
Their field is therefore a field, on a working checkout, that every tool
143
+
reads as no field at all. The buyer types their username. The money lands.
144
+
The order is skipped, the run is green, and nobody is invited. It is the same
145
+
silent shape as a payment link with no fulfillment row, arrived at by
146
+
following the setup guide correctly.
147
+
148
+
The fix has two halves and both are here. The modules now identify the field
149
+
by its key OR by its label, and where more than one field could be it, they
150
+
resolve by the shape of what the buyer typed and REFUSE where even that
151
+
cannot separate them, because inviting a stranger to a private repo has no
152
+
undo. This check is the other half: it reads your live links and tells you
153
+
which ones nothing could deliver against, before a buyer finds out for you.
154
+
155
+
Worth saying plainly: this did not cost us anything, because our own links
156
+
are made over the API and carry the key explicitly. It would have cost the
157
+
next person who set one up by hand, which is exactly who the suite is for.
158
+
159
+
**Checked by:**`unreadable-checkout` (live)
160
+
**Not checked:** Needs a Stripe key to read your links. It judges the field DEFINITION, so it cannot tell you whether buyers are filling it in sensibly, only whether anything here could find it.
161
+
119
162
## A forked store still selling through the original author's checkout
120
163
121
164
`foreign-checkout` · shipped defect: real bug, reproduced, no victim · 2026-07-19 / 2026-07-20
@@ -234,7 +277,7 @@ so buyers stop being delivered to. And the symptom is not an error, it is a
234
277
cron that quietly stopped. Nothing in your store reports it. You find out
235
278
from a customer.
236
279
237
-
Fixed by sizing the cadence to the tier (17,47 = 1,488 min/month, 512 minutes
280
+
Fixed by sizing the cadence to the tier (*/30 = 1,488 min/month, 512 minutes
238
281
of headroom) rather than by rewording the pricing claim.
239
282
240
283
Now the other half, because a catalogue that only frightens people is as
The 4 exposed hold 5 separate findings, each listed above with its fix.
114
114
115
115
Each EXPOSED line above is a specific way this store loses a sale without
116
116
telling you. They are listed with the fix; there is no score to improve.
117
117
118
-
13 catalogue entries · 5 from incidents here, 4 from defects we shipped and caught, 4 guarded before they happened.
118
+
14 catalogue entries · 5 from incidents here, 5 from defects we shipped and caught, 4 guarded before they happened.
119
119
Known gaps we have NOT closed (1). Read audit/CATALOGUE.md:
120
120
- A 200 that acknowledges dispatch, not delivery: A short secondary throttle is retried in-run (30s per wait, 60s per run, 3 attempts); anything longer, including a primary rate limit, is declined by design and falls to the scheduled poll, whose real recovery time is the next run that actually fires, measured here at up to 3h08m on a quiet private repo.
13 catalogue entries · 5 from incidents here, 4 from defects we shipped and caught, 4 guarded before they happened.
198
+
14 catalogue entries · 5 from incidents here, 5 from defects we shipped and caught, 4 guarded before they happened.
199
199
Known gaps we have NOT closed (1). Read audit/CATALOGUE.md:
200
200
- A 200 that acknowledges dispatch, not delivery: A short secondary throttle is retried in-run (30s per wait, 60s per run, 3 attempts); anything longer, including a primary rate limit, is declined by design and falls to the scheduled poll, whose real recovery time is the next run that actually fires, measured here at up to 3h08m on a quiet private repo.
0 commit comments