Skip to content

Review: 9GAG mirror + task verifiers (site by @sarendis56, verifiers by reviewer) - #97

Open
JeremyJC67 wants to merge 10 commits into
aiming-lab:mainfrom
JeremyJC67:review/pr-92-9gag
Open

Review: 9GAG mirror + task verifiers (site by @sarendis56, verifiers by reviewer)#97
JeremyJC67 wants to merge 10 commits into
aiming-lab:mainfrom
JeremyJC67:review/pr-92-9gag

Conversation

@JeremyJC67

Copy link
Copy Markdown

Summary

Review vehicle for #92 (9gag mirror by @sarendis56). This PR keeps the site commit intact (authorship preserved) and adds the reviewer deliverables on top:

  • Grading contractsites/9gag/verify/verify_lib.py + verify_0.py … verify_19.py (deterministic; ground truth hardcoded in the verifiers only), verifier_path + English judge_rubric on all 20 rows of sites/9gag/tasks.jsonl, and sites/9gag/verify/README.md summarising the per-task contract.
  • Validation harnesssites/9gag/verify/tests/ (244 unit cases over copies of the frozen seed + a Playwright live matrix that drives every task end-to-end); excluded from the image by .dockerignore.
  • Docs/registry sweep — README, AGENTS, CONTRIBUTING, CLAUDE, agent_demo/README, the Dockerfile comment and the two registry integration tests updated to 25 sites / 40000-40024 (they fail on the feat(9gag): add high-fidelity mirror #92 head as submitted).
  • One task rewording — T10 now says "original tiny lighthouse office post" so the identical-fact "Community remix" clone is not a valid save target (matches T11–T13/T18/T19 wording).

Full review comment with mechanical / visual / functional / task-quality evidence is posted on #92.

Verifier design

  • Package identity: task_id, agent_done, non-empty answer, every recorded URL http:// on a loopback host and the same port as start_url, every referenced screenshot decodes as PNG.
  • Navigation gates: /search?q= with a task-relevant whole token or the named feed page, plus the exact /gag/<slug> detail page (T19: all three compared posts; T14: the /report form), login-before-action ordering and the typed account identity for stateful tasks.
  • Answers: affirmative-only matchers (negations do not count), standalone counts / number words, decimal-exact 4.2, clock-time variants.
  • SQLite contract: seven tables, pinned schema hash, frozen seed counts and benchmark identities, immutable post catalog; read-only tasks require all tables row-identical, stateful tasks require the exact row delta (+ counter bump on the right post, + password-hash verification for the registration task) and no collateral writes; stale-state preconditions.
  • LLM helpers are kept for parity but are advisory only — every verdict is decided with --no_llm True.

Validation (carcinoma, standalone Flask on 127.0.0.1:45001, agent_demo Playwright env)

Live matrix (verify/tests/live_matrix.py, real Chromium drive per task, real screenshots, seed vs live DB snapshots):

variant expected result
genuine (20 tasks) PASS 20/20 PASS
no-op (homepage only, empty answer, clean DB) FAIL 20/20 FAIL
shortcut (correct answer / self-reported success, no navigation) FAIL 20/20 FAIL
wrong answer (read) / wrong DB target (stateful) FAIL 20/20 FAIL
state mismatch (stateful, clean DB) FAIL 10/10 FAIL

Unit matrix: uv run python -m unittest discover -s sites/9gag/verify/tests -p 'test_*.py' → 246 tests OK (10 skipped = abstract base class). agent_demo/eval_judge.py --run_dir <run> --verifier True invokes the verifiers end-to-end (checked on a genuine and a shortcut run dir).

Site checks (standalone): byte-identical seed md5 44768940829bf4a8bec07d7bdf072d97 after boot 1, boot 2, a full read-only Playwright drive and a fresh reset; all auth/CRUD/form flows persist; 20/20 tasks solvable via the UI with no answer leaks on results cards. Full 25-site Docker environment built from this branch with refs/pr/65 assets: build ok (4.72 GB), 25/25 sites 200, /health 25/25, POST /reset/9gag byte-identical (44768940…) before/after a dirty request and after docker restart, /reset-all 25/25 in 1.65 s, verifier no-op matrix in the container 20/20 FAIL, registry tests green, sites/9gag suite 246 passed.

Still required on the site side (tracked on #92)

  1. Repin .assets-revision to the merged HF sha once discussion Add TED task verifiers (site by @shanjiaming, verifiers by reviewer) #65 lands.
  2. Strip the 73 AppleDouble ._* members from 9gag.tar.gz.
  3. Fix the 768px horizontal overflow (site.css @media(max-width:1000px) grid).
  4. Seed or remove the empty "Memes" entry points (sidebar/top-nav/Recents), the six topic chips and #animals, which all land on "No posts found".

Port 40024 is provisional; #95 (webmd_doctor) claims the same slot — the maintainer reassigns at merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AkNTAZMziDodR6uqa6QBtP

sarendis56 and others added 10 commits September 10, 2026 03:08
The site PR registers 9gag as site index 24 (port 40024) and bumps
EXPOSE, but leaves the shared docs and the two registry integration
tests on the 24-site / 40000-40023 wording, which makes
sites/walmart_careers/tests/test_integration.py and
sites/rotten_tomatoes/tests/test_environment_quality.py fail on the
branch. Apply the same sweep PR aiming-lab#95 does: README, AGENTS, CONTRIBUTING,
CLAUDE, agent_demo/README, the Dockerfile comment, and derive the
documented port range from the control_server SITES registry in the
walmart_careers test. Port 40024 is provisional (maintainer reassigns
at merge).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AkNTAZMziDodR6uqa6QBtP
…tasks

Reviewer grading contract for the 9GAG mirror (PR aiming-lab#92):

- sites/9gag/verify/verify_lib.py: shared utilities (merriam_webster API
  family with walmart_careers-style hardening) - package identity, loopback
  origin + port check on every recorded URL, PNG screenshot decode,
  search/interest/detail navigation gates, login-before-action ordering,
  affirmative answer matchers (counts, decimals, clock times), and a
  SQLite snapshot contract (pinned schema hash, frozen seed counts,
  immutable post catalog, exact per-table row deltas, no collateral
  writes). The anchored LLM helpers are kept for parity but only emit
  advisory evidence; every verdict is decided with --no_llm True.
- verify_0.py .. verify_19.py: one verifier per task with the ground
  truth hardcoded (never in tasks.jsonl). Read tasks accept either the
  original or its identical-fact "Community remix" clone unless the task
  says "original"; stateful tasks require the exact saved/vote/comment/
  hidden/report/post/user delta and verify the registered password hash.
- tasks.jsonl: add verifier_path + English judge_rubric to every row; the
  task 10 wording now says "original" so the remix clone (post 47) is not
  a valid target, matching tasks 11-13/18/19.
- verify/README.md: contract summary per task and how to run the tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AkNTAZMziDodR6uqa6QBtP
- verify/tests/_support.py + test_verify_{0..19}.py + test_verify_lib.py:
  244 unittest cases over copies of the frozen seed (genuine PASS,
  run-dir snapshot discovery, no-op, shortcut, wrong answers per fact,
  wrong task id, unterminated, mixed origin, corrupt screenshot,
  read-only writes, schema/catalog tampering, stale-state preconditions,
  wrong target/account/reason/text, duplicates, counter bumps, collateral
  writes, real werkzeug scrypt hash verification).
- verify/tests/live_matrix.py: drives every task for real in Chromium
  against a running mirror, records agent.py-shaped run dirs with real
  screenshots and DB snapshots, and grades genuine / no-op / shortcut /
  wrong / state-mismatch variants (CONTRIBUTING section C evidence).

Excluded from the image by .dockerignore (sites/*/verify/tests/).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AkNTAZMziDodR6uqa6QBtP
The Memes interest (sidebar entry, top-nav link and the Recents chip), all six topic chips and the
#animals popular tag rendered "No posts found": the seed carries 0 memes posts, and no post carries
the tags "spider man", "usa", "trump", "elon musk", "politics", "latest news" or "animals".

Seeding those interests would require a Hugging Face re-upload of instance_seed/9gag.db, so the dead
entry points are instead repointed at feeds that the sidebar already exposes. Nothing becomes newly
reachable, so no task gains a navigation path its verifier does not expect, and every task's result
set is unchanged (re-measured: science 17, animals 13, sports 5, gaming 4, news 19, lighthouse 10,
rescue cat 9, sourdough 2 - identical to the first-pass review).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AkNTAZMziDodR6uqa6QBtP
At 768px the document was 857px wide on 11 routes. Two causes: the .layout grid used a fixed
minmax(0,640px) track that cannot shrink, and the six-tab .feed-tabs strip needs ~628px while the
main column is narrower. The grid track is now flexible under a 934px cap (preserving the intended
640px reading column at 1000px), and the tab strip tightens and scrolls itself between 681px and
1000px instead of scrolling the page.

The 9GAG greys fail WCAG 1.4.3 on white: #999 at 2.849:1, #888 at 3.545:1 and #777 at 4.478:1
against a 4.5:1 floor, plus white on the brand blue at 4.068:1 and the 26px dismiss glyph at
2.548:1 against a 3:1 floor. A --muted token (#767676, 4.54:1) and an --action token (#0a74e6,
4.52:1) carry the text and the actionable controls; the brand --blue stays for decoration.

Both fixes land in a layer appended at the end of the file: the stylesheet is written as successive
override layers, so the same rules placed earlier are defeated by the later .feed-tabs / .stats /
.actions redefinitions.

Measured with Playwright plus pixel sampling (contributor CSS vs this commit, same instrument):
overflow 11 -> 0 failing route/width cells, contrast 51 -> 0 failing elements, across
1440/768/390/320 x 25 routes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AkNTAZMziDodR6uqa6QBtP
Each item below was reproduced against a running mirror before the change and re-run after it.

- Invalid input was accepted silently, which is the one item here that can corrupt a task grade:
  POST /submit with section=NOT_AN_INTEREST stored that section, POST /report with an arbitrary
  reason stored it, POST /vote with value=garbage was coerced into a DOWNVOTE, and a 5000-character
  title went into a String(300) column. Interests, report reasons and preview images are now one
  tuple each, rendered by the template and validated by the handler so the two cannot drift, and
  bounded_text rejects over-long fields. All four now return 400 and write nothing.
- SECRET_KEY was the tracked literal "webharbor-9gag-deterministic-key". A cookie minted with it
  returned 200 on /account rendering the seeded account and a forged POST wrote a saved_post row.
  Now os.environ NINEGAG_SECRET_KEY or secrets.token_hex(32), as on 7 already-merged sites.
- No CSRF protection (9gag and ikea were the only 2 of 25 sites without it). A cross-origin POST to
  /save wrote a row; now CSRFProtect plus a token in all 13 POST forms, and that request returns 400.
- GET /logout logged the user out; now POST-only, GET returns 405, and Settings posts a form.
- No MAX_CONTENT_LENGTH: a 60 MB multipart POST was accepted in full; now 256 KB, returning 413.
- No error handlers: /nope rendered the bare Werkzeug page, breaking mirror fidelity. 400/404/413/500
  now render a 9GAG-shell error page, and the 500 handler rolls the session back.
- source_id used COUNT+1, which collides after any delete; now MAX(numeric suffix)+1.
- Flask-WTF added to requirements.txt (the site now imports it; 8 merged sites already declare it).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AkNTAZMziDodR6uqa6QBtP
The seed duplicates 35 curated posts as "Community remix N: ...", copying the description verbatim,
so a clone shows exactly the same facts as its original. Tasks 6 and 8 are read-only fact questions,
yet their verifiers accepted only the original slug: an agent that read the right number off the
clone and answered correctly was graded FAIL on a rule stated nowhere the agent could see. The other
seven read-only clone tasks (0-5, 7) already accept either page, so this was also an internal
inconsistency.

Provenance is now enforced only where it changes the graded outcome:
- read-only fact tasks 0-8 accept the original or its clone (identical facts);
- task 9 keeps the original (it compares point counts, which differ between original and clone);
- stateful tasks 10-13, 18, 19 keep the original (the clone is a different row to save/vote/hide).
Every task whose grade still depends on provenance now says so in its ques text, so the requirement
is visible to the agent rather than hidden in the verifier.

Widening the gate is mutation-proofed: each of tasks 6 and 8 gains a case asserting the clone now
passes and a case asserting an unrelated detail page still fails on visited_post_detail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AkNTAZMziDodR6uqa6QBtP
agent_demo/agent.py never writes final_url (ast over its trajectory dict: task, task_id, start_url,
model, max_steps, steps, terminated, termination_reason, final_answer, judge_rubric, verifier_path,
plus success_self_report on done). Both the unit fixtures and the live matrix wrote one, so the
verifiers were only ever proven against a shape the recorder does not emit.

The live matrix now omits it: its Recorder.done() already appends a done step carrying the landing
page url exactly as agent.py does, so the evidence it produces is shape-identical to a real run. The
unit fixtures keep the field, and every read task gains a case that nulls it, so both paths are
covered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AkNTAZMziDodR6uqa6QBtP
main gained fedex (40024) and webmd_doctor (40025). Merged rather than rebased so every
contributor and reviewer commit on this branch is preserved verbatim. Shared registry,
Dockerfile EXPOSE, docs and every site-local port reference move to index 26 / port 40026;
the slot stays provisional and the maintainer reassigns it at merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AkNTAZMziDodR6uqa6QBtP
@sarendis56

Copy link
Copy Markdown
Contributor

Contributor asset follow-up for the remaining packaging finding:

HF PR #65 is updated at d565c6c7016a04baa6f811cfa72e686597a2f078. The archive now excludes AppleDouble/Finder metadata and the unused static/images/reference/ directory. The seed, all 38 post images, and the rail ad are preserved.

I downloaded the uploaded bundle and confirmed it passes validate_asset_archive.py (44 managed members). No code, catalog, tasks, or verifiers changed. HF merge and the subsequent .assets-revision pin remain the release steps; the nonblocking catalog/photo-caption suggestions remain separate.

@JeremyJC67

Copy link
Copy Markdown
Author

Independently validated, and it matches your report. Downloaded refs/pr/65 at d565c6c7 on the review host and ran the repo's own validator rather than taking the numbers on trust:

bytes: 2005927   md5: 1e64ff8d9373774a75c67f1cdfa3dd49
members: 44      AppleDouble/DS_Store: 0
archive roots: 9gag/        second level: 9gag/instance_seed  9gag/static
validate_asset_archive.py -> validated 44 managed members for 9gag

That closes required fix 2 from the audit comment. The 73 AppleDouble members and the unused static/images/reference/ directory are gone, and the seed plus the post imagery are intact.

Remaining on this PR is release mechanics only, and it is not yours: a maintainer merges HF #65, after which I pin .assets-revision to the merge sha. Everything else on the branch is already verified — the branch has since merged the 26-site main and re-slotted to index 26 / port 40026, and the full 27-site container run is green.

@JeremyJC67

Copy link
Copy Markdown
Author

Rebased onto the 26-site main — this branch is now site 27 / port 40026

main merged FedEx (40024) and WebMD Doctor (40025) overnight, which put every open site
branch into conflict. This branch has been brought up to date by merging main rather than
rebasing, so every contributor and reviewer commit is preserved verbatim, and re-slotted to
index 26 / port 40026: registry, Dockerfile EXPOSE, the shared docs and every
site-local port reference follow. The slot is provisional as always — say the word at merge
time and I will re-slot in whatever order you land things.

The whole environment was then rebuilt and re-verified at the new slot:

check result
HTTP sweep 27/27 return 200
GET /health ok, 27/27 alive and ready
POST /reset/9gag → md5 44768940829bf4a8bec07d7bdf072d97, identical to the seed
POST /reset-all ok, 27/27 ready, 1.67 s
verifier no-op matrix 20/20 FAIL
sites/9gag suite in the image 258 passed, 130 skipped

The blocker fix holds in the image: POST /gag/<slug>/vote with value=NOT_AN_OPTION returns
400 instead of silently writing a downvote. Also GET /logout 405, save without a token 400,
branded 404 (1,175 bytes), 300 KB POST /register 413, and seven read-only page loads leave
the DB byte-identical.

Your repacked bundle is validated and reported separately above; the only thing left is a
maintainer merging HF #65.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants