chore: sync develop into develop-v2 (supersedes #1031) - #1039
Merged
Conversation
The fixture drift guard compared only top-level build_state() keys, so nested payload growth (a new field under an existing section) left tests/frontend/fixtures/state.json stale and every component gated on the new field rendering its empty state across the frontend suite and the visual harness. The guard now reruns _gen_state.py (deterministic) as a subprocess to a temp file and diffs sorted dotted key paths — the Python mirror of CONFIG_KEY_PATHS_JQ in tests/integration/lib.sh — in both directions. Shape only, never values, so payload value tweaks do not churn the test. Subprocess, not import: the generator patches time.time process-wide. _gen_state.py takes an optional output-path argv for this. Closes #974. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
After the restore brings the baseline back up, the run no longer trusts "healthy": a stack recreated with harness-rendered creds is internally consistent, mines fine, and 401s every host-side RPC probe — the 2026-08 incident. Two checks now bind in the EXIT-trap epilogue: - env bake: the MONERO_NODE_PASSWORD line baked into the running dashboard container (docker inspect) must equal the on-disk .env line in RESTORE_DIR. The compare is env_bake_verdict in lib.sh — whole KEY=VALUE lines in, a verdict word out, values never printed — pinned by selftest.sh fixtures (match/mismatch/no-disk-value/not-baked). - host RPC: monerod must answer get_info with the on-disk creds (the exact probe the incident broke). The probe script travels over ssh stdin (bash -s) so creds stay on the box and the remote command string stays paren-free; only .status==OK is required, polled 60s. A failed proof — including a failed baseline apply/up, which previously could exit 0 on a passing run — warns RESTORE NOT PROVEN, names the docker-compose-up-from-disk recovery, and exits 1 from the trap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
build_xvb_calc no longer collapses to {"enabled": false} when XvB is off:
the tier table, draw odds, and study-prior band are computable from local
config plus the cached public feeds, and the table is the enable/don't-
enable decision aid — hiding it behind the flag defeated its purpose
(#938). The flag still stops every xmrvsbeast.com fetch (the #726 egress
rule), so a never-enabled box honestly degrades to tier costs only and a
just-disabled box ages out through the existing staleness rules; the
live-credit context goes quiet on its own because build_state computes
realization only while enabled and Metrics reports "Disabled" tiers.
Client: computeXvbTier and xvbDecisionRows drop their enabled gates (the
what-if runs from local hashrate), XvbTierBlock renders when disabled
minus the Current/Target live-credit cards plus an off-explainer line,
and the earnings card keeps the XvB tab whenever the payload carries
tiers — enabled:false still keys every live-donation surface (stats
card, header split, Overview tiles, hero KPIs) off, unchanged.
Closes #938
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
testing-strategy.md's "What runs where" table promised a per-PR test-inventory drift check, but #414 deleted the drift machinery along with the committed file — tests/inventory.sh only generated, and always exited 0. The drift that can still exist is the generator drifting from the codebase: the gathering is grep-based, so a suite that moves or changes shape enumerates as zero without erroring. Make that failure real: after gathering, exit 1 if any suite count is zero (with :-0 defaulting, since grep -c on a deleted file emits nothing rather than 0). Run the script in the existing `shell` CI job, output discarded — the generated file stays git-ignored per #414. Reword the doc's table row and "test code is real code" bullet to claim exactly this check and no more. Not touched here: the diff-cover vacuous pass on shell-only diffs and the push-trigger branch list, both named in #981 as adjacent gaps. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ct, mixed time registers Expected-vs-actual pct: build_earnings_vs_actual withholds pct past 999% instead of publishing the raw ratio — a box idle for most of the window divides normal confirmed payouts by a near-zero expectation and renders five digits that read as a bug. pct=None with available+enabled both true happens only on this path, so ExpectedVsActualCard appends the explanation to the row tooltip without a new payload flag. Tari per-block reward: computeEarnings gated tariRewardPerBlock on tari_available (and on a positive what-if hashrate via the early return), while TariCard prints metrics.tari_reward unconditionally on the same page. The reward is a chain fact, so it now passes through whenever p2pool reported it; time-to-block and the per-day averages keep their channel/hashrate gates. The fiat '≈ per Block' follows since it derives from the same field. Last-block register: Global P2Pool Stats showed format_time_abs — a bare HH:MM:SS with no date or timezone cue — two cards from the cadence card's relative duration. last_blk (its only consumer) now renders '<duration> ago' from the same format_duration, 'Never' before the pool's first block; the cadence tooltip keeps the absolute anchor. Closes #992. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…#904) xmrig-proxy, dashboard, caddy, docker-proxy and docker-control reported Up with no health signal, so a dead-inside service (the v1.8.1 incident: the dashboard serving 502s behind an Up container) was invisible to `pithead status` and the container-health alert (#337). Each probe uses only tooling its image already ships and reflects readiness, not process-up: - xmrig-proxy: in-image script TCP-connects to the HTTP API port (bash /dev/tcp, port from env per the #90 no-creds-in-compose rule). The API is the honest probe: a failed stratum bind kills the process outright, while the API — what the dashboard polls — can die quietly. - dashboard: in-image script HEADs /api/state on the fixed 127.0.0.1:8000 bind with the venv's python3/urllib (slim image has no curl/wget); a 200 proves the server answers where Caddy proxies AND state assembly works. - caddy: busybox wget against the admin API's default localhost:2019 — 200 from /config/ means running with a loaded config. Not a vhost probe: scheme/port/auth vary with config and busybox wget can't speak the internal-CA TLS. - both socket proxies: busybox wget GET /_ping THROUGH HAProxy to the Docker daemon — proves the proxy brokers the socket. /_ping is in the image's default-allow set (PING=1), independent of the POST gating, so the rulesets stay untouched. No flapping or gating changes: none of the five has dependents, `pithead up` and the one-click upgrade never wait on health (`compose up -d`, no --wait), `pithead status` counts "starting" as pending rather than a problem, and the #337 alert already debounces 120s of continuous unhealthy. The dashboard's start_period covers app start plus a SQLite auto-heal rebuild so a warmup blip never false-fails a one-click upgrade's status view (the #622 lesson). All five probes verified against the real pinned images (healthy under cap_drop ALL + read_only + no-new-privileges; closed port / stopped app fails). Tier-1 coverage: tests/stack/test_compose.sh now asserts every rendered service carries a healthcheck, pins the five new probe commands, and checks no probe interpolates the proxy auth token. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#972) A tor container restart/recreate kills every SOCKS connection, and monerod keeps the dead peer sockets: bench-observed at 0 in / 0 out peers for ~6h, get_info synchronized:false, height creeping, every healthcheck green. A plain monerod restart re-dials through the fresh tor and recovers in under a minute — so couple the restart to everything that cycles tor, and alert on the strand for the paths that can't be coupled. Recovery (root cause — tor cycling strands monerod): - compose: monerod's tor dependency gains restart: true, so every compose operation that restarts/recreates tor (up/apply/upgrade recreation, 'pithead restart tor') restarts monerod right after tor is healthy again (verified against compose v2.40: both the restart and the recreate path propagate, health-gated). Guarded by new test_compose.sh invariants. - tor_heal (#424 auto-heal) bypasses compose, so a successful tor heal now cycles monerod itself when the node is local (stop timeout 60s matches monerod's stop_grace_period; HTTP timeout outlasts it per #234). A failed monerod cycle warns and never refunds the tor budget slot. - new 'pithead restart monerod' leg: the manual re-dial for tor restarts that happened outside the stack (docker daemon restart, manual restart). Detection (for whatever still slips through): - MoneroClient.get_sync_status passes the raw get_info `synchronized` flag through — required because a stranded node can report a stale target_height of 0, which the height math reads as "synced". Only the RPC path carries the key; log-scrape/remote paths stay verdict-free. - data_service debounces it with the existing NodeHealthMonitor (down_after=NODE_STALE_AFTER_SEC, 600s env-tunable): ever_up doubles as "ever synchronized", so initial sync never alarms. Surfaced as monero_sync.stale in the snapshot and fed to the alerter. - alert_service emits the out-of-sync/back-in-sync edges through the existing node_down/node_recovered toggles (no new config surface, no reference.json change) and tallies the incident for the daily digest. - doctor: new Monero sync check probes get_info from the host with the .env digest creds (monerod image has no curl) and WARNs on synchronized:false, naming 'restart monerod' as the fix. WARN not FAIL: initial sync reads identically. Tier 1 covers all decision logic (pytest client/data/alert/tor_heal, tests/stack doctor + restart + compose invariants), tier 2 the flag over the wire; the real strand + re-peer remains bench-only (tier 4). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…blast radius, unify the node_down story The docker-control comments documented the start/stop surface as p2pool/xmrig-proxy + tor while tor_heal now also cycles monerod after a heal; both comment sites name it. telegram.md's overview table still described node_down as unreachable-only while its event-key table covered the out-of-sync strand — the two now tell the same story. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r-loss fix: restart monerod whenever tor restarts, and notice when it strands (#972)
…althchecks feat(compose): healthchecks for the five services that had none (#904)
…ble-when-disabled fix(dashboard): keep the XvB decision table visible while XvB is off (#938)
…e-proof test(e2e): prove the restored stack runs the on-disk config (#971)
…ift-nested test(dashboard): fixture drift guard pins every depth, not just top-level keys (#974)
…drift-ci test(ci): the inventory drift check now exists — and inventory.sh can actually fail (#981)
…ities fix(dashboard): three display honesty fixes — pct cap, known reward shown, one time register (#992)
dashboard.js ran its side effects at import, so it was the only frontend module node --test could not even load. Wrap the whole client in an exported initDashboard() whose parameters name the browser seams (DOM, storage, fetch, history, timer, render) with the real facilities as defaults, and guard the boot call on `document` — the browser runs exactly the same statements in the same order; under Node the import is side-effect-free. windowFromUrl/loadSeries take their inputs as arguments and are exported alongside REFRESH_MS/FETCH_TIMEOUT_MS. dashboard.test.mjs (22 tests) drives the real loop through fakes and observes it via the props handed to renderApp: the pre-tick abort invariant and the hung-poll/inflight recovery (#382), disconnected state keeping the last snapshot, boot sequencing and preference seeding, zoom/range/avg query building, and every App handler's state transition + persistence + fetch/no-fetch contract. Render output stays components.test.mjs's job; normalizer semantics stay logic.test.mjs's. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… an overlap check (#1000) diff-cover exits 0 with 'No lines with coverage information' whether the diff is shell/docs-only (fine) or changed dashboard Python a stale coverage.xml never measured (a green gate that proved nothing). make test-patch-coverage now runs scripts/patch-coverage.sh: diff-cover as before, then a file-level overlap check on its green paths. A diff with nothing under build/dashboard/mining_dashboard/ passes loudly and says the gate is not applicable; a changed measured file absent from coverage.xml fails, naming the file. Comment-only changes to measured files still pass — the file is present in the XML, so diff-cover's no-measurable-lines verdict stands. Overlap logic is fixture-tested via --self-test, driven from the stack suite like the operator-strings guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…js-tests test(frontend): the client entry point gets its tier — 22 tests via the issue's own seam refactor (#903)
…-vacuous test(ci): the patch-coverage gate can no longer pass vacuously (#1000)
…ary (#1001) RigForge writes noop (already on target) and throttled as first-class terminal statuses since rigforge#320 (v1.12.0), and control-apply can end terminal failed — but the upgrade poll matched only applied|rolled_back| failed and the apply poll only applied|rejected|rolled_back. A noop or throttled upgrade burned the full 90s poll cap and recorded "accepted — upgrade still running"; a failed apply burned the 20s deadline and recorded "accepted". - Upgrade poll: noop and throttled are terminals now, recorded with the rig's own reason (the frontend already renders both statuses). - Apply poll: failed is a terminal now (the rig could not restore its rollback backup), recorded with its reason and audited as failed. - The legacy failed+"throttled…" remap stays for pre-v1.12.0 rigs (v1.11.2 is the supported floor) but is anchored to the leading word: a modern rig's genuine failed can mention the throttle too ("throttle state unavailable", rigforge#321's fail-closed refusal) and must stay a fault. Drop the remap when the fleet floor reaches v1.12.0 — the v2 appliance bakes v1.15.0. Stack suite gains 7 assertions across both polls, fail-first-proven: exactly those 7 fail against the old poll cases (1704+7=1711 green). Dashboard doc outcome lists updated to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ll-vocab fix(control): the worker pollers speak RigForge's full terminal vocabulary (#1001)
The Tari chain on the live reference deployment measured 149 GiB on 2026-08-15 (du -sh, gouda). The repo's own figure history makes the growth rate linear: ~135 GB (2026-06-12, #232) -> ~139-140 GB (2026-07-02, #334) -> 149 GiB, i.e. ~6.6-6.8 GiB/month over two independent intervals. The 170 GiB budget's remaining headroom is ~21 GiB — about three months. 200 GiB restores ~51 GiB of headroom (~8 months at the observed rate). The summed minimums move with it: ~330 GB pruned / ~530 GB full across setup preflight, doctor, and every doc that cites them. Touched doc lines adopt the develop-v2 reconciled measured figures (#1005: Monero ~270 GB full / Tari ~150 GB) so both branches read identically and the develop -> develop-v2 sync merges clean. Mirrors PR #1011 (develop-v2). Upstream's integration guide still advises "50GB+ SSD" for a base node — a third of the live archival chain — so live measurement stays the budget's source of truth. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…abulary (#1009) parse_worker_control_status filtered rigforge.control's mirrored outcome to {applied, rejected, rolled_back} — a subset that predates rigforge#320's noop/throttled terminals and #1001's own failed addition to the apply poll. RigForge v1.15.0 now ships the #346 control mirror in its enriched feed for the first time, so this stale filter is live: a mirrored failed outcome drops silently, leaving a #185 change-history row "accepted" forever in exactly the slow-terminal case the mirror exists to close. - xmrig_client._CONTROL_TERMINAL: applied/rejected/rolled_back/failed/ noop/throttled — the same six statuses pithead's own control_worker_apply/ control_worker_upgrade poll cases accept (#1001). started (rigforge#320's in-flight upgrade marker) stays non-terminal alongside this dashboard's own accepted/running placeholders. - storage_service.reconcile_worker_config_status: the same six-status allowlist (module constant _RECONCILE_TERMINAL, mirroring the client's _CONTROL_TERMINAL) gates the accepted-row UPDATE. status is recorded verbatim — the frontend's STATUS_META (workerview.mjs) already renders every member of this vocabulary, added for the #597 upgrade badge. - docs/workers.md: the reconcile vocabulary list now enumerates all six outcomes, and the stale "RigForge does not ship this mirror yet" note is replaced with the v1.15.0 floor. Dashboard suite gains 3 assertions, fail-first-proven against the old allowlist (1703+3=1706 green). Patch coverage 100% on the 3 changed lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two tuples are declared separately on purpose (parser with the client, allowlist with the store, neither importing the other for six strings) — so nothing stopped them diverging. A status the parser accepts but the store does not act on silently stops reconciling and freezes history rows at accepted: precisely the bug the pair was just widened to fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dget-develop fix: raise the Tari disk budget to 200 GiB
…ncile-vocab fix(dashboard): the enriched-feed reconciler speaks RigForge's full terminal vocabulary (#1009)
…#942, #1002) #942 adds five rows to the live e2e matrix, each following the existing axis/scenario idioms in scenarios.sh + assert_running_state: - tari.mode=remote (remote-tari-main-secure): the bundled tari container/onion absent, the sync gate proven against a remote target — needs --remote-tari-host, mirrors monero's own remote-mode handling. - p2pool.stratum_tls=true (local-pruned-main-stratum-tls): a live TLS handshake on the published stratum port, and the served cert's fingerprint matches the one operators are told to pin. - network.tor_egress_firewall=false (local-pruned-main-firewall-off): no pithead-tagged rule installed AND a real clearnet dial from a mining_net container succeeds — the mirror of the fail-closed default assert_egress_posture/fault_firewall_rollback already prove elsewhere. - payout confirmation (local-pruned-main-payout-confirm): a "payout_confirm=env" marker resolves to a real monero.view_key (+ optional tari pair) from IT_MONERO_VIEW_KEY/IT_TARI_VIEW_KEY/ IT_TARI_SPEND_PUBLIC_KEY; asserts PAYOUT_CONFIRM_ENABLED and the dashboard's own earnings.confirmed.enabled read true (a real confirmed payout needs days no e2e run has). - dashboard.secure=false + p2pool.pool=main (local-pruned-main-insecure): decouples insecure mode from its previously nano-only pairing. expected_services/absent_services now gate tari on tari.mode (was hardcoded always-on) and wallet-rpc/tari-wallet on view_key presence. #1002 adds two legs to run_rigforge_control: (a) --rigforge-upgrade: POSTs the rig's own already-installed version through /api/control/worker-upgrade and asserts noop — exercises the dashboard->host-runner->rig /upgrade chain (or its client-side shortcut, depending on the dashboard's poll-cache freshness) without ever rebuilding the rig, including the noop/throttled/failed vocabulary #1001 taught control_worker_upgrade's poll loop. The host independently re-derives "latest" and refuses any mismatch before dialing, so this can only ever land on noop or a safe rejected. (b) a second writable-key edit, pools, beside the existing max_temp_c one. The enriched feed exposes no live readback for writable config values, so the restore target is the dashboard's own last_applied.pools record — the same source Worker Inspect's real editor prefills from — and the probe value is operator-supplied (IT_RIG_POOLS_PROBE) since pithead treats pools as opaque passthrough. selftest.sh: 165 -> 201 assertions, all passing (36 new, pinning the pure resolve_overrides/expected_services logic + scenario existence). The live-only proofs (TLS handshake, firewall dial, payout wiring, pools/upgrade legs) have no unit tier, same as every other --rigforge-control leg in this file — they need bench validation. docs/dev/integration-testing.md and docs/dev/testing-strategy.md updated for the new axes/legs/flags; the "insecure + main matrix row" known gap is marked resolved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ing upgrade history (#1013-#1015) Three issues sharing one data plumbing seam (per-rig hashrate history + per-rig change records), implemented together on one branch. fix (#1014): a one-click rig upgrade left no per-rig record. handle_worker_upgrade returns 202 immediately (a rebuild can take minutes) and never awaited a result the way handle_worker_apply does, so nothing ever called _record_worker_result for it. Now a tracked background task (server.py's _finalize_worker_upgrade, referenced in app["_bg_tasks"] so it can't be GC'd mid-flight, cancelled on app shutdown) waits for the host runner's terminal result and records it — one row per upgrade attempt, worker + target version + terminal status + reason — independent of whether the operator's tab stays open to see it land. worker_config gains a `type` column ('apply' | 'upgrade', migrated forward for existing installs) so get_last_applied_worker_config can exclude an upgrade's {"version": ...} marker from the config-editor prefill, while get_worker_hashrate_by_config's existing status=='applied' boundary logic picks up an applied upgrade as a version boundary for free — hashrate no longer misattributes a build change to whatever config happened to be active. The recordable-status set widens to include the upgrade-only noop/throttled terminals. The change-history table's "Changed" column now reads "upgrade → vX.Y.Z" for an upgrade row instead of the literal key name "version". feat (#1013): Worker Inspect gets the fleet chart's range-selectable hashrate view, sized to what a single rig's data can honestly support. /api/worker takes range=/from=/to= (mirroring /api/state) and feeds get_worker_history through the existing _downsample_gauge_rows via _gauge_series — no new table, no new collector. The client reuses chart.mjs's palette/gradient idioms in a new WorkerChartCard, not a second charting approach: 24 Hr/1 Wk/All only (worker_history samples ~5 min, so "1 Hr" would be dishonest; retention is 30 days, so "All" already means what "1 Mo" would), no avg-window toggle (only h15 is stored). A rig with no samples yet renders an explicit empty-state message instead of an empty axis. Read access rides the existing DASHBOARD_CONTROL_ENABLED gate unchanged — deliberately deferred, see below. feat (#1015): config applies and rig upgrades mark the chart, a fourth instance of the hidden-0-1-axis scatter pattern Events/Raffle/Payouts already use. One marker dataset, per-point pointStyle (diamond for a config apply, triangle for an upgrade) and colour (accent for an applied outcome, muted for rejected/rolled_back/failed/throttled/noop/accepted — shown, not dropped, so "we tried and it bounced" still explains a flat stretch). Markers are the same range/window slice of worker_config as the hashrate line, so nothing renders outside the chart's own visible window. Deliberately deferred, with reasons: - Read-gating: #1013 raises whether viewing a rig's own telemetry should require the control channel. Left riding the existing gate — widening a read boundary is a separate access-model decision, not a rider on a charting change. - Accepted/rejected share-delta series on the worker chart: #1013 marks this optional ("out of scope unless it's cheap"); no per-poll differencing path exists for worker-level counters today, so it isn't. - control_audit's `keys` sanitizer (naming the rig in a worker-upgrade audit row): #1014 itself flags this as "changed carefully or not at all" — a JSONL-injection chokepoint in the host bash script, out of scope for this Python/JS change. - RigForge's own /status mirror for upgrades (the "second bug" #1014 flags as unverified): the worker_config row this change now writes would close that gap by construction if RigForge mirrors upgrades the same way it mirrors applies, but RigForge's source isn't in this repo to confirm the premise. make lint: clean (sh/py/js/yaml/md/docs-voice/operator-strings/proto/toml). make test-dashboard: 1717 passed, 97.01% coverage (gate 80%). make test-frontend: 317 passed, 0 failed. make test-patch-coverage: 100% on all 4 changed Python files (gate 90%). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… key (#1023) The runner's pre-download guard tested the LOCAL cosign.pub, so it was blind to the one upgrade that needs it: an install cut before signing engaged moving to a signed release. Prod hit this on v1.18.1 — the guard passed, the bundle downloaded and extracted, and the abort landed inside the new CLI's image gate after a full config re-render, leaving the operator to finish from a shell. Every release bundle ships cosign.pub (make_bundle copies the committed key unconditionally), and control_upgrade already refuses source checkouts up front, so cosign is a flat precondition of a one-click upgrade. Check it beside the source-checkout refusal: before the throttle claim and before the GitHub dial, so a refusal costs nothing and the operator can install cosign and retry at once. Tier 2 covers both halves of the widened condition — key-less and key-holding — each asserting the refusal lands with nothing dialled, nothing extracted and no throttle claimed. The $UPG fixture gains a cosign stub so the paths that are meant to proceed no longer depend on whether the host happens to have one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…grade-precheck fix(upgrade): require cosign before the one-click download, key or no key (#1023)
The docs were written when only monero.mode had a remote option and never caught up with tari.mode: remote (#103, v1.14.0). hardware.md never mentioned tari.mode at all, and stated flatly that "remote-node mode still runs Tari" — true of the Monero half only. Tari is the largest line item in the budget the code actually enforces (disk_component_gib: 200 GiB, against 120 pruned / 320 full for Monero), so the one mode that helps a small host most was the one the sizing page omitted. hardware.md gains a "Running a node elsewhere" section with the per-mode disk matrix derived from disk_component_gib, plus what a remote node changes for RAM, initial sync, egress and onions. The rest of the doc set gets the same qualifier where it gave sizing or "what runs here" advice. Corrections found while checking every claim against the code: - hardware.md recommended a 240 GB SSD for a config whose own budget is ~330 GB — setup would warn on the machine the docs suggest. - The setup/doctor pre-flight note claimed both checks scale with config. The disk budget does; the 16 GB RAM warning is unconditional. doctor's memory check is a different check (HugePages + MemAvailable), not a repeat. - P2Pool's "--light-mode saves ~2 GB" describes a knob the stack never passes. - On an 8 GB host, --skip-optimize also redefines Tari's auto ceiling to ~75% of RAM (6 GB of 8) — the escape hatch quietly hands the box to Tari. - SECURITY.md still said cosign.pub was uncommitted and every release unsigned; it is committed and ships in the bundle. - SECURITY.md's "Tor for all node networking" ignored both remote modes and the clearnet initial sync. - architecture.md claimed every service runs non-root; caddy and the two Docker socket proxies run as uid 0 (pinned by the integration suite). - privacy.md's "only LAN-facing port is stratum" omitted the host-networked Caddy on :443, and the Tari DNS resolvers are real public DoT servers that are never queried, not "a dead address". - The remote-node example used a public hostname, but the Tor-egress firewall drops everything outside RFC1918 + CGNAT — a public node cannot work by default. Those rules live in DOCKER-USER, so they cover the bridged containers only; the dashboard's own gRPC poll of a remote Tari node is not behind them. - The control runner dispatches seven actions, not the documented three. - operations.md's command table omitted three commands `help` prints. - configuration.md claimed ~113 keys; config.reference.json has 92 leaves across 13 sections under the doc's own exclusion rule. - Data dirs are chowned to a fixed uid 1000, not "your user". - workers.md still showed the dashboard.workers[] key removed in v1.9. Verified with make lint (full suite) and an internal link/anchor check.
The adversarial verify stage confirmed a further batch after the first commit landed. Each re-derived from source: - configuration.md documented the control channel's password refusal but not its second one: `dashboard.control.enabled: true` on a published onion with `client_auth: false` is refused outright — and the client_auth row offers exactly that setting as a supported choice, so the operator meets a hard apply failure the docs never predicted. - SECURITY.md told reporters to run an up-to-date *checkout*; the documented install is a release bundle. Now `./pithead version` / `./pithead upgrade`. - dashboard.md's badge table omitted the two disk badges the same code path emits (`Disk N% full` at 85%, `⚠ Disk N% full` at 95%) — the one warning a full disk gives before it corrupts monerod's DB mid-write. - dashboard.md's top-bar list claimed total hashrate; the header renders the last-update time, and total hashrate is documented one section down. - privacy.md claimed to list every off-box connection but had no row for the XvB warm-standby pull. Added with its real routing rule: Tor unless the source is a provably-private IP literal. - README's doc index still said "remote Monero nodes" only. Verified with make lint (full suite).
Third pass, clearing every confirmed finding that the first two left open. Each re-derived from source: - SECURITY.md repeated architecture.md's since-corrected claim that every service runs non-root. Caddy and the two Docker socket proxies run as uid 0; the integration suite pins exactly that. - configuration.md called the core group "the ones setup asks about", but `workers.list` is in config.core-keys.json and the wizard never prompts for it. - getting-started.md described setup as two stages; it asks a third thing afterwards — the dashboard hostname. - dashboard.md said the Configuration button "then appears" once the channel is enabled; it renders unconditionally and explains itself when off. - dashboard.md sent readers to Worker Inspect to edit `workers.list[]`. Worker Inspect writes only the rig's own keys (WORKER_WRITABLE_KEYS), and the host gate refuses `workers.list` in either edit mode because it carries each rig's host and token. That edit is host-CLI only. - dashboard.md documented the security card's hour/day/month grouping; that became a page-size control (5/10/20/50/100) with range presets and search doing the time navigation. - telegram.md's alert table omitted the high-reject-rate and payout found/confirmed alerts, both of which fire and both of which the per-event toggle table further down already lists. Verified with make lint (full suite) and an internal link/anchor check.
Two more instances of the claim already corrected in dashboard.md: the hour/day/month grouping was replaced by range presets, date fields and search, with paging doing what grouping used to. SECURITY.md and operations.md both still described the old control. Verified with make lint (full suite).
A sweep for each error class already fixed turned up more instances of the same claims elsewhere: - faq.md asserted onion addresses for Monero, Tari and P2Pool in two more places; only P2Pool's is unconditional, the node onions exist while that node runs here. - architecture.md's opening still said all node P2P and tx traffic routes over Tor, which a remote node's direct leg contradicts. - README's Tor bullet named the clearnet initial sync as the one exception; a remote node is a second. - README's doc index omitted the nine-on-a-default-install caveat that docs/README.md already carries. - configuration.md's stratum_bind row still treated 3333 as fixed rather than p2pool.stratum_port's default. CHANGELOG's hour/day/month mention is left alone on purpose: it records what shipped at the time. Verified with make lint (full suite).
Matches the v2 branch: architecture.md's opt-in wallet rows now carry `wallet-rpc` and `tari-wallet`, so the service list greps cleanly against docker-compose.yml and against operations.md's logs list. Verified with make lint (full suite).
…-node-docs-e62736 docs: correct the remote-node story across the doc set, hardware first
11 conflicted files, mostly the same doc pass (PR #1034 on develop, #1035 on develop-v2) landing twice with small wording drift. Every resolution below was checked against the code, not against which branch it came from. - Makefile (test-patch-coverage): took develop's `bash scripts/patch-coverage.sh` — closes the vacuous pass where diff-cover exits 0 on "No lines with coverage information" (#1000). scripts/patch-coverage.sh exists in the merged tree. Reproduces the known-good resolution from the earlier attempt at this merge. - build/dashboard/mining_dashboard/web/server.py: UNION. Kept v2's handle_control_backup/handle_backup_download (#908) and develop's _RECORDABLE_WORKER_STATUSES + change_type param on _record_worker_result (#1014), dropping v2's now-superseded 4-arg _record_worker_result definition. Verified: no duplicate defs, change_type defaults to "apply" so the 4-arg worker-apply caller at server.py:353 still works, and the 5-arg change_type="upgrade" caller at server.py:373 (already auto-merged in from develop) resolves correctly. bash -n / ast.parse both clean. - README.md / docs/configuration.md ("14 sections" vs "13 sections"): kept v2's "14". config.reference.json (unchanged by this merge, so it stays v2's copy) has 14 top-level sections once `_docs` and the deprecated `xmrig_proxy` alias are excluded — counted directly against the file, and it includes `ssh` which only exists on v2's schema. - SECURITY.md (upgrade guidance): kept v2's version — it names both `./pithead upgrade` (Compose) and `./pithead os-update` (appliance); grepped pithead and confirmed `os-update` is a real subcommand (pithead:2799, PITHEAD_COMMANDS list at pithead:8417). develop's text only mentioned `upgrade`, which is incomplete on this branch. - docker-compose.yml (caddy service): UNION. v2's `PITHEAD_TLS_DIR` volume mount and develop's admin-API healthcheck are unrelated additions to the same service block; kept both, validated with `yaml.safe_load`. - docs/dashboard.md and docs/faq.md (Tari-outage rejection behavior): kept v2's claim that a Tari outage never rejects workers regardless of `dashboard.tari_required`. Verified against build/dashboard/mining_dashboard/config/config.py:471-473 and service/data_service.py:727-754 — `_apply_worker_rejection` takes only `monero_down`, and readmission reads `self.monero_health.healthy` alone; `tari_down` (data_service.py:1517) is computed but never passed in. develop's text claimed a remote Tari node's reachability gates rejection via `tari_required`, which the code does not do. - docs/dev/testing-strategy.md (tier-1 table, section C): UNION. Kept develop's new #972 "Monero reachable but out of sync" row (genuinely new content — the `monero_sync_stale` debounce it describes exists in the merged data_service.py) and v2's "double outage; readmit follows monerod alone" row over develop's competing "readmit only when both healthy" row, since the latter contradicts the _apply_worker_rejection reading above. - docs/operations.md (4 hunks): (1) doctor row — kept develop's fuller text adding the `check_monerod_synchronized` check (pithead:4118, called from doctor at pithead:1274) that v2's row omitted. (2)+(3) UNION — kept v2's render/support-bundle/config-reset/uninstall rows and its whole "Appliance-only commands" section; develop's side was simply empty for both (genuinely new content, not a competing claim) — confirmed each command exists in PITHEAD_COMMANDS and its case arm. (4) remote-node doctor behavior — kept develop's fuller sentence (a superset of v2's, adding that the Monero sync check skips with no local monerod, consistent with check_monerod_synchronized's `container_is_running monerod || return 0` early-out). - pithead (control_upgrade): UNION. Kept v2's `is_appliance` refusal (an appliance updates via signed OS images, not tarballs) ahead of develop's #1023 cosign-precondition guard — both are real, independent preconditions for the one-click upgrade path, and ordering the appliance check first means an appliance host gets the correct refusal reason instead of a cosign complaint. tests/stack/run.sh's `is_appliance` and #1023 cosign fixtures don't set PITHEAD_APPLIANCE, so is_appliance() falls through to its /etc/rauc probe (false in the test sandbox) and the existing cosign assertions still exercise the same path. Gates: no conflict markers repo-wide; `bash -n pithead` clean; `make lint` exit 0; `bash tests/stack/run.sh` 2333 passed/0 failed; `make test-frontend` 375 passed/0 failed; `make test-dashboard` 1818 passed/0 failed, 96.79% total coverage (gate is >=80%). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6 tasks
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.
Supersedes #1031, which went stale when #1035 landed on
develop-v2. Fresh merge from the current tips.Eleven conflicts, every one decided against the code rather than by preferring a branch, with the evidence cited in the commit message. Highlights:
docs/dashboard.md/docs/faq.md— kept v2's claim that a Tari outage never rejects workers regardless ofdashboard.tari_required. Verified indata_service.py:727-754:_apply_worker_rejectiontakes onlymonero_down, and readmission checksmonero_health.healthyalone;tari_downis computed but never passed in. develop's wording was code-false.docs/dev/testing-strategy.md— UNION: develop's new monerod loses all peers when the tor container is recreated, and nothing notices #972 stale-sync row, plus v2's "readmit follows monerod alone" over develop's contradicting "readmit only when both healthy".pithead/control_upgrade— UNION, appliance refusal ordered before the One-click upgrade: the cosign pre-check is blind to the unsigned→signed upgrade, so it fails mid-install instead of refusing #1023 cosign precondition so an appliance operator gets the informative answer.Makefile— took develop'sscripts/patch-coverage.sh(diff-cover's vacuous pass: 'No lines with coverage information' exits 0 and reads as a green patch-coverage gate #1000), which closes the vacuous diff-cover pass.web/server.py— UNION of v2's backup handlers (Appliance backup export from the dashboard: encrypted archive + one-time emergency kit #908) and develop's_RECORDABLE_WORKER_STATUSES+change_type; verified no duplicate definitions and that both call sites resolve.README.md/docs/configuration.md— "14 sections" verified by countingconfig.reference.jsontop-level keys directly.Gates
make linttests/stackOne judgment call surfaced rather than smoothed over: the
is_appliance-before-cosign ordering incontrol_upgradeis reasoned, not pinned by an existing fixture.