From a7ea47fd63f0b8baea19d39e1b13a2a254853627 Mon Sep 17 00:00:00 2001 From: Grant Date: Fri, 8 May 2026 10:44:46 -0500 Subject: [PATCH] =?UTF-8?q?v0.1.0:44=20=E2=80=94=20DLQ=20in=20dashboard,?= =?UTF-8?q?=20trait=20migration=20completes,=20worker=20+=20crosscheck=20t?= =?UTF-8?q?ests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps version with release notes covering everything since :43: - Webhook DLQ visible in admin SPA with one-click retry - reconcile.rs + tipping.rs migrated onto PaymentProvider trait (production refactor; daemon's non-test code now contains zero calls to the BTCPay-specific compat accessors) - 3 worker integration tests pin the retry/dead-letter behavior empirically against real HTTP receivers - 4 daemon-side crosscheck tests pin the wire-format parser against the same vector.json the SDKs use independently Test count: 30 (was 23). Straight drop-in upgrade from :43. --- startos/versions/v0.1.0.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/startos/versions/v0.1.0.ts b/startos/versions/v0.1.0.ts index 2fc03ed..7378094 100644 --- a/startos/versions/v0.1.0.ts +++ b/startos/versions/v0.1.0.ts @@ -9,8 +9,20 @@ import { VersionInfo } from '@start9labs/start-sdk' export const v0_1_0 = VersionInfo.of({ - version: '0.1.0:43', + version: '0.1.0:44', releaseNotes: [ + `Alpha-iteration revision 44 of v0.1.0 — DLQ visible in the admin UI, payment-provider trait migration completes, worker behaviour pinned by tests, and the daemon's wire-format parser is now cross-checked against the shared SDK vectors. Test count: 30 (was 23). Operator-visible: failed webhook deliveries now show up in the dashboard with one-click retry.`, + ``, + `**Webhook DLQ in the dashboard.** The admin endpoints from :43 (\`GET /v1/admin/webhook-deliveries\`, \`POST /v1/admin/webhook-deliveries/:id/retry\`) were operator-actionable via curl but invisible in the UI. The Webhooks page now has a "Delivery history" section directly below the registered endpoints, defaulting to the "Failed (DLQ)" filter so the problem case is what you see first. Each row shows created-at, event type, status badge, attempt count, last status code, and the last error inline beneath the status badge — no separate "details" view to chase. Non-delivered rows get a Retry button that re-queues via the existing endpoint; the worker picks it up on its next 5s tick.`, + ``, + `**PaymentProvider trait migration completes.** v0.1.0:43 migrated \`purchase::start\` off the legacy \`state.btcpay_client()\` compat accessor onto the abstract \`state.payment_provider()\` trait. v0.1.0:44 finishes the job for the other two consumers: \`reconcile.rs\` (the every-60s loop that catches missed BTCPay webhooks) and \`tipping.rs\` (the LN-payout flow for policy tip recipients). Both now go through \`provider.get_invoice_status()\` / \`provider.pay_lightning_invoice()\` returning typed enums instead of raw JSON. The daemon's non-test code now contains zero calls to the BTCPay-specific compat accessors. Zaprite's drop-in (v0.3) needs only to implement the trait — no remaining BTCPay-specific assumptions in the call sites. The compat accessors stay on \`AppState\` for v0.2 (no need to break things gratuitously) but are now dead code in the production path.`, + ``, + `**Worker behaviour pinned by integration tests.** Three new tests in \`tests/worker.rs\` drive the outbound-webhook worker (\`webhooks::tick\`) directly against tiny in-process axum receivers that return programmable status codes. Verifies: (1) a 500 response increments \`attempt_count\` and schedules a retry with \`next_attempt_at\` set; (2) crossing the 10-attempt cap correctly sets \`next_attempt_at = NULL\` (the dead-letter signal that :43's admin DLQ filter matches against); (3) a 2xx response stamps \`delivered_at\`. \`webhooks::tick\` is now \`pub\` so future tests can drive it synchronously without waiting on the 5s background-task interval.`, + ``, + `**Daemon-side wire-format crosscheck.** \`tests/crosscheck.rs\` loads the shared \`tests/crosscheck/vector.json\` (the same file the TS, Python, and Rust SDKs each test against independently) and verifies the daemon's \`crypto::parse_key\` produces field-by-field identical values for all three wire-format variants (v1 legacy, v2 trial with entitlements, v2 perpetual unbound). What was missing: the SDKs each ran their crosscheck against the shared vectors, but the daemon itself never did. The daemon shares no parser code with the SDKs (separate trees, separate implementations of the same byte layout), so drift in the daemon's parser could ship undetected until an SDK on the wire couldn't validate a daemon-issued key. This closes the loop. When \`fingerprintRaw\` is provided, also cross-checks that \`hash_fingerprint(raw)\` equals the wire bytes — pinning the SHA-256 contract the SDKs rely on.`, + ``, + `**Upgrade path.** v0.1.0:43 → v0.1.0:44 is a straight drop-in. No new migrations, no schema changes, no behaviour changes for buyers or for any wire format. Operators gain the new dashboard view; nothing else moves.`, + ``, `Alpha-iteration revision 43 of v0.1.0 — Webhook delivery DLQ, purchase-flow refactor onto the PaymentProvider trait, and three more integration tests. Operator-visible: there's now an admin surface for inspecting and retrying failed outbound webhook deliveries.`, ``, `**Webhook DLQ.** The delivery worker has always retried failed outbound webhooks with exponential backoff up to 10 attempts (5s, 10s, 30s, 1m, 5m, 15m, 30m, 1h, 2h, 6h), then set \`next_attempt_at = NULL\` and stopped. Until now, those "dead-lettered" rows sat in the database forever with no operator-facing surface — a subscriber endpoint that was down for >6h during a license-issuance burst silently lost those events. v0.1.0:43 adds:`,