Content Sync Visibility

A GitSync import carries a Space's content/** binaries inline, as bytes on a message. Every transport underneath the mesh bounds how large one message may be, so some of those deliveries are refused — correctly, by the guards described in Oversized Delivery Refusal.

A refusal that is correct at the transport is still a delivery failure at the content layer. For months it was reported as nothing at all.

The defect: "refused" and "has no content" were the same answer

Both content passes in StaticRepoImporter folded an unsuccessful sync into 0 files:

.Select(r => r.Success ? r.FilesImported : 0)   // no log at all on the false arm

Zero files is exactly what a Space with no content reports. So the import summed zero, returned "Imported", and stamped Succeeded at that fingerprint — and Succeeded at a fingerprint is the durable short-circuit, so every later import of the same repo content skipped the Space without reading it. A Space whose assets were refused on every attempt was indistinguishable from one fully in sync, permanently. The person who found out was a learner opening a course page with a missing video.

That is the same shape as a claim that blocks a create (#2211), one layer down: the source declares it, something refuses it, and no boot can change that.

The three things a refusal now says

Question Where it is answered
Did this pass leave the Space in the state the marker claims? StaticRepoImportResult.Outcome = ImportedWithRefusedContent, which becomes a Warning marker — so the next boot re-attempts instead of short-circuiting on green.
Why? StaticRepoImportResult.RefusedContent — one RefusedContentSync(NodePath, Reason) per owning node, carrying the transport's own verdict plus the producer's measurement. Named in the import activity's summary too.
How does the author of the content find out? An _Activity/content-sync ledger on the node itself.

Why the reason had to be carried, not guessed

The response already knew. ImportContentResponse.Error and the DeliveryFailureException message were discarded at both call sites, leaving the activity to write prose in their place — "most often a delivery over the transport's size budget". Three very different problems reach that arm:

Only the first is about size, and a report that guesses at the cause sends every reader after the wrong one.

Why the producer names the size

SyncContentFilesBuilder already measures every file in order to split the write across deliveries (#2885). It threw the numbers away. ContentDeliveryBudget is now the one place that answers what a file weighs and against which limit, so the partitioner and the failure report can never describe different deliveries:

ContentDeliveryBudget.BudgetBytes                  // 1,048,576 — Orleans' memory-stream block
ContentDeliveryBudget.PackagedCost(file)           // 4 × ⌈len/3⌉ + path.Length — never touches the bytes
ContentDeliveryBudget.DescribeOverBudget(files)    // null when every file fits

When a sync fails and a file is individually over budget, Post() folds that sentence into the reason:

Refused to dispatch delivery '…' to grain 'messagehub/AgenticEngineering': its payload is
149,199,409 bytes, at or over the 104,857,600-byte Orleans MaxMessageBodySize … —
12 of 25 file(s) exceed the 1,048,576-byte per-delivery content budget ON THEIR OWN, and a file is
never split — so the delivery carrying one is over the budget however the set is partitioned.
Largest: 'content/videos/module1-intro.mp4' at 13,188,820 packaged bytes (12.6× the budget).

🚨 ContentDeliveryBudget measures; it never refuses. The budget is the Orleans memory-stream block size, which binds only where that transport is in the path — a monolith carries an over-budget file perfectly well. A producer-side rejection would stop content that works today from syncing, which is the opposite of the defect: the bug is a refusal nobody can see, not a delivery nobody refused.

The ledger: one node, updated in place

Each owning node whose content this pass tried to sync gets an Activity satellite at {nodePath}/_Activity/content-sync:

State Status Message
Refused Warning 📦 CONTENT SYNC REFUSED — this node's assets are NOT in the mesh. (reason, with sizes and limits named)
Delivered Succeeded ✔ Content assets in sync

Three deliberate choices:

The whole ledger path is best-effort: observability must never break the import it observes.

What this does NOT fix

A single file over the budget still travels whole. #3097 partitions by aggregate; a file is the atom the receiving handler writes and is never split, so the guarantee is delivery ≤ budget + largest single file. Measured 2026-09-03 against Systemorph/MeshWeaver.Education@61cbbac, every Space in that repo has at least one file over the 1 MiB budget — 25 files in total, the largest packaging to 12.6 MB:

Space files over budget largest packaged
AgenticEngineering 25 12 12.6 MB
AgenticBusiness 9 4 10.4 MB
AgenticPrimerDe 7 3 4.1 MB
AgenticPrimer 7 3 3.7 MB
AdvancedBusinessRules 2 1 12.0 MB
DataModeling 3 1 10.4 MB
AgenticOffice 3 1 9.8 MB

🚨 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.

The durable cure is out-of-band asset transfer: a file that size belongs behind a content-store handle (write the bytes once, ship a reference), not inline on a message. That landed as issue #3233 — the bytes go into the destination collection's reserved staging folder and the delivery carries a content-addressed handle, so those 25 files now arrive. See Out-of-Band Content Transfer for the design, the lifetime rules and the idempotence argument.

What this page describes remains load-bearing after that fix, and the fix depends on it. An out-of-band transfer can itself be impossible — a Space whose store the producer cannot reach — and the rule is that it then falls back to the inline road and the failure names BOTH halves: the over-budget file with its packaged size and the limit (this page), and why the out-of-band road was unavailable. A handle the receiver cannot resolve is likewise reported as the failure it is, never as a file quietly written empty. Trading a loud refusal for a quiet success would be this page's own defect one layer down.

🚨 Measure it on a repo-backed install. memex.meshweaver.cloud serves the Education assets from its own store — they were uploaded there by hand on 2026-07-17/18 — so that portal cannot answer whether a sync would deliver them.

Reconnecting…
The server was updated. Reloading the page to pick up the latest version.