Commit Graph
255 Commits
Author SHA1 Message Date
swung0x48 f8a9466b93 [Fix] (CI): retrace exit detection tolerates a single failed pidof, and artifact cleanup lists jobs in pages the API serves. 2026-10-07 02:19:59 -04:00
swung0x48 c1e985c893 [CI] (P13 W5): the verify lane's omitted-fill-point control withholds the pack state at ReadPixels, and the data arm is logged at bring-up. 2026-10-06 15:45:14 -04:00
swung0x48 851251671c [CI] (P13 W5 S4): an include-closure probe keeps the record core clear of MG_Remote and the wire serializer. 2026-10-06 13:13:39 -04:00
swung0x48 e2c9098648 [Refactor] (P13 W3c): delete ResidualValueBlock, op 46 becomes a retired declined row and the verify comparator owns the capability check 2026-10-06 04:04:51 -04:00
swung0x48 a4d10dd9b6 [CI] (P13 W3b): retired-switch hygiene gate and the fatal census at its new floor 2026-10-06 04:04:50 -04:00
swung0x48 5e547add80 [CI] (P13 W3b): memo-key purity has no exempted arm once the legacy memos are gone 2026-10-06 04:04:47 -04:00
swung0x48 fc57a79bf1 [Build] (P13 W3b): no MOBILEGL_PIPE_PUSH macro or CMake variable, drop the HandleRecycle Legacy and AbaControl lanes 2026-10-06 04:04:46 -04:00
swung0x48 55de98a006 [Build] (P13 W3a): MOBILEGL_PIPE_PUSH is no longer an option, the macro stays 1 until W3b 2026-10-06 04:04:42 -04:00
swung0x48 9f05e66591 [CI] (P13 W2): retire the pull control library, the transport control runs the push library without MG_Remote 2026-10-06 04:04:41 -04:00
swung0x48 68b56165da [Build] (P13 W2): push by default, drop the Gradle pipePush switch so FCL embeds the push monolith 2026-10-06 04:04:40 -04:00
swung0x48 5ff33cbb77 [CI] (P13 W1): push-without-MG_Remote control and zero-MG_Remote check, monolith means no transport, skip census 2026-10-06 02:39:49 -04:00
swung0x48 aee0632a82 [CI] Move Test runs to a new concurrency group so they skip the stuck self-hosted run 2026-10-05 22:45:29 -04:00
swung0x48 1d1cfa6603 [CI] Temporarily run all workflows on GitHub-hosted runners 2026-10-05 11:32:08 -04:00
swung0x48 d409731632 [Fix] (CI): restore hosted builds and pin CMake 3.31.10 2026-10-05 10:41:03 -04:00
swung0x48 039b342b97 [Fix] (CI): separate CPU builds from GPU stages and repair CTest metadata 2026-10-05 10:00:48 -04:00
swung0x48 4eee6ae251 [Fix] (CI): remove empty retrace environment mapping 2026-10-05 09:19:03 -04:00
swung0x48 ee9919ba39 [Fix] (CI): merge Arch WSL runner and prefer it over hosted runners 2026-10-05 09:16:06 -04:00
swung0x48 5331106e90 [CI] Use uname for Arch runner hostname 2026-10-05 08:57:17 -04:00
swung0x48 ca981a8fc1 [CI] Add Arch WSL GPU runner smoke workflow and configuration 2026-10-05 08:56:27 -04:00
swung0x48 24889447cc [Fix] (CI): simplify integration job display names 2026-10-05 08:55:52 -04:00
swung0x48 d8edc8baa2 [Fix] (MG_IntegrationTest): validate specialized controls and classify strict skips 2026-10-05 08:45:28 -04:00
swung0x48 880be57bb6 [CI] (Watchdog): disable GPU hang detection in all CI runs 2026-10-04 10:39:39 -04:00
Swung0x48 2d42414667 [CI] (Test): annotate failed ctest entries with their failure lines, since the run logs need a signed-in reader 2026-10-04 07:20:04 -04:00
Swung0x48 c1e957cb98 [CI] (Gates): retire the P3a/P4a untouched-region gates; those phases closed and later phases legitimately changed the regions 2026-10-04 06:30:24 -04:00
swung0x48 0222852b94 [Fix] (Espryt, P8-SE): a spawn / tcp server publishes the default framebuffer's depth/stencil format (it has no frontend placeholder; the case leaves pending-fix for the three arms); the server CPU mip arm declines by name (generate-mipmap-store-declined) instead of filtering a store the driver wrote over, and E's refused-readback knob reaches it (CpuMipRefused. entries); WireIndirectDrawScenario case 3 runs on the Espryt split arms; copy-image-shadow-mirror goes and unmigrated_emulation_sites.py holds PipeCatalogueTest's list to the call sites 2026-09-30 03:44:29 -04:00
swung0x48 976fbe4cd4 [Test] (P8-A): register the monolith-only integration cases on the split, spawn and tcp arms (P8Coverage.cmake: 246 ambient cases, five server-knob lanes each on its own tcp supervisor), retire the ClientSideIndices and GuiBatch exclusions, and gate the rest with split_coverage.py and its exemption table 2026-09-29 22:14:31 -04:00
swung0x48 2a902cbd79 [Test] (P11 A1/A2): LargeArenaAdoptionScenario asserts the tier its arena landed in and reads the tcp AdoptTier0 refusal; registered on Split/Spawn/Tcp for both backends (Espryt through the split-arms macro, Magma's Arena. block and Buffers lane), the monolith lanes declare adopted 2026-09-29 07:23:41 -04:00
swung0x48 0fe015886e [Docs] (Disaggregated): the top-level files become short indexes in plain language; the design text moves to design/, the how-to and reference material to guide/
- README, ARCHITECTURE, ROADMAP, MEASUREMENTS and CURRENT_STAGE_PROGRESS are rewritten as indexes
  (84 KB -> 15 KB): what the project is, where it stands, and where each detail lives. ROADMAP is one
  plain sentence per phase; MEASUREMENTS is the five conclusions that matter plus one line per phase;
  CURRENT_STAGE_PROGRESS is the P12 goal, a checklist and the next steps.
- ARCHITECTURE's body moves unchanged into ten design/ files, keeping §1-§17 and appendices A/B, so
  `ARCHITECTURE.md §N` references in code resolve through the table in ARCHITECTURE.md.
- guide/ holds build-and-run, code map, glossary and the engineering discipline (from README and
  ROADMAP), and the device pinning record that used to sit in devices/.
- The open-debt table moves to notes/DEBTS.md; the detailed current-stage tables (with decision IDs,
  the open line items and evidence locations) move into notes/p12/README.md. notes/README.md maps
  every moved path and section.
- The documentation citation lint in test.yml also covers design/ and guide/, which describe the
  current tree as the top-level files did.
2026-09-24 22:39:46 +00:00
swung0x48 3d7cc9592b [MG_Remote, MG_IntegrationTest, CI, scripts] (P7): add Ph fuzz arm 1 for malformed control frames 2026-09-23 13:45:00 -04:00
swung0x48 1ef27dcd5c [MG_Test, MG_IntegrationTest, CI] (P7): add fuzz arm 3 - a peer that stops draining, on shm and tcp 2026-09-23 13:44:13 -04:00
swung0x48 877c531a0d [CI] (P7): tighten renamed CI titles 2026-09-23 13:23:09 -04:00
swung0x48 d7b9218618 [CI] (P7): short, readable job and step titles 2026-09-23 13:23:09 -04:00
swung0x48 e23e01e7ab [CI] (P7): one live Test and APK run per branch, and docs-only pushes skip the APK workflow 2026-09-23 13:23:09 -04:00
swung0x48 b13045918a [CI, scripts, tools] (P7): the pull control strips ctest -V's line prefix, retrace-split pins lavapipe, and the retrace summary reads role logs 2026-09-23 13:23:09 -04:00
swung0x48 cdf49a4624 [CI, tools] (P7): every retrace-split step gets the Magma repair knobs, and a verify retrace reads the pull library's mobilegl.log 2026-09-23 13:23:09 -04:00
swung0x48 15ce495363 [MG_IntegrationTest, CI] (P7): the dual-block census names the nine split-arm skips and both buffer lists require all twenty-one cases 2026-09-23 13:23:09 -04:00
swung0x48 ffc4fb7d77 [MG_Impl, MG_IntegrationTest, ci] (P7): the first perturbation block's falsifier is the DirectVulkan entry
- measured with one block removed at a time: without the re-applied block both server halves drop to 1 (DirectGLES 1 / DirectVulkan 1, both entries red); without the first block DirectVulkan drops to 1 (red) while DirectGLES stays at 2 (green), because the GLES backend reads the pack field twice after the neutral pack is installed (3 = the saved-pack read + 2 post-install) and DirectVulkan once (2 = 1 + 1)
- so the >= 2 requirement falsifies the re-applied block on both backends and the first block on DirectVulkan; the CI step runs both backends and needs both, which it already does
- the scenario, hook and CI comments now say exactly that, instead of "removing either block drops it to 1"
2026-09-22 23:02:08 -04:00
swung0x48 403b566949 [MG_Impl, MG_IntegrationTest, ci] (P7): the read-side control counts to two, one per perturbation block
- the server reads GetPixelStoreParameters twice inside the ReadPixels window - PipeApplier::read_pixels saves the application's pack through the accessor before it installs the neutral one, and the backend's ReadPixels reads the field after - and both reads sit inside ServerReadsInsideTheNeutralPackWindow; the hook's first perturbation is what turns the saved-pack read red (past the first compare the application's pack differs from the neutral oracle on its own), and only the perturbation re-applied after the window's overwrite can turn the post-install read red, because there the stored value is the neutral pack
- so a control that accepted ONE `where=read` line was satisfied by either block alone and falsified neither: the second block, which protects the lane's post-install backend reads, had no falsifier
- CorruptedFieldIsReportedOnTheServerRead now counts the `where=read` reports for the field in the server half since its mark and requires at least two; the CI read-side step does the same with grep -c; both block comments in the hook say which read each turns red
- measured on the host: both entries green, server half DirectGLES 3 / DirectVulkan 2; removing only the second block rebuilds green and turns both entries red with exactly one report per server half (1 / 1, ctest rc 8); the file was restored byte-identical and rebuilt
2026-09-22 23:02:08 -04:00
swung0x48 c69d8dfdc8 [MG_Remote, MG_Test, ci] (P7): sha256(protocol.fbs) is pinned to MOBILEGL_PROTOCOL_CONTROL_REVISION, so a control-schema edit without a bump is red in the unit lane and in flatc-check instead of a silently absent field
- the revision is a hand-bumped integer mixed into wireFingerprint and nothing tied it to protocol.fbs's content; scripts/ci/protocol_revision_pin.py holds {revision -> sha256(protocol.fbs)} in protocol_revision_pins.json and refuses a schema whose digest is not the current revision's, or a revision with no row
- registered as ProtocolSchema.RevisionPinsDigest (label unit, MG_Test/Wire/CMakeLists.txt, --self-test proves both perturbations red before it says green) and as a step of CI's flatc-check job, the one that regenerates the header on every schema edit
- --write records a bump's row and refuses to re-pin an existing revision to a different digest, which is what a schema edit without a bump looks like
- mg_protocol_base.h says where the pin lives beside the revision it pins
- red-once: protocol.fbs touched with one trailing comment, revision left at 1 -> "protocol.fbs has sha256 a7b5c7fb... but MOBILEGL_PROTOCOL_CONTROL_REVISION is still 1, which is pinned to 7fc859f7..." rc=1; restored, green
2026-09-22 21:54:46 -04:00
swung0x48 30d3595213 [ci] (P7): the verify-split artifact keeps the green lane run, not the controls that overwrote 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
2026-09-22 21:35:06 -04:00
swung0x48 b4f830942b [MG_IntegrationTest, ci] (P7): the verify-split arm proof is a per-entry census, not a floor
- 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
2026-09-22 21:35:06 -04:00
swung0x48 e9b3f5fe04 [MG_Impl, MG_IntegrationTest, ci] (P7): the compare-at-read hook gets a negative control of its own
- 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
2026-09-22 21:35:06 -04:00
swung0x48 8fa1821ab2 [MG_Remote, MG_Test, ci] (P7): one constant-time token comparison serves both server policy sites, a configured token shorter than 16 bytes refuses the listen by name, and CI builds flatc so the TCP supervisor smoke runs and must pass 2026-09-22 21:00:57 -04:00
swung0x48 266d6a32f8 [tools] (P7): the wire declines audit carries a self-test whose fixtures must red, and the CI step runs it before the tree audit
Fable review of the B3 rework, should-fix: the audit had no negative
control, so a green run could not say it would have been red. It now takes
--self-test, which writes each inline fixture as a one-file renderer tree in
a temp dir, runs the audit on it and checks both the rc and the line the
audit must print (so a red is red for the fixture's reason):

  comment-only site (//)                -> rc 1, "has NO SITE"
  comment-only site (/* */)             -> rc 1, "has NO SITE"
  string-literal site                   -> rc 1, "has NO SITE"
  other-branch log                      -> rc 1, "with no MGLOG_W/E in its own block"
  nested-branch log                     -> rc 1, same
  log only in a /* */ comment above     -> rc 1, same
  nearest log is MGLOG_D                -> rc 1, same
  good sites (AT form, bare Count under
    its own MGLOG_E_ONCE, def row with a
    trailing comment)                   -> rc 0, "0 unlogged, 0 unknown"

8/8, rc 0. Mutants: with the strip disabled 5 of 8 fail (the four
comment/string reds go green and the good fixture's commented row is
lost); with the old 8-line window in place of the block walk 2 of 8 fail
(other-branch, nested-branch). Against the audit as it stood at aab01275,
six of the seven red fixtures passed and the good one failed.

.github/workflows/test.yml: the "Wire declines audit (B3)" step runs
--self-test and then the tree audit, the way the next step runs
trace_cases.py --self-test-transport-matrices before it computes matrices.
2026-09-22 20:49:45 -04:00
swung0x48 38bfdf6d63 [MG_IntegrationTest, MG_Pipe, ci] (P7): VERIFY_CORRUPT and POISON_OMIT each go red on the split arm, the poison through the server's own stamp with the ReadPixels:GetPixelStoreParameters pair, and verify stays inproc-only because its two arms share only that process 2026-09-22 20:34:50 -04:00
swung0x48 64a2f4a300 [MG_IntegrationTest, MG_Pipe, ci] (P7): every verify case runs on the split (inproc) arm of both backends under integration-verify-split, armed per entry, and the comparator's oracle for the pack half inside the server's read_pixels window is the ID-49 neutral pack 2026-09-22 20:34:50 -04:00
swung0x48 b8d10d0730 [MG_Backend, tools] (P7): every WireDeclines.def row has a site and every site a warning-or-error line, and a tree-only audit fails the job when one does not
The B3 review (ID-P7-34) counted 12 of the 52 rows with no site at all -
TexViewFormat, TexNoApplierRecord, TexRecordDeadOrStale and the nine Shape*
rows were declared and never counted - and ten sites that counted with no (MGLOG_D included)
line, so "a name, a tally and a decline line" did not describe the tree.

- SyncWireTextureShape: all nine Shape* exits now count; the ViewOf exit
  and the preserve-flush exit were bare `return false` and are now
  MGL_WIRE_DECLINE_AT; the image-flags exit logged only MGLOG_D, which a
  Release build compiles away, and is promoted likewise.
- The preserve-copy exit was the one Shape exit with no row: appended as
  ShapePreserveCopyFailed (rows are append-only), 53 rows.
- SyncTextureResourceByHandle / UploadPendingWireLevels: the count-only
  sites without a log of their own are promoted to MGL_WIRE_DECLINE_AT;
  the ones already under a W/E _ONCE line keep that line and gain the count.
- scripts/ci/wire_declines_audit.py: fails on a row with no site, a site
  naming no row, or a bare Count() with no MGLOG_W/E within 8 lines above
  it. WireDeclineTally.h (the macro's definition) is not a site. Wired into
  the job that runs the fatal census.

Audit before: 52 rows, 40 with sites, 10 unlogged under the W/E rule (rc 1). After: 53 rows,
53 with sites, 0 unlogged (rc 0). Forced red (ShapeNoVkFormat made true for
1x1 textures, reverted): Split.Fm.F1WireScenario.VertexIdSampler... fails on
pixels with ShapeNoVkFormat=2 / TexShapeSync=2 in the shutdown tally.
2026-09-22 20:24:00 -04:00
swung0x48 394df89237 [scripts, ci] (P7): the memo purity gate asserts its own anchors and allow-list, and stops calling a policy exemption an absence
Rework round 3 of 4 after the D2 review (fable, ID-P7-8). Three defects, each of a kind
the gate could not have reported about itself.

(a) TWO STRUCTURAL SELF-CHECKS. family_spans() returned whatever matched and nothing ever
asserted that each of the five FAMILIES anchors found its declaration, so a renamed struct
would drop a whole family while every rule kept passing. And an allow-list entry no rule
can consult documents a carve-out that is not happening - a claim about the tree that
nothing verifies. main() now fails on either, naming the anchor or the entry and saying
which of the two repairs is wanted. Both are exercised: renaming an anchor and adding a
dead entry each red the gate.

(b) THE DOCSTRING AND THE test.yml COMMENT WERE WRONG about what the guards mean.
MOBILEGL_PIPE_LEGACY_MEMOS defaults ON (CMakeLists.txt:43) and no job here clears it, so
CI split builds DO compile those arms - TwinLookupMemo (DirectGLES.cpp:114) sits inside
one and is compiled into every lane this repo runs. The rule is "the legacy arm MAY KEEP
pointer keys", a policy exemption; only !MOBILEGL_BUILD_DISAGGREGATED is code a split
build genuinely never sees. Both now say so.

(c) FRONTEND HAD NO LEADING WORD BOUNDARY, so BackendSamplerObject* matched SamplerObject
and the gate reported the memos' backend VALUES as frontend keys - which the first version
answered with two allow-list entries, i.e. it documented a false positive instead of
fixing it. (?<!\w) added, with controls both ways: the backend twins must stay green and
the qualified MG_State::GLState::ProgramObject* must still match.

The allow list therefore goes from six entries to ONE. Four were dead on arrival - two
false positives now fixed at the source, one naming a typedef with no frontend type in it,
and const void* program / Uint64 programLifetimeId, which no rule could ever consult
because void is not in FRONTEND and neither line calls GetLifetimeId(). The
ResolvedTextureBindingMemo monolith key those last two described is still deliberate and
still there; it simply never needed an exemption, and saying it did was misleading. What
remains is the twin registry's UnorderedMap<StateObject*, Entry>, which really is
suppressed and really is the pull build's only arm.
2026-09-22 18:17:25 -04:00
swung0x48 6567bc1e16 [scripts, ci] (P7): a purity gate for Espryt's re-keyed memo families
P3b/P4b deepening, landing under the P7 program (PLAN-PH-P34B-P7.md 1.2, wave 2-D
package D2): "一条纯度 grep 门:split 构建下这些 memo 键里不得出现前端指针类型;接 CI".

scripts/ci/espryt_memo_purity.py, a sibling of link_seam_purity.py in the same style:
a hard-coded source list, a code_only() comment strip, (pattern, message) rules, and
a --self-test that runs the negative controls and then the real scan in one
invocation - which is what lets test.yml pass only --self-test and still get the
gate. Wired in next to link_seam_purity.py.

It scopes the rules to the BRACES of the families the plan names -
ResolvedTextureBindingMemo, SamplerPassMemo, UnitSamplerLookupMemo, TwinLookupMemo,
StateBackendObjectRegistry (the program registry and its five siblings) - plus the
image sweep, which is seven file statics rather than a struct and is matched by name.
Scoping matters: these two files pass frontend pointers around legitimately in
hundreds of places, and a file-wide grep would be a list of exceptions rather than a
gate.

Guard-awareness is a small conditional walker, not a preprocessor: it recognises
#if MOBILEGL_PIPE_LEGACY_MEMOS, #if !MOBILEGL_BUILD_DISAGGREGATED and the #else of
#if MOBILEGL_BUILD_DISAGGREGATED, which is every spelling these files use, and says
so in the docstring.

Six allow-list entries, each carrying its one-line reason. The two that matter:
StateBackendObjectRegistry's UnorderedMap<StateObject*, Entry> and
ResolvedTextureBindingMemo's type-erased `const void* program` are the PULL build's
only arms - deleting them moves pull .text and fails G1's 0/0/0/0, so they retire
with the pull path (P13), not here. The other four are memo VALUES rather than keys.

Seven negative controls turn red; four positive controls stay green, including a
LEGACY_MEMOS arm, the #else of a DISAGGREGATED arm, the allow-listed type-erased key
and a frontend pointer outside every memo family. Verified once more by injecting a
fresh unguarded ProgramObject* field into the real ResolvedTextureBindingMemo: the
gate reddens and names it.
2026-09-22 17:31:51 -04:00
swung0x48 8bb6309a1c [scripts, CI, docs] (P7): the 184-symbol link-closure ratchet lands as a gate
CONTRACT-P6.md 12.1 has carried "a ratchet recomputes the 184 in CI and asserts it only
ever falls" as an obligation since P6. The roadmap handed it to P6.5; the P6.5 line never
received it, and scripts/ci/ has never had anything that computes this number. ID-P7-7
gives it to P7 wave 0. This is it.

scripts/link_ratchet.py mechanises a6's hand experiment: partition the per-object output
of the single MobileGL target into SERVER / FRONTEND / SHARED, then compute
(SERVER undefined) - (SERVER defined) - (SHARED defined) intersect (FRONTEND defined),
which is the undefined-symbol list a real link of a server-only image would print. The
partition is an explicit table with a rationale per row, because it IS the module boundary
the build system does not have (SOURCE_FILES is one flat list; boundaries are P13's work).
An object matching no row is a hard failure, as is a build whose CMakeCache lacks the three
disaggregated options, or an empty role set - every way this gate could go green without
having measured anything is an explicit exit 2.

Measured on 78b7d6be: 186 symbols. The three backend buckets reproduce a6's hand-made
101 / 14 / 60 exactly, as do its per-object referrer and definer distributions
(UniformManager 90, VulkanRenderer 60, Managers 53, DirectGLES 51; TextureObject 59,
SamplerObject 30, BufferObject 18, FramebufferObject 16, PipeFill 9). Nothing from a6's
184 has been cleared; the +2 are both P6.5's:
  Client::ClientSession::StartSpawned()                 - fork-per-session
  GLState::ProgramArtifactsSchemaFingerprint()          - archive v2 caps

The baseline stores the LIST, not a count (ID-P7-7): a count-only ratchet goes green on the
day one symbol is cleared and another added. --assert-monotone fails only on symbols absent
from the baseline and names each one's defining object and every referring object; symbols
that disappeared are reported with an instruction to re-baseline in the same commit, and
never fail.

The CI step goes in build-linux-split, not build-linux: build-linux takes the three options
from MOBILEGL_CI_DISAGGREGATED, which is ON only on feat/disaggregated and
codex/fix-github-ci, so on any other branch it builds a monolith and the gate would measure
a SERVER set with no transport half. The JSON is uploaded even when the gate fires.

Two findings for the integrator, both written up in the note:
  - a6's four published bucket totals sum to 181 against its own headline of 184. These
    buckets are a strict partition and always sum to the headline; the difference is in
    a6's core bucket, which it listed selectively.
  - 5 of the 14 "P3b/P4b" symbols are MG_Pipe slot-allocator and apply-thread plumbing
    that sits under MG_Impl/Pipe/ by file location alone. P3b/P4b cannot clear them by
    migrating Espryt functionality; they need P13's module move. That bucket's floor is
    5, not 0, so the plan's 0.5-day Tier 1 estimate for "D12's 14 symbols" should be
    re-cut against 9. All 7 symbols from that object carry a `# P13` annotation.

red-once (R-16) is recorded verbatim in the note. Worth knowing: the plan's own example
(SamplerObject::GetMinFilter) does NOT turn this gate red, because it is already in the
baseline - the ratchet reddens on new symbols, so a negative control has to pick one that
is not in the 186. A reach to GetBorderColorForm() from DirectGLES/Utils.cpp does, naming
the symbol, its definer and its referrer, exit 1; reverted and green again.
2026-09-22 14:26:04 -04:00