Skip to main content

max / makenotwork

synckit-client: document the already_exists consumer contract BlobUploadUrlResponse.already_exists is a server-asserted bool that gates whether the client sends bytes it holds locally, and nothing said what a consumer may conclude from it. A server answering true for content it does not have turns the upload into a silent no-op, so the blob is simply absent later. That is a liveness failure, not an integrity one: download_one re-hashes what it receives and rejects a mismatch with IntegrityFailed, so a lying server can withhold a blob but cannot substitute one. The field doc now says that, and says to read a skipped upload as unconfirmed rather than as proof the server holds the content. Also notes at the skip site that on_uploaded is deliberately not fired there: nothing was uploaded and the paired confirm never happened. Inert today, since the trait default is a no-op and TestPolicy is the only implementor, but a policy that clears a presence flag in that hook would re-offer the blob every pass. The SyncStore ports are what add real policies, and audiofiles' cloud_only is the shape that would hit it. Docs only, no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-20 17:51 UTC
Commit: 17bb0a86ca9d22e35df2d490ba6186bf592a2c35
Parent: 5710a60
3 files changed, +27 insertions, -1 deletion
@@ -28,6 +28,10 @@ impl SyncKitClient {
28 28 /// Request a presigned upload URL for a blob.
29 29 /// Returns (upload_url, already_exists). If `already_exists` is true,
30 30 /// the blob is already on the server and no upload is needed.
31 + ///
32 + /// `already_exists` is the server's claim, not a verified fact. See
33 + /// [`BlobUploadUrlResponse::already_exists`] for what a false `true` costs
34 + /// (a withheld blob, never a substituted one).
31 35 #[instrument(skip(self))]
32 36 pub async fn blob_upload_url(
33 37 &self,
@@ -183,7 +183,19 @@ async fn upload_one<T: BlobTransport>(
183 183 }
184 184 let target = client.upload_url(&blob.hash, blob.size).await?;
185 185 if target.already_exists {
186 - return Ok(true); // content-addressed dedup: already on the server
186 + // Content-addressed dedup: the server says it holds this hash, so the
187 + // bytes stay home. Two consequences worth knowing, both bounded:
188 + // - The claim is unverified. A server that answers `true` wrongly
189 + // withholds the blob from other devices; it cannot substitute one,
190 + // because `download_one` re-hashes before committing the file.
191 + // - `on_uploaded` is deliberately not fired: nothing was uploaded,
192 + // and the confirm call that pairs with it never happened. A policy
193 + // using that hook to clear a presence flag will keep re-offering
194 + // the blob on every pass, costing one `upload_url` round trip each
195 + // time. Harmless for the no-op default (the only implementors
196 + // today), but a policy with an explicit flag needs its own way to
197 + // settle the dedup case.
198 + return Ok(true);
187 199 }
188 200 let data = tokio::fs::read(&path).await.map_err(|e| io_err("read", e))?;
189 201 client.upload(&blob.hash, &target.upload_url, data).await?;
@@ -511,6 +511,16 @@ pub struct BlobUploadUrlResponse {
511 511 /// Presigned S3 PUT URL. Empty string when `already_exists` is true.
512 512 pub upload_url: String,
513 513 /// True if the server already has a blob with this hash (skip upload).
514 + ///
515 + /// Consumer contract: this is a *server-asserted* bool, and a consumer that
516 + /// acts on it skips sending bytes it holds locally. A server that answers
517 + /// `true` for content it does not actually have makes the upload a silent
518 + /// no-op, so the blob is simply absent later. That is a liveness failure,
519 + /// not an integrity one: nothing accepts unverified bytes as a result. The
520 + /// download path re-hashes what it receives and rejects any mismatch
521 + /// (`SyncKitError::IntegrityFailed`), so a lying server can withhold a blob
522 + /// but cannot substitute one. Treat a skipped upload as unconfirmed rather
523 + /// than as proof the server holds the content.
514 524 pub already_exists: bool,
515 525 }
516 526