max / makenotwork
3 files changed,
+36 insertions,
-5 deletions
| @@ -343,6 +343,7 @@ fn requires_https(server_url: &str) -> bool { | |||
| 343 | 343 | /// Returns the app name on success, or an error if the key is invalid or the server | |
| 344 | 344 | /// is unreachable. This is intended for setup UIs that need to verify a key before | |
| 345 | 345 | /// saving it. | |
| 346 | + | #[tracing::instrument(skip(api_key))] | |
| 346 | 347 | pub async fn validate_api_key(server_url: &str, api_key: &str) -> Result<String> { | |
| 347 | 348 | let url = format!("{server_url}/api/v1/sync/validate-app"); | |
| 348 | 349 | let http = reqwest::Client::builder() |
| @@ -229,6 +229,7 @@ impl SyncKitClient { | |||
| 229 | 229 | /// Fetch the pricing-formula constants for this app. No JWT needed; the | |
| 230 | 230 | /// app's API key is sent in the body. Safe to call before login so the | |
| 231 | 231 | /// UI can show pricing on the marketing/onboarding view. | |
| 232 | + | #[tracing::instrument(skip(self))] | |
| 232 | 233 | pub async fn get_app_pricing(&self) -> Result<AppPricing> { | |
| 233 | 234 | let body = Bytes::from(serde_json::to_vec(&AppPricingRequest { | |
| 234 | 235 | api_key: &self.config.api_key, | |
| @@ -247,6 +248,7 @@ impl SyncKitClient { | |||
| 247 | 248 | /// Server-side price quote for a (cap, interval). Use this to confirm | |
| 248 | 249 | /// the number you display matches what Stripe will charge; the result is | |
| 249 | 250 | /// authoritative. | |
| 251 | + | #[tracing::instrument(skip(self))] | |
| 250 | 252 | pub async fn quote_price(&self, cap_bytes: i64, interval: BillingInterval) -> Result<PriceQuote> { | |
| 251 | 253 | let token = self.require_token()?; | |
| 252 | 254 | let body = Bytes::from(serde_json::to_vec(&QuoteRequest { | |
| @@ -268,6 +270,7 @@ impl SyncKitClient { | |||
| 268 | 270 | /// Fetch the authenticated user's email and username. | |
| 269 | 271 | /// | |
| 270 | 272 | /// Used by apps to display "logged in as ..." in their cloud-sync UI. | |
| 273 | + | #[tracing::instrument(skip(self))] | |
| 271 | 274 | pub async fn get_account_info(&self) -> Result<AccountInfo> { | |
| 272 | 275 | let token = self.require_token()?; | |
| 273 | 276 | self.retry_request_json(Idempotency::Safe, || { | |
| @@ -281,6 +284,7 @@ impl SyncKitClient { | |||
| 281 | 284 | /// | |
| 282 | 285 | /// Returns `SubscriptionStatus` with `active: true` if sync is allowed, | |
| 283 | 286 | /// or `active: false` if the user needs to subscribe. | |
| 287 | + | #[tracing::instrument(skip(self))] | |
| 284 | 288 | pub async fn get_subscription_status(&self) -> Result<SubscriptionStatus> { | |
| 285 | 289 | let token = self.require_token()?; | |
| 286 | 290 | self.retry_request_json(Idempotency::Safe, || { | |
| @@ -293,6 +297,7 @@ impl SyncKitClient { | |||
| 293 | 297 | /// Create a Stripe Checkout session for subscribing to cloud sync at the | |
| 294 | 298 | /// chosen storage cap. Returns a URL that should be opened in the user's | |
| 295 | 299 | /// browser. | |
| 300 | + | #[tracing::instrument(skip(self))] | |
| 296 | 301 | pub async fn create_subscription_checkout( | |
| 297 | 302 | &self, | |
| 298 | 303 | cap_bytes: i64, | |
| @@ -318,6 +323,7 @@ impl SyncKitClient { | |||
| 318 | 323 | /// Queue a storage-cap change that takes effect at the next billing | |
| 319 | 324 | /// cycle. Returns the updated subscription status with the new cap in | |
| 320 | 325 | /// `pending_storage_limit_bytes`. | |
| 326 | + | #[tracing::instrument(skip(self))] | |
| 321 | 327 | pub async fn queue_storage_cap_change(&self, cap_bytes: i64) -> Result<SubscriptionStatus> { | |
| 322 | 328 | let token = self.require_token()?; | |
| 323 | 329 | let body = Bytes::from(serde_json::to_vec(&CapChangeRequest { cap_bytes })?); |
| @@ -67,8 +67,16 @@ impl CleanChanges { | |||
| 67 | 67 | ); | |
| 68 | 68 | return false; | |
| 69 | 69 | } | |
| 70 | - | committed_hlc(&p.entry.table, &p.entry.row_id) | |
| 71 | - | .is_none_or(|committed| p.entry.hlc > committed) | |
| 70 | + | let apply = committed_hlc(&p.entry.table, &p.entry.row_id) | |
| 71 | + | .is_none_or(|committed| p.entry.hlc > committed); | |
| 72 | + | if !apply { | |
| 73 | + | tracing::debug!( | |
| 74 | + | table = %p.entry.table, | |
| 75 | + | row_id = %p.entry.row_id, | |
| 76 | + | "clean change gated out: older than the committed HLC" | |
| 77 | + | ); | |
| 78 | + | } | |
| 79 | + | apply | |
| 72 | 80 | }) | |
| 73 | 81 | .map(|p| p.entry) | |
| 74 | 82 | .collect() | |
| @@ -255,7 +263,7 @@ pub fn resolve_lww_at(local: &ChangeEntry, remote: &PulledChange, now: DateTime< | |||
| 255 | 263 | } | |
| 256 | 264 | _ => {} | |
| 257 | 265 | } | |
| 258 | - | match local.hlc.cmp(&remote.entry.hlc) { | |
| 266 | + | let resolution = match local.hlc.cmp(&remote.entry.hlc) { | |
| 259 | 267 | std::cmp::Ordering::Greater => Resolution::KeepLocal, | |
| 260 | 268 | std::cmp::Ordering::Less => Resolution::KeepRemote, | |
| 261 | 269 | std::cmp::Ordering::Equal => { | |
| @@ -265,7 +273,16 @@ pub fn resolve_lww_at(local: &ChangeEntry, remote: &PulledChange, now: DateTime< | |||
| 265 | 273 | Resolution::KeepRemote | |
| 266 | 274 | } | |
| 267 | 275 | } | |
| 268 | - | } | |
| 276 | + | }; | |
| 277 | + | // Every LWW resolution discards one side; log at debug so a lost edit is | |
| 278 | + | // diagnosable after the fact rather than vanishing silently. | |
| 279 | + | tracing::debug!( | |
| 280 | + | table = %local.table, | |
| 281 | + | row_id = %local.row_id, | |
| 282 | + | kept = ?resolution, | |
| 283 | + | "LWW resolved", | |
| 284 | + | ); | |
| 285 | + | resolution | |
| 269 | 286 | } | |
| 270 | 287 | ||
| 271 | 288 | /// Deterministic byte encoding of a row payload for the exact-HLC tiebreak. | |
| @@ -310,7 +327,14 @@ pub fn resolve_field_merge( | |||
| 310 | 327 | ) else { | |
| 311 | 328 | // No usable base: last-writer-wins on timestamp, keeping local when it is | |
| 312 | 329 | // strictly newer instead of dropping it. | |
| 313 | - | return if local_ts > remote_ts { | |
| 330 | + | let keep_local = local_ts > remote_ts; | |
| 331 | + | tracing::debug!( | |
| 332 | + | keep_local, | |
| 333 | + | %local_ts, | |
| 334 | + | %remote_ts, | |
| 335 | + | "field-merge base unavailable; fell back to timestamp LWW" | |
| 336 | + | ); | |
| 337 | + | return if keep_local { | |
| 314 | 338 | Resolution::KeepLocal | |
| 315 | 339 | } else { | |
| 316 | 340 | Resolution::KeepRemote |