@ssheleg/sheleg-dev 0.11.7 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/package.json +4 -3
- package/plugins/sheleg-dev/.claude-plugin/plugin.json +1 -1
- package/plugins/sheleg-dev/skills/ad-tracking/SKILL.md +24 -8
- package/plugins/sheleg-dev/skills/ad-tracking/references/consent-mode.md +57 -24
- package/plugins/sheleg-dev/skills/ad-tracking/references/event-tracking.md +0 -13
- package/plugins/sheleg-dev/skills/ad-tracking/references/gtag-api.md +0 -17
- package/plugins/sheleg-dev/skills/ad-tracking/references/performance-security.md +0 -11
- package/plugins/sheleg-dev/skills/crypto-payments/SKILL.md +24 -26
- package/plugins/sheleg-dev/skills/crypto-payments/references/callback-route-hardening.md +52 -0
- package/plugins/sheleg-dev/skills/crypto-payments/references/heleket-provider.md +42 -23
- package/plugins/sheleg-dev/skills/error-tracking/SKILL.md +34 -6
- package/plugins/sheleg-dev/skills/error-tracking/references/scrubbing.md +65 -2
- package/plugins/sheleg-dev/skills/error-tracking/references/setup.md +0 -1
- package/plugins/sheleg-dev/skills/frontend-performance/SKILL.md +51 -23
- package/plugins/sheleg-dev/skills/google-auth/SKILL.md +13 -1
- package/plugins/sheleg-dev/skills/google-auth/references/adc-and-service-accounts.md +13 -16
- package/plugins/sheleg-dev/skills/google-auth/references/oauth2-web-server.md +150 -79
- package/plugins/sheleg-dev/skills/google-auth/references/sign-in-with-google.md +36 -33
- package/plugins/sheleg-dev/skills/google-auth/references/workload-identity.md +0 -13
- package/plugins/sheleg-dev/skills/google-signin/SKILL.md +55 -23
- package/plugins/sheleg-dev/skills/google-signin/references/full-guide.md +150 -49
- package/plugins/sheleg-dev/skills/stripe-billing/SKILL.md +9 -7
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/README.md +17 -1
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/assert-money-invariants.mjs +171 -5
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/manifest.json +333 -198
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/reference-handler.mjs +259 -35
- package/plugins/sheleg-dev/skills/stripe-billing/references/subscription-lifecycle.md +149 -28
- package/plugins/sheleg-dev/skills/stripe-billing/references/webhook-events.md +76 -5
|
@@ -220,18 +220,40 @@ cannot mask the defect — which is the only reason this fixture measures the or
|
|
|
220
220
|
|
|
221
221
|
## Idempotency store
|
|
222
222
|
|
|
223
|
+
A claim records **receipt**, and only completion records **completion**. A row that
|
|
224
|
+
cannot tell the two apart turns every crash between them into a swallowed payment: the
|
|
225
|
+
retry is told "duplicate" about work that never finished. So the row carries a state —
|
|
226
|
+
`'processing'` on claim, `'completed'` exactly once, in the same transaction as the
|
|
227
|
+
grant — and a claim on an existing row answers by that state, never by mere existence.
|
|
228
|
+
|
|
223
229
|
```sql
|
|
224
230
|
CREATE TABLE processed_webhook_events (
|
|
225
231
|
id TEXT PRIMARY KEY, -- Stripe's evt_… id
|
|
226
232
|
source TEXT NOT NULL, -- 'stripe' — the table serves every provider
|
|
227
|
-
|
|
233
|
+
state TEXT NOT NULL DEFAULT 'processing', -- 'processing' → 'completed', nothing else
|
|
234
|
+
claimed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
235
|
+
completed_at TIMESTAMPTZ -- set once, in the transaction that granted
|
|
228
236
|
);
|
|
229
|
-
CREATE INDEX ON processed_webhook_events (
|
|
237
|
+
CREATE INDEX ON processed_webhook_events (state, claimed_at);
|
|
230
238
|
```
|
|
231
239
|
|
|
232
|
-
- `claimEvent` = `INSERT`; unique violation
|
|
233
|
-
|
|
234
|
-
|
|
240
|
+
- `claimEvent` = `INSERT`; a unique violation is not yet an answer — read the row's
|
|
241
|
+
state. `'completed'` means duplicate: answer 200 and stop. `'processing'` younger
|
|
242
|
+
than the claim expiry means another worker is (or may still be) on it: answer 5xx so
|
|
243
|
+
this delivery retries too. `'processing'` older than the expiry belonged to a worker
|
|
244
|
+
that died between receipt and completion — take it over with an `UPDATE … SET
|
|
245
|
+
claimed_at = now() WHERE id = … AND state = 'processing' AND claimed_at < now() -
|
|
246
|
+
<expiry>`, one row updated means the takeover is yours, zero means somebody beat you.
|
|
247
|
+
- `completeEvent` = `UPDATE … SET state = 'completed', completed_at = now()` — inside
|
|
248
|
+
the transaction that writes the grant, so a row that says completed is one whose
|
|
249
|
+
business write committed.
|
|
250
|
+
- `releaseEventClaim` = `DELETE … WHERE state = 'processing'`, called only when
|
|
251
|
+
processing threw and the route answered 5xx. A crashed worker never reaches this
|
|
252
|
+
line — that is what the expiry exists for.
|
|
253
|
+
- The claim expiry is comfortably longer than your slowest handler run (say, twice the
|
|
254
|
+
route timeout). Too short reprocesses live work; there is no "too long" that loses an
|
|
255
|
+
event, only one that delays its recovery.
|
|
256
|
+
- Prune completed rows older than ~30 days on a schedule. Stripe stops retrying after three
|
|
235
257
|
days, so anything older is dead weight — but do not prune to a window shorter
|
|
236
258
|
than the retry window, or a late retry reprocesses.
|
|
237
259
|
- A failure to *claim* (database down) is a 503, never an optimistic "probably
|
|
@@ -244,6 +266,55 @@ delivery to answer `duplicate` and the renewal notice and the conversion to fire
|
|
|
244
266
|
that separates this claim from the per-period marker, because the marker reads before it
|
|
245
267
|
writes and the claim does not.
|
|
246
268
|
|
|
269
|
+
## One transaction, then the outbox
|
|
270
|
+
|
|
271
|
+
The business application is ONE transaction: the entitlement, the business
|
|
272
|
+
dedup marker (the per-period grant row), the completion mark and the **outbox
|
|
273
|
+
rows** for every side effect commit together, or none of them exist. A crash
|
|
274
|
+
before the commit applies nothing — the claim still says `processing`, and the
|
|
275
|
+
retry runs the whole application again. A crash after the commit changes
|
|
276
|
+
nothing either: the work is durable, the retry reads `completed`, and the side
|
|
277
|
+
effects wait in the outbox.
|
|
278
|
+
|
|
279
|
+
The outbox delivers **at least once**, so the consumer carries its **own
|
|
280
|
+
dedup key** — event id + effect kind — remembered across rows. A redelivered
|
|
281
|
+
outbox row must send nothing twice: the email provider and the ad platform do
|
|
282
|
+
not participate in your transaction, and the consumer key is the only thing
|
|
283
|
+
standing between a queue hiccup and a second "your renewal" email with a
|
|
284
|
+
double-counted conversion.
|
|
285
|
+
|
|
286
|
+
**The mirror is state; the ledger is history — and conflicts only delay.**
|
|
287
|
+
The subscription mirror (current period, status, quantity) lives apart from
|
|
288
|
+
the grant ledger: the mirror moves FORWARD only (a late-arriving older event
|
|
289
|
+
never rewinds a confirmed period — the ordering rule), while the ledger keeps
|
|
290
|
+
one row per granted period forever. And a database serialization conflict
|
|
291
|
+
retries inside the same claim, bounded (three attempts in the reference);
|
|
292
|
+
past the bound the route answers 5xx with the claim released, so the
|
|
293
|
+
redelivery brings the renewal back — a conflict may delay a grant, never
|
|
294
|
+
lose it, and a swallowed conflict answered 200 is a renewal that silently
|
|
295
|
+
never happened.
|
|
296
|
+
|
|
297
|
+
**The crash between receipt and completion is proved too.**
|
|
298
|
+
`crash-after-receipt-is-retryable` kills the worker after the claim and requires the
|
|
299
|
+
retry arriving past the claim expiry to be let in and to grant;
|
|
300
|
+
`in-flight-claim-answers-retry-later` requires the retry arriving *inside* the window
|
|
301
|
+
to be told 5xx come back — not "duplicate", which is an answer about a completed row;
|
|
302
|
+
`completion-is-recorded-with-the-grant` requires the claim row to leave `'processing'`
|
|
303
|
+
the moment the grant commits; and `duplicate-of-completed-never-regrants` ages a
|
|
304
|
+
completed row past the expiry and requires the late retry to answer duplicate and
|
|
305
|
+
grant nothing. All four run against `fixtures/reference-handler.mjs`.
|
|
306
|
+
|
|
307
|
+
**And the transaction boundary is proved.** `transaction-rolls-back-whole`
|
|
308
|
+
crashes inside the transaction and requires no grant, no marker, no outbox row
|
|
309
|
+
and no completion to survive; `crash-before-commit-applies-nothing` lets the
|
|
310
|
+
retry in through the expired claim and requires everything applied exactly
|
|
311
|
+
once; `committed-retry-sends-once` redelivers every outbox row and requires
|
|
312
|
+
the notice and the conversion to fire once — the consumer key at work.
|
|
313
|
+
`serialization-conflict-does-not-lose-the-renewal` injects a conflict and
|
|
314
|
+
requires the retried transaction to land the grant;
|
|
315
|
+
`exhausted-retries-answer-5xx-and-the-redelivery-lands` exhausts the bound
|
|
316
|
+
and requires an honest 5xx followed by exactly one grant on redelivery.
|
|
317
|
+
|
|
247
318
|
## What to log
|
|
248
319
|
|
|
249
320
|
Structured, and enough to answer "what happened to this payment" without the
|