fix(streaming): stop replaying committed recenter retirements

Root cause: pending-only live projection buckets were misclassified as landblock presentation owners during origin recentering. That manufactured a second full cleanup receipt for a generation whose first receipt was still advancing; the duplicate guard threw and the broad retry path replayed the already-committed detach 243 times.

Keep pending live projections through the spatial identity map without issuing another receipt, and fail fast when a receipt-ledger invariant occurs after detachment. Evidence: docs/research/2026-08-02-collision-throughput-handoff/p1-retirement-receipt-loop.md. Release suite, lifecycle gate, and nine-stop soak pass.
This commit is contained in:
Erik 2026-08-02 20:53:11 +02:00
parent c65559d8f8
commit 01f4791e95
9 changed files with 309 additions and 9 deletions

View file

@ -322,8 +322,25 @@ public sealed class LandblockPresentationPipeline
{
GpuWorldRecenterRetirement detached =
_state.DetachAllForOriginRecenter();
Exception? adoptionFailure =
_retirements.AdoptDetachedFull(detached.Landblocks);
Exception? adoptionFailure;
try
{
adoptionFailure =
_retirements.AdoptDetachedFull(detached.Landblocks);
}
catch (Exception error)
{
// Spatial detachment is already committed. A receipt-ledger
// invariant failure cannot be retried by detaching the same
// generation again; doing so was the 243-frame origin-recenter
// exception loop. Surface the committed edge so the controller
// fails fast instead of pretending the operation is resumable.
throw new StreamingMutationException(
"Origin-recenter retirement receipt adoption failed after " +
"the spatial generation detached.",
mutationCommitted: true,
error);
}
Exception? failure = (detached.ObserverFailure, adoptionFailure) switch
{
(null, null) => null,