release: v1.15.1 - #368
Closed
VijitSingh97 wants to merge 43 commits into
Closed
Conversation
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@9c091bb...3d3c42e) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* fix(tune): runtime HugePages reservation is grow-only (#328) tune_kernel wrote the miner's computed requirement to vm.nr_hugepages absolutely, which shrinks a pool another consumer already reserved (a co-hosted pithead stack's p2pool/monerod share the same 2MB pool) down to its in-use floor — zero free pages on both sides, measured live on the pithead#797 appliance bench. The write is now availability-based: pages the miner can draw on = HugePages_Free + whatever a running miner already holds (it re-uses its pages across a restart). The pool grows by the shortfall only and never shrinks. Fresh single-purpose rigs see the same reservation as before; re-runs that find enough available write nothing. This is the runtime half of #305's co-resident keep-existing guard. The setup --dry-run plan previews the same decision from the same availability check. Closes #328 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(tune): cover the held-pages probe, fallback, and dry-run no-change branches Three lines diff-cover flagged: the real-MainPID HugetlbPages read (now exercised via a stub systemctl reporting a live pid), the no-proposed-grub 3072 fallback (now asserted grow-only too), and the dry-run plan's covered-pool wording. The probe also always prints a number now — a status file without a HugetlbPages line previously produced empty output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ead#797 R1) (#330) * feat(setup): appliance mode — baked deps, /run units, GRUB skip (pithead#797 R1) One opt-in env flag, RIGFORGE_APPLIANCE=1, for running setup on the Pithead appliance image: read-only root, volatile /etc overlay, and a boot leg that re-runs setup every boot instead of relying on persisted state. Under the flag: - dependencies are verified (command -v), never installed — a missing tool fails loudly naming the image-build gap; a prebuilt tree needs envsubst only (the R0 bench re-ran from cache with a half-broken compiler) - the GRUB leg takes the skip branch deliberately: the kernel cmdline, incl. any 1GB-hugepage reservation, is image-owned - units render into /run/systemd/system (SYSTEMD_DIR preset) and enable with systemctl enable --runtime - hugetlbfs mounts at runtime; no fstab or limits.conf appends (the unit already sets LimitMEMLOCK=infinity) - runtime tuning unchanged: modprobe msr, grow-only HugePages sysctl (#328), performance governor setup --dry-run previews the same decisions through the shared helpers, never a second copy. Every decision was proven on the pithead#797 R0 bench. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(appliance): close the two review gaps — jq install and the logrotate drop-in Adversarial review of the appliance flag found two writes that survived the R1 contract: check_prerequisites still apt/brew-installed a missing jq (appliance mode never installs — hard-fail naming the baked-image fix instead), and generate_xmrig_config still wrote /etc/logrotate.d/ xmrig onto the volatile overlay of an image that runs no logrotate (log policy belongs to the integration layer, pithead#797 R2). Tests cover both; the missing-jq case clears bash's command hash table first, since sourcing the script hashes jq's real path and command -v would ignore the emptied PATH. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The appliance full-run asserted --runtime for xmrig.service only; the
other enable sites (timers, api, control) share the same
${ENABLE_RUNTIME:+...} mechanism, but a future call site that forgets
the expansion would persist its enable into the volatile /etc overlay
and silently vanish on reboot. One guard closes the class: no
[systemctl] enable line in the appliance run's call log may lack
--runtime.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…from MSR failure (#333) (#334) * fix(doctor): detect kernel lockdown directly instead of inferring it from MSR failure (#333) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(docs): rewrap line so markdownlint doesn't parse '+ Curve Optimizer' as a list (#333) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ls don't fail (#327) (#337) The apt dependency list named linux-tools-common unconditionally, but that package is Ubuntu-only — Debian ships cpupower as linux-cpupower — and apt's install transaction is all-or-nothing, so the one unknown name failed the ENTIRE dependency install: git, build-essential and cmake never arrived either. Probe with apt-cache show (the same guard the kernel-versioned linux-tools-$(uname -r) package already had) and add whichever name the distro actually ships; when neither exists, warn and carry on — cpupower is a tuning aid, never worth losing the compiler toolchain over. Closes #327 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…y slow (#339) * feat(doctor): surface missing AES-NI / AVX2 instead of mining silently slow (#338) RandomX without AES-NI falls back to XMRig's soft-AES path, roughly 4x slower, and nothing anywhere said why — the last undelivered acceptance criterion from #1 ("unsupported hardware is surfaced rather than silently failing"). setup/apply now warn at configure time when the CPU flags lack aes (and, advisory, avx2 — that one only slows dataset init), and doctor counts a missing AES-NI as an issue. Judged only when an x86-style flags line exists in /proc/cpuinfo (same CPUINFO override util/proposed-grub.sh already uses); macOS, ARM and stubbed sandboxes read as unknown, and unknown never manufactures an issue — the #333 lockdown stance. Never aborts: a knowingly-old rig is a valid choice. Closes #338 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor: collapse one-arm case blocks to [[ ]] guards (ponytail-review) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four comments/docs still described the pre-acceptance design — "the rig re-derives latest and refuses anything else" — contradicting accepted D4: the staged body IS the target (the dashboard re-derives latest host-side, over Tor); the rig makes no version check of its own and only BOUNDS the supplied target with the monotonic anti-rollback + reachable-from-main guards. Reworded util/control-server.py (_handle_upgrade comment), systemd/rigforge-control-upgrade.service.template, the ADR 0002 Alternatives bullet, and the control_upgrade() header in rigforge.sh. Comments and docs only; no behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lity (#342) pithead#261 shipped: the stack serves TLS on the SAME :3333 (per-connection detect, mixed fleet migrates one rig at a time), generates a self-signed cert on first apply, and prints the lowercase 64-hex SHA-256 fingerprint (pithead status repeats it). Rotation is delete the proxy-tls files + re-pin. Rewrite docs/pithead-integration.md "Stratum over TLS" accordingly: drop the unshipped claim, the :3334 example port, and the "once pithead#261 fixes its port model" caveat; state that the rig side is config-file-not-prompt; point the troubleshooting row at pithead status as the canonical pin source. Ride-along audit one-liners (2026-08-14): - docs/README.md: ADR 0002 status Proposed -> Accepted (D-OPEN-1 signed off); integration row mentions the optional :8081 sister API like the root README - README.md: platform badge Ubuntu 22.04+ -> Ubuntu 22.04+ / Debian 12 (Debian shipped in v1.14.0, #327) - docs/operations.md: drop the word "simply" Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pithead's host runner caps its synchronous /status poll after a
worker-apply at 20s, but control-apply's auto-rollback can take minutes —
the change-history row froze at 'accepted' with no way to catch up short
of a new authenticated dial to the rig's control port (a token the
dashboard container never holds).
The rig now mirrors its own last outcome into the already-open read feed:
_api_control_json reads {change_id, status, reason} from the status.json
_control_status already writes 0644, and _api_rigforge_block serves it as
rigforge.control. Missing, unreadable, or malformed status.json degrades
to control: null — the mirror can never break the feed — and pithead's
parse_worker_control_status filters non-terminal records itself.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…341) XMRig's /2/summary reports "hugepages" as a [loaded, total] pages array; jq's @TSV refuses nested arrays (exit 5), so _status_api_summary's single stats-row fork died on every healthy rig — the || true swallowed the exit but the ERR trap still printed an abort, and the Hashrate/Pool/Uptime/ Shares block never rendered. Serialize the array to "loaded/total" before @TSV; scalar and absent shapes pass through unchanged. Tests: the shared curl stub now emits the real array shape (the old bool-shaped fixture is how this slipped past the suite), the #143 assertion tracks it, and a new #341 section renders status against array, scalar, and absent hugepages bodies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…iner (#348) tests/e2e/linux.sh now runs TWO passes, a fresh disposable container each: the standard deploy, then RIGFORGE_APPLIANCE=1. The appliance branch in in-container.sh reuses the shared harness setup (real apt prereqs, /etc seed, stubs, config) and asserts the mode's contracts on the container's real filesystem across a first run and an every-boot re-run: - units render into /run/systemd/system (real envsubst|tee), and nothing lands under /etc/systemd/system; - no package installs: install_dependencies reads the toolchain as baked (new cc stub feeds the command -v probe) and a logging apt-get stub proves apt is never invoked; - /etc/fstab, /etc/security/limits.conf and /etc/default/grub stay byte-identical (grepping for "memlock" would false-fail: the stock ubuntu limits.conf documents it in comments), no modules-load or logrotate drop-ins; - every systemctl enable carries --runtime, pinned non-vacuous by the xmrig enable line and a zero count of enables without it; - hugetlbfs mounted at runtime (argument-level: mount is stubbed, the real mountpoint probe reports not-mounted in a fresh container). Honesty boundary stated in the harness comment: systemctl/mount stay stubbed (no pid-1 systemd, no privileges in the container), so those contracts are proven at the argument level; the filesystem effects are real. To make argument-level assertions readable at all, the container stubs now log "[cmd] args" to $CALL_LOG (run.sh's idiom) — this also turns run 3's previously vacuous "no git clone" assert into real evidence, since the git stub never logged before. CI wiring is the existing e2e job's existing step: make test-e2e runs both passes, so local runs and CI cover appliance identically with no matrix plumbing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed (#347) An interrupted tune --live (or autotune / tune --now) persisted whatever mid-sweep candidate was in flight: _measure_live writes each candidate into tune-overrides.json and restarts the miner on it, but the EXIT trap only cleaned the temp dir and restarted a bench-stopped service — the candidate stayed saved AND running, silently costing hashrate until the next re-tune. Mechanism: tune() snapshots the pre-sweep overrides and marks the file dirty for the duration of a live sweep; the existing EXIT trap (which bash runs on INT/TERM/HUP and on errexit) now restores that snapshot FIRST — through _restore_overrides, the exact restore+apply step the --confirm revert leg already used, factored out so the abort path cannot diverge from the completion path — before any other service handling, so every later restart re-reads the restored file. The winner write clears the flag: a completed decision stands down the abort-restore. autotune gets the same protection via its own EXIT trap: the pre-sweep prefetch mode is re-merged with _autotune_set_prefetch (the sweep's own write path, shared via _autotune_settle with the clean-completion tail), so knobs pinned by an offline tune survive the abort exactly as they survive a completed run. Tests: two black-box sections kill the real process mid-iteration (the fake API flags the candidate window, then blocks; exec makes the backgrounded PID the trap-owning shell) and assert the overrides come back byte-identical (tune --live) / mode-restored with merged knobs intact (autotune), plus a post-kill service restart. All six behavior asserts fail on the unfixed script. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… of assuming it (#343) doctor and apply gave a clean bill of health while the rig mined nothing: with pools[0].url unresolvable, xmrig loops on DNS errors every ~5s, yet doctor printed "all critical checks passed" (no pool check existed) and apply reported "Applied config and restarted xmrig." with no warning. Mechanism: the miner's local API already knows the truth — /2/summary's connection.uptime is positive exactly while a stratum connection is live and 0 while it retries — so both verbs read that rather than dialing the pool from the script (a second dial can disagree with the miner's proxied/TLS view; the miner is the party that has to be connected). - doctor gains a named pool-connection check right after the service check: connected -> ok line (pool, connection age, accepted shares); running-but-disconnected -> counted issue (exit 1 per #149); silent API -> advisory only (the miner may still be starting). Service stopped -> one guarded TCP dial of pools[0] stands in (host/port as positional args, never interpolated into the -c string; timeout-bound). - apply's post-reconcile summary polls the API briefly (APPLY_POOL_TRIES x APPLY_POOL_IVL, default 5x3s, early exit once connected) and WARNS when no live connection appears — warn, never refuse: the pool may be legitimately down at apply time, so the exit code stays 0. tune and autotune are untouched (they restart via _apply_runtime and already verify liveness with _wait_miner_live). The TSV parse places its "?" placeholder jq-side: a disconnected xmrig reports pool "", and since tab is IFS whitespace, a leading empty field would be swallowed by read and shift every later field one left — "failures: 2" would have parsed as a live connection. Tests: a doctor section (connected / disconnected / empty-pool fallback to pools[0] / api-down / stopped+reachable / stopped+unreachable / real closed-port dial) and a black-box apply section (connected report; disconnected warn with rc 0; api-down warn with rc 0); the shared curl stub's /2/summary body gains realistic connection.uptime/failures fields. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rig has no non-latest check — a newer-but-not-latest target is accepted; the refusal category is a target outside the allowed bound. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ring continuations diff-cover graded the reshaped filter's two continuation lines as 0% — bash coverage marks only the command line, and a quoted string's continuation lines can never execute. Same filter, one physical line; the tests already prove all three hugepages shapes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pages-array fix(status): render XMRig's array-valued hugepages instead of aborting (#341)
docs: pithead stratum TLS is shipped, same-port — rewrite the stale section (#342)
…nguage docs: stop claiming the rig re-derives latest — five sites, accepted D4 semantics (#352)
…container-pass test(e2e): a second in-container pass with RIGFORGE_APPLIANCE=1 (#348)
…ability # Conflicts: # CHANGELOG.md # tests/run.sh
…bility fix(doctor,apply): read the miner's own pool-connection state (#343)
…tatus-mirror # Conflicts: # CHANGELOG.md
…restore # Conflicts: # CHANGELOG.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…atus-mirror feat(api): mirror the control status into the enriched feed (#346)
…estore fix(tune): an interrupted live sweep restores the pre-sweep config (#347)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First fleet-history entry since v1.11.1 for a loaner rig (#354's drift); recorded by the release perf gate on real hardware after the v1.15.0 forward upgrade. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…aging traps, appliance disable --runtime, comment splices, reader dedup Six small debts from the 2026-08-14 audit, bundled per the issue: 1. CURRENT_STEP was only ever set inside main() (setup), so an unexpected failure in any other verb reported the stale "aborted while starting up" default. Set it once at dispatch time from the verb name itself — setup's own fine-grained per-phase steps still overwrite it a moment later. 2. _apply_plan's step 3 named 3 reconciles (autotune/API/firewall) while apply() actually runs 6 install_* calls, silently omitting watchdog and control from the printed plan. Named all of them; added the same name-vs-plan drift guard _setup_plan already has for main(). 3. backup/restore/support_bundle's mktemp -d staging (config.json, tokens) leaked on a set -e abort — the explicit `rm -rf` cleanup lines were never reached. Gave all three the EXIT-trap treatment tune() got in #135. (The trap has to reference a script-global, not a `local`: a `local` staging var is out of scope by the time the trap fires on a normal return, and dies "unbound variable" under set -u — caught by the existing backup/restore/support-bundle black-box tests.) 4. Verified empirically against a real systemd (255, enable/disable round-trip in a container) that a plain `systemctl disable` only ever removes the /etc-side wants-symlink: a --runtime-enabled unit's /run symlink survives, and `is-enabled` still reports "enabled-runtime". Every disable call now passes ${ENABLE_RUNTIME:+...}, mirroring its enable counterpart. 5. Two comment splices: xmrig_already_built()'s docstring was sitting above the unrelated _sha256() instead of itself; a section-header banner (`--- Auto-tuning: search strategies & seeding ---`) had landed mid- sentence inside _hillclimb()'s docstring. 6. _read_api_summary and _xmrig_summary_json were near-identical /2/summary readers, differing only in an API_CMD hook and whether a curl failure propagates or is swallowed. Merged into one function with a propagate/swallow mode argument, preserving each call site's original behavior exactly. Tests: tests/run.sh gains one section per item above (black-box + unit), 153 -> 158 sections. Full suite: 1685 passed, 0 failed (up from 1668 baseline). make lint clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pt-in (#350) tests/e2e-real.sh's upgrade phase only ever proved the two REFUSAL legs (noop, forged-tag rollback) unless an operator remembered to set E2E_UPGRADE_TARGET — so a broken fetch/rebuild path could pass the release gate silently. The forward leg is now on by default. It auto-derives the previous real release tag and the current/installed one from `git tag` (the release this gate is actually cutting has no tag yet at this point in RELEASING.md's flow, so "current" stands in for it), rewinds the checkout to the previous tag out of band (git checkout + `rigforge.sh upgrade`, the same two steps _control_upgrade_do takes — D10's anti-rollback in control_upgrade() refuses a downgrade POST, so this can't happen through the control channel itself), then drives a real forward upgrade back to current through the same wire/path-unit/oneshot chain the other legs use. _upgrade_cleanup restores the checkout to exactly where the phase started on any exit, success or a hard abort mid-leg — unlike the old opt-in leg (which permanently advanced the checkout past "current"), this one is repeatable, which is what lets it be the default. E2E_UPGRADE_TARGET=vX.Y.Z still works as an explicit override (same PERMANENT semantics as before — useful for a deliberate real deploy). E2E_UPGRADE_SKIP_REASON="..." is the new escape hatch: it skips the leg but requires (and logs) a reason, so a skip is never silent. RELEASING.md's `upgrade` line updated to describe the new default and the skip knob. Needs live-rig validation — this whole leg (the rewind, the real fetch/build through the control channel, the restore) has no automated coverage; it can only run for real on the release rig. See #362's require_traversable_checkout for the one piece of this file that IS unit-tested via extraction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…amicUser (#362) Found live (miner-2): running tests/e2e-real.sh from a checkout under $HOME (mode 750) makes the control phase fail 6 asserts confusingly — the receiver dies in a restart loop with "can't open file .../util/control- server.py: Permission denied", because rigforge-control.service and rigforge-api.service run as systemd DynamicUser and can't traverse the home directory. The phase then reads as a product failure (receiver down, POST 000, DONATION unchanged) when it's a harness-placement problem — nothing pointed at the path. require_traversable_checkout() walks every ancestor directory from the checkout up to /, checking the "others" execute bit on each (root can always stat regardless of permissions, so this never false-fails), and dies immediately with the fix (move the checkout to /opt) if any of them block traversal. Wired into require_linux_root(), which every phase already calls first — so a lone `control` or `upgrade` re-run gets the same immediate, honest failure, not just a fresh `provision`. Split into its own function (rather than inlined in require_linux_root) so it's unit-testable in isolation via the same extraction+eval technique tests/run.sh already uses for rig_lock/_perf_judge: `stat` is faked, so the test's result never depends on the real host's tmp/HOME permissions (which turned out to vary by OS during development — macOS's own mktemp -d default and its TMPDIR parent are both mode 700). One line added to the header comment block per the issue's ask. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tually delivers (#354) RELEASING.md said to record each rig's benchmark after every fleet re-tag. In practice only miner-0 (the dedicated release-gate rig, per tests/README.md) has a current entry every release; miner-1..7 stop at v1.11.1 — three releases of fleet perf history the fleet was never actually re-tagged for, or the recording step was skipped. Rewrote the paragraph to describe the real policy instead of inventing one nobody follows: record whichever rig(s) you actually re-tag, which in practice means miner-0 every time (the gate always runs there) plus whatever else happens to get touched that release. tests/perf-baselines/ legitimately carries gaps between releases for untouched rigs — that's now stated as expected, not implied to be a lapse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ort while succeeding (#364) (#366) The API readers had a "propagate" mode that let curl's exit escape, so an unreachable miner would "surface upstream". Nothing upstream ever read it — every caller branches on an empty body — and letting it escape broke two ways depending on the bash running the script: * Any caller that was NOT guarded aborted outright. Measured on miner-0 and the HP appliance (Linux, bash 5.2): with the API refusing connections, tune/autotune's sampling loop and a bare _status_api_summary both died with "[ERROR] rigforge aborted ... (exit 7)". An API that went away mid-sweep — the miner restarting under you — took the sweep with it. This is the failure #210 first hit on miner-0 and papered over with a `|| true` at ONE call site. * On bash 3.2 (macOS) even GUARDED callers printed the banner. `status` wraps its read in `( ... ) || true` and still emitted two "[ERROR] rigforge aborted while running 'status'" lines before printing its correct "worker API not reachable" line and exiting 0 — the shape reported in #364. set -E inherits the ERR trap into the $( ) the caller reads through, and 3.2 does not carry the caller's suppressed-errexit context into that child, so the trap fires there once per frame the failure unwinds through. 5.2 does carry it and stays quiet, which is why this showed on dev machines and not on the rigs. The mode is gone. Both readers now always return 0 with an empty body when the API is unreachable — the contract every caller already assumed — so neither failure shape is reachable, and #210's guard-inside-the-$( ) idiom is no longer needed for these readers. The watchdog's `|| true` goes with it: the reader owns the guarantee now (verified unguarded on both rigs). `set +e` inside svc_status's subshell was considered and rejected: bash's ERR trap fires independently of errexit, so it silences nothing. Coverage: 7 assertions, each verified to FAIL on the pre-fix script. The tune-side test is driven as a separate bash process on purpose — run in a subshell it inherits the suite's own errexit context, which disarms the failure under test on one bash or the other and passes vacuously. Closes #364. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Contributor
Author
|
Closing: main carries rebased twins of develop's commits, so a rebase promotion is CONFLICTING (same divergence cfd92fa called out at v1.15.0). Re-opening the promotion from a reconcile branch whose tree is pinned byte-identical to develop's release commit, matching the v1.15.0 precedent. |
Merged
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.
Promote develop to main for the v1.15.1 release.
Patch release: #364 — an unreachable worker API no longer aborts the run (tune/autotune sampling died with exit 7 on any bash), or prints spurious
[ERROR] rigforge abortedon bash 3.2 while succeeding.Release gate (e2e-real, miner-3, EPYC 7642): provision 7 / verify 38 / control 8 / upgrade 15 / perf 2 / teardown 13 — all PASS.
Gate ran on miner-3 rather than miner-0: miner-0 has Secure Boot enabled, so kernel lockdown denies MSR writes and doctor cannot pass (pre-existing, reproduces on v1.15.0).
One transient verify failure on the first attempt, filed as #367 rather than silently re-run.
CI green on the merged commit including the macOS bash 3.2 job.
Also carries the already-merged #353/#350/#354/#362 work from PR #365, which landed after the v1.15.0 tag.