Skip to main content

max / makenotwork

audit Run 16 Phase 4: Resilience - s3-storage: add a TimeoutConfig (10s connect, 60s per-attempt) to the shared client so a hung S3 endpoint (e.g. a stalled blob-confirm HeadObject) can no longer wedge a caller forever. Bounds the request round-trip, not large-body streaming, so uploads/downloads are unaffected. - subscription audit ledger: log_subscription_event already no-ops a redelivered event via ON CONFLICT DO NOTHING, so an Err return is always a genuine DB failure. Bump the 23 webhook call-site logs from warn! to error! so a dropped revenue-reconciliation row is alertable instead of buried, and document the invariant on the function. Free-content CDN URL revocation surfaced as a design decision (todo). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-02 13:41 UTC
Commit: 0770f2736c2f7529f60ed148e6c189270dc506a7
Parent: 557cb17
5 files changed, +40 insertions, -25 deletions
@@ -763,8 +763,11 @@ pub async fn get_subscriptions_for_export_page(
763 763
764 764 // ── Event log ──
765 765
766 - /// Log a subscription webhook event for debugging and idempotency.
767 - /// The UNIQUE index on stripe_event_id makes duplicate events a no-op.
766 + /// Log a subscription webhook event for debugging, idempotency, and revenue
767 + /// reconciliation. The `ON CONFLICT (stripe_event_id) DO NOTHING` makes a
768 + /// redelivered event a silent no-op — never an error — so an `Err` return is
769 + /// always a genuine DB failure, which callers log at `error!` (a dropped
770 + /// reconciliation row is worth alerting on, not burying at `warn!`).
768 771 #[tracing::instrument(skip_all)]
769 772 pub async fn log_subscription_event(
770 773 pool: &PgPool,
@@ -44,7 +44,7 @@ pub(super) async fn handle_invoice_payment_succeeded(
44 44 &state.db, None, event_id, "invoice.payment_succeeded.synckit_app_sub",
45 45 &serde_json::json!({"stripe_sub_id": stripe_sub_id, "is_renewal": is_renewal}),
46 46 ).await {
47 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
47 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
48 48 }
49 49 return Ok(());
50 50 }
@@ -64,7 +64,7 @@ pub(super) async fn handle_invoice_payment_succeeded(
64 64 &state.db, None, event_id, "invoice.payment_succeeded.synckit",
65 65 &serde_json::json!({"stripe_sub_id": stripe_sub_id, "synckit_app_id": app_id.to_string()}),
66 66 ).await {
67 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
67 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
68 68 }
69 69 return Ok(());
70 70 }
@@ -154,7 +154,7 @@ pub(super) async fn handle_invoice_payment_succeeded(
154 154 &state.db, None, event_id, "invoice.payment_succeeded.fan_plus",
155 155 &serde_json::json!({"stripe_sub_id": stripe_sub_id, "is_renewal": is_renewal}),
156 156 ).await {
157 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
157 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
158 158 }
159 159 return Ok(());
160 160 }
@@ -167,7 +167,7 @@ pub(super) async fn handle_invoice_payment_succeeded(
167 167 &state.db, None, event_id, "invoice.payment_succeeded.creator_tier",
168 168 &serde_json::json!({"stripe_sub_id": stripe_sub_id, "is_renewal": is_renewal}),
169 169 ).await {
170 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
170 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
171 171 }
172 172 return Ok(());
173 173 }
@@ -205,7 +205,7 @@ pub(super) async fn handle_invoice_payment_succeeded(
205 205 &state.db, sub_id, event_id, "invoice.payment_succeeded",
206 206 &serde_json::json!({"stripe_sub_id": stripe_sub_id, "is_renewal": is_renewal}),
207 207 ).await {
208 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
208 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
209 209 }
210 210
211 211 Ok(())
@@ -231,7 +231,7 @@ pub(super) async fn handle_invoice_payment_failed(
231 231 &state.db, None, event_id, "invoice.payment_failed.synckit",
232 232 &serde_json::json!({"stripe_sub_id": stripe_sub_id, "synckit_app_id": app_id.to_string()}),
233 233 ).await {
234 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
234 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
235 235 }
236 236 if let Some(ref wam) = state.wam {
237 237 let title = format!("SyncKit app payment failed: {app_id}");
@@ -248,7 +248,7 @@ pub(super) async fn handle_invoice_payment_failed(
248 248 &state.db, None, event_id, "invoice.payment_failed.fan_plus",
249 249 &serde_json::json!({"stripe_sub_id": stripe_sub_id}),
250 250 ).await {
251 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
251 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
252 252 }
253 253 return Ok(());
254 254 }
@@ -262,7 +262,7 @@ pub(super) async fn handle_invoice_payment_failed(
262 262 &state.db, None, event_id, "invoice.payment_failed.creator_tier",
263 263 &serde_json::json!({"stripe_sub_id": stripe_sub_id}),
264 264 ).await {
265 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
265 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
266 266 }
267 267 return Ok(());
268 268 }
@@ -275,7 +275,7 @@ pub(super) async fn handle_invoice_payment_failed(
275 275 &state.db, sub_id, event_id, "invoice.payment_failed",
276 276 &serde_json::json!({"stripe_sub_id": stripe_sub_id}),
277 277 ).await {
278 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
278 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
279 279 }
280 280
281 281 // Create WAM ticket for subscription payment failures
@@ -141,7 +141,7 @@ pub(super) async fn handle_purchase_checkout_completed(
141 141 &state.db, None, event_id, "checkout.session.completed.purchase",
142 142 &serde_json::json!({"session_id": session_id}),
143 143 ).await {
144 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
144 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
145 145 }
146 146
147 147 // Check for a pending refund that arrived before this payment webhook.
@@ -396,7 +396,7 @@ pub(super) async fn handle_subscription_checkout_completed(
396 396 &state.db, Some(sub.id), event_id, "checkout.session.completed.subscription",
397 397 &serde_json::json!({"session_id": session_id, "stripe_subscription_id": stripe_subscription_id}),
398 398 ).await {
399 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
399 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
400 400 }
401 401
402 402 Ok(())
@@ -463,7 +463,7 @@ pub(super) async fn handle_fan_plus_checkout_completed(
463 463 &state.db, None, event_id, "checkout.session.completed.fan_plus",
464 464 &serde_json::json!({"session_id": session_id, "stripe_subscription_id": stripe_subscription_id}),
465 465 ).await {
466 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
466 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
467 467 }
468 468
469 469 Ok(())
@@ -575,7 +575,7 @@ pub(super) async fn handle_creator_tier_checkout_completed(
575 575 "tier": sub.tier,
576 576 }),
577 577 ).await {
578 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
578 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
579 579 }
580 580
581 581 Ok(())
@@ -53,7 +53,7 @@ pub(super) async fn handle_subscription_updated(
53 53 &state.db, None, event_id, "customer.subscription.updated.synckit",
54 54 &serde_json::json!({"status": sub.status, "synckit_app_id": app_id.to_string()}),
55 55 ).await {
56 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
56 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
57 57 }
58 58 return Ok(());
59 59 }
@@ -76,7 +76,7 @@ pub(super) async fn handle_subscription_updated(
76 76 &state.db, None, event_id, "customer.subscription.updated.synckit_app_sub",
77 77 &serde_json::json!({"status": sub.status}),
78 78 ).await {
79 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
79 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
80 80 }
81 81 return Ok(());
82 82 }
@@ -102,7 +102,7 @@ pub(super) async fn handle_subscription_updated(
102 102 &state.db, None, event_id, "customer.subscription.updated.fan_plus",
103 103 &serde_json::json!({"status": status_str}),
104 104 ).await {
105 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
105 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
106 106 }
107 107 return Ok(());
108 108 }
@@ -124,7 +124,7 @@ pub(super) async fn handle_subscription_updated(
124 124 &state.db, None, event_id, "customer.subscription.updated.creator_tier",
125 125 &serde_json::json!({"status": status_str}),
126 126 ).await {
127 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
127 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
128 128 }
129 129 return Ok(());
130 130 }
@@ -145,7 +145,7 @@ pub(super) async fn handle_subscription_updated(
145 145 &state.db, sub_id, event_id, "customer.subscription.updated",
146 146 &serde_json::json!({"status": status.to_string()}),
147 147 ).await {
148 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
148 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
149 149 }
150 150
151 151 Ok(())
@@ -167,7 +167,7 @@ pub(super) async fn handle_subscription_deleted(
167 167 &state.db, None, event_id, "customer.subscription.deleted.synckit",
168 168 &serde_json::json!({"stripe_sub_id": stripe_sub_id, "synckit_app_id": app_id.to_string()}),
169 169 ).await {
170 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
170 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
171 171 }
172 172 return Ok(());
173 173 }
@@ -187,7 +187,7 @@ pub(super) async fn handle_subscription_deleted(
187 187 &state.db, None, event_id, "customer.subscription.deleted.synckit_app_sub",
188 188 &serde_json::json!({"stripe_sub_id": stripe_sub_id}),
189 189 ).await {
190 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
190 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
191 191 }
192 192 return Ok(());
193 193 }
@@ -210,7 +210,7 @@ pub(super) async fn handle_subscription_deleted(
210 210 &state.db, None, event_id, "customer.subscription.deleted.fan_plus",
211 211 &serde_json::json!({"stripe_sub_id": stripe_sub_id}),
212 212 ).await {
213 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
213 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
214 214 }
215 215 return Ok(());
216 216 }
@@ -229,7 +229,7 @@ pub(super) async fn handle_subscription_deleted(
229 229 &state.db, None, event_id, "customer.subscription.deleted.creator_tier",
230 230 &serde_json::json!({"stripe_sub_id": stripe_sub_id}),
231 231 ).await {
232 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
232 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
233 233 }
234 234 return Ok(());
235 235 }
@@ -264,7 +264,7 @@ pub(super) async fn handle_subscription_deleted(
264 264 &state.db, sub_id, event_id, "customer.subscription.deleted",
265 265 &serde_json::json!({"stripe_sub_id": stripe_sub_id}),
266 266 ).await {
267 - tracing::warn!(event_id = %event_id, error = ?e, "failed to log subscription event");
267 + tracing::error!(event_id = %event_id, error = ?e, "failed to log subscription event");
268 268 }
269 269
270 270 Ok(())
@@ -54,11 +54,23 @@ impl S3Client {
54 54 "s3-storage",
55 55 );
56 56
57 + // Bound every S3 operation so a hung endpoint (e.g. a stalled
58 + // HeadObject on a blob-confirm path) can't wedge a caller forever. These
59 + // apply to establishing the connection and to a single attempt's
60 + // round-trip to first byte of the response — they do NOT cap the time
61 + // spent streaming a large object body, so big uploads/downloads are
62 + // unaffected. The SDK's default retry policy still applies per attempt.
63 + let timeout_config = aws_sdk_s3::config::timeout::TimeoutConfig::builder()
64 + .connect_timeout(Duration::from_secs(10))
65 + .operation_attempt_timeout(Duration::from_secs(60))
66 + .build();
67 +
57 68 let s3_config = aws_sdk_s3::Config::builder()
58 69 .behavior_version(BehaviorVersion::latest())
59 70 .region(Region::new(config.region.clone()))
60 71 .endpoint_url(&config.endpoint)
61 72 .credentials_provider(credentials)
73 + .timeout_config(timeout_config)
62 74 .force_path_style(true)
63 75 .build();
64 76