- integration-verify goes 1136 to 1138 (273 skip): the new CorruptedFieldIsReportedOnTheServerRead case is discovered by the ambient Verify. registration too, where it skips for want of the CORRUPT knob - the same shape CorruptedFieldIsReported already has there
- integration-verify-split is 1072/1072 with 117 skip, re-measured on the final head
- 0 gains a row for the fix round, one line per finding, pointing at the section each landed in
- 2.2 and the hook's own comment both said the comparator can no longer see a client that pushed a WRONG pack state on the server's ReadPixels. Too broad: PipeApplier.cpp reads savedPack THROUGH THE ACCESSOR, and therefore through the hook, BEFORE it installs the neutral value, so the pushed pack is compared against the live one right there
- a correct push is equal and returns; a wrong push that is not exactly the neutral pack fails both compares and is still Fatal; only a wrong push that equals {SwapBytes 0, LSBFirst 0, RowLength 0, ImageHeight 0, Skip* 0, Alignment 1} is invisible, and only while the application's own pack differs from it
- notes/p7/verify-split.md 2.2 and 6 say so, and the comment above ServerReadsInsideTheNeutralPackWindow says so
- the measured evidence is the read-side control's own server half: 2 to 3 where=read reports per readback, which is the savedPack read plus the backend read
- the verify-split block and notes/p7/verify-split.md 3.3 both said the spawn server runs no compare "because MG_Impl is not in it", which is false and sent a reader looking for a link-time gap: MobileGLServer is an executable over ServerEntry.cpp linked against the WHOLE library (top-level CMakeLists.txt), PipeFill.cpp included, so MGPipeVerifyReadHook is present and is called on every backend read
- the true mechanism: the server image's main reaches MG_Backend::InitServerRoleForSpawn() and never MG_State::Init(), so MG_State::pGLContext is null and the hook returns at its ctx == nullptr line before comparing anything
- the inproc-only conclusion is unchanged; what changes is the shape of the fix, which is a cross-process compare rather than a link edit
- CONTRACT-P7 12 records that debt by name, and the note's 6 cites it
- both negative-control steps re-run 12 ambient VerifySplit. entries with a knob exported, and an ambient entry writes to its own per-entry path under verify-split-logs/ which the library opens "w" - so the upload at the end of the job collected twelve CONTROL logs sitting under the LANE's names
- if: always() made it worse rather than better: a failure investigation is exactly when the overwritten half is the half somebody needs
- the arm proof is now followed by a cp -a to verify-split-logs-lane/ (plus the lane's JUnit), and the upload takes both trees - the green run the arm proof read, and whatever the controls left behind
- the landing shape was a count - at least 850 armed of at least 1000 client logs - which leaves the remaining 155 unnamed: the lane could lose fifty arms, gain fifty skips, and the floor would not move
- the rule is now per entry and two-sided, the one dualblock-expected-fatals.txt already rides: each client log must carry the arming line, or belong to an entry ctest reported SKIPPED, or be named in the new Harness/verify-split-unarmed-expected.txt
- a gtest skip is the recognised no-verb marker and is NOT named, because the skip set moves with the driver - naming it would ratchet on lavapipe rather than on the comparator
- both other directions fail: a listed entry that armed, and a listed name with no client log in this run
- split_log_paths.py gains the verify-split-arming mode; the lane step gains --output-junit so a skip can be told from a pass; test_split_log_paths.py gains six controls covering the four ways the census can be wrong
- measured on the host over 1064 private client logs: 909 armed, 99 unarmed and skipped, 56 unarmed and named in five classes - caps/limits/extension advertisement 22, shader and program object surfaces 16, object state written and read back or a call rejected before it becomes a verb 14, framebuffer completeness queries 2 (DirectGLES only; DirectVulkan skips both), the poison parent that only forks 2
- ReadChildLog concatenated the child's two role halves and the assertion searched the union, so VerifySplitPoisonOmitted. would have been just as green if the stamp the knob withheld had been the CLIENT's - which is the case it exists to rule out, the mechanism under test being the server-side verb stamp honouring MOBILEGL_PIPE_POISON_OMIT
- kOmittedPairs carries FiresOnTheServer; ReadChildLogHalves keeps Client, Server and the union apart; the split pair now asserts the Fatal in the child's SERVER half under MOBILEGL_BUILD_DISAGGREGATED, the monolith pair keeps the union because it has one role
- measured on both backends: client half 0 Fatal{UnmigratedPipeInput lines, server half 1 each - the union search could not tell them apart
- monolith PoisonOmitted. entries stay green (2/2), split VerifySplitPoisonOmitted. stay green (2/2)
- MGPipeVerifyReadHook honours MOBILEGL_PIPE_VERIFY_CORRUPT: it perturbs the ORACLE after CopyField, and AGAIN after the ID-49 neutral-pack overwrite, because that overwrite replaces the pack half wholesale and CorruptStorage perturbs an array's element 0 - which is the pack half
- until now the knob only ever reached EntryCompare, so every red it could produce was the client thread's where=entry and the hook that runs on the server's apply thread at every backend read had no falsifier at all
- new entry per backend, DirectGLES/DirectVulkan.VerifySplitReadCorrupted., with CORRUPT=GetPixelStoreParameters and VERIFY_FATAL=0 - the field is in the kReadback fill mask, so the client entry compare would otherwise abort the process before the server reached the read
- the case reads the SERVER role's half of its private log alone: with the knob armed a union search over both halves would be satisfied by the arm that is not under test
- PipeStatsWindow.h gains ReadServerLogSince for that
- test.yml grows the matching control step - run the two entries, then re-read both server halves from outside for the where=read report
- measured: 2/2 green, server halves 3 and 2 lines, client halves 0 lines; revert-and-run with the two corruption blocks removed gives 2/2 red and both server halves 0 lines
- the lane goes 1070 to 1072
- MG_Pipe/MGPipeTypes.h gains inline MGPipeNeutralReadPixelsPack() next to MGPPixelPackState, whose owner it is; the applier that installs the neutral state and the comparator whose oracle it is now call the same function
- MG_Remote/Server/PipeApplier.cpp read_pixels takes the constant from there instead of building it field by field
- MG_Impl/Pipe/PipeFill.cpp drops ServerReadPixelsNeutralPack, the re-typed second copy: a drift between two hand-typed spellings costs a FALSE DIVERGENCE on the server read, not a build break, which is the direction that fails quietly
- notes/p7/verify-split.md 2.2 says one spelling and why the old reasoning (do not reach into MG_Remote/Server from MG_Impl) did not survive the failure-direction argument
- 144 commits from 785fed0b0 onward were rewritten to drop the Co-Authored-By trailer the
project forbids; origin/feat/disaggregated moved from 1135c664 to 3c80cd62 (same trees)
- the old->new map is ~/w7/logs/sha-map-20260922.txt; every 7..40-hex citation in the
Disaggregated docs and the MG_Remote contracts that matched an old SHA is replaced by the
same-length prefix of its rewrite; no other content changes