feat(wizard): #1855 #1848 the setup page can decline merge-mining, and a new machine arrives declining it - #1919
feat(wizard): #1855 #1848 the setup page can decline merge-mining, and a new machine arrives declining it#1919VijitSingh97 wants to merge 4 commits into
Conversation
…d can leave the raffle build_config now writes tari.mode EXPLICITLY on every submit. That is the one place this function departs from its own omit-and-inherit rule, and it departs on purpose: a config with no tari.mode still parses as "local", so an omitted key would quietly merge-mine on a machine whose operator answered No. The parser default is right only for configs written before the question existed. A machine that declines Tari is asked for no payout address — the key is omitted rather than sent empty, since an empty string is a value that would override the documented default. When Tari IS on, an empty address still flows through so the HOST produces the rejection, exactly as it does for Monero. #1848: xvb.enabled is true in config.reference.json, so only the OFF answer is written. Writing True would pin a default the operator never chose to pin. Two existing tests asserted the law this overturns, both now corrected in place rather than deleted, each saying what it used to claim: - test_wizard.py's "tari.mode is local|remote only — there is no Monero-only mode" (line-neutral; that file is at its 974-line ceiling) - test_wizard_form.py's "mode" not in cfg["tari"], which stays narrowly about the remote block it was written to guard Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZuzdtxNX8ae4rnDtBqEqR
…and whether to join the raffle The host half (#1905) taught the stack `tari.mode: "off"` and the no-JS form to write it. The page an operator actually sees could not say it: the select was `remoteTari ? "remote" : "local"`, two states for what is now three. A machine holding "off" rendered as "Run the bundled node on this machine" — the wizard misreported the machine to its own operator — and one touch of that control wrote a yes back. There was no way to answer No at all, which is the finding. The question is now "Merge-mine Tari?", No first and default, and the Tari payout address moved under it. It had to move: the field carried `required`, so left in the Payout addresses section it would have blocked submit on a form that never asks the question. It keeps `required` behind a yes — the same bar the Monero address holds. tariAnswer is the migration rule, and it reads the direction the host reads it. Only the literal "off" is a decline; a missing key is `local`, because that is what 28-parse-and-validate-config.sh does with a config written before this question existed. Reading an absent key as "off" would tell an upgraded 1.x machine it had declined merge-mining and write that decline back on submit. #1848 adds the raffle switch under Mining, opt-out where Tari is opt-in, its note lifted from the FAQ rather than invented. `xvb.enabled` is true in config.reference.json, so the default answer is the state the machine is already in. Both go in a new module: wizard.mjs was 884/889 and wizard.test.mjs 765/765, so the budget decided the design, as it did for #1850 and #1853. wizard.mjs comes out 21 lines lighter. Four existing render probes asserted the old heading and the retired Tari question. Corrected in place and line-neutral (765/765 holds). The three `doesNotMatch(/Payout addresses/)` among them mattered most: left alone they would have passed over a rendered "Payout address" forever. Prose the change falsifies, swept for rather than remembered: README's "every hash merge-mines Tari", appliance.md's "Required, like the Monero one: this stack always merge-mines both coins", and the two dev recipes whose M6 says to paste both addresses into a form that now asks for one. NOT done, and not mine: `./pithead setup` (24-config-wizard.sh) still demands a Tari address and never asks the mode, so the CLI path cannot decline. Filed separately. appliance.md says a remote Tari node saves ~200 GB while the wizard says a local one adds ~170 GB; both predate this change and I did not reconcile them by guessing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZuzdtxNX8ae4rnDtBqEqR
… the decline Found by re-reading the KVM provision leg #1847 just merged, not by the tests: it posts back whatever `/api/wizard-state` served it, so I went to check what that is for a fresh box. It was `tari.mode: "local"`. The commit before this one gave the page three answers and put No first. That is not the same as No being the answer a new machine arrives holding. The state API builds the page's config as the reference merged under the last attempt, and for a machine with no attempt it fell through to a seed carrying only `local_miner.enabled`. So the reference decided, the reference says `local`, and every new operator would still have opened the wizard on "Yes — run a Tari node on this machine". The whole finding survives a correct component. The seed gains `"tari": {"mode": "off"}` and gets a name, because what it is FOR is now load-bearing: it is the page's answer for a machine that has none, and it is reached only through the `or`. The reference keeps saying `local` and must — it is the default for a config that ALREADY EXISTS, and moving it there would stop merge-mining on every upgraded install. Those two facts look contradictory and are the design; the comment says so where the constant lives. The tests go in a new sibling file for the reason its three siblings give: test_wizard.py is at its 974-line ceiling. They pin the served value rather than the component, and the pair that matters is the migration one — a last attempt with NO tari key at all is still a machine WITH answers, so it gets the reference's `local`, never the new machine's decline. The raffle needs no seed: #1848 is opt-out and the reference already says true. Asserted by moving the reference and watching the served value follow, since a value that happens to equal the reference proves nothing about its source. ⛔ THIS BRANCH MUST NOT MERGE BEFORE #1905. `28-parse-and-validate-config.sh:78-81` on `origin/develop` today is `local|remote` and errors on anything else, so a tree with this and without the host half refuses to provision ANY new machine — including #1847's browser-submit leg, which posts the served config whole. Told the appliance lane; it has recorded the order. PROVEN at this tree: 2582 passed / 0 failed, coverage 97.58% (make test-dashboard); the six wizard modules 132 passed settled from --junitxml; ruff lint+format, file-budget (wizard.py 645/674), pithead parity. A 4-mutation battery on the seed, each proven applied and restored: reverting to the old seed, answering `local`, forcing the seed over an existing attempt, and pinning the raffle each reddened the row that names them, none survived. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZuzdtxNX8ae4rnDtBqEqR
2812f16 to
c04d122
Compare
Non-author PASS — #1919 at head
|
Non-author review — #1919 at head
|
…tead of a view that cannot change it The second reviewer's RETURN, taken. Two sentences this branch introduced told the operator they could turn merge-mining on later from the dashboard's Configuration view. They cannot. Re-derived at source rather than taken on the reviewer's word — all three legs hold: - `control_service.py` builds the editable set from EDITABLE_ENV_KEY_PATHS, whose only tari entries are `dashboard.tari_required`, `tari.mem_limit`, `tari.data_dir` and `tari.clearnet_initial_sync`. There is no `tari.mode`, so the field renders greyed. - The host agrees independently: no TARI_MODE in CONTROL_DASHBOARD_EDITABLE_KEYS or the confirm map (`42-control-policy-and-host-checks.sh:61-79`), and the policy is default-deny. - #1905 adds TARI_MODE to `.env` (`33-render-env.sh:470`) and adds it to NEITHER list — I checked both rather than assuming the host half covered it. The capability is real, only the route named was wrong: a set-up-again boot re-runs the wizard seeded from the live config (`12a-setup-again.sh:26-28`), so the select opens on the stored value. Both sentences now say that, following the phrasing `docs/appliance.md:250-254` already uses for the other host-only switches. NOTHING GUARDED THIS — no test asserted either sentence, which is why a false claim about another subsystem's editability shipped. Added one, scoped to TariSection ON PURPOSE: the XvbField sibling says "Changeable later" and that is TRUE (XVB_ENABLED -> xvb.enabled), so a needle swept over the whole form would have pinned the honest row. The needle forbids the PROMISE, not the phrase — the correct copy names the Configuration view too, in order to say it does not carry this switch, so a bare /Configuration view/ needle reddens on the fix as readily as on the defect. Proven: mutation control — reverting the copy to the old sentence reddens exactly this guard, restored by sha256. Frontend suite 594 passed rc 0; lint-js, lint-docs-voice, lint-operator-strings all rc 0, the last run AFTER staging. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZuzdtxNX8ae4rnDtBqEqR
RETURN taken at
|
Non-author delta pass —
|
| tree | doesNotMatch /(turn|change) it on later from the dashboard/ |
doesNotMatch /later from the dashboard's Configuration view/ |
match /Configuration view does not carry this switch/ |
match /setting this machine up again from the boot menu/ |
|---|---|---|---|---|
c04d122e (unfixed) |
FAILS | FAILS | FAILS | FAILS |
30c8ddf8 (fixed) |
passes | passes | passes | passes |
All four red on the unfixed tree. The guard forbids the PROMISE rather than the phrase, which is the right shape here — the honest copy names the Configuration view too, so a bare /Configuration view/ needle would redden on the fix as readily as on the defect. Scoping it to TariSection is also right and I checked why: the sibling XvB row says "Changeable later" and that is TRUE (XVB_ENABLED -> xvb.enabled, control_service.py:131), so a form-wide needle would pin the wrong subject.
Stated limit: my derivation is over source text with whitespace collapsed, not over renderToString output. The rendered side is covered by Frontend logic tests (node --test) = success at this head, which is what proves the fixed text actually renders and matches.
THE MERGE-ORDER CONDITION — re-derived by me, on the arm that would break a machine
At origin/develop, 28-parse-and-validate-config.sh:31-33 reads
if [ -z "$MONERO_WALLET" ] || [ -z "$TARI_WALLET" ]; then error "Missing required wallet addresses..."
— an unconditional refusal of an empty Tari address. At pull 1905's head 14133730 the same gate reads || { [ -z "$TARI_WALLET" ] && [ "$TARI_MODE" != "off" ]; }, above a new tari.mode case that accepts local|remote|off.
So this branch merged before 1905 ships a wizard that can write a config the host validator refuses — declining merge-mining produces tari.mode: off with an empty tari.wallet_address, and provisioning then fails on the required-fields gate. Merge 1905 first. I re-derived this arm at both shas; the earlier verdict's other two arms (the mode-value case and the address decode) I am relaying, not re-deriving.
CI at 30c8ddf8
14 of the 15 required develop contexts are green; Shell tests (shellcheck + pithead suite) is still RUNNING — not failed. Total 22 / success 19 / skipped 2 / failing 0. I enumerated the required list against the success∪skipped set rather than reading a summary, and the comparison returned both answers (14 present, 1 absent), so it is not an instrument stuck on "green". I am not calling Shell tests passed — the merge wants it green first. The delta touches no shell file, which is a reason to expect it, not evidence that it happened.
Budget: wizardmining.mjs (82) and wizardmining.test.mjs (294) do not exist on origin/develop — they are new in this branch, carry no budget row, and are both under the 400-line target. docs/appliance.md 617 -> 618, no row.
Non-blocking, not conditions
docs/appliance.md:233now says it "cannot be turned on from the dashboard afterwards". True as written. A host console./pithead applyremains a route the sentence does not mention — moot for the appliance audience this file addresses, since that machine has no shell, so I am not asking for a change.- The guard is deliberately scoped to
TariSection, which is correct today; it would not catch the same false promise if that copy later moved to another section. - Surviving from the earlier verdicts, relayed:
configlogic.mjs:28's missing"off"option is cosmetic (the field is greyed and has no write-back path, which is the same fact that made the blocker a blocker);wizard_form.py:87-89writestari.clearnet_initial_sync: Trueeven when the mode isoff— inert, and cannotKeyErrorsincecfg["tari"]is always created.
Proven by me vs relayed
PROVEN BY ME at the shas named: both editable/confirm maps and both host allowlists, with the TARI_MODE absence sweep and its TARI_REQUIRED control; the 12a-setup-again.sh accept-path header; the full delta diff; the four-assertion discrimination table across both trees; the 28-parse-and-validate-config.sh required-fields gate at origin/develop and at 14133730; the check-run tally and the 15 required contexts enumerated against the success set; every line count above.
RELAYED, not re-derived: the earlier verdicts' other two validator arms; the author's mutation control on the new guard; the three non-blocking items in §3 above.
NOT RUN: no local suite, make, node --test, pytest, shellcheck, docker or KVM. The box's 1-minute load average was 31 with the RC1 battery holding the bench, so this pass is git plumbing, the GitHub API and CI results only. Nothing here measures a rendered pixel.
I am not the author of this PR.
The setup page can finally answer No to merge-mining, and a new machine arrives holding that answer. This is #1855's wizard half plus #1848, and it completes the operator's finding that a fresh box showed Tari coming up for someone who never asked for it.
⛔ This must not merge before #1905.
28-parse-and-validate-config.sh:78-81onorigin/developtoday acceptslocal|remoteand errors on anything else, so a tree carrying this branch without the host half refuses to provision any new machine — #1847's browser-submit KVM leg included, since it posts the served config whole. Nothing mechanical enforces the order. The appliance lane has recorded it (its battery tip will not carry this).What the operator sees change on the appliance
The three commits, and why the third exists
build_config): writestari.modeexplicitly on every submit, omitstari.wallet_addresson a decline, writesxvb.enabled: falseonly on the Off answer.wizardmining.mjs, becausewizard.mjswas 884/889 andwizard.test.mjs765/765 — the budget decided the design, as it did for Configuration view: hide ssh.* entirely — SSH is a dev-only feature, a user never shells into the appliance #1850 and Dashboard header: show the onion URL under the host line when the dashboard is exposed over Tor #1853.wizard.mjscomes out 21 lines lighter./api/wizard-stateserved it, and going to look at what that is. It waslocal. The state API merges the reference under the last attempt, and a machine with no attempt fell through to a seed carrying onlylocal_miner.enabled, so the reference decided and the reference sayslocal. A page whose component can render No while its served config says local shows every new operator a Yes, and the finding survives the fix intact. The seed gains"tari": {"mode": "off"}and a name.config.reference.jsonstill saystari.mode: "local"and must. That is the migration trap in #1855: the reference is the default for a config that already exists, and moving it there stops merge-mining on every upgraded install. The two facts look contradictory and are the design; the comment says so where the constant lives.Tiers
Tier 1 throughout, at the layer that can actually see each claim.
tariAnsweralone for the migration rule — only the literal"off"is a decline; a missing key islocal, because that is what the host does with a config written before the question existed.off, a machine with any prior attempt gets the reference'slocal, and a rejected submission keeps the decline the operator just made.Two mutation batteries, each mutation proven to have applied (pattern matched exactly once, file byte-compared after write and after restore), each run against a 0-red baseline. 9 mutations on the JS half and 4 on the seed; every one reddened the row that names it, none survived. They include the exact pre-fix states: the two-state coercion, and the seed without
tari.mode.Prose the change falsifies — swept for, not remembered
README.md's "every hash merge-mines Tari";docs/appliance.md's "Required, like the Monero one: this stack always merge-mines both coins", plus its Tari-node row and a new raffle row; and the two dev recipes whose M6 tells a tester to paste both addresses into a form that now asks for one. Four render probes inwizard.test.mjsasserted the old heading and the retired question — corrected in place, line-neutral (765/765 holds). The threedoesNotMatch(/Payout addresses/)among them mattered most: left alone they would have passed over a rendered "Payout address" forever.Over-engineering pass (ponytail gate)
Run over the whole three-commit diff. One finding, applied:
setupOnin the new frontend test carried anextraparameter and anObject.assignno caller ever used — dead API on a helper written minutes earlier. Held after review, with the reason each was weighed against: the new module (the budget ceiling forced it, it was not chosen);tariAnsweras its own export (four lines that carry the migration rule and are provable with no DOM); the test's vnode walker (string matching cannot reach a handler, and the battery shows two mutations that would have survived without it);_NEW_MACHINE_ANSWERSas a named constant (the name is load-bearing — the comment on it explains a design that reads as a contradiction); and the separate Python test file (three siblings already split for the same 974-line ceiling). The weakest thing kept is the eight-line "renders from props alone" probe, which earns its place only by proving neither export reaches back intoWizardApp.PROVEN at this head (
2812f163, rebased on4402eec5)make test-dashboard2582 passed / 0 failed, coverage 97.59%;make test-patch-coverage100% on 8 changed lines, with the wrapper's overlap check confirming it is not #1000's vacuous pass;node --test dashboard/tests/frontend/593 passed; the six wizard Python modules 132 passed, settled from--junitxmlrather than a count line;lint-js,lint-py,lint-md,lint-docs-voice,lint-operator-strings,lint-topology,lint-file-budget(wizard.mjs863/889,wizard.py645/674),lint-pithead-parity.ASSUMED: all CI. NOT RUN:
lint-sh, docker, KVM, a real browser.Said rather than hidden
./pithead setupstill cannot decline. The CLI wizard demands a Tari address and never asks the mode, so only the browser path can express the ruling's default.lib/pithead/is not this lane's — filed as The CLI setup wizard cannot decline Tari — no mode question, and the payout address is still mandatory #1916, not an RC1 blocker.lint-operator-stringswas false. It enumerates withgit ls-files, so it scanned a clean tree while my new module was untracked, and flagged it only after the commit. It also treats a#NNNNinside a JSDoc block as operator text although its own header exempts docstrings. Both controlled and filed as lint-operator-strings: an untracked frontend file scans as clean, and a JSDoc #NNNN is flagged as operator text #1918. I worked around the second by writing the module's comments as//rather than touching a shared gate mid-RC — a workaround, not a repair, and I say so on the issue.docs/appliance.mdsays a remote Tari node saves ~200 GB while the wizard says a local one adds ~170 GB. Both predate this branch. I used each doc's own existing number rather than guessing which is right.tari.mode: offthe bundled node stops, but p2pool is handed its merge-mine arguments until that lands. That is the host half's carve-out, unchanged by this PR.fixesPR — no hunk of this branch toucheswizard.css,wizard.py's first screen, or the password card. It rebases cleanly onorigin/developat4402eec5.Addresses #1855 and #1848 — deliberately no closing keyword: the host half carries the other half of #1855, and #1903 is still open behind it.