QuizWizz probe harness
This page records the executable runtime proof boundary for QuizWizz on WRDP. It proves bench behavior; it does not confer release eligibility.
For the latest full-run result and prior-run history, see Current bench result.
Command
Run from /home/loca/dev/wrdp:
./scripts/quizwizz-probe.sh --wave 1
./scripts/quizwizz-probe.sh --wave 2
./scripts/quizwizz-probe.sh --wave 3
./scripts/quizwizz-probe.sh --wave 4
./scripts/quizwizz-probe.sh --wave 5
./scripts/quizwizz-probe.sh --wave 6
./scripts/quizwizz-probe.sh --wave 7
./scripts/quizwizz-probe.sh --wave 8
./scripts/quizwizz-probe.sh --wave 9
./scripts/quizwizz-probe.sh --wave 10
./scripts/quizwizz-probe.sh --wave 11
./scripts/quizwizz-probe.sh --wave allThe harness accepts --wave 1|2|3|4|5|6|7|8|9|10|11|all and nothing else; any other value exits 64 with the usage line (scripts/quizwizz-probe.sh:8-14, dispatch at :3240-3254). On 2026-09-02 a green --wave all was 94/94 (1..94, # assertions=94 failures=0); the current contract per the Sep-8 receipt and re-proved on 2026-09-14 is 1..98, # assertions=98 failures=0, printed from the running counter rather than a hardcoded total. The counter is per invocation, so a single-wave run renumbers from 1 and reports only its own total: waves 1 through 11 contribute 17, 3, 6, 4, 2, 14, 6, 4, 21, 4 and 17 assertions. Evidence: the amended 2026-09-01 polish-drive receipt.
The harness targets https://wrdp.loca.zone by default. A local operator may set QWIZZ_BASE_URL to an equivalent isolated WRDP endpoint. It requires curl, sha256sum, passwordless sudo -n, Docker Compose, and the running WRDP stack; it does not read or print .env.
The harness reads logs/debug.log and this file must exist before a run.
Output and exit contract
- Every executed assertion emits TAP-style
okornot okoutput. - Unexecuted checks are never reported as passing.
- Any
not ok, setup failure, malformed fixture, missing prerequisite, or cleanup-sensitive command failure exits nonzero. - HTTP evidence records status, body size, selected headers, and a SHA-256 for non-secret bodies. Access tokens remain only in private temporary process state and are never printed.
- Browser-authoritative checks stay outside the shell proof until completed through the cache-disabled Chromium procedure linked from the masterplan.
Durable OMP browser smoke
The browser-authoritative contracts have a credential-free OMP browser gate at scripts/quizwizz-browser-smoke.js. It is not part of ./scripts/quizwizz-probe.sh; evaluate it only from an already authenticated administrator Chromium session, with cache disabled by the script. Both files are bench-level harnesses under scripts/, outside the plugin mirror, so a harness correction does not advance or become a commit in the plugin mirror.
The 2026-09-01 standing execution contract was three serial bounded runs, never one all run:
2026-09-02 supersession
The three-run paragraph and sample below are retained as the 2026-09-01 contract. The current six-selector contract is defined in the 2026-09-02 extension later on this page.
const source = await Bun.file('/home/loca/dev/wrdp/scripts/quizwizz-browser-smoke.js').text();
const run = async (gate) => {
globalThis.QWIZZ_BASE_URL = 'https://wrdp.loca.zone';
globalThis.QWIZZ_QUIZ_EDIT_ID = 99; // revalidate at least two rendered rows
globalThis.QWIZZ_BROWSER_WAIT_MS = 12000;
globalThis.QWIZZ_BROWSER_GATE = gate;
return await eval(source);
};
return [
await run('legacy'),
await run('autostart,auto-resume,sound-toggle,play-destination,tier-selector'),
await run('welcome,superglued-rail,single-column,selection-borders,trivia-html'),
];Preconditions: evaluate the file inside an OMP browser.run context exposing the page object; direct node scripts/quizwizz-browser-smoke.js execution is invalid and throws the explicit missing-page error at scripts/quizwizz-browser-smoke.js:14-17. The browser session must already be logged in as a WordPress administrator; page 57 must remain the public Builder fixture; and the runner must pass a finite positive QWIZZ_QUIZ_EDIT_ID for an editable quiz with at least two rendered question rows. The script accepts the top-level overrides above, contains no credentials, performs no permanent login change, and assumes no default quiz-editor fixture id. Gate selection accepts legacy, comma-separated unique subsets of the ten names below, or syntactically all (:22-55); operational acceptance forbids all because a 300-second outer kill can bypass cleanup.
The legacy/common partition covers public Builder desktop/mobile H1 semantics and overflow; Settings dirty → saving → saved autosave, dirty tab copy, reversible restoration, mobile save/status reachability, active workspace jump, and dirty beforeunload; mobile workspace-nav visibility/current-tab behavior while allowing internal horizontal scroll; quiz-editor toolbar roving tabindex plus client-only remove → undo without saving; computed WCAG contrast ratios; the corpus-repair additions; and the HOLD-remediation behaviors described below. Result JSON carries timestamp, preconditions, viewport definitions, assertion records, console/page errors, restoration status and overallPass; failures throw only after cleanup and carry the same result on the thrown error object.
The 2026-08-31 corpus-repair drive appended seven contracts in exerciseCorpusRepairContracts() and took the assertion array to a then-current 37 (scripts/quizwizz-browser-smoke.js:359-430 in that drive). That historical gate included a source-text FL-002 bound check and a total-wide TOUR-08 sectionMatches <= 1 check; neither was sufficient proof of the later HOLD findings.
The HOLD remediation retains the FL-002 source check, removes the defect-pinning TOUR-08 total-wide check, and adds nine behavioral assertions in exerciseHoldRemediationContracts() (current source scripts/quizwizz-browser-smoke.js:819-976): duplicate-export 409 continuity; resolved mobile scrollMarginTop === 276; activated target top at least 270; four unique print section rows; the PDF row routing to qw-pdf; and Label-in-Name for Create export, Import JSON, Download and Cancel job. Replacing one old assertion with nine moved that historical array by eight, from 37 to the HOLD-era 45. The current console collector excludes only text containing 409 (Conflict) whose URL matches /quizwizz/v1/admin/portability/export (:76-80); independently, the gate requires that exact export response to be HTTP 409 and that a live active export status resumes (:831-876). No other console or page error is suppressed.
2026-09-01 named-gate extension
The accepted browser contract is 60/60 unique assertions across three serial bounded chunks: 46/46 legacy; 8/8 autostart,auto-resume,sound-toggle,play-destination,tier-selector; and 12/12 welcome,superglued-rail,single-column,selection-borders,trivia-html. Require failed=[], consoleErrors=[], pageErrors=[], restoration complete and overallPass=true in every chunk. Never run one monolithic all. Final-harness-byte evidence: /tmp/s10-final-harness-browser-{legacy,player,admin-trivia,aggregate}.json; before/after parity: /tmp/s10-final-harness-regate-state-comparison.json.
| Gate selector | Exact green assertion contract | Fixture and browser authority |
|---|---|---|
autostart | stored player.autostart controls ordinary embedded quiz entry | Configured ordinary embed plus explicit play route; rendered entry state in cache-disabled Chromium. |
auto-resume | stored attempt automatically resumes beyond question one without intro | Stored attempt on the configured quiz; rendered progress/intro state. |
sound-toggle | sound_default controls one visible sound toggle and its pressed state changes | Stored Settings leaf plus real player control; visible-control count and live aria-pressed transition. |
play-destination | Library CTA honors configured themed play page and zero fallback is accepted | Ephemeral configured page/quiz destination plus zero fallback; real CTA URL, query and themed-page chrome. |
tier-selector | settings tiers hide Expert rows, reveal them at Expert, and suspend during search | Real Settings DOM; computed visibility at tier 1, Expert and search states. |
welcome | Welcome frame appears once with required copy and stays dismissed | Disposable administrator lifecycle; first view, real dismissal and revisit. |
superglued-rail | one superglued statistics rail spans native screens, sticks at admin chrome, and swaps skin in place | Native quiz/question/editor/settings screens; node identity, geometry and computed sticky/skin state. |
single-column | Settings sections remain one measured column at 1280px | Real Settings page at 1280 px; computed grid tracks and panel-row geometry. |
selection-borders | fully deselected theme card has no stale selection border or focus; collapsed quiz search clears selected option and active descendant | Computed theme-card paint plus live quiz-editor combobox selection, result-node and ARIA state. |
trivia-html | Trivia HTML generates a downloadable attachment; Trivia HTML playback data is valid and self-contained; Trivia HTML playback makes no HTTP(S) requests; Trivia HTML score arithmetic matches independent canonical answers | Real builder/export path plus downloaded HTML in cache-disabled Chromium; attachment, payload, network capture and independently derived score. |
Accepted execution rule
ACCEPT with waived residuals — all four review P1s resolved; residuals: Gitea #13 (endpoint non-atomic response revision, pre-existing product defect, out of drive scope), external process kill bypasses in-process harness cleanup, benign per-run operator session tokens (never prune blindly), content-identical foreign saves indistinguishable by construction. Run the exact three bounded selectors above serially; reject any result if an assertion fails, either error array is non-empty, restoration is incomplete, or the 95-row baseline changes.
Owner isolation and backup restoration are executable contracts. The harness provisions a marked disposable administrator, restores the operator session and removes only uniquely owned fixtures (scripts/quizwizz-browser-smoke.js:660-868,2763-2820). Backup cleanup removes only attributed harness rows and verifies every pre-existing survivor (:451-532). Per-run operator session tokens are benign; never prune them blindly (:1-13).
Settings isolation P1 resolved
Final harness bytes SHA-256
deb5266f…refuse a direct predecessor unequal toacceptedHeadbefore POST (scripts/quizwizz-browser-smoke.js:887-907), require UI begin and backup observation at exact head (:991-1015), adopt only the sealed accepted response and require follow-up GET equality (:1094-1140), then restore only from exact head with CAS/409 re-check (:1180-1220). Adversarial proof injected a foreign revision; it was not adopted and guarded restoration refused it (agent://HarnessFixer;agent://ReVerdict2).
Refusal and external-kill boundaries
Fail-closed restoration can leave harness test values live for operator repair instead of overwriting a foreign save. One adversarial run left
autostart=falseandauto_advance_ms=0; both were repaired to documentedtrueand2500before close-out (agent://HarnessFixer;/tmp/s10-gates-evidence.md:368-378). External termination separately bypasses all in-process cleanup (scripts/quizwizz-browser-smoke.js:1-13), so residue inspection remains mandatory after a killed run.
Out-of-scope endpoint residual
SettingsController::save()performssave_if_revision(), then separately re-reads Settings and separately computes the response revision (wp/wp-content/plugins/quizwizz/includes/Rest/SettingsController.php:101-107,148-157). A concurrent save can therefore make the response report a foreign revision. This is pre-existing, was not introduced or fixed by the drive, and remains open as Gitea #13.
Security and fixture lifecycle
Each run creates a private mktemp state root at mode 0700. Temporary PHP probes live in a separate secret-free 0755 mount directory at mode 0644, because the Compose wpcli service runs as UID/GID 33. Only the PHP file is mounted read-only; token and state files are never mounted into the container.
Fixture creation and database assertions run through WordPress/WP-CLI APIs. The EXIT trap permanently deletes uniquely titled probe posts through wp_delete_post() so QuizWizz cleanup hooks remove referenced private banner objects. The trap also removes all host temporary state. No MariaDB credential is read or passed directly.
Wave 1 assertions
Wave 1 creates isolated bannerless, public-banner, and private-ephemeral-banner quizzes plus an access token. Native save hooks demote questionless probe posts, so the fixture setup deliberately sets only their test status through $wpdb, immediately calls clean_post_cache(), and deletes every post through WordPress afterward.
Assertions:
- bannerless quiz: empty-body HTTP 404
- nonexistent quiz: empty-body HTTP 404
- public banner: HTTP 200, non-empty PNG, immutable public cache headers
- private banner with invalid token: empty-body HTTP 404
- private banner with valid token: HTTP 200, non-empty PNG, private no-store cache headers
- debug-log slice created after harness start: no new
LogoSource/finfofatal
The reproduction baseline is the pre-fix debug.log event on 2026-07-27 at 22:02:54 UTC: guest banner request reached LogoSource::detect_mime('') and raised ValueError at the old line 393. Wave 1 supplies the post-fix proof without temporarily restoring vulnerable code.
Wave 2 assertions
Wave 2 creates isolated administrator, editor, subscriber, and limited-capability users; public, draft, and private/published ephemeral quizzes; an access token; attempts; and owner-scoped backup rows. A shutdown handler removes every fixture, attempt, backup row, user, and temporary role even when an assertion fails.
Assertions cover:
- quiz create/update capability boundaries, derived authorship, anti-spoofing, and publish capability
- capability-free guest creation with forced private/ephemeral/author-zero state and bounded expiry
- public, private, published-ephemeral, and draft read rules with valid, invalid, or missing tokens
- token-aware experience and attempt start, including no attempt row on denial
- settings-backup
manage_optionsgating, two-administrator ownership isolation, indistinguishable foreign/missing 404 responses, per-writer count pruning, owner-zero count immunity, and global age pruning - CLI import denial without an explicit user and a successful one-row dry run for a qualifying administrator
Wave 3 assertions
The PHP packet creates isolated current and legacy attempts and removes them through a shutdown handler. It proves:
- key-off completion returns only canonical
score,total, and percentage and stores no answer-key material - key-on completion returns the same aggregate plus a complete review whose correctness matches the server choices
- legacy finished rows are reconstructed without rewriting stored rows
- contradictory stored results return 409
- malformed answer state is rejected while preserving the retryable attempt
The HTTP packet proves the quiz-specific fallback returns 200 with one main region, title, and player and without the generic theme byline. Cache-disabled Chromium additionally proved:
- plugin fallback, theme-owned
single-qw_quiz.php, and theme-owned block-template precedence - no horizontal overflow, overlap, or clipping at 320, 390, and 740 px; timing controls remain at least 44 px high
- pause holds the count, add-time adds ten seconds, resume decrements it, and manual advance moves to the next question
- RTL at 320 px remains contained; reduced-motion mode disables automatic progression and retains the manual action
- automatic/de_DE, forced English/de_DE, forced German/en_US, and automatic/en_US resolve the expected interface language, including singular/plural countdown strings
- a logged-out ten-question playthrough renders
4 / 10 (40%), focuses the completion heading, announces the same aggregate, copies the share link, emitsbeforeprint, and restarts at question one - direct WP-CLI probes reject 21 normalized categories with
qw_too_many_categories - the interface-language gate offers
auto/en/dewhile onlyde_DEcatalogs are bundled, excludesfr/es, degrades a storedfrtoauto, and reports the same set through the REST schema enum, the storage sanitizer, and both settings UIs; temporary readablefr_FRgate fixtures (header-only.moplus both script JSONs) madefrselectable and resolve tofr_FR, and removing them restored the three-language set. The fixtures prove file-set gating and locale selection only — not catalog parsing or French rendering. - cache-disabled Chromium on 2026-08-02 proves the unified Builder action set is exactly Print and Play, the first Print click opens fine-tune instead of submitting an unapproved request, and approval returns HTTP 200
application/pdfwithout the former “Build and approve” error - cache-disabled Chromium on 2026-08-02 also proves the PDF-allowed/editor-unavailable fallback: with
matrix.editor={st:hidden,def:false}, Print remained present withdata-pdf-actionand withoutdata-edit-action; no fine-tune script, stylesheet, data object, or modal was present; one Science Print click made one admin-post request and returned HTTP 200application/pdfwith an attachment filename, without the former “Build and approve” error. The exact serializedqw_exposure_settingssnapshot was restored byte-for-byte, after which the editor-enabled Print and fine-tune assets returned.
Wave 4 assertions
The server packet proves the shared SVG and retained-boundary contracts:
- 4096-byte SVG and eight-character text values are accepted; 4097-byte SVG and nine-character text values are rejected
- stored oversize SVG renders no node; event attributes and active SVG features do not survive
- an HTTP(S) PDF logo source is rejected before mPDF while vetted local and built-in data sources are accepted
- a valid question pack imports and is removed again; non-JSON content renamed as
.jsonreturns stableqw_pack_json - dead
count_query()and the obsolete dashboard script have no remaining references, and the script file is absent
Cache-disabled Chromium proved exact SVG root/descendant attribute rejection, forbidden-element rejection, byte/text boundaries, invalid-render cleanup, and inert rendered subject icons. The delegated copy handler produced success status for existing and dynamically inserted controls and failure status for an invalid selector; headless Chromium did not expose a reliable operating-system clipboard call counter, so the native/fallback API call count remains source-reviewed rather than OS-observed.
Wave 5 assertions
The portability packet proves twelve PHP-level contracts: the five quizwizz/v1/admin/portability routes and methods; export-job creation and defaults; duplicate-export conflict; the nine-key public job shape; worker scheduling; cancel and idempotent cancel; expired-job terminal handling without an artifact or schedule; the seven-stage export sequence preflight → serialize_terms → serialize_questions → serialize_quizzes → serialize_settings → seal_artifact → completed; malformed-envelope rejection; and import idempotency.
It also proves zero residue: no portability_jobs, private_files, or private_file_chunks rows; no scheduled qw_process_portability_job event; no post retaining a portability:<uuid>: marker; and no drift in qwizz_portability_last_error.
The user-surface packet proves seven PHP-level contracts across POST /quizwizz/v1/user/quizzes/(?P<id>\d+)/unpublish and DELETE /quizwizz/v1/user/quizzes/(?P<id>\d+): REST nonce authentication, owner unpublish and delete, and indistinguishable foreign-quiz, wrong-type-post, and subscriber-owned denial shapes — qw_user_quiz_not_found / Quiz not found. and qw_user_quiz_forbidden / You cannot modify this quiz.
Each PHP packet contributes one shell TAP assertion; its internal count appears in a # Wave 5 PHP assertions=N failures=0 comment line. Since 2026-09-14 the portability packet also prints its six residue components unconditionally — # wave5 residue jobs=N files=N chunks=N cron=N markers=N option_drift=N — and a failing inner assertion 12 carries the same values as a :: detail suffix, so a red is attributable (Wave 5 residue diagnostics).
Wave 6 assertions
Wave 6 adds fourteen shell TAP assertions. Five cover a temporary published password-protected quiz page: HTTP 200, nontrivial body, native password form, no player, and retained quiz title. Five cover the durable Library fixture at page 1289: HTTP 200, nontrivial body, both configured column counts, and the unique alpha marker. Three paired source checks require both status-ink token definitions, both admin-shell token references, and both Settings explanation strings. The final TAP assertion wraps an eighteen-check PHP contract packet in scripts/quizwizz-probe.sh:1597-2074; the packet reports its own total in a # Wave 6 PHP assertions=18 failures=0 comment line and still contributes exactly one shell assertion.
The PHP packet proves direct and embedded password denial, generic draft denial, the attempt-resume POST route, start/resume payload parity, stored-answer continuity, malformed and unknown-token denial, accepted identical retry, conflicting changed-answer retry, site sound/final-cue ceiling behavior, category admin/builder projection rules, Builder-only any, canonical zero-pool category restoration, both list-table subject controls, and subject-query application/removal. Its shutdown and explicit cleanup restore posts, attempt rows, Settings, request globals, query globals, screen state, and current user.
The zero-pool assertion deliberately uses the existing canonical science-computers term. SubjectTaxonomy::admin_tree() projects the plugin’s fixed canonical leaf map, so an arbitrary temporary term cannot appear in that tree; using a pre-existing zero-pool canonical leaf tests the observable restoration contract without creating or deleting site taxonomy.
Mutation proof changed the Library request from page 1289 to nonexistent page 999999. Wave 6 exited nonzero with four not ok results: HTTP 404 and three absent Library markers. Restoring page 1289 returned 1..14, assertions=14 failures=0, with # Wave 6 PHP assertions=16 failures=0. Isolation readback matched the pre-run Settings SHA-256 c238fa16551c2c24a8bf56cdf10068b46635de8bf9d9a41526bcd8acd775881e, retained 13 published quizzes, and found zero Wave 6 posts, zero qw-wave6* terms, and zero attempts created since the proof window.
The assertions=16 figure in that proof is the record of that run and stays as observed. The packet has since grown to eighteen checks, so a green Wave 6 today prints # Wave 6 PHP assertions=18 failures=0 while the shell total stays 1..14.
Wave 7 assertions
Wave 7 now adds six shell TAP assertions, outer numbers 47 through 52, over ordinary embedded entry plus the /?qwizz_play=<id> route. It lives in scripts/quizwizz-probe.sh like every other wave, so it decides host-observable behavior of the installed plugin and is not plugin source itself.
Assertions:
ok 47 - passive embed follows autostart setting— with storedplayer.autostart=true,players=1 asks=0 autostarts=1ok 48 - passive embed asks under stored autostart=false—players=1 asks=1 autostarts=0 restored=1ok 49 - play route forces autostart under stored false—status=200 asks=0 autostarts=1 restored=1ok 50 - play route document title—titles=1 quiz-titled=1ok 51 - play route document identity—single-qw_quiz=1 postid=1 home=0 page-id-2=0ok 52 - play route layout wrapper—wrapper=1 h1=0
Every Wave 7 assertion is theme-independent: the served play-route markup carries no theme attribute, and the palette is applied client-side from the REST payload, so no assertion depends on which palette a fixture resolves to.
Wave 7 writes no post, term or attempt row. Its stored-false phase does temporarily change the player.autostart leaf, but restores the full setting and reports restored=1 in both dependent assertions. The assertion source is scripts/quizwizz-probe.sh:2303-2322.
Current non-vacuity proof: forcing the ordinary-entry fallback false in includes/Frontend/QuizEmbed.php made only passive embed follows autostart setting red; exact-SHA restoration returned Wave 7 to 6/6 and the full shell gate to 94/94. The four route-surface mutations and five-assertion shape from 2026-08-26 remain dated history in the linked repair record; the 2026-09-01 red/re-contract/green sequence is in the polish-drive receipt.
Full detail is in the 2026-08-26 play surface repair record.
Wave 8 assertions
Wave 8 (playable-quiz contract) adds four shell TAP assertions, outer numbers 53 through 56, over the wp-admin Dashboard recent-quiz list and the play route’s empty-render branch. Like every other wave it lives in scripts/quizwizz-probe.sh, a host-owned harness file, not plugin source.
Assertions:
ok 53 - dashboard offers only playable quizzes—rows=5 playable=5 dead-play=0ok 54 - zero-question quiz excluded but preserved—excluded=1 preserved=1, against a dynamically selected zero-question quizok 55 - empty-content play route serves the unavailable card—status=200 titles=1 wrapper=1 card=1 unavailable=1 wrapped=1 player-card=0 catalog-css=0 empty-css=1(scripts/quizwizz-probe.sh:2559-2561). The last two terms are the current live expectation and were inverted for one cluster: before the Cluster 4 stylesheet split this assertion pinnedcatalog-css=1, so a page reading the older text would misread a correct green run as wrongok 56 - populated play route unaffected— quiz 99 still serves the player withdata-autostart="1", one wrapper, zero<h1>, no unavailable card
Assertion 53 was initially written wrong, recorded here because a probe contract page is exactly where that belongs. As first written it demanded with-play=rows, that every listed quiz offer a Play link. It passed under --wave 8 alone and failed under --wave all with with-play=4, because an earlier wave creates an access-gated fixture quiz that holds questions but legitimately fails QuizAccess::can_read() and therefore correctly gets no Play CTA. The assertion, not the fix, was wrong. It now pins the defect direction instead: dead-play=0, the count of rows that do offer a Play link while holding zero questions, with an unresolved row counted as unplayable so a Play link on one still reads as dead. Rows offering no Play link are reported in a diagnostic but are not failures.
Fixture-construction constraint, discovered rather than assumed: QuizService::register_hooks() binds save_post_qw_quiz → manage_native_quiz at priority 30, and QuizService.php:500-525 calls force_draft() for a quiz with no valid questions and back-fills empty content. So wp post create --post_status=publish with empty content silently returns draft, and an anonymous fetch of a draft hits the untouched 404 branch. Wave 8 therefore detaches exactly that one hook for the single insert. Every fixture the gate created was force-deleted afterwards and the post-run census matched the pre-run census.
Mutation proof ran in two passes against live code, both under an ERR trap with byte-exact restore afterwards:
DashboardPresenter.phpreverted to its pre-cluster-2 bytes →assertions=4 failures=2; assertion 53 detailactual=rows=5 playable=0 dead-play=5, the defect stated numerically, and assertion 54 detailactual=excluded=0 preserved=1. Assertions 55 and 56 stayedok.QuizQueryViewer.phpreverted alone, Cluster 1 left intact →assertions=4 failures=1; assertion 55 detailactual=… card=0 unavailable=0 wrapped=0 catalog-css=0, which also proves the catalog stylesheet reaches<head>only because of the precompute. Assertions 53, 54 and 56 stayedok.- after each pass:
php -lclean, postimage digests 3/3 OK,--wave 8green again.
Reverting QuizEmbed.php alone is not a valid mutation: a public caller against a private method is a fatal that takes out 55 and 56 together.
Coverage caveat, stated rather than hidden: Wave 8 pins the query clause. It does not independently pin the $count > 0 CTA belt, because with the query filter in place a zero-question quiz never reaches the CTA, so that branch is unreachable and a CTA-only revert fails no assertion. The gate is kept as cheap insurance against a future query change, not as a tested contract.
Full detail is in the 2026-08-26 playable-quiz repair record.
Wave 9 assertions
Wave 9 (performance remediation contract) adds seventeen shell TAP assertions, outer numbers 57 through 73, over the Cluster 4 performance work. Like every other wave it lives in scripts/quizwizz-probe.sh, a host-owned harness file, not plugin source. It creates no posts, terms or attempts: every assertion reads existing state, quiz 99 and page 1289, so it leaves --wave all residue-free without needing a cleanup path.
Assertions, grouped:
- four query budgets —
admin_tree()≤6,build()≤10,attempt_payload( 99, … )≤8, settings page assembly ≤13. Counted withSAVEQUERIESdefined at runtime inside the probe, never inwp-config.php. - three output digests —
admin_tree(), the stable subset ofbuild(), andattempt_payload()must hash to the values pinned when the cluster landed. A faster builder returning different numbers is a regression, not a result. - node shapes — 2 main nodes, 10 standalone leaves, every
poola non-negative integer, and child rows carrying exactlyid,nm,pool,on. The three shapes are deliberately different sizes; normalizing them is an output-contract change. - read-only guarantee —
attempt_payload()must not insert an attempt row. - three stale-while-refresh paths — a content mutation clears both stores and forces a fresh build; a TTL lapse serves the mirror in ≤2 queries with the stored generation rather than a rebuilt one; a mirror past
STALE_MAXforces a synchronous rebuild. - storage and teardown — the 48KB mirror option is not autoloaded, the refresh worker is hooked, and both the option name and the scheduled hook are registered in
StateRegistryso uninstall leaks neither. - the stylesheet split — asserted on rendered HTML, not the handle registry: a registered handle that is never printed would pass a registry check and still ship the wrong bytes. The player surface must serve
qwizz-empty.cssand no catalog sheet; page 1289 must serve both. - schema version
1.6.8, the migration that carries the two new keys.
The digests are pinned as shell constants at the top of the wave rather than recomputed, and they deliberately exclude generated_at, quizzes and activity: the first always moves, the second moves when any quiz is created or deleted, and the third moves when a play is recorded — including by this harness.
Wave 8 required one reconciliation. Its empty-content assertion pinned catalog-css=1, which the Cluster 4 stylesheet split deliberately inverts; it now pins catalog-css=0 empty-css=1. The full gate failed exactly there once before the change, which is the intended signal that the two waves encode the same contract.
Mutation proof ran in four passes against live code, each reverting one subsystem to its pristine bytes and reinstalling with a verified sha256:
SubjectTaxonomy.phpreverted —admin_tree2q → 48q, assertion 57 failedAdminStatistics.phpreverted —build10q → 12q and the stale serve 0q → 9q with served-from-mirror 0; assertions 58, 66, 67 and 70 failedQuestionProjector.php+QuizController.phpreverted —attempt_payload5q → 33q, assertion 59 failed- the four stylesheet-split files reverted — assertions 71 and 72 failed
The reverted query counts reproduce the Cluster 3 audit baseline exactly at 48, 12 and 33.
Coverage caveats, stated rather than hidden — three assertions cannot be made to fail by reverting a file:
- the
STALE_MAXceiling assertion passes against pristine code, because code that never serves a stale mirror trivially satisfies “a stale mirror past the ceiling is not served”. It guards only a future regression that keeps stale-serving while dropping the ceiling. - the three digest assertions pass against pristine by definition; pristine is the reference. They are regression guards, not fix-presence guards.
- the schema assertion reads database state, and reverting the two schema declaration files does not drop keys that already exist.
Full detail is in the 2026-08-27 performance remediation record.
Wave 10 assertions
Wave 10 (not-found contract and empty-card token scope) adds four shell TAP assertions, outer numbers 74 through 77. Like every other wave it lives in scripts/quizwizz-probe.sh, a host-owned harness file, not plugin source. It creates nothing.
It does not hardcode its control quiz. The bench’s unreadable population is auto-draft qw_quiz rows that WordPress itself prunes, and the id named in the original Cluster 1 finding had already been pruned — which is why the finding could not be reproduced on it. Wave 10 discovers the newest draft quiz instead, derives an absent id as MAX(ID) + 100000, and uses durable page 1289 as the not-a-quiz control.
Assertions:
- the discovered control quiz exists and is genuinely unreadable, so it is a valid control rather than a silent pass
- all three not-found conditions answer HTTP 404, with zero home-page markers and a body under 80,000 bytes: an unreadable existing quiz, an id no post has, and the id of a post that is not a quiz. The byte bound is deliberately loose so the theme’s exact 404 size is not pinned, while still excluding the 121,285-byte home page the route used to serve
- the readable quiz is unaffected: 200, one player card, one title
.qwizz-library__emptyappears as a selector in all three token cascade layers: the base public-surface rule, the@supports ( color-mix )refinement and the@media ( forced-colors: active )floors. Counted as selector positions rather than substrings, because the explanatory comment above the base rule also names the class and a substring count passed spuriously at 4
Mutation proof ran in two passes with byte-exact restores:
includes/Frontend/QuizQueryViewer.phpreverted — the unreadable draft went back to 404 with 121,285 bytes and a home-page marker, and the other two conditions went back to HTTP 200; assertion 75 failedassets/css/qwizz-tokens.cssreverted — selector count 3 → 1; assertion 77 failed
The reverted draft figure reproduces the Cluster 1 measurement of 121,285 bytes exactly.
Authority boundary for this wave: the shell can prove the three cascade layers exist; only cache-disabled Chromium can prove the card’s border and icon tile actually resolve, and that the library page did not shift. Those computed-style comparisons are recorded in the 2026-08-27 not-found and token-scope record.
Wave 11 assertions
Wave 11 (corpus-repair contracts) remains seventeen outer shell TAP assertions, now numbers 78 through 94. Its current implementation spans scripts/quizwizz-probe.sh:3042-3235: sixteen assertions are installed-byte token checks, while supply count column renders usable authority runs one behavioral PHP packet through wp_eval_file() and contributes one outer TAP result (:3203-3209).
The behavioral packet at :3042-3178 creates fresh source and target subjects, a published question through QuestionService::create(), and a published quiz through QuizService::create(). It attaches the target subject only to the quiz, proves target core term count 1 against AdminStatistics usable count 0, activates the real qw_supply=empty taxonomy-screen path, requires the target in that get_terms() result and the supplied source subject absent, and renders the real column filter to require <bdi>0</bdi> plus a link carrying both qw_subject and qw_valid=1. A shutdown cleanup force-deletes the question and quiz and deletes both terms, including failure exits.
Authority is therefore mixed rather than source-only. Sixteen checks prove installed source markers; the subject-supply packet proves one live service/filter/renderer contract without a browser. Rendered geometry, computed colour, search routing, accessible names and portability poll timing remain browser-authoritative: the historical hardened 60-assertion run is documented above, and the current six-selector contract appears below.
Assertions, grouped by the row each defends:
- TOUR-02, one assertion —
manage_posts_extra_tablenavno longer carries the empty state, written as an absence check overAdmin/ListTables.php. The pre-drive baseline registeredempty_actionson that hook (cf075304:includes/Admin/ListTables.php:37, gated on'top' !== $whichat:315-318); the installed file stages the same block into thenot_foundandnot_found_in_trashlabels fromadmin_head-edit.phpinstead (:38,:330-344), so the table renders it insidetbody - TOUR-06, two assertions —
data-question-type-helpinAdmin/views/question-editor.php:8, one helper sentence per question type, andquestion-type-helpinassets/js/qwizz-admin-editor.js:49, the script that keeps only the selected type’s sentence visible without a reload - TOUR-11, one assertion — the
_n()singular branchhas %2$s usable question;inAdmin/DashboardPresenter.php:69-74and:94-99 - TOUR-12, one behavioral outer assertion — the packet at
scripts/quizwizz-probe.sh:3042-3178proves the realqw_supply=emptyfilter and count-column renderer use usable-count authority when core term count is 1 and usable count is 0; it requires the target term in the empty result,<bdi>0</bdi>, and aqw_subjectplusqw_valid=1link. The outer TAP seam is at:3203-3209 - TOUR-15, one assertion —
Wizzardabsent fromAdmin/views/settings-page.php - TOUR-16, one assertion —
data-qw-dangerin the settings view (:888), the attribute the destructive border is scoped to atassets/css/qwizz-admin-settings-core.css:1690-1692 - TOUR-17, four installed-byte assertions — the visible literals
Create export,Import JSON,DownloadandCancel jobatAdmin/views/settings-page.php:900-909. These checks do not decide accessible names; the browser gate does. Export, import and cancel now rely on their visible/native labels, while Download retainsaria-label="Download portability export", which contains its visible label (:900-909) - TOUR-23, one assertion —
title: __( 'Advanced'absent fromassets/js/qwizz-block-editor-enhance.js(scripts/quizwizz-probe.sh:3224-3225), so no plugin-injectedAdvancedpanel survives in the craft inspector. The installed file renders onecomponents.PanelBody,Builder profileat:502-503, and carries the former Advanced controls inside it as fiveinspectorGroup()fieldsets (:517,:545,:585,:651,:715); the stock WordPressToggleControlis deliberately left alone (:10) - TOUR-24b, one assertion —
'data-theme'inFrontend/QuizEmbed.php:49, unset only for inheritedsitepresentation at:54-55, so the server-rendered shell carries the stored theme before hydration - 3.2.1-CEIL, one assertion — a single
data-site-ceiling-helpdescription inAdmin/views/settings-page.php(:378-379,:388) that eight controls reference througharia-describedby: four toggles at:396,:400,:427,:448and the four completion actions generated at:453-461 - 3.2.1-SCOPE, one assertion —
data-exposure-labelin the same view, emitted at:49,:65and:69from the qualifier closure at:44-47, which is the row context that makes each repeated state word a distinct accessible name - 3.2.1-VIEWS, one assertion —
views_edit-qw_subjectinSubject/SubjectSupplyFilter.php:24, whose handler builds the supply view links at:178-200 - 3.2.1-RUNS, one assertion —
finished play eventsinAdmin/AdminStatistics.php:752,:757and:807, where each activity number states its own event basis
Every assertion was authored before the fix it defends existed. The first run of the newly authored wave reported 1..16 with # assertions=16 failures=16: sixteen assertions red on pristine bench bytes. After batch 1 the full gate stood at 1..92 with failures=3, the three reds being the question-type markup, the question-type script and the pre-hydration theme — exactly the batch-2 slices that had not yet installed.
Per-slice mutation pairs, each restoring that slice’s pre-slice mirror bytes and reinstalling the fix afterwards:
- slice A (
Admin/ListTables.php,Subject/SubjectSupplyFilter.php,Admin/AdminStatistics.php,Admin/DashboardPresenter.php) restored fromcf075304— four assertions red, numbered 1, 5, 15 and 16 in that pass’s ordering; restored green - slice B (
Admin/views/settings-page.php) restored from92ea358— all eight settings assertions red, numbered 6 through 11 and 13 through 14; restored green - slice F (
Admin/views/question-editor.php,assets/js/qwizz-admin-editor.js) restored from8494823— both question-type helper assertions red; restored green - slice G (
Frontend/QuizEmbed.php) restored fromb3ee3df— the pre-hydration theme assertion red; restored green, with the raw pre-hydration HTML for quiz 413 carryingdata-theme="love" - the corrected TOUR-23 assertion at mirror commit
33989f9,assets/js/qwizz-block-editor-enhance.jsrestored fromf02cba9— assertion 13 red and nothing else; restored green - HOLD-remediation F5 — replacing the renderer’s usable-count read with native
$term->countmade the behavioral Wave 11 packet red because it rendered 1 where the contract requires 0; restoring the fixed file returned Wave 11 to 17/17. The matched mutation output is recorded in the HOLD-remediation receipt
Those numbers are each pass’s own ordering rather than the list above, with one exception: the corrected TOUR-23 pass ran against the seventeen-assertion wave, so its assertion 13 is the numbering above. The wave changed shape twice before that. FL-001’s source-marker assertion was deleted when FL-001 was refuted rather than shipped, and two assertions were added in batch 2 — the dashboard _n() singular branch and a thirteenth assertion over the block-editor script — which is how sixteen became seventeen.
That thirteenth assertion was then replaced, recorded here because a probe contract page is exactly where a wrong assertion belongs. As first written it matched data-qwizz-source, an editor-chrome marker installed under TOUR-23’s row that did not touch the duplicate panel TOUR-23 names: the second PanelBody titled Advanced was still present at 2327946:assets/js/qwizz-block-editor-enhance.js:590-591 with className: 'qwizz-craft-inspector-advanced' while that assertion was green. The assertion, not the fix, was pinning the wrong thing. It now pins the panel’s absence, and the marker is gone from the installed file along with the panel.
The pre-HOLD corrected wave was green at 33989f9 and inside the historical f79feb6 full gate; restoring the block-editor script from f02cba9 reddened only block editor removes duplicate Advanced panel, then reinstalling returned green. Cache-disabled Chromium also observed one German core Erweitert panel button, zero injected Advanced panels, and all five groups in the open Builder-Profil panel before deleting its transient fixture. HOLD remediation then replaced the old usable-count token assertion one-for-one with the behavioral packet above, preserving Wave 11 at 17 and all waves at 93. At final HEAD 87a0e61, Wave 11 was 17/17 and --wave all was 93/93; the $term->count mutation made the replacement assertion red and restoration returned it green.
Coverage caveats, stated rather than hidden:
- the dashboard
_n()assertion has no observed failing run: slice A’s mutation pass records four reds where this wave now reads five assertions from slice A’s files, so it was written after that pass closed. Its non-vacuity is a byte comparison instead — the pre-drive baseline carries a plural-only__( '%1$s has %2$s usable questions; …', 'quizwizz' )atcf075304:includes/Admin/DashboardPresenter.php:69and:89, which the pattern cannot match, while the installed file matches at:70and:95. That is weaker than a red run and is recorded as weaker - the merge that removed the panel also removed the
data-qwizz-sourcechrome the superseded assertion matched: the pre-merge 922-line file matchestitle: __( 'Advanced'at2327946:assets/js/qwizz-block-editor-enhance.js:590-591, while the installed 835-line file matches neither that title, norqwizz-craft-inspector-advanced, nordata-qwizz-source. Mirror commit8494823, which installed the marker, is superseded rather than accepted - TOUR-02, TOUR-15 and TOUR-23 are absence assertions, so each would also pass against a renamed or emptied file. Each has a positive half the assertion itself does not carry: TOUR-02’s is the label staging in source plus the empty card observed in
tbody; TOUR-15’s is the rendered settings page checked clean of the misspelling in cache-disabled Chromium on 2026-08-31; TOUR-23’s is the single openBuilder-Profilpanel with all five groups observed in the same Chromium session - TOUR-15’s assertion is scoped to the settings view, so it does not by itself decide the rest of the plugin. The interface-language install at
f79feb6regenerated the catalogs against the corrected strings —languages/quizwizz-de_DE.po:7401-7406andlanguages/quizwizz.pot:3606,:3622now carryQuizWizz for visitorsandEnable QuizWizz for visitors, with German translations for both — and a plugin-wide search for the misspelling now returns nothing.Admin/views/settings-page.php:825recordsQuizWizzas the provisional spelling pending the owner’s canonical-name decision, so this row is repaired, not closed - FL-001 has no assertion in either gate: it was refuted, not fixed, and its marker assertion was removed rather than kept as a tautology
- TOUR-07 has no assertion: it did not reproduce at either viewport in the truth round, so an assertion would have been green before any fix existed
- As of 2026-08-31, TOUR-19 and TOUR-20 had no assertion for want of a fixture. TOUR-19 is superseded on 2026-09-02 by the rendered fine-tune fixture and
uiux-perfectionstep f, which proves controls hidden at rest and visible on hover. TOUR-20 remains unasserted because no locked embed exists on the bench - TOUR-03, TOUR-09, TOUR-15’s canonical product name and TOUR-24a are design-class owner decisions rather than defects, and are deliberately unasserted
Historical authority boundary for the corpus-repair drive: its durable smoke gained seven contracts and reported the then-current 37 of 37 with restoration complete=true, failed=[], consoleErrors=[] and pageErrors=[] on 2026-08-31 (/tmp/corpus-wpqp-browser-smoke.final.json; replay command/result: corpus-repair receipt — Stage C). The later HOLD 45/45 authority, its HTTP/status/delete coverage and its cleanup contract are documented above. Both packets are historical; neither changed the then-current 93-assertion shell count.
2026-09-02 UI/UX-perfection extension — dated contract
On 2026-09-02 the shell contract was ./scripts/quizwizz-probe.sh --wave all → TAP 1..94, # assertions=94 failures=0; the current contract is 1..98 (Sep-8 receipt, re-proved 2026-09-14). Run it only on an idle bench. Post-amendment authority is /tmp/qwux/wave-all-final2.tap:267-268; /tmp/qwux/wave-all-final.tap is the pre-amendment green record, while /tmp/qwux/wave-all.tap retained one Wave 5 portability-residue failure and /tmp/qwux/wave-5-rerun.tap passed the isolated replay 12/12. The load-sensitive failure is characterized below rather than dismissed as a product regression. Evidence: /tmp/qwux/evidence-dossier.md:46-51; /tmp/qwux/w5-{1..6}.tap; /tmp/qwux/w5c-{1..6}.tap.
The current browser contract is six serial selectors. Each invocation must be bounded to 240 seconds maximum, cache-disabled, and restore its own state:
| Run | QWIZZ_BROWSER_GATE | Additional environment | Accepted close-out |
|---|---|---|---|
| 1 | legacy | none | 47/47 |
| 2 | autostart,auto-resume,sound-toggle,play-destination,tier-selector | none | 9/9 |
| 3 | welcome,superglued-rail,single-column,selection-borders,trivia-html | none | 13/13 |
| 4 | uiux-perfection | QWIZZ_UIUX_STEPS=a-d | 8/8 |
| 5 | uiux-perfection | QWIZZ_UIUX_STEPS=e-g | 7/7 |
| 6 | uiux-perfection | QWIZZ_UIUX_STEPS=h-j | 7/7 |
Never invoke uiux-perfection without one of the three named step ranges and never substitute a monolithic all run. Every post-amendment accepted close-out record has overallPass=true, restoration.complete=true, consoleErrors=[], pageErrors=[], and foreignRowsDuringRun=[]. Current harness SHA-256: be619c00ac67d076b517f8703da578147fbf76d05cd292c18a4fe769165e9ac6. Evidence: /tmp/qwux/e3-final-{legacy,player,admin,uiux-ad,uiux-eg,uiux-hj}.json; agent://SliceE3:117-182.
Re-contracted assertions
Four old predicates were replaced rather than pinned to obsolete geometry:
- resolved Settings offset equals
round(toolbar.bottom) + 12, and the clicked panel header lands in its bounded clearance window; - Settings search renders zero
SECTION MATCHrows, oneAlso in:row containingPDF & Print, and its jump reaches#qw-pdf; - mobile Settings status retains visible text (
Saved) at 390px; - the Settings toolbar top is at or below the dashbar bottom on desktop and mobile.
The always-run fixture/cleanup contract now also asserts that every Settings-backup row created during the run belongs to the disposable harness administrator. Preserving a foreign row but still returning green is no longer allowed. Evidence: agent://SliceE2:5-10,26-40; local://qwizz-mutation-matrix.md:1-46.
uiux-perfection step map
| Step | Rendered contract |
|---|---|
| a | every visible Settings workspace tab activates itself and honors the measured sticky offset |
| b | every [data-effect-sentence] node has zero visual rectangles |
| c | walk every visible SVG under #qwizz-settings; exclude .screen-reader-text and text rectangles at most 1px wide; compare the nearest qualifying same-line text rectangle; name each {gap,text,host} violation; require checked >= 60 and zero gaps below 6px |
| d | preview limitation copy exists and no obsolete preview tabs exist |
| e | Builder grids are at least 4/2 columns at desktop/mobile and the disabled-on-load outcome is visible, sticky and form-aligned |
| f | fine-tune Cancel/Print stays inside the viewport and per-question tools are opacity 0 at rest / 1 on hover |
| g | a first qw_pool_short reroll retries with exclude_ids: [] and preserves a nonblank row count |
| h | no dashbar secondary segment overflows |
| i | the quiz-list context is one compact line |
| j | dashboard duplicate summary/actions remain absent and one native page-title action remains |
Rows a–j are mutation-proved in 16 rows. Rows e and i deliberately mutate the effective seams: markup plus state JavaScript for e, separators plus flex layout for i. The weaker markup-only mutations were inert and are not presented as proof. Post-publication rows 15–16 specifically defend step c: restoring the old selector made only c red with 22 violations; deleting the combined spacing rule made only c red with 34. Both exact restores returned CSS SHA 92d09a8e… and green 76/0. Evidence: local://qwizz-mutation-matrix.md:47-141; agent://SliceE2:43-155; /tmp/qwux/e3-row15-{red,green}.json; /tmp/qwux/e3-row16-{red,green}.json; agent://SliceE3:19-104.
Icon-gap non-vacuity rule
The original step c reused the implementation selector #qwizz-settings svg.qwizz-icon:first-child:not(:only-child). In a heading such as <h3><svg …> Difficulty choices</h3>, the label is a text node, so the SVG is still :only-child; the implementation and assertion therefore skipped the same broken heading. The published 3/25 → 0/24 measurement is superseded and is not evidence. Current source samples all visible Settings SVGs and enforces a non-trivial denominator (scripts/quizwizz-browser-smoke.js:3088-3148). Evidence: SET-07 amendment.
Cache-disabled operator measurement checks 77 icon/text pairs; the disposable harness administrator checks 76 because its control-less Protected backups card is hidden. The repair passed 77/0 for the operator, while each exact-restoration mutation replay passed 76/0 for the harness identity. A selector matching nothing can no longer pass because checked >= 60 is part of the predicate. Evidence: /tmp/qwux/icongap.js; agent://SliceA:11-25; agent://SliceE3:1-18,69-116.
Wave 5 gate reliability
Until 2026-09-14 Wave 5 assertion 12 ANDed jobs, files, chunks, cron schedules, source-marked posts and option equality into one result and emitted only portability probe failed on failure. Repeated measurement on 2026-09-02 found:
| Bench condition | Runs | Failures | Evidence |
|---|---|---|---|
| idle | 6 | 0 | /tmp/qwux/w5-1.tap through /tmp/qwux/w5-6.tap |
| concurrent HTTP loop over Settings and page 57 | 6 | 1 | /tmp/qwux/w5c-1.tap through /tmp/qwux/w5c-6.tap; failure /tmp/qwux/w5c-4.tap:18-35 |
The orchestrator’s immediate read-only WP-CLI/SQL readback after the loaded failure returned empty from wp option get qwizz_portability_last_error, 0 from SELECT COUNT(*) FROM wp_qwizz_portability_jobs, and 0 from wp cron event list --hook=qw_process_portability_job --format=count. No JSON artifact was written; those exact commands are the reproducible evidence record. $residue_cron / wp_next_scheduled() at scripts/quizwizz-probe.sh:1328-1331 is the sole plausible timing-sensitive sub-condition, but that attribution is UNVERIFIED because the aggregate assertion does not print its six component values. Track diagnostic hardening in loca/quizwizz #15. 2026-09-14 update (Wave 5 residue diagnostics): the assertion now prints all six components (# wave5 residue … plus a :: detail suffix on failure, mutation-proved red→green); a fully specified public-route load experiment (6 idle, 6 at 4× curl loops, 6 at 8× loops, every loaded run 100 % 200) measured 0/18 failures — verdict not reproduced at 4×/8× loops; hypothesis remains unverified. The same session found the all-wave gate red in Waves 7-9 because the operator’s 2026-09-09 Settings reconfiguration (play_page_id=2, two categories off) had outrun two pinned contracts; the harness now pins player.play_page_id=0 for Waves 7-9 with a byte-exact restore and Bail out! on mismatch, and the Wave 9 tree digest is re-pinned to 9f4c1bbb… after proving the delta is exactly those two toggles. Until the race is settled, no concurrent HTTP traffic may run during a gate: idle bench is part of the standing execution contract. Evidence: the twelve TAP records above; orchestrator readback; /tmp/qwux/wave-all-final2.tap:107-134,267-268.
Identity and residue rule
Settings verification must use the harness-created disposable administrator. Do not browse QuizWizz Settings as owner 1 to discover identity or to perform a test write. Core /wp-admin/profile.php is the identity lookup path. The harness must attribute and remove its disposable-user Settings backups and auto-draft questions, then prove zero foreign rows appeared. Owner 1 currently has 11 backup rows against a per-writer cap of 10; another owner-identity Settings save can displace history. Evidence: /tmp/qwux/evidence-dossier.md:56-60; /tmp/qwux/backup-recovery.json:5-16; agent://SliceE2:22-41,212-253.
2026-09-14 harness contract update
Five browser-harness contract changes are installed as of 2026-09-14. They are recorded here against the installed bytes of scripts/quizwizz-browser-smoke.js, not against a run, and each item names the search string that locates it. This subsection appends to the dated contract above and rewrites none of it.
uiux-perfectionstep f — searchtoolsRestOpacity(:3375-3394). The assertion name now endsshows row tools at rest, full on hover, and the opacity predicate istoolsRestOpacity >= 0.5 && toolsHoverOpacity >= 0.95(:3390-3391), replacing the hover-onlyopacity 0 at rest / 1 on hovercontract that step-map row f above still describes; row f stays as the 2026-09-02 record and is superseded by this line. The rest value is a product fact rather than a tolerance:.qwizz-editor-item__toolscarriesopacity: .55at rest (wp/wp-content/plugins/quizwizz/assets/css/qwizz-finetune.css:705) andopacity: 1under:hover, :focus-within(:710-713), because hover-only actions are forbidden by the ui-ux rules — the in-source rationale is the expectation string at:3393. The pointer is moved to(1, 1)before the rest read (:3375-3377), so a stale hover cannot manufacture a rest pass.legacygate mobile Settings geometry — searchsettings tier (legacy geometry)(:1978-1982). The PDF section is hidden at the default settings tier since the 2026-09-04 tier change, so the gate snapshotslocalStorage.qwizzSettingsTierand sets it to3before measuring the mobile offset, resolved scroll margin and search jump. The snapshot is taken throughsnapshotLocalStoragewith no group argument, so its restore lands on the always-run cleanup stack rather than a gate-local group (:1399-1425;deferCleanupat:670-673), and a restore that does not return the exact prior value throwslocalStorage settings tier (legacy geometry) did not restore exactly.(:1413-1415).superglued-railgate — searchexpectedTop(:2599-2603, asserted at:2654-2656). The sticky-rail expectation is derived from#wpadminbarinstead of a constant:expectedTopisMath.max(0, Math.round(adminBarBottom))when the bar computesposition: fixed, and0otherwise, because WordPress makes the barposition: absoluteat 600px and below so it scrolls away (comment at:2599-2600). Tolerance is 2px either way for the three desktop surfaces and the mobile Settings surface; the expectation string at:2668readsdashbar sticky flush under the admin bar (or at 0 where WordPress makes the bar absolute).- Companion product change for that gate —
wp/wp-content/plugins/quizwizz/assets/css/qwizz-admin-responsive.css:369-386. Inside@media (max-width: 600px)the rail rules are nowbody.qwizz-admin:has(.qwizz-admin-rail) #wpcontentfor the flow padding (:376-378),body.qwizz-admin .qwizz-admin-railfor zero rail padding (:380-382) andbody.qwizz-admin .qwizz-admin-rail > .qwizz-stats { top: 0 }(:384-386). The in-file comment at:370-375records the change as generalised on 2026-09-14 from the Settings screen to every QuizWizz screen that renders the rail, so the quiz-list and quiz-editor screens no longer reserve 46px under a scrolled-away admin bar. The sibling toolbar rule at:388-390deliberately keeps the Settings-onlybody.qwizz_page_qw-settingsscope. trivia-htmlgate — searchanswerKeyBaseline(:2869-2882). The gate readspresentation.answer_key_questionsfrom its settings snapshot and accepts any boolean baseline; the comment at:2871-2873records that the operator configuration has carriedtruesince 2026-09-09. It writes the settings document only when the baseline is not alreadytrue(:2878-2882), so on the current bench the gate performs no Settings write at all. The observed baseline is pushed torestoration.notes(:2877) andsnapshotSettingsrestores the stored value. A non-boolean baseline throws before any write (:2874-2876); the gate catch converts that into the failed assertionTrivia HTML gate completed without runtime exception(:2999-3000), which is a bail-class refusal to run rather than a measured red.- Known latent locale coupling, no action taken — search
statusShort === 'Saved'(:1722). Thelegacyassertionsettings mobile save/status remains reachablerequiresstatusState === 'saved'plus the English literalSaved, read from the status span’sdata-shortattribute (:1705,:1720-1724). That is correct on the en_US bench and would fail under a de_DE admin locale, where the short label is translated. Recorded as a known limitation; no localized-admin run has been observed and no harness change was made. uiux-perfectionstep c — searchsettings tier (uiux workspace)is not present; searchuiux-perfection settings tier(:3022-3026). The icon-gap assertion requires at least 60 icon/text pairs, and the default tier exposes only 26 (measured{"checked":26,"violations":[]}on the first 2026-09-14 run), so the gate now setslocalStorage.qwizzSettingsTierto3under the snapshot it already took; the re-run measured 80 pairs, zero violations.uiux-perfectionstep f row count — searchrequestedCount(:3366-3368, asserted at:3393-3394). The literalrowCount === 10is gone: the Builder’s question count is operator-owned (count.default, 7 since the operator’s 2026-09-09 Settings change), so the contract reads[data-qwizz-count]from the Builder and requiresrowCount === requestedCountwithrequestedCount >= 1. The first 2026-09-14 run failed only on this literal (rowCount: 7); the re-run recordedrowCount: 7, requestedCount: 7.- Driver trap for any runner that is not an OMP browser cell: with puppeteer-core 25.3.0,
page.evaluateexecutes in__puppeteer_utility_world__25.3.0(DOM shared, page globals invisible), so the harness’s identity and REST-client waits time out unlesstab.evaluateis bound to a main-worldRuntime.evaluate. Observed on 2026-09-14 by the probe-runner: raw CDP returnedfunction | object | objectfortypeof jQuery, typeof wp, typeof qwizzAdminDatawhilepage.evaluatereturnedundefinedfor all three on the same page. Not a product fault.
Harness identity, measured on 2026-09-14 from /home/loca/dev/wrdp after the last reconciliation: sha256sum scripts/quizwizz-browser-smoke.js returns 6d0803b2121ef1d352e5a1b6d606aae915773902257214cc118f303ac7b25289 and wc -l returns 3716 lines. That is the current anchor; the intermediate 2026-09-14 state 77371c8da0d196b55671888e552377cb3aea3abe1b761a4e5d39f13573e40e81 (3709 lines, before the step c and step f reconciliations) and the two earlier pins stay as the record of their own runs: be619c00ac67d076b517f8703da578147fbf76d05cd292c18a4fe769165e9ac6 in the 2026-09-02 dated contract above, and 25a12d994470163bcafb723a3cc2f82b27dc48d45e9270bf25bf38313ed0b30a from the 2026-09-08 Editor lifecycle (q5vault/assets/evidence/2026-09-08-live-punchlist/final-editor-runtime.json:10; the 2026-09-08 punchlist repair record).
The shell TAP contract is untouched by this update and remains 1..98 with # assertions=98 failures=0 on an idle bench. Round-F browser run record (2026-09-14, disposable admin qwvisual-20260914, one chunk at a time, bench idle): legacy 47/47, welcome,superglued-rail,single-column,selection-borders,trivia-html 13/13, uiux-perfection a-d 7/7, e 5/5, f 5/5, g 5/5, h-j 7/7, plus the earlier player 9/9 — every record with foreignRowsDuringRun=[], restoration complete, identityEqual and survivingIdentityEqual true, Settings option SHA-256 89877cf601ca195bf0ff75c936637e485d8b6de454c301934bff54ab0838ff9f and 94 backup rows / 10 by owner 1 after every chunk; one first-pass chunk3 run carried a single console error from a third-party secure.gravatar.com fetch (net::ERR_NETWORK_CHANGED) and was re-run clean. Records: evidence/2026-09-14-uiux-round/smoke/ (private). An aborted first attempt left harness user qwharness_browser_smoke (ID 1182) with two backup rows and one qw_question auto-draft; they were removed before any gate resumed, which is the leak the bench law warns about. These bench changes remain releaseEligible: false.
Phase G browser and shell run record (2026-09-14, disposable admin qwvisual-20260914, one chunk at a time, bench idle, harness bytes unchanged at 6d0803b2…): legacy 47/47 (g-legacy), uiux-perfection a-d 7/7 (g-uiux-a-d), uiux-perfection h-j 7/7 (g-uiux-h-j), and tier-selector,single-column,selection-borders,superglued-rail,welcome 10/10 (g-admin-settings) — every record overallPass true with consoleErrors=[], pageErrors=[], foreignRowsDuringRun=[], restoration complete and both identityEqual and survivingIdentityEqual true, harness writers 1198-1201 created and deleted one per chunk. Shell --wave all on the same idle bench exited 0 at TAP 1..98 with # assertions=98 failures=0 and zero not ok, Wave 9 printing # admin_tree 2q build 10q attempt_payload 5q settings page 4q / 150490 bytes inside its 6/10/8/13 ceilings, with logs/debug.log at 38,699 lines before and after; the TAP record is /tmp/qwfix/settings/wave-all-g.tap, SHA-256 b653db020e0787bb95d5a81690a4d47ed41692637d339ec77ee00cb75c9c1d22. These are phase G figures on the Settings restyle bytes; the round-F line above stays as its own record.
Coverage caveat measured against the same installed harness bytes: the a-d step group emits three UI/UX assertions — UIUX (a) at :3081, UIUX (b) at :3157 and UIUX (c) at :3166 — and there is no UIUX (d) assertion anywhere in the script. The Settings Preview modal appears only as two waitFor barriers at :3173-3179, which open it and close it through button[data-preview-close]; neither is a counted assertion, and nothing there checks focus containment, Escape or focus return to the opener. A green a-d chunk therefore decides nothing about the Preview contract, which rests on the phase G keyboard walk alone (Settings premium restyle section 4). Tracked as ledger row UIUX-SET-07, together with the 1..76-versus-1..98 documentation drift on this page as UIUX-SET-08.
Phase G amendment run record, the operator-approved JavaScript focus fix (2026-09-14, disposable admin re-created as ID 1208, one chunk at a time, bench idle, harness bytes still 6d0803b2…): legacy 47/47 (js-legacy), uiux-perfection a-d 7/7 (js-uiux-a-d), uiux-perfection h-j 7/7 (js-uiux-h-j), and tier-selector,single-column,selection-borders,superglued-rail,welcome 10/10 (js-admin-settings) — every record overallPass true with consoleErrors=[], pageErrors=[], restoration complete and the backup-table identity digests equal on both readings, the harness rows it created tracked, requested and removed. Shell scripts/quizwizz-probe.sh --wave all on the same idle bench exited 0 at TAP 1..98 with # assertions=98 failures=0 and zero not ok; the record is /tmp/qwfix/settings/wave-all-js.tap, plan and summary at :288-289. logs/debug.log measured 38,703 lines before and 38,703 after. The gated surface is the change itself: one focus listener per [data-workspace-tab] at wp/wp-content/plugins/quizwizz/assets/js/qwizz-admin-settings.js:1485-1491, live bundle SHA-256 a7b6768e…, whose keyboard evidence is section 11 of the phase G receipt. This run changes no harness byte and no assertion count, so the shell contract above is unaffected, and the UIUX-SET-07 Preview caveat still stands: these four chunks decide nothing about the Preview modal, which the amendment walk measured separately.
Current bench result
On 2026-09-02 at pushed plugin HEAD 715474f, the post-amendment authoritative shell run passed 94/94; the six standing browser selectors passed 47/47 + 9/9 + 13/13 + 8/8 + 7/7 + 7/7, with uiux-perfection executed separately as a-d, e-g, and h-j. All 16 mutation rows reproduced their named red and returned green after exact restoration. Final residue remains 95 backup rows at ordered SHA-256 86100bbc6dd9b7a8312ef96cbdcb8a314e9fc53092eebd384233d5573f43898f; all final browser records report foreignRowsDuringRun=[], empty console/page errors and restoration complete. Final verdict: ACCEPT-WITH-WAIVERS, zero blockers; the metric, reviewer and Wave 5 reliability follow-ups are recorded in the receipt and open work. releaseEligible: false. Evidence: /tmp/qwux/wave-all-final2.tap:267-268; /tmp/qwux/e3-final-{legacy,player,admin,uiux-ad,uiux-eg,uiux-hj}.json; agent://SliceE3:117-215.
Historical 2026-09-01/02 authority at clean plugin HEAD 2da4b577e38bacfe30afefd814e6080d864ce770: shell passed 94/94; final-harness-byte browser passed 46/46 + 8/8 + 12/12 = 60/60 unique; final-byte mutations 2/10/11 reproduced only their intended reds, restored SHA-exact and passed green (/tmp/s10-final-harness-row{2,10,11}-{red,green}.json); exact before/after state retained the 95-row baseline (/tmp/s10-final-harness-regate-state-comparison.json). Its final verdict was ACCEPT with waived residuals; all four review P1s resolved. Residuals: endpoint #13, external-kill cleanup, benign per-run operator tokens and content-identical foreign saves. releaseEligible: false.
On 2026-08-31 at pushed, clean plugin source/catalog HEAD 87a0e612c358c36e1359e7324ee4f77f09cc0d39, ./scripts/quizwizz-probe.sh --wave 11 passed 17/17 and --wave all passed 93/93: TAP 1..93, # assertions=93 failures=0. The superseding cache-disabled authenticated OMP browser run started 2026-08-31T16:40:11.214Z and passed 45/45 with overallPass=true, consoleErrors=[], pageErrors=[], and both restoration paths true; its note records job 2b710ef4-938c-44c7-80b3-1bf4d2a7c282 cancelled and deleted, and the immediate portability-jobs table count was 0. These observed results are recorded in 2026-08-31 HOLD remediation.
In the 2026-08-31 HOLD record, the shell total remained 93 because the change replaced Wave 11’s old usable-count token check one-for-one with the behavioral subject-supply packet; it did not add a shell assertion. The browser total moved from the corpus-repair drive’s historical 37 to 45 by replacing one stale TOUR-08 check with nine HOLD behavioral assertions. That record’s post-review zero-residue correction changed only the bench-level browser harness under scripts/, outside the plugin mirror; it was not committed or pushed by qwgit, and plugin source/catalog HEAD at that point remained 87a0e61. All bench changes remained releaseEligible: false, and the green replay did not pre-empt the Phase 7 fresh adversarial verdict then still pending. The 2026-09-01 result later superseded that record and is itself historical beside the 2026-09-02 current result above.
On 2026-08-27, after Cluster 5 added Wave 10, the harness also reported 1..76, assertions=76 failures=0, with the plugin mirror clean at 6f82b3d and the installed-affected-files list matching the expected 14 paths (/tmp/qw-b42-recheck2/all-wave.log:1-89; /tmp/qw-b42-recheck2/qwgit-status.out; /tmp/qw-b42-recheck2/changed-files.txt:1-14; /tmp/qw-b42-recheck2/diff-installed-final.out, zero bytes). That run is a dated cluster-installation contract; the current 94-assertion contract is stated above.
On 2026-08-27, after Cluster 4 added Wave 9, ./scripts/quizwizz-probe.sh --wave all reported TAP 1..72 with assertions=72 failures=0; the harness then accepted --wave 1|2|3|4|5|6|7|8|9|all. The seventeen added assertions are TAP 56 through 72, so the change from 1..55 is an expected addition, not a regression. One existing Wave 8 assertion was also rewritten in the same cluster, because the stylesheet split inverted the contract it pinned; that is a contract change, not a silenced failure, and it is recorded in the Wave 9 section above. Patch, digest sidecars, index EXPLAIN evidence, and the browser and mutation records are in the 2026-08-27 performance remediation record; that run is bench evidence and confers no release eligibility. Every statement below stays as written, each being the record of its own cluster.
Also on 2026-08-26, after Cluster 2 added Wave 8, ./scripts/quizwizz-probe.sh --wave all reported TAP 1..55 with assertions=55 failures=0; the harness then accepted --wave 1|2|3|4|5|6|7|8|all. The four added assertions are TAP 52 through 55, so the change from 1..51 is an expected addition, not a regression. Patch, digest sidecars, and the browser and mutation records are in the 2026-08-26 playable-quiz repair record; that run is bench evidence and confers no release eligibility. Both statements below stay as written: the 1..51 paragraph is the Cluster 1 record and the 2026-08-17 paragraph is the Option F record.
On 2026-08-26 ./scripts/quizwizz-probe.sh --wave all reported TAP 1..51 with assertions=51 failures=0, after the play-surface repair added Wave 7; the harness then accepted --wave 1|2|3|4|5|6|7|all. The five added assertions are TAP 47 through 51, so the change from 1..46 is an expected addition, not a regression. Patch, digest sidecars, and the browser and mutation records are in the 2026-08-26 play surface repair record; that run is bench evidence and confers no release eligibility. The 2026-08-17 statement below is the Option F record and stays as written.
The latest Option F verification on 2026-08-17 reported ./scripts/quizwizz-probe.sh --wave all at TAP 1..46 with assertions=46 failures=0, after the durable browser gate passed twice consecutively at 30/30 assertions with no console or page errors. Exact machine-readable facts are in /home/loca/dev/wrdp/evidence/2026-08-17-option-f/verify.json; the launch decision is NO-GO with operational gates green, owner/canonical inputs remain in Owner decision intake, and installed-byte preservation is linked from the 2026-08-17 preservation page. Earlier full runs reported 1..30 before Wave 5 and 1..32 after Wave 5; the count changes are expected additions, not regressions.
- Focused WP-CLI contracts proved password denial/valid-password access, attempt privacy export and erasure, accurate pseudonymous-AI retention disclosure, AI reservation release after a thrown transport exception, repeat native-pack import idempotency, malformed nested import values returning stable errors instead of throwing, English/German default enablement, schema
1.6.7, InnoDB enforcement for the attempts table, and transactional uninstall cleanup of the statistics generation option, play-event claim prefix, and onboarding user metadata. - The backup wrapper rejected
/as a destination with exit 64; every changed PHP source linted in-container. - Cache-disabled Chromium at
1600×1000and390×844proved the settings search focus ring, scoped action tokens, scrollable mobile settings navigation, reduced-motion switch/chip durations, public-player layout, and the mobile answer-recorded transition without console errors or document overflow. /home/loca/dev/wikis/build.sh wrdp --check-onlyvalidated 51 Markdown inputs and emitted 328 check artifacts successfully in the final 2026-08-17 Option F verification.- W10-01: the Builder main-world collector saw no uncaught error; Customize exposed 3 count-preset pills, 5 pills total and 97 form controls.
- W10-02: all 10 owner-visible backup rows rendered Delete in
rgb(214, 54, 56); Download to Restore measured 4 px and Restore to Delete 16 px. - W10-03: a real question at
320×720hadscrollWidth === clientWidth === 320and no overflowing descendant. - W10-04: the admin rail rendered Dashboard, Quizzes, Questions, Subjects and Settings in that order, and each corresponding screen marked its own row
.current. - W10-05: the archive rendered 8 players and 8 start intros without a passive request. One gesture produced one
/quizwizz/v1/quiz/<id>/startrequest and one persisted attempt; the measured request used quiz 99. The dedicated single-quiz page alone autostarted into its question. - W10-06: the live German locale rendered
Nächste Fragefor source EnglishNext question; the visible control measured200×48with a20×20glyph and no duplicatearia-labelortitle. The sound toggle exposed the state-neutral accessible nameTonboth before and after toggle on the live German locale, corresponding to source EnglishSound, with state carried byaria-pressed. - W10-07: the 10-item review was a direct child of
section.qwizz-player__result, not the disclosure. The closed disclosure held exactly five secondary actions underShare, print and start again; its gap to the review was 28 px at1600×1000and390×844. - W10-08: the plugin-owned target census was empty below 24 px on Dashboard and Settings at exact viewports
1280×900and390×900. It examined visiblea,button,input,selectand[tabindex]elements, skipped zero-size elements, and excluded anything whose nearest ancestor was#adminmenumain,#wpadminbar,.row-actions,.check-column,.tablenavor#screen-meta. The four shortcode-copy controls measured487×24on desktop and328×24on mobile. - Reduced-motion verification returned
0sfor the card, progress, answers, submit and feedback transitions or animations; the full browser run recorded no console errors. - Runtime disclosure: the pre-remediation exploratory run grew
logs/debug.logfrom 38,408 to 38,409 lines through one WordPress-corehtml_entity_decodedeprecation atwp-includes/widgets.php:1652; the final W10 run held its 38,409-line baseline unchanged. The post-W10 full run began and ended at 38,411 lines. The attempt table changed from 130 rows to 12 because QuizWizz expiry cleanup removed expired rows; 0 expired rows remained. Browser verification created only ordinary live attempts and did not delete rows directly. - Final 2026-08-17 Option F verification began at 38,416 lines and ended at 38,417. The sole new event was the same WordPress-core
html_entity_decodenull deprecation atwp-includes/widgets.php:1652, not a QuizWizz error or fatal.
The shell harness does not assert the browser-authoritative W10 surfaces or the remaining post-W10 interactions: Settings save-status and dirty-state behavior, settings-rail scroll-spy, beforeunload, editor roving tabindex, editor undo, mobile save reachability, or computed color contrast. It likewise does not replace browser evidence for Builder main-world behavior, backup-control styling, 320 px reflow, admin-menu rows and .current state, player gesture/autostart gating, localized advance-control geometry and sound names, result hierarchy and review placement, plugin-owned target geometry, reduced-motion styles, or browser console output. Cache-disabled Chromium measurements in the dated records are authoritative for those claims; the shell harness is authoritative for its declared HTTP, WP-CLI, database, portability, access, attempt-continuity, category-projection, list-filter, Library-fixture, and source-token/copy contracts.
Native-token and final UI verification
- Default public
sitemode inherits type, colour, and accent, omits playerdata-theme, and prints its PDF palette light. Explicit player palettes retain their own action pair. - The inherited accent is not a filled-control background: public actions use
--qw-action-bg/--qw-action-ink. With Twenty Twenty-Five’s#FFEE58accent, the inherited-player action contrast measured 18.88:1 and derived cards were opaque. - CDP forced-colors emulation confirmed that explicit players resolve
CanvasText,Canvas, andLinkTexttokens, including explicit palette-token overrides. - Cache-disabled Chromium confirmed that settings, subjects, question editor, and portability roots were visible with zero document horizontal overflow at 1600, 782, and 390 px. At 390 px, the one-column settings grid and anchored 150 px help bubbles eliminated document overflow.
Authority boundary
- shell harness: authoritative for service, HTTP, header, database, cleanup, non-disclosure, portability REST and job lifecycle, owner-scoped quiz unpublish/delete, password and embed access, attempt resume/retry and site-ceiling behavior, category projection, list-table subject retrieval, the durable Library fixture, and paired source-token/copy contracts. Waves 7 through 11 extend that boundary with:
- play-route autostart, document title, document identity and layout wrapper (Wave 7)
- the Dashboard playable-quiz query contract and the play route’s empty-render branch (Wave 8)
- query budgets, output digests,
admin_tree()node shapes, the read-only guarantee, the three stale-while-refresh paths, mirror storage and teardown registration, the stylesheet split asserted on rendered HTML, and schema version1.6.8(Wave 9) - the three not-found conditions answering 404 under the byte bound, the readable-quiz control, and
.qwizz-library__emptypresent as a selector in all three token cascade layers (Wave 10) - the installed corpus-repair source tokens: the retired top-tablenav empty-state hook, the state-specific question-type helper and its editor script, the dashboard
_n()singular branch, the usable-count column authority and the subject supply views, the misspelled product name gone from the settings view, the danger-row state attribute, the four visible portability labels, the shared site-ceiling explanation, the exposure-name qualifier, the pre-hydration player theme attribute, the absent duplicateAdvancedinspector panel, and the event-basis wording on dashboard activity labels (Wave 11)
- cache-disabled Chromium: authoritative for rendered template precedence, responsive timing layout, keyboard/focus behavior, RTL, reduced motion, locale precedence, completion output, SVG/copy DOM behavior, every W10 rendered-behavior and geometry claim identified above, the ten named 2026-09-01 gates, and current
uiux-perfectionsteps a–j covering Settings, Builder, fine-tune and compact admin surfaces - CDP forced-colors emulation: authoritative for the explicit-player token-resolution scenario recorded above; it is not a claim of exhaustive assistive-technology coverage.
Explicit non-claims
- This probe page does not claim canonical release eligibility.
- It does not close the five compiled block defects retained in the visual triage.
- It does not convert the retained Site Health / Privacy screenshot gaps into visual proof.