Review: Add Versus mirror + task verifiers (site by @Sun-sunshine06, verifiers by reviewer) (#41) - #111
Draft
jackjin1997 wants to merge 15 commits into
Draft
Review: Add Versus mirror + task verifiers (site by @Sun-sunshine06, verifiers by reviewer) (#41)#111jackjin1997 wants to merge 15 commits into
jackjin1997 wants to merge 15 commits into
Conversation
sites/versus builds its seed at image build time rather than shipping an instance_seed asset, which is fine and matches what osu and rotten_tomatoes already do on main. But seed_benchmark_users() called generate_password_hash() for each account, and werkzeug draws a fresh scrypt salt on every call, so two builds of the same commit produced different seed bytes: 96d9d1dc... and 568bfb9d... The logical content was identical both times (5 categories, 20 products, 4 users, 3 saved comparisons hash the same with password_hash excluded); only the salts moved. The in-image reset contract still held, because the seed is copied from that same generation, so this never surfaced as a failing md5 check -- what it broke is pinning the seed hash in review evidence or in a verifier. PYTHONHASHSEED=0, which osu uses for its build-time seed, does not help here. Freeze the hash the way osu freezes BENCHMARK_PASSWORD_HASH instead. Two builds now agree: 231149c4... twice.
23 of the 25 sites on main install CSRFProtect; versus did not, and a POST to /compare/<l>-vs-<r>/save carrying Origin and Referer of another site was accepted and wrote the row. Install CSRFProtect, add the token to the two posting forms, and declare Flask-WTF in the site's requirements (the image already pins 1.2.2). Also stop pre-filling the benchmark email and password into the login inputs. The credentials stay in the task text, which is this repo's convention, but a page that ships them in value attributes lets an agent sign in without having read anything.
The missing /favicon.ico was the only console error on every page load.
Reviewer-side work per CONTRIBUTING: the contributor supplied 10 task definitions and no verifiers. This adds a deterministic verifier per task and a judge_rubric, and reworks the questions that could not be graded honestly. Answer leakage. Versus prints Score, Price and Year on every product card, and /rankings lists all 20 products ordered with score, price and category and needs no filter. Three of the original tasks were therefore answerable without opening anything: "which of these two scores higher" (--0), "highest ranked graphics card" (--2), and "highest scoring smartphone under $1000" (--6). The fix is not to hide the ranking -- it is a real page of the site -- but to ask for a fact the list does not carry. Every spec value (camera score, ANC score, megapixels, burst, VRAM, power, benchmark, battery hours, weight, display) is rendered only on detail and comparison pages, so each rewritten question now ends in one of those. A no-op stateful task. The only task that changed state asked Alice to save the RTX 4080 Super vs RX 7900 XTX comparison, which seed_benchmark_users() already saves for her, and save_comparison() de-duplicates. The after state was identical whether or not the agent acted. It now targets a pair she does not have, and a second save task uses Bob, who starts with none. An ambiguous answer. "A camera with 40 megapixels or more" had two valid answers (Nikon Z8 at 45.7 and Fujifilm X-T5 at 40.0). It now asks for the highest count, which is unique. Ground truth is derived from the passed initial_db rather than frozen into the verifier, so the expected answer moves with the seed and a stale verifier fails loudly instead of grading against a dead value. Where a question implies a superlative, unique_extreme() returns None on a tie and the verifier fails closed rather than picking one. Malformed or missing input produces a structured FAIL, never a traceback. Stateful tasks read the after_db and also assert the pair was absent before, so a task that regresses into a no-op is caught by the verifier itself. 17 tasks, 17 verifiers, 17 rubrics.
Each test fails against the code as submitted, so a later change that reintroduces one of these fails here rather than inside a benchmark run: seed byte-reproducibility across two builds, the two site registries agreeing and the task URLs deriving their port from them rather than freezing it, every task carrying a verifier that exists plus a rubric and no answer key, the task count staying in the review guide's range, no save task being already satisfied at seed state, and CSRF protection being installed with tokens on the posting forms.
simpleArgParser builds its parser from the dataclass field types, so `from __future__ import annotations` turned every field into the string 'str' and every verifier died with "'str' is not callable" before it could grade anything. The merriam_webster reference lib does not use it either.
The adversarial matrix caught this: every navigation check was a bare substring match on the path, so taking a canonical trajectory and rewriting its URLs from localhost:40024 to localhost:40007 -- same paths, different mirror -- still passed all 17 verifiers. A run on a different site is not evidence that the agent visited this one. step_urls() now keeps only steps on this site's own origin, and the port is derived from control_server.py's registry rather than frozen, so a registry reorder moves the verifier with it instead of silently accepting whatever site now owns the old port. WH_SITE_ORIGINS overrides it for a harness that maps the site to another address. The two save tasks also now require a final answer that exists and does not deny having saved. They are graded on the state change, so a badly worded report of a correctly performed action still passes; a report that contradicts the state does not.
Upstream advanced to f20b5ee while this review was open, merging the FedEx mirror (aiming-lab#82) and WebMD Doctor (aiming-lab#95). Both took registry slots, so the three registry files conflicted again. Resolved by keeping the slots those two already merged into and appending versus after them: fedex stays on 40024, webmd_doctor on 40025, versus takes 40026. No earlier site moves. The Dockerfile site count and EXPOSE range follow the 27-site registry, and the two new build-time seed steps are kept alongside this branch's. Task rows are re-pointed from :40024 to :40026. The regression test derives the port from both launchers rather than freezing it, so it follows this move on its own; so does the verifiers' origin check, which is why that was made derived rather than constant in 0cbdc87. The Versus seed step now also clears instance/ and __pycache__ after copying the seed, matching what osu, rotten_tomatoes, fedex and webmd_doctor do. The image no longer ships a stale instance dir that websyn_start.sh would overwrite at boot anyway.
…ted synthetic art Two changes that could not be cleanly separated -- the art had to be designed against the new palette, and both touch the same templates and app.py. ## Interface The mirror shared no design language with versus.com: light blue-and-white template, a round "VS" badge, two flat sections and a one-line footer, against a near-black to deep-purple editorial site. Rebuilt from the evidence captured before the source started refusing this client: dark ground with the purple wash and the wave cutout, lowercase wordmark, the oversized two-line "compare everything" hero with its accent underline, pill controls, score rings on products, four-column footer. The comparison page also carried the wrong shape. The source resolves a comparison per area with a margin per row, not as one overall number, so the table now has a Margin column with the leading side marked and the ruling band reads "leads in N of M areas". What the site *declares* the winner is deliberately unchanged -- still the higher Versus Score, via winner() -- because tasks 9 and 16 are graded on that, and changing it would have moved the answer under the verifier rather than fixing the presentation. Colours are read off screenshots, not sampled from the source stylesheet: the 403 landed before exact values could be extracted. The CSS header records them as estimates rather than claiming they are exact. Adds /about, stating which values are sourced and which are synthetic; the footer carries the short version on every page. The Versus Score is synthetic and now says so in the product, not only in review notes. Not mirrored, and not pretended otherwise: the editorial/blog mosaic, the ~100 other categories, the glossary, the locale switcher. ## Art The art was drawn per request by a Flask route: no files, no hashes, no gate, and a placeholder that did not say it was one. It is still synthetic, deliberately. versus.com stayed blocked across repeated probes and no attempt was made to work around that. Freely licensed photography for these exact models could not be matched reliably either -- a Wikimedia Commons sweep found a freely licensed candidate for 18 of 20 products, but strict model matching showed most hits were the wrong item (a OnePlus 8 for the OnePlus 12, an A7R IV for the A7 IV, a 4070 Ti Super for the 4070 Super, a card-slot close-up for the Nikon Z8, earbuds for over-ear headphones). Shipping those would put false product facts into a benchmark built on factual navigation, which is worse than art that admits what it is. So the art follows the precedent already on main -- webmd_doctor ships Pillow-drawn avatars and gradient panels "instead of photography" -- and is held to the same contract: generate_art.py is deterministic (no RNG, clock or locale, Pillow's bundled font, fixed PNG compression, no ancillary chunks); generated_asset_inventory.json pins every path, byte length and SHA-256; check_generated_assets.py enforces coverage, size, hash and PNG decode and runs in the Docker build. Tiles are build products -- static/images/ stays gitignored and the inventory is what travels in Git. Each tile carries a visible SYNTHETIC ART label. No task answer depends on reading an image: all 17 verifiers are deterministic and never open one. This is a deviation from the reviewer checklist's "Real images" line and NOTICE.md says so rather than glossing it. It is the maintainers' call.
An independent blind reviewer passed all 17 executions and, on a run it passed, recorded that the final answer had been emitted at chrome-error://chromewebdata/ after a failed navigation. Every fact-bearing read had already happened, so the answer was right. All 17 deterministic verifiers passed that run too, and that is the actual finding. The navigation checks only look for evidence that the right page *was* opened, and step_urls() filters off-origin steps out on purpose, so a run that crashed and then answered was indistinguishable from a clean one. Evidence that hides its own failure grades as clean. Reproduced before fixing: an error_page_ending fixture, built by rewriting a canonical bundle's done step to a chrome-error URL, passed verify_0, verify_7 and verify_10 unmodified. answered_on_site() requires the step carrying the final answer to sit on this site's origin, and terminal_state_is_sound() wires it into all 17 verifiers. The fixture now fails everywhere, the clean runs still pass, and the real bundle that prompted this correctly failed and was re-recorded rather than accepted. Pinned in tests: answered_on_site must reject a chrome-error terminal step, and every verify_*.py must carry the check.
Upstream advanced to a437bd4, merging Healthline (aiming-lab#105), which took index 26 and port 40026 -- the slot this branch was using. Resolved by taking upstream's registry verbatim and appending versus after healthline: healthline keeps 40026, versus takes 40027. No earlier site moves. Dockerfile site count and EXPOSE range follow the 28-site registry, and the task rows and site README follow the slot. The regression test derives the port from both launchers rather than freezing it, so it follows this move on its own; so does the verifiers' origin check. Recorded trajectories carry the old port, so the runs are re-recorded at 40027 rather than graded with an origin override.
The terminal-state regression hardcoded localhost:40026. Healthline took that slot upstream, versus moved to 40027, and the test started failing against correct code. Third instance of one mistake: a frozen port in the verifier's navigation check (silently accepted trajectories from another mirror), a frozen port in the adversarial foreign-origin fixture (silently stopped testing anything), and now one here. This one at least failed loudly rather than going quiet, which is the behaviour the other two lacked. All three now derive the port from control_server.py's registry.
The verifier derived its expected origin from control_server.py's registry, so every recorded trajectory expired the next time an upstream merge re-slotted the site. That happened four times in one day -- 40015, 40024, 40026, 40027, 40028 -- and each move meant re-recording 22 executions to grade evidence that had nothing wrong with it. At that point the checker is the thing breaking, not the evidence. The port was also buying less than it looked. What binds a run to this environment is the before/after seed SHA-256 in its manifest and the code SHA, neither of which rots. A genuine cross-site replay is caught by the paths, not the port: the other mirrors serve /article/, /section/ and /track/, not /item/ and /compare/. The foreign_origin fixture only ever rewrote the port while keeping versus's own paths, which is not a replay anyone could actually perform. run_origin() now takes the origin from the trajectory's own start_url, so the check is internal consistency: every fact-bearing step, and the step carrying the final answer, must sit on the origin the run started from. That keeps the property that earned its place -- a step recorded on chrome-error://chromewebdata/ after a failed navigation is still not evidence that a page was seen -- and drops the property that only made evidence perishable. Verified rather than assumed: with the site re-slotted to 40028 and the runs still recorded at 40027, all 17 canonical executions verify with no re-recording, and both error_page_ending and foreign_origin still fail as they should (foreign_origin now fails for a better reason -- the run is internally inconsistent). Also carries the upstream merge that re-slots versus behind kaggle to 40028.
…es a re-slot The terminal-state test still called site_origins(), which the previous commit replaced. It errored rather than passing quietly, which is the behaviour this suite is for -- but I pushed before running it, so it went out broken. Rewritten against run_origin(): an arbitrary port is used deliberately, since the check is now internal consistency rather than today's registry value. Adds the case the change exists for -- a run recorded at 40027 still verifies after the site is re-slotted -- and the case that a trajectory with no start_url has no origin to be consistent with.
Raibows
added a commit
to TabsPhasers/WebHarbor
that referenced
this pull request
Sep 14, 2026
… brand hue
Measured against rendered pixels: --nv-green-d #5e9400 was 3.68:1 as text on white
(card series label, newsroom category, article eyebrow, active pill, green links)
and the star gold #f5a623 was 2.03:1, both below the 4.5:1 body-text requirement;
the --nv-green #76b900 focus ring was 2.41:1 on white and 2.25:1 on #f7f7f7, below
the 3:1 non-text requirement, and .field input:focus{outline:none} suppressed the
ring on form fields entirely.
Tokens are now role-based: --brand stays decorative (dark surfaces, button fill,
logo), --action #4a7500 is 5.48:1 on white and 5.11:1 on #f7f7f7 for text,
--focus #4a7500 clears 3:1 on white, #f7f7f7, #000, aiming-lab#111 and #1a1a1a, and --star
#9c6200 is 5.05:1. test_ui_contract.test_09_contrast_tokens derives the thresholds
from the stylesheet; the pixel audits confirm 0 text failures and 8/8 focus
indicators at 3.84-5.48:1.
Evidence: _wh_review_tools/pr107-fixes/fixes/M1M2/{after,mutation}.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reviewer pass over #41 (Versus mirror by @Sun-sunshine06). The site commit is kept as the author's, rebased onto current
main; everything after it is reviewer work.Draft on purpose — one deliberate deviation from the reviewer checklist is called out at the bottom for the maintainers to rule on. The environment, interface and grading work is finished and reproducible.
What the contributor supplied
A Versus mirror under
sites/versus(Flask app, code-generated SQLite seed, 20 products across 5 categories) and 10 task definitions with the contributor keys only. No verifiers — correct for the contributor role perCONTRIBUTING.md.@Susan571 reviewed it at source level on 2026-06-21 and asked to pair it with a Docker-capable mechanical pass. This is that pass, plus the grading contract.
Environment fixes
The generated seed was not byte-reproducible across builds. Two builds of the same commit produced different seed bytes (
96d9d1dc…vs568bfb9d…). Logical content was identical both times — 5 categories, 20 products, 4 users, 3 saved comparisons hash the same withpassword_hashexcluded — so the only divergence wasgenerate_password_hash()drawing a fresh scrypt salt per build. The in-image reset contract still held (the seed is copied from that same generation), which is why it never surfaced as a failingmd5check; what it broke is pinning the seed hash anywhere.PYTHONHASHSEED=0, whichosuuses for its build-time seed, does not help. Frozen the hash the wayosufreezesBENCHMARK_PASSWORD_HASH. Two builds now agree on231149c4….No CSRF protection. A
POST /compare/<l>-vs-<r>/savecarryingOrigin/Refererof another site was accepted and wrote the row. 23 of the 25 sites onmaininstallCSRFProtect; this one did not. Installed it, added tokens to the two posting forms, declared Flask-WTF in the site requirements. Also stopped pre-filling the benchmark email and password into the login inputs — the credentials stay in the task text, which is the repo convention, but a page that ships them invalueattributes lets an agent sign in without reading anything.Port and favicon. The branch targeted the 15-site tree and claimed 40015.
versusnow registers afterfedex,webmd_doctorandhealthlineat index 27 and serves on 40027, and every task row followed the old port. The missing/favicon.icowas the only console error on every page load.Mechanical results at this head: all 9 routes return 200 at 1440/768/390/320 (36/36), zero external requests, zero page errors,
POST /reset/versussucceeds and leavesmd5(instance) == md5(instance_seed), and a functional/negative sweep passes 19/19 — wrong password rejected,/accountand the save endpoint gated when signed out, no cross-user leak between benchmark accounts, unknown slugs 404, hostile search string and non-numeric filters handled.Grading contract
17 tasks, 17 deterministic verifiers, 17 rubrics.
Three of the original questions were answerable without opening anything. Versus prints Score, Price and Year on every product card, and
/rankingslists all 20 products ordered with score, price and category and needs no filter — so "which of these two scores higher" (--0), "highest ranked graphics card" (--2) and "highest scoring smartphone under $1000" (--6) could all be read off a list. The fix is not to hide the ranking, which is a real page of the site, but to ask for a fact the list does not carry: every spec value (camera score, ANC score, megapixels, burst, VRAM, power, benchmark, battery, weight, display) renders only on detail and comparison pages, so each rewritten question now ends in one of those.The one stateful task was a no-op.
Versus--7asked Alice to save the RTX 4080 Super vs RX 7900 XTX comparison, whichseed_benchmark_users()already saves for her, andsave_comparison()de-duplicates — the after state was identical whether or not the agent acted. It now targets a pair she does not have, and a second save task uses Bob, who starts with none.One question had two valid answers. "A camera with 40 megapixels or more" matched both the Nikon Z8 (45.7) and the Fujifilm X-T5 (40.0). It now asks for the highest count.
Verifier design: ground truth is derived from the passed
initial_db, not frozen, so the expected answer moves with the seed and a stale verifier fails loudly instead of grading against a dead value. Superlative questions use a helper that returns nothing on a tie and fails closed. Malformed or missing input produces a structured FAIL, never a traceback. Stateful tasks read the after-state DB and also assert the pair was absent before, so a task regressing into a no-op is caught by the verifier itself.For the record, one item from the earlier review does not hold:
Versus--5putting the benchmark password in the task text is not a leak — 59 task rows across the existing sites do the same.Verification
17 canonical UI runs, re-recorded after the interface rebuild. Each resets through the control plane, starts at the site root and clicks its way to the answer, recording a trajectory in
agent_demo/agent.py's schema plus screenshots and before/after DB snapshots. All 17 complete with zero page errors and all 17 pass their verifier. Environment sweep at this head: 40/40 route×viewport responses are 200, with zero external requests and zero console errors.Independent blind review
A separate session with no access to the verifiers, their verdicts, this branch or these PRs judged the 17 frozen executions from a hashed packet. It returned 17/17 PASS, agreeing with the deterministic verifiers on every task — and, on runs it passed, recorded two things the verifiers had missed.
A run answered from a browser error page. On
Versus--10the final answer was emitted atchrome-error://chromewebdata/after a failed navigation. Every fact-bearing read had already happened, so the answer was right — and all 17 verifiers passed it. That is the finding: navigation checks only look for evidence that the right page was opened, so a run that crashed and then answered was indistinguishable from a clean one. Reproduced with a fixture before fixing;answered_on_site()now requires the answer-bearing step to be on this site's origin, the runner aborts on off-site navigation instead of recording it as a step, and the defective bundle was re-recorded rather than accepted.Superlative runs opened only some candidates. On five tasks the guided run opened two of four products in the category. The reviewer checked the seed, confirmed each superlative still held, and passed them while noting it. That is a guided-run artefact, not a verifier defect — requiring exhaustive visits would reject legitimate routes such as search or the ranking view, which the alternative-route runs exercise. Fixed in the evidence: those five now open all four.
Not captured, and recorded as a gap rather than reconstructed: the blind reviewer's overall statement of model, coverage and unperformed checks. The per-task rationales establish the packet identity; the model identity is not established.
One run deserves mention. During the batch,
Versus--7came back with an after-state byte-identical to the seed — its save had been lost to container instability — while the agent's own answer claimed success. The verifier failed it, which is exactly what the DB check exists for. Re-run in isolation the save persists and it passes. Run manifests now recordbefore_sha256/after_sha256/state_changed, and arun-integrityindex asserts stateful tasks changed state and read-only ones did not: 17/17 consistent.These are guided runs — the reviewer knew the answers, and every run manifest says so. They show each task is solvable through the UI and they exercise the verifiers on real trajectories. They are not a measurement of agent capability and should not be read as one.
Adversarial matrix: 205 cases, 0 expectation mismatches, 0 tracebacks. Before trusting any verdict the harness grades a genuinely defective bundle while deliberately asserting it should pass, and aborts if it reports a match — so the green result is not vacuous. Cases: no-op, correct-answer-without-navigation, wrong answer, negated answer, truncated trajectory, foreign origin, wrong-task replay, unparseable trajectory, empty run dir, missing after-state.
The matrix found a real hole in my own verifiers.
foreign_origininitially passed all 17: every navigation check was a bare substring match on the path, so rewriting a canonical trajectory's URLs fromlocalhost:40024tolocalhost:40007— same paths, different mirror — satisfied all of them. Navigation checks now keep only steps on this site's own origin, with the port derived fromcontrol_server.py's registry rather than frozen, so a registry reorder moves the verifier with it.Five legitimate alternative routes pass (tasks 0/3/6/12/16: two detail pages instead of the comparison; search by name instead of category word; rankings instead of the price filter) — the counterweight to the negative cases, so a correct execution that took a different path is not failed.
A regression suite pins each of these defects; 7 of its 11 tests fail against the branch as submitted.
Reproduce
Grading a run uses the standard entry point:
uv run python agent_demo/eval_judge.py --run_dir <run> --verifier True.Rebased onto current main, twice
The branch was first rebased from the 15-site tree onto
3600493. Upstream thenadvanced three times while this review was open — FedEx (#82), WebMD Doctor
(#95) and Healthline (#105) — so this branch carries merges that keep the slots
those three already occupy and append
versusafter them: fedex 40024, webmd_doctor 40025,healthline 40026, versus 40027. No earlier site moves.
Only the address changed, not the site — but the recorded trajectories carried
the old port and the verifiers derive their expected origin from the registry,
so all 22 runs were re-recorded and re-verified after each move rather than
graded with an origin override. Every number above is from the current port.
Data scale — a limitation, stated
The catalogue is 5 categories with 4 products each. Filtering and sorting
exist and are exercised, but they operate on four-item sets where the same
answer is readable off four cards, and no surface has enough rows to
paginate — in a mirror of a large-catalogue comparison site. The tasks are
solvable and graded correctly; the paths they exercise are thinner than the
source's.
The comparable upstream figure — products per category — could not be read,
because versus.com refuses this client, so it is recorded as unknown rather than
estimated. Widening the catalogue means sourcing real specs and prices for each
added product, which is a scope call rather than something to quietly expand.
Interface rebuilt against the source design
The mirror shared no design language with versus.com — a light blue-and-white template with a round "VS" badge, two flat sections and a one-line footer, against a near-black to deep-purple editorial site.
Rebuilt from evidence captured before the source began refusing this client: dark ground with the purple wash and wave cutout, lowercase wordmark, the oversized two-line
compare everythinghero with its accent underline, pill controls, score rings, four-column footer. The comparison page also carried the wrong shape — the source resolves a comparison per area with a margin per row, not as a single number — so the table now has a Margin column with the leading side marked and the ruling band reads "leads in N of M areas".What the site declares the winner is deliberately unchanged: still the higher Versus Score. Tasks 9 and 16 are graded on that, and matching the source there would have moved the answer under the verifier rather than fixing presentation. The per-area counts are shown alongside.
Colours are read off screenshots, not sampled from the source stylesheet — versus.com started returning CloudFront 403 to this client mid-review and stayed blocked across repeated probes, and no attempt was made to work around it. The CSS header records them as estimates rather than claiming they are exact.
Not mirrored, and not pretended otherwise: the editorial/blog mosaic, the ~100 other categories, the glossary, the locale switcher.
Product art: synthetic by design, and gated
The art was previously drawn per request by a Flask route — no files, no hashes, no gate, and a placeholder that did not say it was one. It is still synthetic, deliberately, and now holds to the contract
webmd_doctorestablished onmain(it ships Pillow-drawn avatars and gradient panels "instead of photography"):generate_art.pyis deterministic — no RNG, clock or locale, Pillow's bundled font, fixed PNG compression, no ancillary chunksgenerated_asset_inventory.jsonpins every tile's path, byte length and SHA-256check_generated_assets.pyenforces coverage, size, hash and a full PNG decode, and runs in the Docker buildstatic/images/stays gitignored and the inventory travels in GitSYNTHETIC ARTlabelWhy not real photography: the source is blocked, and freely licensed images for these exact models could not be matched reliably. A Wikimedia Commons sweep found a freely licensed candidate for 18 of 20 products, but strict model matching showed most hits were the wrong item — a OnePlus 8 for the OnePlus 12, an A7R IV for the A7 IV, a 4070 Ti Super for the 4070 Super, a card-slot close-up for the Nikon Z8, earbuds for over-ear headphones. Shipping those would put false product facts into a benchmark built on factual navigation, which is worse than art that admits what it is.
This deviates from the reviewer checklist's "Real images" line.
NOTICE.mdstates that plainly rather than glossing it, along with the non-affiliation notice and a removal procedure. It is the maintainers' call whether to accept the deviation.The Versus Score is synthetic, and the site now says so
Product names, brands, release years, list prices and published specifications follow the manufacturers' figures — spot-checked against Momentum 4 60 h, WH-1000XM5 30 h / 250 g, Nikon Z8 45.7 MP, RTX 4080 Super 16 GB / 320 W, iPhone 15 Pro $999, S24 Ultra $1299. The Versus Score is not versus.com's value; it is benchmark data, as are all accounts and saved comparisons.
Rather than invent a provenance for it, the mirror discloses it: a new
/aboutpage, the footer on every page,NOTICE.mdandREADME.md. Six of the 17 tasks derive an answer from score, so if a maintainer later re-sources it those tasks need re-running; the spec-, weight-, battery- and price-keyed ones do not.Remaining, non-blocking
.dockerignoreexcludessites/*/tests/but notsites/*/verify/, so verifiers ship inside the image — repo-wide, matchingmerriam_webster, not introduced here. Product summaries are template strings rather than editorial text./rankingssorts on score with no secondary key and the global list contains ties; no current task depends on tie order.The anchored LLM check at the end of each verifier is unverified: this host has no
OPENAI_API_KEY/JUDGE_MODEL, so everything ran deterministic-only and those checks reported[SKIP]. The deterministic checks carry every result above.The full 27-site image build and
reset-allwere not run — the host is well below the disk headroom that needs. All checks ran against a single-site container.Site and mirror by @Sun-sunshine06 (#41); source-level review by @Susan571. Environment fixes, interface rebuild, task rework, verifiers and verification by the reviewer. Head:
7aba94b58bf0aae083c45fec026fb5ecfcc27f74.🤖 Generated with Claude Code