integrate(rc2): land reviewed onion, Tari, topology, and harness fixes - #1972
Merged
Conversation
…oard is on Tor The dashboard onion had no surface anywhere in the browser: the only place its address appeared was a `pithead status` line, and on the appliance nobody has a shell to run that in. Someone who wanted to open the dashboard from a phone over Tor had to reach the CLI first. The three DASHBOARD_ONION_* values now reach the dashboard service, and the header renders the URL in full under the machine name, with a copy control and a note when client authorisation is on — an onion that is enabled but not yet provisioned renders as nothing, because an empty row reads as breakage rather than as "Tor is off". No client-auth key material is passed into the container. host_display_addr moves to the new web/header.py beside the onion reader: both are ways IN to the machine rather than readings off it, and views.py was at its recorded file-budget ceiling. The frontend fixture generator patched `views.detect_host_ipv4`, which the move would have left binding a dead attribute while the fixture went machine-dependent in silence; it now patches the function where it is looked up. This contradicts a stance docs/dashboard.md took: the doctor panel redacts the onion "because the address is worth keeping out of" the container. That prose is corrected rather than left standing — the panel still redacts, the header does not, and the doc now says so instead of implying the address is absent from the browser. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZuzdtxNX8ae4rnDtBqEqR
…s, not three The reviewer's finding on PR #1880 is true and I re-derived it at source. The paragraph I added claimed `pithead doctor` prints the Monero, Tari and P2Pool onion addresses and that an operator can run it to read one. It does not, and they cannot: - `06-doctor.sh:295` emits `dr_ok "$k set."` on the success path — the variable name and a verdict, never the value. - The failure path (`:294`) prints `(value: '${onion:-empty}')`, and `onion_missing` (`32-onion-provisioning.sh:66-68`) is `[ -z "$1" ] || [ "$1" == "placeholder" ]`, so the only values it can print are the literals `empty` and `placeholder`. - The remote-node path prints "not needed" and no value at all. - The one onion the report carries in full is the dashboard's own (`06-doctor.sh:304` via `dashboard_onion_status`, `04-status.sh:38-49`). - `doctor --json` (`06-doctor.sh:359-363`) `jq -Rs`-wraps that same text, so it is not a second document with more in it. So the panel's redaction acts on exactly one address here — the one the header now prints — and the honest version of the paragraph says that. The pointer for the three node onions goes to the stack `.env` (`33-render-env.sh:382-384`), which the encrypted backup archive carries (`45-control-backup.sh:104`); the undefined term "emergency kit" is gone with it. Tier: none — prose only, no behaviour change. Green at this head: lint-docs-voice, lint-md, lint-topology, lint-file-budget, lint-operator-strings. Repo-wide phrase sweep for the claim leaves one hit, the corrected sentence. What the operator sees change on the appliance: nothing in the product. The Diagnostics doc no longer sends them to a command that will not answer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZuzdtxNX8ae4rnDtBqEqR
…y announces itself The design lane's non-author RETURN on PR #1880 (comment 5556336128) was posted at b62d37f and I had not read it — my previous push answered only the other reviewer's finding. Its three asks, addressed here: 1. The note rendered "Get the key withpithead onion-client-key". htm strips a whitespace run CONTAINING A NEWLINE from both ends of every static text chunk, so breaking the line before the <span> deletes the space. `${" "}` is the repo's existing answer at 13 sites in 3 files, because a field is pushed as its own child and never goes through that regex. Design measured the broken render at 1400px and 390px in both themes; the second reviewer derived the same result from the vendored parser's source. 2. The note named a shell command on a product whose first paragraph says nobody has a shell, and `dashboard.onion.client_auth` defaults to true while `dashboard.onion.*` is never committable from the Configuration view — so an appliance shows this note by default to a reader who cannot act on it. The sentence now says who it is for. The reachability gap itself is #1882, not this PR. 3. "Copied" was a status message with no live region (WCAG 4.1.3). The label itself is the status, so `aria-live="polite"` goes on the button. The old tests were green on defect 1: one asserted "Client authorisation is on" and one asserted "onion-client-key", and each matches one side of the join. The new assertion strips tags and pins the whole phrase. Proven to fire: reverting only the `${" "}` reddens it 7/8 and restoring greens it 8/8, with the mutation asserting its own needle so a no-op substitution cannot pass as a control. Green at this head: node --test onionurl 8/8, make test-frontend 574/574, make lint-js (biome 2.5.0, which does lint CSS as well as JS), lint-file-budget. Repo sweep: no doc or Python string quotes the old note wording. What the operator sees change on the appliance: the client-auth line under the onion URL reads as a sentence instead of running two words together, and it now says the command is for a machine they can log in to. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZuzdtxNX8ae4rnDtBqEqR
…nd says so The prose promised the header to the reader who cannot have it. `dashboard_onion()` reads three environment variables; docker-compose.yml passes them, and the appliance's podman quadlet for the dashboard does not (`36-quadlet-units.sh` writes that unit's `Environment=` line with none of the three; all three rendered `os/quadlet/*dashboard.container` carry zero `onion` matches against a `HOST_IP` control of one, while `tor.container` carries two — so the sweep is not blind to the string). On an appliance the server therefore sends no onion and the block is correctly absent — which is exactly the machine the removed sentences named as the reason the block exists. The code is right and fails in the safe direction; the documentation was wrong, so the documentation moves. The quadlet gap is #1896, filed against the paths that own it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZuzdtxNX8ae4rnDtBqEqR
`aria-live` on the button made the label the status message, and the label never came back down: `copied` was set true and nothing ever set it false, so the region's content stopped changing and a live region with unchanged content announces nothing. The first copy was announced and every copy after it was silent — to exactly the reader the attribute was added for. It also left the control named "Copied": a state, where a returning reader needs the action. The button keeps one name, "Copy address". The confirmation moves to a sibling `role="status"`, which is rendered empty rather than conditionally — a live region inserted with its message already inside it presents no change to announce — and comes down on a timer, so the next copy is a change again. A failed copy clears a standing confirmation and disarms the pending timer, so the previous "Copied" cannot read as this attempt's answer. Proven to fire, each mutation naming its own row: removing the revert timer reddens the clears-itself test; dropping the `clearTimeout` reddens the failed-copy test; putting the confirmation back in the label, or making the status region conditional, reddens the markup test. Restored tree byte-identical to pre-battery. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZuzdtxNX8ae4rnDtBqEqR
…s not sanitise
CodeQL read the test's `replace(/<[^>]*>/g, '')` as an incomplete multi-character
sanitizer and posted a new high-severity alert, which held the required CodeQL
check red. The string never reaches a DOM — it is read by assert.match — but a
sanitizer-shaped line in a security-reviewed surface is worth not writing.
Same extraction via split/join, plus the half that was undocumented and is load
bearing: this substitutes NOTHING for a tag. The sibling in xvbview.test.mjs
substitutes a space, and a space would rejoin "to,<span>pithead" into readable
text and pass over the very defect the assertion exists to catch.
Proven by a controlled pair, not by the suite going green: with the explicit
${" "} removed from onionurl.mjs:90 the assertion fails on
"log in to,pithead onion-client-key prints it." and the two narrower assertions
above it stay green, which is what the comment claims; restored by sha256 and
10/10 pass again.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KZuzdtxNX8ae4rnDtBqEqR
…mes what it saw The RC1 battery's provision phase reddened on the bare word no-served-config, which covered a non-200, a 5 s timeout on a cold first read and a non-JSON body alike. The helper now reads /api/state up to six times, 5 s apart, and when no read serves a config the reason carries the status, curl's rc, the try count and the head of the body. Measured on the host: a timeout parses to http=000 with an empty body, a live 200 without .config falls through to the reason, a JSON body with a trailing newline yields .config intact. run.sh is set -uo pipefail, so the failing assignment cannot exit the harness. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeSaJPWy7AkhYcYpGVkxBJ
…n values The appliance's dashboard unit never received DASHBOARD_ONION_ENABLED / _ADDRESS / _CLIENT_AUTH, so the header's .onion block (#1853, compose side in #1880) could not render on the machine it was written for. The quadlet writer now puts the three on the dashboard unit's Environment= line, next to the other DASHBOARD_* flags, read from the rendered .env like every other value; the three fixtures are re-rendered (one line each). Display-only: the client keys are still never passed in. test-render-quadlet gains the rule that would have caught this: every DASHBOARD_* key the compose dashboard service carries must be on the rendered quadlet unit, with a control row that the compose parse sees the service's own keys. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeSaJPWy7AkhYcYpGVkxBJ
The host half (#1855) renders TARI_MODE=off and starts no Tari node, but the p2pool entrypoint still exec'd an argv carrying `--merge-mine <url> <address>`, so an off machine merge-mined against a node that was not there. When TARI_MODE is the literal "off" the entrypoint drops the triple, in either spelling, before the Tor block reads argv, so the merge-mine bridge cannot fire on it either; a flag in the address slot is kept, the rule _redact_argv already applies. Unset or any other value leaves argv exactly as it arrived. A sibling fragment, test-p2pool-tari-off.sh, pins the off shape (both spellings, the positional launch line, no Tari bridge), the flag-in-value-slot rule, and the untouched argv for local and for an unset variable. test-monero-tari.sh is at its ceiling, so the rows live beside it; run.sh gains the stanza and loses a separator, line-neutral. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeSaJPWy7AkhYcYpGVkxBJ
… drop line is claimed only on a drop Review return on #1944 (R1, R2, N1, N2): rows 7-9 asserted one token, so a strip that ate the flag alone, or the address alone, read as untouched; they now pin ARGC. The "dropped from the launch" line fires only when a triple was removed, with a row for the no-triple argv. The entrypoint comment says that until #1905 renders TARI_MODE the block strips nothing. Out-of-lane (currency: build/p2pool/entrypoint.sh; tests: the fragment), disclosed in the PR body. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeSaJPWy7AkhYcYpGVkxBJ
…ute, not the dashboard's The bench said it, six of six: http=404 body "404: Not Found". The helper read /api/state, which only the provisioned dashboard serves; the wizard registers /api/wizard-state (wizard.py:526), the route run.sh's own pre-fill check already reads. Both reads in the helper move to it. The browser-shaped provision leg could not have passed on any tree since #1847. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeSaJPWy7AkhYcYpGVkxBJ
… state route too The fourth /api/state read in tests/os — the fixture-works control at the top of the reinstall pre-fill submit leg — used the wizard's session jar against a route only the provisioned dashboard registers. The wizard serves /api/wizard-state (wizard.py:526), and its `config` carries `monero.wallet_address`, the field the control compares. Without this the first install run to get past the restore leg (PR #1947) would have redded with "pre-fill NOT armed (served wallet: none)" — loud, but naming the wrong cause. Found by the fixes lane at source (git grep at d973163: zero "/api/state" in wizard.py, two "/api/wizard-state"); re-derived here before the edit. The three /api/state reads left in run.sh are basic-auth reads against the provisioned dashboard and stay. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeSaJPWy7AkhYcYpGVkxBJ
…hat it saw One helper, wizard_state_poll, now does the /api/state read for both consumers: up to six reads 5 s apart until a jq filter yields a value, and on no hit a reason naming the status, curl's rc, the read count and the head of the body. provision_browser_submit (#1932) calls it for `.config`; the reinstall pre-fill leg's fixture-works control (#1936) calls it for the served wallet, so its red names the wallet it saw, or what the reads saw when none carried one, instead of "served wallet: none" for a timeout, a 401 and a non-JSON page alike. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeSaJPWy7AkhYcYpGVkxBJ
…e option comment is true The HTML shape's body now exceeds the 60-byte bound, so `head -c 60` is pinned (the reviewer's M3 mutant, `60`→`40`, now reddens one check); the self-test's `set` line names the options run.sh actually uses, `-uo pipefail`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeSaJPWy7AkhYcYpGVkxBJ
…nly the poll's logic The fixes lane's review mutated /api/wizard-state to a bogus path at every site and the self-test still passed: the curl shim branched on $shape and never read its arguments, so the URL was invisible to it by construction. The shim now records the URL (curl's last argument) instead of a placeholder, and one case asserts it once against the wizard's registered route (#1932). Measured here: the mutated copy now fails that case with the bogus route in the message; the read-count cases are unchanged (one line per call either way). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeSaJPWy7AkhYcYpGVkxBJ
…ads consecutive trailing comments
The gate's shfmt -i 4 -d aligned curl() {'s existing trailing comment against the new one on the
echo line; an early lint-sh failure also skips the suite steps, so --self-test never ran in CI at
3d34444. Diagnosed and the fix measured by the fixes lane; re-run here: shfmt clean, self-test
passes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeSaJPWy7AkhYcYpGVkxBJ
…machine" The Stack Topology diagram routed the rigs -> xmrig-proxy stratum hop and the browser -> caddy HTTPS hop as LOCAL, while its own legend defines Local as "this machine" and LAN as "a hop that leaves it but stays on your network", and both nodes sit in the LAN zone (topology_graph.py:39-40). The comment above those two edges already read "Ingress from your LAN"; only the code disagreed. Neither hop is a leak either way, so no leak count, headline or legend entry changes. Second half: the #424 Tor egress probe now appears in both privacy views. tor_heal sends PROBE_URL through TOR_SOCKS_PROXY every five minutes while tor.auto_heal is on (tor_heal.py:63, :130-134). It was in neither the "All connections" list nor the diagram, nor anywhere in docs/privacy.md, so an operator auditing that list was reading one that omitted a connection. egress.py is at its file-budget ceiling, so edge/ext_node move to topology_graph.py, whose docstring already records "egress has no room" as the reason the route vocabulary lives there. That rename collided with the loop variable in the firewall-tagging pass, which the suite caught, not review. Tier: unit. test_tor_probe_egress.py is a new module because test_egress.py is at 451/451; the ingress test there is corrected in place, line-neutral, and now checks both edges' route where it checked only the rigs one. The frontend fixture is regenerated. It was ALREADY stale on develop and had been hand-edited: it carried tari values the generator does not produce at defaults and indentation json.dump cannot emit. Its drift guard compares dotted key paths only, never values, so value drift there is invisible to CI. What the operator sees: the two ingress arrows turn blue (LAN) rather than grey, and "All connections" gains a Tor egress probe row, inactive unless tor.auto_heal is on. NOT done, and #1856 stays open for it: the on-box local_miner internal edge (row 1's second half) needs a new node id, which touches TOPOLOGY_NODES, the frontend POS map and two canonical-id lists, one of them in test_egress.py at its ceiling. Rows 4/5/15 are #1849's and row 2b is #1853's, per the issue. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZuzdtxNX8ae4rnDtBqEqR
Missed in the previous commit: it was staged before this edit, so the tree shipped a comment naming _notify_knobs, which this branch renamed to _shared_knobs. Comment only, line-neutral, test_egress.py stays 451/451. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZuzdtxNX8ae4rnDtBqEqR
…e-pr1940 # Conflicts: # tests/os/provision-browser-submit.sh # tests/os/reinstall-prefill-submit-leg.sh
…e-pr1944 # Conflicts: # tests/stack/run.sh
VijitSingh97
marked this pull request as ready for review
September 7, 2026 05:37
Collaborator
Author
|
Independent integration review: PASS at Sol/high security and Luna/high verification confirmed the five exact source heads, required onion dependency order, intended exclusions, and the two integration-only adjustments. Source reviews carry their runtime limits. Focused results: 17 frontend, 53 Python, 31 fake contracts, 37 Quadlet, 14 Tari-entrypoint and 34 harness assertions passed; relevant lint and generated CLI parity passed. Full required PR CI and the combined runtime/OS battery remain separate gates. Original PRs stay open until this replacement merges. |
This was referenced Sep 7, 2026
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.
Closes #1936.
Closes #1896.
Addresses #1853 now that the header and appliance quadlet plumbing land together. The merge seat
will close that issue by hand after the combined change lands, matching the original PR's intent.
Addresses the container half of #1903. Its host half, #1905, is already on
develop; the merge seatwill close the issue by hand after the combined change lands, matching the original PR's intent.
Result
This integrates five already reviewed RC2 fixes on the current
developbase so CI judges theirrelease-shaped interaction once:
receiving client keys;
tari.mode: offremoves Tari merge-mining arguments before bridge setup;inventory, and the opt-in Tor health probe appears in the egress model.
The #1880 documentation and code comments now describe both deployment paths because #1942 closes
the appliance gap in this same tree. The merge itself required no conflict resolution. Current
develophad grown the stack runner by one line since #1944 was reviewed, so one separator blankline was removed to keep its 440-line ceiling; source order and behavior are unchanged.
#1856 remains open for its wider inventory and local-miner work. The remote configured-node route
classification is unchanged: direct/private paths do not become Tor routes. The existing Tor probe
remains opt-in and SOCKS-only, with no clearnet fallback.
Preserved source heads and review evidence
98ae32a1705fe5c312026f0810d7d77c3c2cb5d2— non-author PASS, with bench evidence transferred to the exact head.6e41f28e0514743a4ca399eeaed52d9cf6290ba7— non-author delta PASS.e07077eec3a88bcc30c6ff406767899ec5e43052— Sol/high and Luna/high integration PASS. This head preserves the already merged test(os): #1932 the browser-shaped submit polls the state read and names what it saw #1934/KVM provision phase: the browser-shaped submit (#1847) gets no served config from the wizard, and the harness cannot say why #1932 stack ancestry.852cf5c5fc1087d996051b3e9a4e7e17a08d83e3— Sol/high and Luna/high integration PASS.d40e43a079a8d452fdc3a11fa8f5996d7c8cf40c— local Sol/high security and Luna/high verifier PASS after replacing the misleading unconditional LAN/exposure claim with neutral client wording. The original remote fix(dashboard): #1856 the two LAN ingress hops stop drawing as "this machine" #1953 head remains unchanged and will be superseded only after this integration merges.All five source heads are ancestors of this branch through normal merge commits. PRs #1939, #1952,
and #1947 are not included.
Focused checks on the combined tree
operator-string, topology-class, file-budget, and generated
pitheadparity checks passed.No full suite, container build, KVM run, hardware run, key access, release action, or deployment was
performed on this integration branch. The source reviews retain their stated runtime limits; remote
CI is the combined candidate gate.
Merge
This is a draft for the non-author merge seat. Do not merge until the exact-head combined review is
recorded and required CI is green.