[MG_State, MG_Backend, MG_Remote, MG_Impl, CI] (Disaggregated): P5c gt - the role guards close the phase

Layer 1 arms the texture object surfaces at MipmapStorage (the one point every concrete
texture class and view forwards through): AllocateStorage, TruncateMipmapLevels,
UpdateMipmapSubData, MapMipmapData, MarkStorageDirty(Region), IsStorageDirty,
GetStorageDirtyRegion(s) take Fatal{RoleViolation, texture-legacy-arm} from the apply
thread with an active transport. What the lane then exposed is fixed, not exempted: the
completeness gate answers from the staged store's defined-ness, the two logging reads go
through the descriptor macro, and the remint replay loop keeps its named
Fatal{UnmigratedEmulation} ahead of the guard. Magma's texture sync read path is the one
named exemption (MGPipeTextureLegacyArmScope, P7). Layer 2 wires InBarrierWait as the
client half of the gPipeInputs single-writer rule (RefusePipeInputsTouchWhileApplierOwnsIt:
applier owns it AND this thread not in a barrier wait => Fatal{RoleViolation,
gPipeInputs}; the barrier-off negative control is deferred to its own Fatal), and stale
reads of RECORD-SUPPLIED / APPLIER-DERIVED fields under a server stamp now report
RoleViolation with the field named. The RemoteGuards suite pins all nine texture surfaces
plus HasDefinedContent by name, and a negative control for the fill window. CI gains the
integration-split-strict job: unit lane as the hard gate under MOBILEGL_IPC_STRICT_ERRORS=1,
the scenario lane expected-red with the marker asserted (rewritable to plain green once P7
retires the object-class rows). rsp is per-frame in the stats line and measured on the
locally available traces (bsl in-world 948.5/frame avg over 123 frames, complementary
1591.9, iterationrp 286 until its named remint-pull Fatal); value-class pulls are zero by
rv's pinned list, what remains is the object-class fifteen.

Evidence: unit 2187/2187 (also under STRICT_ERRORS=1); integration-split 111/111; red-once
per layer (the texture guard and the gPipeInputs guard each turned their named death test
red and restored green). CONTRACT-P5C sections 5.4, 6.
This commit is contained in:
2026-09-17 11:20:44 -04:00
parent c74de6b506
commit a0865b00f4
10 changed files with 469 additions and 3 deletions
@@ -969,6 +969,25 @@ namespace MobileGL::MG_Remote::Client {
Bool ClientSession::ApplyThreadIsInsideApplier() {
return g_applyThreadInsideApplier.load(std::memory_order_acquire);
}
void ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt(const char* surface) {
if (Server::ServerLoop::OnApplyThread()) return; // the applier owns the block inside a verb
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
if (!ClientSessionInstance().Started()) return; // the bring-up window pre-dates the roles
// MOBILEGL_IPC_VERB_BARRIER=0 is R-1's NEGATIVE CONTROL: the single-writer rule is off
// by the operator's own hand there, and EmitAndWait's Fatal{BarrierViolation} owns the
// red. Firing here instead would pre-empt the control's evidence line.
if (!ClientSessionInstance().BarrierArmed()) return;
if (!ApplyThreadIsInsideApplier()) return;
if (InBarrierWait()) return;
MGLOG_F("MGPipe: Fatal{RoleViolation, \"gPipeInputs\"} - the GL thread touched gPipeInputs "
"(%s) while the apply thread was inside the applier and this thread was not in a "
"barrier wait. R-1's barrier is the only thing that makes one process-wide "
"gPipeInputs legal (CONTRACT-P5 table 3); a touch in this window races the "
"applier's own reads of it",
surface);
std::abort();
}
void ClientSession::NoteApplyThreadEnteredApplier() {
g_applyThreadInsideApplier.store(true, std::memory_order_release);
}