Out-of-Band Content Transfer

A GitSync content sync mirrors a Space's git-committed content/** binaries into the Space's content collection. Until now the bytes travelled inline, on the SyncContentFilesRequest itself.

#2885 stopped the producer building one delivery per Space, so a delivery is now ≀ budget + largest single file. That closed the aggregate axis and left one residual, which that page records against itself:

A single file larger than the budget travels whole. […] A file that large belongs behind a content-store handle rather than inline.

This page is that handle. A file whose packaged cost alone exceeds ContentDeliveryBudget.BudgetBytes no longer rides the message: its bytes go into the destination collection once, and the delivery carries a content-addressed reference to them.

Why the residual could not be closed by moving a number

ContentDeliveryBudget.BudgetBytes is DeliveryPayloadBounds.MemoryStreamBlockBytes = 1,048,576 β€” Orleans' memory-stream block size, hard-coded in MemoryAdapterFactory with no configuration surface. It is not a knob, and even if it were, raising it is the move Oversized Delivery Refusal exists to forbid.

The scale is not an edge case. Measured on Systemorph/MeshWeaver.Education@f7ae723 (2026-09-04, unchanged from the 2026-09-03 measurement on 61cbbac):

Space files over budget total largest packaged
AgenticEngineering 25 12 101.2 MB 13,188,871
AgenticBusiness 9 4 27.2 MB 10,910,243
AgenticPrimerDe 7 3 10.9 MB 4,291,888
AgenticPrimer 7 3 9.8 MB 3,873,652
DataModeling 3 1 8.6 MB 10,929,144
AdvancedBusinessRules 2 1 9.5 MB 12,540,448
AgenticOffice 3 1 8.2 MB 10,224,527

Every Space in the repo has at least one file over budget β€” 25 in total. 🚨 The axis is "has a video", not "is large": AdvancedBusinessRules totals 9.5 MB β€” one of the smallest Spaces there β€” and carries the second-largest single file. Sorting Spaces by total size does not identify the affected set.

Where the bytes land

In the destination collection itself, under a reserved staging folder β€” ContentStaging.Folder (_staging/), at the collection root.

That choice is not arbitrary; it is the only location that needs no new configuration and no new assumption about the deployment:

The producer reaches the destination collection the way MeshOperations.Upload and the content route already do: it asks the owning node's hub for the collection config with a GetDataRequest(ContentCollectionReference) β€” a few hundred bytes β€” registers it locally under the qualified name {nodePath}/{collection}, and resolves a provider over it. Only the config crosses the mesh; the bytes never do.

producer (import hub)                       receiver (Space-root node hub)
──────────────────────                      ──────────────────────────────
GetDataRequest(collection) ───────────────▢ config   (a few hundred bytes)
       ◀─────────────────────────────────── ContentCollectionConfig
write _staging/{sha256}  ══════▢ content store ◀══════ read _staging/{sha256}
SyncContentFilesRequest{ StagedFiles:[…] } β–Ά SaveFile(videos/intro.mp4)
       ◀─────────────────────────────────── ImportContentResponse
delete _staging/{sha256} ══════▢ content store

What the handle is

public record StagedContentFile(string Path, string Handle, long Length);

SyncContentFilesRequest carries them in a new StagedFiles list beside Files. A sync with no over-budget file produces a request byte-for-byte identical to what it produced before β€” StagedFiles is null and nothing else changes.

Content-addressing is what makes the transfer idempotent. Two files with identical bytes stage once. A sync that runs twice writes the same blob at the same key and the same file at the same destination path β€” no duplication anywhere. A staged blob that is already present with the right length is not rewritten, so a retry after a partial run does not re-copy 100 MB over SMB.

Who owns the lifetime

The producer owns every blob it stages, from Post() to the last delivery's answer.

How the receiver resolves a handle

ContentImportExtensions.SyncFiles writes the inline files exactly as before, then writes the staged ones:

target.GetContent($"{ContentStaging.Folder}/{staged.Handle}")
    .SelectMany(stream => stream is null
        ? Observable.Throw<int>(new InvalidOperationException(
            $"Staged content '{staged.Handle}' for '{staged.Path}' is not in the collection's "
            + "staging area β€” the out-of-band transfer did not complete."))
        : target.SaveFile(dir, name, () => stream).Select(_ => 1))

Every leaf runs on the collection's own IIoPool; the hub action block only subscribes and returns. The bytes are streamed from the staging blob into the destination file β€” they are never materialised as a byte[] on the receiver, which is the whole point.

Failure behaviour stays honest

Content Sync Visibility's entire contribution was making a refused sync observable. This change must not trade a loud refusal for a quiet success, so:

What this does not change

Rules

Reconnecting…
The connection to the server was interrupted. Trying to restore it…
Trying again…
The connection could not be restored. Reloading the page…
The server was updated. Reloading the page to pick up the latest version.