diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f7576f0e..fd132039 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -48,6 +48,34 @@ updates: - dependency-name: "*" update-types: ["version-update:semver-major", "version-update:semver-minor"] + # The appliance rootfs base images (#833, #1163). Deliberately asymmetric, and it has to be: + # Dependabot reads this file from the DEFAULT branch only, so the entry lives here on `develop`, + # while the path it names exists only on `develop-v2`. `target-branch` is the mechanism for + # exactly that — Dependabot resolves `directory` against it and opens the PR there. Anyone + # applying "the twins are level" mechanically will read this as wrong; moving it to develop-v2 + # is what makes it stop running, which is how it went unnoticed (CONTRIBUTING.md § The two lanes). + # + # The stakes here are higher than for build/* above: the appliance's Debian userland has no apt + # at runtime, so a base digest bump plus a rebake IS its patch channel. There is no other way for + # a fixed openssl or kernel package to reach a flashed box between releases. + - package-ecosystem: "docker" + directory: "/os/rootfs" + target-branch: "develop-v2" + schedule: + interval: "weekly" + groups: + docker-appliance: + patterns: ["*"] + ignore: + # Same policy as the docker entry above, and it binds the golang builder tag: a 1.26 -> 1.27 + # move is a deliberate rebake, not a security update. It does NOT constrain + # `debian:trixie-slim` — a single-segment codename tag yields no semver comparison for these + # conditions to act on, so what holds the appliance to trixie is the tag written into the + # FROM line, not this block. Digest-only bumps, which are the whole point here, carry no + # semver change and so pass both entries untouched (see PRs #729 and #1116). + - dependency-name: "*" + update-types: ["version-update:semver-major", "version-update:semver-minor"] + # Third-party image digests pinned directly in docker-compose.yml (Tari node + wallet, # docker-socket-proxy, caddy) — outside build/*, so the `docker` entry above never sees them, # and they feed both channels: pulled on the DIY stack, baked into the appliance image (#833). diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6273cbb9..636c9619 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,6 +109,16 @@ jobs: - name: Scan image for CVEs (Trivy) # Gate on actionable (fixable) HIGH/CRITICAL only; accepted findings live in .trivyignore. # Must stay green before v1.1 images publish (#282). + # + # cache: false is load-bearing (#1159). The action caches the vulnerability database under + # `cache-trivy-$(date +%F)`, and Actions caches are immutable and scoped per ref — so the + # first run of the calendar day on a branch freezes that branch's database until midnight. + # On 2026-08-20 the identical dashboard image passed on one branch and failed on another 40 + # seconds apart, because one restored a snapshot written at 00:15 and the other one written + # at 13:33. A gate whose answer depends on when its branch last started a run is not a gate. + # Measured cost of downloading it every time: 108.49 MiB from mirror.gcr.io/aquasec/trivy-db + # in 3.3s, against the 13.9s the ~997 MB cache restore was taking. It also returns ~7.5 GB of + # the repo's 10 GB Actions cache budget, which was 92% near-duplicate copies of this one DB. uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: image-ref: pithead-${{ matrix.service }}:ci @@ -117,6 +127,7 @@ jobs: ignore-unfixed: true exit-code: "1" trivyignores: .trivyignore + cache: "false" hadolint: name: Dockerfile lint (hadolint) diff --git a/.github/workflows/os-rootfs.yml b/.github/workflows/os-rootfs.yml new file mode 100644 index 00000000..f86563b4 --- /dev/null +++ b/.github/workflows/os-rootfs.yml @@ -0,0 +1,114 @@ +name: OS rootfs scan + +# Build the appliance rootfs (os/rootfs/Dockerfile) and Trivy-scan it (#833). The appliance's +# Debian userland — podman, netavark, openssl, the kernel — is frozen at bake time and has no +# apt at runtime, so this scan is the eye on the base OS between releases. Runs on PRs that +# touch os/ and weekly on a schedule; a red scheduled run in the Actions tab is the alert, the +# lychee.yml posture. It is its own workflow (not a build-images matrix entry in ci.yml) +# because the xmrig prebuild makes this the slowest image in the repo — the paths filter keeps +# it off stack-only PRs. +# +# THIS FILE LIVES ON THE DEFAULT BRANCH (`develop`) AND MUST STAY THERE, even though everything it +# scans is on `develop-v2` (#1162). GitHub fires `schedule:` from the default branch only, so while +# this file was develop-v2-only its weekly sweep never ran once — 96 runs over 18 days, every one a +# `pull_request`. Living on `develop` is only half of it: a job on `develop` still checks out +# `develop`, which has no os/, so the checkout below names the appliance branch explicitly for every +# trigger that is not a PR. Do not "fix" the asymmetry by moving the file back — the rule and the +# one-command check for it are in CONTRIBUTING.md § The two lanes. +on: + pull_request: + # A PR whose BASE has no os/ cannot be scanned, and a green check named "Build + scan the + # appliance rootfs" that scanned nothing is the same lie in a smaller font. `.trivyignore` is in + # the paths filter below and lives on both lanes, so without this a PR editing it on `develop` — + # #1156 added four mutes that way — would report the appliance clean under a mute it never + # applied. Excluding the two os/-less long-lived branches keeps the filter honest while leaving + # stacked appliance branches covered. The appliance-side sync PR is what scans a `.trivyignore` + # change for real. + branches-ignore: + - "develop" + - "main" + paths: + - "os/**" + - ".github/workflows/os-rootfs.yml" + - ".trivyignore" + schedule: + - cron: "0 6 * * 1" # Mondays 06:00 UTC, after ci.yml's 05:00 image sweep + workflow_dispatch: + +# Least privilege (#282): read-only, same as ci.yml. +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + rootfs-image: + name: Build + scan the appliance rootfs + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # A PR run scans the PR (the empty string is checkout's default: the triggering ref). + # Everything else — the weekly sweep and any manual dispatch — is asking about the + # appliance, which lives on develop-v2 whichever branch the trigger fired from. Naming it + # here is what makes the sweep real rather than a green skip (#1162). It also means a + # dispatch is a true rehearsal of the schedule instead of a different code path. + # Written negated on purpose: in an Actions expression `A && B || C` is not a + # ternary, it is real boolean logic, and '' is FALSY. `event == 'pull_request' && ''` + # would collapse to '' and fall through to 'develop-v2', so every PR would scan + # develop-v2 instead of the PR. The truthy value has to be the develop-v2 arm. + ref: ${{ github.event_name != 'pull_request' && 'develop-v2' || '' }} + persist-credentials: false + - name: Check the appliance tree exists + # Only a PR may legitimately miss the tree: a PR into `develop` can reach this workflow + # through the .trivyignore path filter, and `develop` has no os/. Every other trigger has + # just checked out develop-v2 by name, so a miss there means the checkout did not do what + # it was asked — and a quiet skip would report a green weekly sweep of nothing, which is + # the exact failure #1162 was filed for. Fail instead. + id: tree + env: + EVENT: ${{ github.event_name }} + run: | + if [ -f os/rootfs/Dockerfile ]; then + echo "present=true" >>"$GITHUB_OUTPUT" + elif [ "$EVENT" = "pull_request" ]; then + echo "present=false" >>"$GITHUB_OUTPUT" + echo "os/rootfs/Dockerfile not on this branch; nothing to scan" + echo "The appliance rootfs was NOT scanned: os/rootfs/Dockerfile is not on this PR's branch." \ + >>"$GITHUB_STEP_SUMMARY" + else + echo "::error::checked out develop-v2 but os/rootfs/Dockerfile is missing — the appliance checkout did not do what it was asked, so this run cannot report a scan it did not do" + exit 1 + fi + - name: docker build os/rootfs + if: steps.tree.outputs.present == 'true' + # BUILD_COMMIT is a build artifact (os/build-image.sh stamps it; the Dockerfile COPYs + # it), so stamp it here too. PITHEAD_UPDATER=rauc matches build-image.sh's default so + # the scan covers the updater packages; the test args stay empty — release variant. + # images/ holds only .keep here: the staged wizard image is a separate scan target + # (ci.yml builds and scans the dashboard image directly). + run: | + git rev-parse HEAD > os/rootfs/BUILD_COMMIT + docker build -f os/rootfs/Dockerfile -t pithead-os-rootfs:ci \ + --build-arg PITHEAD_UPDATER=rauc . + - name: Scan rootfs for CVEs (Trivy) + if: steps.tree.outputs.present == 'true' + # Same gate as ci.yml's image scan: actionable (fixable) HIGH/CRITICAL only; accepted + # findings live in .trivyignore with a rationale and how they clear. + # + # cache: false for the reason spelled out over ci.yml's scan (#1159) — this is the repo's + # other trivy call site. It matters more here: the appliance's Debian userland is baked and + # has no apt at runtime, so this scan is the only eye on podman, netavark, openssl and the + # kernel between releases, and since #1162 that eye is finally open on a schedule. + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: pithead-os-rootfs:ci + scanners: vuln + severity: HIGH,CRITICAL + ignore-unfixed: true + exit-code: "1" + trivyignores: .trivyignore + cache: "false" diff --git a/.github/workflows/pin-watch.yml b/.github/workflows/pin-watch.yml new file mode 100644 index 00000000..2d1b9616 --- /dev/null +++ b/.github/workflows/pin-watch.yml @@ -0,0 +1,120 @@ +name: Upstream pin watch + +# Weekly upstream-currency report (#1128). It REPORTS and never bumps: a Tari or monerod minor is +# a data migration to schedule, not a bump to merge (#1129 carries three one-time migrations and a +# one-way wallet-DB change). RigForge's xmrig-bump.yml opens a build-verified PR instead — same +# shape, different output, because XMRig is a drop-in binary and its build gate proves the +# candidate. +# +# ONE tracking issue per lane, edited in place. An issue persists, is assignable and milestonable, +# and lets a human write "held until the bench is free, here's why" in a comment — which a report +# artifact nobody opens cannot do, and which is the failure mode #1128 was filed about. Two lanes +# rather than one table because the two trees have different pins and different owners; merging +# them would mean one lane's rows silently vanishing whenever the other lane could not be read. +# +# This file lives on the DEFAULT branch on purpose. The previous pin-watch.yml lived on +# `develop-v2`, where a `schedule:` can never fire, and so ran exactly zero times — the appliance +# pins it watched were never checked once. That is the same defect class as #1048 and #1064. +# +# Living on the default branch means only `develop`'s tree gets read, and `develop` has no `os/` — +# so the appliance's own two pins were invisible in a run that looked complete (#1146). The `lane` +# matrix below fixes that by checking out `develop-v2` explicitly for a second report. It is the +# same defect as the paragraph above, one level in: the previous watcher never ran, this one ran +# with a lane missing. Both look identical from the Actions tab. +on: + schedule: + - cron: "30 6 * * 1" # Mondays 06:30 UTC, after the image sweeps + workflow_dispatch: + +# Least privilege (#282): issues.write is the job's one output — it never pushes or publishes. +permissions: + contents: read + issues: write + +jobs: + pin-watch: + name: Compare upstream component pins against their latest releases (${{ matrix.lane }}) + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + # fail-fast: false so a lane that cannot report does not cancel the lane that can — a missing + # report is the thing this watcher exists to make loud, not a reason to lose the other one. + fail-fast: false + matrix: + include: + # `ref: ""` is checkout's own default: the ref that triggered the run. On the weekly + # schedule that is the default branch, which is what this lane has always read. + - lane: product + ref: "" + title: "Upstream pin currency (weekly report)" + # The appliance rootfs COMPILES docker-compose and cosign from `ARG` values, so nothing + # else can see them: dependabot's docker ecosystem reads `FROM` lines, and `cosign` is the + # verifier the whole signed-update chain rests on. They exist on this lane only. + - lane: appliance + ref: develop-v2 + title: "Upstream pin currency — appliance rootfs (weekly report)" + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ matrix.ref }} + persist-credentials: false + - name: Build the currency report + id: report + env: + GH_TOKEN: ${{ github.token }} + run: | + set -uo pipefail + # NOT `set -e`: a non-zero exit here means "one or more lookups could not run", which is + # a result to publish, not a reason to skip publishing it. The exit code is carried to + # the last step instead, so a watcher that could not do its job says so in the artefact + # a human reads AND fails the run. + bash scripts/pin-watch.sh >report.md + rc=$? + # An empty report is itself a failure to report — never publish silence. + [ -s report.md ] || { echo "The pin watcher produced no report at all — see the run log." >report.md; rc=1; } + echo "rc=$rc" >>"$GITHUB_OUTPUT" + - name: Publish it to this lane's tracking issue + env: + GH_TOKEN: ${{ github.token }} + TITLE: ${{ matrix.title }} + RC: ${{ steps.report.outputs.rc }} + run: | + set -Eeuo pipefail + body=$(cat report.md) + # Exact title match over the open list, NOT `--search`: the search index lags behind + # issue creation by minutes, so a search-based dedup files a second issue on the very + # next run and then keeps both stale. + n=$(gh issue list --state open --limit 200 --json number,title \ + --jq "map(select(.title == \"$TITLE\")) | .[0].number // empty") + # A run that could not do its job replaces the report with a failure notice, which would + # otherwise DELETE the "last fully successful check" date — and that date is the only + # thing separating "failed once this morning" from "has been dead for six weeks". Carry + # the previous one forward. A watcher whose own silence is invisible is the exact defect + # this watcher exists to catch, so it must not have it. + if [ "$RC" != "0" ] && [ -n "$n" ]; then + prev=$(gh issue view "$n" --json body --jq .body | grep -a "^_Last fully successful check:" || true) + # A real `if`, not `[ -n "$prev" ] && body=...`. The && form is safe HERE (set -e is + # ignored for a non-final command in an AND-OR list, checked rather than assumed), but + # it evaluates to 1 when $prev is empty — so it only stays safe while something else + # follows it. That is a reordering hazard for one saved line. + if [ -n "$prev" ]; then + body="$body"$'\n\n'"$prev (this run could not complete)" + fi + fi + if [ -n "$n" ]; then + gh issue edit "$n" --body "$body" + echo "updated #$n" + else + gh issue create --title "$TITLE" --label infra --body "$body" + fi + - name: Fail the run if any lookup could not be made + if: steps.report.outputs.rc != '0' + run: | + echo "::error::one or more upstream lookups could not run — those pins are UNCHECKED, not current" + exit 1 + # Digest-level check (#1137): does this lane's own docker-compose.yml pins still match what + # each tag resolves to right now. Separate from the report above on purpose — a version can + # be current while its digest has drifted, and that half-done-bump case is this step's job, + # not the report's. A mismatch or failed lookup fails this step, which fails the run. + - name: Check third-party image pins against their registries + run: bash scripts/resolve-pins.sh diff --git a/.trivyignore b/.trivyignore index b79eae0f..d6d97a98 100644 --- a/.trivyignore +++ b/.trivyignore @@ -13,10 +13,23 @@ CVE-2026-45447 CVE-2026-23949 CVE-2026-24049 -# util-linux family (bsdutils, libmount1, libuuid1, login, mount, util-linux): the fix is a -# base-distro point release, 2.41.5-0+deb13u1, and no published python:3.11-slim carries it yet — -# the newest digest still ships 2.41-5. Same shape as CVE-2026-45447 above: `--ignore-unfixed` hid -# this until Debian published a fix, at which point it reddened every branch at once without any -# change on our side. Cleared when a base bump brings 2.41.5-0+deb13u1 in; the weekly CVE sweep -# (#833) re-surfaces it regardless, so this cannot rot silently. +# util-linux family (bsdutils, libblkid1, liblastlog2-2, libmount1, libsmartcols1, libuuid1, login, +# mount, util-linux) — FOUR advisories now, all with the same fix and the same clearing condition. +# The fix is a base-distro point release, 2.41.5-0+deb13u1, and no published python:3.11-slim +# carries it: `docker pull python:3.11-slim` returns the very digest build/dashboard/Dockerfile +# already pins, and it still ships 2.41-5. So this cannot be closed by dependabot or by a manual +# base bump — only by upstream rebuilding the image on the fixed Debian packages. Verified by +# pulling the tag and reading dpkg, not from the release notes (#1156). +# +# Same shape as CVE-2026-45447 above: `--ignore-unfixed` hid these until Debian published a fix, at +# which point they reddened every branch at once with no change on our side. 53615 arrived that way +# on 2026-08-19 and the other three on 2026-08-20, between one CI run and the next on a branch +# nobody had touched. +# +# Cleared when a base bump brings 2.41.5-0+deb13u1 in — `docker run --rm python:3.11-slim +# dpkg-query -W util-linux` is the one-line check, and all four go together. The weekly CVE sweep +# (#833) re-surfaces them regardless, so this cannot rot silently. +CVE-2026-53612 +CVE-2026-53613 +CVE-2026-53614 CVE-2026-53615 diff --git a/CHANGELOG.md b/CHANGELOG.md index 408430d5..71a9cb8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,74 @@ Pithead ships as **one product, one version** — the version lives in the top-l [`VERSION`](VERSION) file and every released image is tagged with it. Releases are cut per the process in [`docs/dev/releasing.md`](docs/dev/releasing.md). +## [1.19.3] - 2026-08-21 + +### Changed + +- **Bundled components brought current + ([#1144](https://github.com/p2pool-starter-stack/pithead/issues/1144) report rows):** + Monero **v0.18.5.1** (upstream bugfix point release), P2Pool **v4.16 → v4.18** (P2P DoS + hardening, Stratum performance; no merge-mining changes — the v4.18 SOCKS5 behaviour change was + audited against source and both intra-stack bridges remain correct), and the Docker socket proxy + **v0.4.2 → v0.5.0** on both proxies (additive upstream change only; the new pause/unpause routes + are opt-ins this stack does not enable, and the control-proxy hardening assertion now denies them + explicitly so a future new endpoint class is visible to the gate). Tari stays at v5.3.1 + deliberately: v5.6.0 carries a one-way wallet-database migration and is scheduled bench-gated + work ([#1129](https://github.com/p2pool-starter-stack/pithead/issues/1129)), not a patch-day bump. +- Base-image and Python dependency refreshes via Dependabot. + +### Fixed + +- **A release can no longer announce a version the stack is not running + ([#1137](https://github.com/p2pool-starter-stack/pithead/issues/1137)).** In a `tag@sha256` + image pin the digest is what actually runs and the tag is decoration, so a half-done bump — tag + moved, digest left behind — was green at every tier. The compose assertions now bind the whole + reference (both socket proxies counted, the console wallet included), and a new release-preflight + check asks each registry what the pinned tag resolves to right now and refuses the cut on any + mismatch or any failed lookup. The same check runs in the weekly pin watch. +- **The pre-release gate could not go green on a healthy box + ([#1180](https://github.com/p2pool-starter-stack/pithead/issues/1180)).** The end-to-end suite + asserted the Caddy scheme by reading line 1 of the rendered Caddyfile, which stopped being the + site block when the global options block landed. A false RED on the one gate between a bad + release and the world — and because an exit-1 run stops at its first red, it also hid later + results. The assertion now finds the dashboard's site block wherever it is. +- **The wizard and a provisioned box no longer reflect the request's Host header into redirects + ([#1123](https://github.com/p2pool-starter-stack/pithead/issues/1123)).** Caddy now disables + automatic HTTP→HTTPS redirects and serves an explicit redirect block bound to the configured + address. +- **Upgrade-path proofs that could not fail, fixed at the harness tier:** the post-restore control + check ran downstream of its own repair + ([#1085](https://github.com/p2pool-starter-stack/pithead/issues/1085)); the release-smoke + `--upgrade` assertion read the old install directory, so a successful upgrade reported as a + failure ([#1068](https://github.com/p2pool-starter-stack/pithead/issues/1068)); the borrowed + bench rig's injected pool is now tagged so the harness can tell its own entry from the + operator's, ending a self-perpetuating config contamination + ([#1178](https://github.com/p2pool-starter-stack/pithead/issues/1178)); and the suite's one + host-global fixture path is sandboxed so two runs cannot collide + ([#1104](https://github.com/p2pool-starter-stack/pithead/issues/1104)). +- **GitHub API reads in the one-click upgrade path are rate-limit aware + ([#1081](https://github.com/p2pool-starter-stack/pithead/issues/1081)).** +- **The dashboard's "Your Stack" section no longer trails half its height in blank space + ([#991](https://github.com/p2pool-starter-stack/pithead/issues/991)).** A grid row is as tall as + its tallest card, and the tallest card's explainer prose anchored every row. That prose now sits + in a collapsed-by-default disclosure, and cards are ordered tall-with-tall — measured 27% shorter + at desktop width with no copy changes. +- **The dashboard's editable-key security perimeter is pinned directly + ([#1094](https://github.com/p2pool-starter-stack/pithead/issues/1094)):** sixteen + never-committable keys (auth, bind address, onion exposure, the control channel itself, Tor + egress, wallet credentials) are asserted absent from every allowlist copy independently, so a + key added to all copies at once can no longer pass. + +### Security + +- The upgrade escape path for installs older than v1.19.1 was driven end to end on real installs + at v1.18.1 and v1.19.0 for the first time + ([#1111](https://github.com/p2pool-starter-stack/pithead/issues/1111)): both refuse exactly as + documented with no host cosign, and the pinned cosign **v2.6.3** clears the refusal. One + correction to the previous guidance: a current cosign **v3** (v3.1.3) also verifies these + bundles correctly — the v3 flag breakage applies to the release box's signing flags, not the + operator's verify path. + ## [1.19.2] - 2026-08-18 ### Fixed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d73698c5..272c0306 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -74,6 +74,45 @@ runs `ruff` (plus a few hygiene hooks) on your changed files. If you change depe user-facing change. To see what the suites cover, `make test-inventory` writes a generated (git-ignored) inventory you can read locally. +### The two lanes, and what that means for CI config + +The stack ships two ways from one repo. `develop` is the integration branch for the Docker Compose +product and is the repo's **default branch**. `develop-v2` is its twin: everything on `develop`, +plus the appliance OS tree under `os/`. Appliance work targets `develop-v2`; everything else +targets `develop`, and `develop` is merged into `develop-v2` to keep the twins level. + +**Automation that GitHub reads from a fixed location must live on `develop`, and must name the +appliance branch explicitly when it needs the appliance tree.** GitHub fires a workflow's +`schedule:` trigger from the default branch only, and Dependabot reads `.github/dependabot.yml` +from the default branch only. A scheduled workflow or a Dependabot entry that lives on +`develop-v2` never runs — and a job that never runs looks exactly like a job that ran and found +nothing, which is why this went unnoticed three times (#1146, #1162, #1163). #1048 is its sibling +and worth knowing next to it: there the schedule did fire, and the job skipped itself behind an +unset repository variable, so `main` showed green for a gate that had never run. + +Living on `develop` is only half of it. A workflow on `develop` still checks out `develop`, which +has no `os/`, so the appliance lane is reached by an explicit ref +(`.github/workflows/os-rootfs.yml`) or by `target-branch:` (`.github/dependabot.yml`). Both files +carry a comment saying why they are deliberately asymmetric; do not "tidy" either onto `develop-v2`. + +Check this from run history, never from the file — the file always looks fine: + +```bash +gh run list --workflow=.yml --limit 200 --json event \ + --jq '[.[].event] | group_by(.) | map({event: .[0], n: length})' +``` + +No `schedule` key in that breakdown means the schedule has never fired. A `schedule` key is +necessary but not sufficient — #1048's shape passes that test — so open the newest scheduled run and +check its steps actually ran rather than skipping: + +```bash +gh run view --json jobs \ + --jq '.jobs[].steps[] | "\(.conclusion) \(.name)"' +``` + +The Dependabot equivalent of the first check is to group its PRs by `baseRefName`. + ## Opening a pull request - Target the `develop` branch and fill out the PR template. diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md index eb98be92..b2a69473 100644 --- a/THIRD_PARTY_LICENSES.md +++ b/THIRD_PARTY_LICENSES.md @@ -10,8 +10,8 @@ Version-pinned, sha256-verified, **unmodified** upstream binaries (pin + hash in | Binary | Version | License | Source | |--------|---------|---------|--------| -| monerod | v0.18.5.0 | BSD-3-Clause | | -| p2pool | v4.16 | **GPL-3.0-or-later** | | +| monerod | v0.18.5.1 | BSD-3-Clause | | +| p2pool | v4.18 | **GPL-3.0-or-later** | | | xmrig-proxy | 6.26.0 | **GPL-3.0-or-later** | | | tor | distro | BSD-3-Clause | | diff --git a/VERSION b/VERSION index 836ae4ed..1b92e588 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.19.2 +1.19.3 diff --git a/build/dashboard/mining_dashboard/client/docker/docker_control.py b/build/dashboard/mining_dashboard/client/docker/docker_control.py index 28e95139..e2b2056e 100644 --- a/build/dashboard/mining_dashboard/client/docker/docker_control.py +++ b/build/dashboard/mining_dashboard/client/docker/docker_control.py @@ -13,7 +13,7 @@ class DockerControl: Goes through a dedicated `docker-control` socket proxy whose ruleset allows exactly `POST /containers//{start,stop}` and nothing else — not the read-only `docker-proxy` - the dashboard uses for stats/logs. (tecnativa/docker-socket-proxy v0.4.2 denies all POST + the dashboard uses for stats/logs. (tecnativa/docker-socket-proxy v0.5.0 denies all POST unless POST=1, and POST=1 on the read proxy would also open create/kill/exec via its CONTAINERS grant — so container control lives on its own minimal proxy instead.) diff --git a/build/dashboard/mining_dashboard/web/static/components.mjs b/build/dashboard/mining_dashboard/web/static/components.mjs index 7c42fbad..1187b843 100644 --- a/build/dashboard/mining_dashboard/web/static/components.mjs +++ b/build/dashboard/mining_dashboard/web/static/components.mjs @@ -852,7 +852,11 @@ class EarningsCard extends Component { return html`

P2Pool Earnings (estimated)

-

Estimated XMR from P2Pool mining plus the Tari merge-mined alongside it — excludes XvB donations.

+
+ About this estimate +

Estimated XMR from P2Pool mining plus the Tari merge-mined alongside it — excludes XvB donations.

+

${e.disclaimer}

+
` : null } -

${e.disclaimer}

`; } } @@ -1260,6 +1263,12 @@ function DashboardView({ // Layout by operator relevance (#159): the at-a-glance chart and the rigs themselves lead (this // stack may drive many machines), then this stack's own detail cards, then pool-wide and network // context as reference at the bottom — "mine" first, "the world" last. + // Within "Your Stack" (#991), the advanced-only cards are ordered tall-with-tall / short-with-short: + // the auto-fit grid's rows are as tall as their tallest card, so pairing similarly-sized neighbours + // (XvBStats+EarningsCard, then NodeStats+ExpectedVsActualCard, then TariCard+CadenceCard) cuts the + // blank space shorter cards used to trail. Overview is Simple-view-only (display:none in Advanced, + // so its position never affects the Advanced grid) and ExpectedVsActualCard shows in both views — + // neither constrains this ordering. return html`
@@ -1295,12 +1304,12 @@ function DashboardView({
<${Overview} state=${state} /> - <${ExpectedVsActualCard} summary=${state.earnings_summary} /> - <${NodeStats} state=${state} /> <${XvBStats} state=${state} /> <${EarningsCard} earnings=${state.earnings} xvb=${state.xvb_calc} energy=${state.energy} /> - <${CadenceCard} cadence=${state.cadence} /> + <${NodeStats} state=${state} /> + <${ExpectedVsActualCard} summary=${state.earnings_summary} /> <${TariCard} tari=${state.tari} /> + <${CadenceCard} cadence=${state.cadence} /> <${GlobalStats} state=${state} /> <${NetworkCard} state=${state} /> diff --git a/build/dashboard/mining_dashboard/web/static/dashboard.css b/build/dashboard/mining_dashboard/web/static/dashboard.css index 3dba2390..57b6411a 100644 --- a/build/dashboard/mining_dashboard/web/static/dashboard.css +++ b/build/dashboard/mining_dashboard/web/static/dashboard.css @@ -316,8 +316,19 @@ h3 { } /* Earnings calculator what-if input (Issue #12) */ -.earnings-subtitle { +/* The subtitle + disclaimer prose (#991): collapsed by default, same pattern as .egress-details — + * these two paragraphs were the single biggest block in the tallest card in the "Your Stack" grid + * section, so every shorter neighbour trailed blank space under it. */ +.earnings-details { margin: -4px 0 12px 0; +} +.earnings-details > summary { + cursor: pointer; + color: var(--text-muted); + font-size: 0.8rem; +} +.earnings-subtitle { + margin: 8px 0 0 0; line-height: 1.4; } .earnings-input { diff --git a/build/dashboard/pyproject.toml b/build/dashboard/pyproject.toml index 8efad63c..43db141a 100644 --- a/build/dashboard/pyproject.toml +++ b/build/dashboard/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=83.0.0"] +requires = ["setuptools>=84.0.0"] build-backend = "setuptools.build_meta" [project] @@ -7,7 +7,7 @@ name = "mining-dashboard" # Keep in lockstep with the top-level VERSION file — the single source of truth for the stack version # (#44). A shell test (tests/stack/run.sh) fails if these drift; the dashboard *displays* the version # from VERSION (baked in as PITHEAD_VERSION, #58), so this is packaging metadata only. -version = "1.19.2" +version = "1.19.3" description = "Monitoring dashboard and XvB switching engine for Pithead" readme = "README.md" requires-python = ">=3.11" @@ -34,11 +34,11 @@ test = [ # diff-cover (#286): patch-coverage gate — new/changed lines must be >=90% covered. "diff-cover>=10.5.0", # hypothesis (#284): property-based tests asserting invariants on the money/numeric logic. - "hypothesis>=6.165.2", + "hypothesis>=6.165.9", ] # Developer tooling (Wave 7, #280). Pinned so local, pre-commit, and CI all run the SAME ruff # — lint output is version-sensitive, so a floor would let CI and a contributor disagree. -dev = ["ruff==0.16.2", "pre-commit>=4.6.1"] +dev = ["ruff==0.16.3", "pre-commit>=4.6.2"] [tool.setuptools.packages.find] include = ["mining_dashboard*"] diff --git a/build/dashboard/tests/frontend/components.test.mjs b/build/dashboard/tests/frontend/components.test.mjs index 32651a21..8d9b0c27 100644 --- a/build/dashboard/tests/frontend/components.test.mjs +++ b/build/dashboard/tests/frontend/components.test.mjs @@ -113,7 +113,7 @@ test('chart range buttons include All, active on the default full-history view ( test('chart legend renders a toggle for every layer, including the marker datasets (#652)', () => { const html = renderApp(); for (const label of ['P2Pool (routed)', 'XvB (routed)', 'Shares', 'Events', 'Raffle wins']) { - assert.match(html, new RegExp(`legend-item[^>]*>.*?${label.replace(/[()]/g, '\\$&')}`), + assert.match(html, new RegExp(`legend-item[^>]*>.*?${label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`), `missing legend toggle: ${label}`); } // A hidden marker layer renders its button in the off state, like the line series do. diff --git a/build/dashboard/tests/service/test_control_service.py b/build/dashboard/tests/service/test_control_service.py index 21f31f3f..4976c582 100644 --- a/build/dashboard/tests/service/test_control_service.py +++ b/build/dashboard/tests/service/test_control_service.py @@ -387,6 +387,69 @@ def test_confirm_keys_have_no_intra_repo_drift(): assert set(control_service.CONFIRM_ENV_KEY_PATHS.keys()) == pithead_keys +# The security perimeter (#1094): env keys that must never be dashboard-committable at all — auth, +# onion exposure, the bind/host, the control channel itself, Tor egress, and wallet/node +# credentials. The two drift tests above only catch the editable/confirm allowlists' two hand-kept +# copies disagreeing with EACH OTHER; a key added to BOTH copies at once (#1094's mutation proof: +# DASHBOARD_AUTH_HASH_B64, or DASHBOARD_HOST, added to pithead's list and EDITABLE_ENV_KEY_PATHS +# together) leaves them in perfect agreement and both drift tests stay green. This list is the +# claim those tests can't make: not "do the two copies match" but "is this key committable at all". +NEVER_COMMITTABLE_ENV_KEYS = frozenset( + { + "DASHBOARD_AUTH_USER", + "DASHBOARD_AUTH_HASH_B64", + "DASHBOARD_AUTH_PW_FP", + "DASHBOARD_HOST", + "DASHBOARD_CONTROL_ENABLED", + "DASHBOARD_ONION_ENABLED", + "DASHBOARD_ONION_ADDRESS", + "DASHBOARD_ONION_CLIENT_AUTH", + "TOR_EGRESS_FIREWALL", + "MONERO_WALLET_ADDRESS", + "MONERO_VIEW_KEY", + "MONERO_NODE_USERNAME", + "MONERO_NODE_PASSWORD", + "WALLET_RPC_PASSWORD", + "TARI_VIEW_KEY", + "TARI_WALLET_PASSWORD", + } +) + + +def test_perimeter_env_keys_never_committable_from_either_copy(): + """#1094: names the security perimeter directly and checks each of the four allowlists (pithead's + editable + confirm sets, EDITABLE_ENV_KEY_PATHS + CONFIRM_ENV_KEY_PATHS) against it + independently, so a key added to every copy at once still fails — unlike the drift tests above, + which compare the copies only to each other.""" + import re + from pathlib import Path + + here = Path(__file__).resolve() + pithead_path = next((p / "pithead" for p in here.parents if (p / "pithead").is_file()), None) + if pithead_path is None: + pytest.skip("pithead CLI not present in this test context (dashboard-only image)") + pithead = pithead_path.read_text() + editable_m = re.search(r"CONTROL_DASHBOARD_EDITABLE_KEYS='([^']*)'", pithead) + confirm_m = re.search(r"CONTROL_DASHBOARD_CONFIRM_KEYS='([^']*)'", pithead) + assert editable_m and confirm_m, "could not find pithead's editable/confirm allowlists" + pithead_editable = set(editable_m.group(1).split()) + pithead_confirm = set(confirm_m.group(1).split()) + py_editable = set(control_service.EDITABLE_ENV_KEY_PATHS.keys()) + py_confirm = set(control_service.CONFIRM_ENV_KEY_PATHS.keys()) + + for key in NEVER_COMMITTABLE_ENV_KEYS: + # A perimeter entry whose spelling no longer exists in the codebase guards nothing: the + # real (renamed) key could be added to every allowlist while this list stays green. Anchor + # each entry to the pithead text so a rename kills the test, not the protection. + assert key in pithead, ( + f"{key} appears nowhere in pithead — dead perimeter entry (key renamed?)" + ) + assert key not in pithead_editable, f"{key} in pithead's CONTROL_DASHBOARD_EDITABLE_KEYS" + assert key not in pithead_confirm, f"{key} in pithead's CONTROL_DASHBOARD_CONFIRM_KEYS" + assert key not in py_editable, f"{key} in control_service.EDITABLE_ENV_KEY_PATHS" + assert key not in py_confirm, f"{key} in control_service.CONFIRM_ENV_KEY_PATHS" + + class TestWorkerApply: """Worker config-apply spooling + validation (#185). The intent carries only the worker name + writable-key changes — never a host, port, or token (those stay host-side, #440).""" diff --git a/build/dashboard/uv.lock b/build/dashboard/uv.lock index b351fc9f..496159db 100644 --- a/build/dashboard/uv.lock +++ b/build/dashboard/uv.lock @@ -419,7 +419,7 @@ toml = [ [[package]] name = "diff-cover" -version = "10.5.0" +version = "10.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "chardet" }, @@ -427,9 +427,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/c7/b2883d341261dfa1cdc4386929e967ff4af9c201405f6f72275abefd5f2a/diff_cover-10.5.0.tar.gz", hash = "sha256:a1734543baaaa7d0860a6526016d487be24a0ffe9efb4eca0824c391d4a09d06", size = 110762, upload-time = "2026-08-08T17:25:19.494Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/74/b71a61b2610b5866aa29a9388bc1b58738e49600345dbe0598676b436717/diff_cover-10.5.1.tar.gz", hash = "sha256:f1c9417c62111e40a81c482c692c5cdd131ff93317baa993044cb291912ebba3", size = 113297, upload-time = "2026-08-16T18:09:19.466Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/c8/5345dfb7c955639454fffc767c3eaf9863d79a1cdb0081392a71102fedfa/diff_cover-10.5.0-py3-none-any.whl", hash = "sha256:5619f924e838b8f8c4c7b243b32a6068d8c449c1766c6f6a1110b152195ed9b9", size = 61184, upload-time = "2026-08-08T17:25:18.337Z" }, + { url = "https://files.pythonhosted.org/packages/3d/da/ac0ad341c7377e82f7d6dcb18461029579da224c3923f73ff6537d8591cf/diff_cover-10.5.1-py3-none-any.whl", hash = "sha256:67ce07494e605b5d7d7b04aeec2cf524950ed0c12f026dd176883cf33c74fba0", size = 62924, upload-time = "2026-08-16T18:09:18.387Z" }, ] [[package]] @@ -608,67 +608,86 @@ wheels = [ [[package]] name = "hypothesis" -version = "6.165.2" +version = "6.165.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ea/73/fc3743243603dc49911a1ec073a3a524ea8e1c7d48218d3c2a3faa9a8709/hypothesis-6.165.2.tar.gz", hash = "sha256:680a1adf523ac792b46064f425b112ce6c08a7a8f50e65d08e029de6aa11df95", size = 502277, upload-time = "2026-08-05T21:32:43.713Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/e2/0fad246d2b6330e1f78479bfc566b5c22be82aee8a865cde9a08f648487d/hypothesis-6.165.10.tar.gz", hash = "sha256:68b45e09834cd80523cb1eb274463073c7a9af4e4ef7cff34d9615f355572d32", size = 503703, upload-time = "2026-08-16T22:56:15.404Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/63/46c9908fe7bd5ffa5002fa88fe289dfe6d3cea3fad1ab8942fe11f1c8a2b/hypothesis-6.165.2-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:33a7303566e660664f3f02ea1df85f7b966cd6723165c696996cfd8630913b3a", size = 781704, upload-time = "2026-08-05T21:32:03.548Z" }, - { url = "https://files.pythonhosted.org/packages/c5/7f/fdce62542a514f6b33c4fc0a760e6d17bb57602b28cb079b78e55b8ea32d/hypothesis-6.165.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:06d8fe4c82a935f67e610c99848360f5caeb04f547c7d7a830a5c74fb96f053a", size = 777243, upload-time = "2026-08-05T21:32:17.546Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c8/39cd922bf3e1ec84977b768d4e8be31ae051e3a6b400b4fdd7ebcddb1eed/hypothesis-6.165.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de2e3f6a6f75c876be481138c6c0802ebe10deef9f13ce1cdd6e0ed21d8e1e28", size = 1106492, upload-time = "2026-08-05T21:31:47.844Z" }, - { url = "https://files.pythonhosted.org/packages/0f/91/7bb502379a8dcc43f2538530c05cf16fd4e386afa587d65cc289484425cf/hypothesis-6.165.2-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:21668cb5a8a694d45ff4c43f20a8fc577a47467b5102c680a43638b027136368", size = 1135105, upload-time = "2026-08-05T21:31:49.453Z" }, - { url = "https://files.pythonhosted.org/packages/e5/04/4ce8ae1bf78d09d7543ab7037a3beedc7f3d963c7aa04e82842415284689/hypothesis-6.165.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eea4ab5cfdd6c6a23a60777559ea06c34868234fff6542ff6125c0250429348e", size = 1156034, upload-time = "2026-08-05T21:31:44.687Z" }, - { url = "https://files.pythonhosted.org/packages/49/de/b074d899f4a04fa8b99a5bbd66f209c1c02bc21f9f87404539b28b99aff0/hypothesis-6.165.2-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:0a6add02d9b3b73b59f4d69f5b15abbc07113cd335cc140815cec2877e6b496c", size = 1111344, upload-time = "2026-08-05T21:32:27.211Z" }, - { url = "https://files.pythonhosted.org/packages/e8/38/5a8514683a181f82a8ad9f6d084b704fea7a97c9814033939fc493b55fca/hypothesis-6.165.2-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11013896b6a2ed497079558cb9f89e8b3b564f8859782b38f72cfc1dbeca66ad", size = 1148115, upload-time = "2026-08-05T21:31:01.009Z" }, - { url = "https://files.pythonhosted.org/packages/88/c7/08cf7930d8bec7f1df971c948af2ccb5a402d5b8b19b306af655a603b180/hypothesis-6.165.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4ceabc69a95e761f381663c6452537fde12a2a6e0275e095b6822c0e0f3b1364", size = 1280321, upload-time = "2026-08-05T21:31:14.843Z" }, - { url = "https://files.pythonhosted.org/packages/c5/91/c9ebb7da3b6e06c47aecceb959cf7df6af75025663af543373c5692f97ac/hypothesis-6.165.2-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:11e1ce261765ffa6acbaf540358426519ca4a5e46b825cf39b429c77c1895689", size = 1408134, upload-time = "2026-08-05T21:31:27.383Z" }, - { url = "https://files.pythonhosted.org/packages/ed/24/13c2fd9f253ba3a92d4aaa61ae45a8b130df57ab5bd6fc481e2aae520e36/hypothesis-6.165.2-cp310-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:b0250099b2e55d72872319918aacc501110a182938a3d56bcae4a999bee5db08", size = 1280884, upload-time = "2026-08-05T21:32:00.188Z" }, - { url = "https://files.pythonhosted.org/packages/6e/fa/2820bdbe0660394544b9e120b03ea7020702d7fa5c76236166de6ababc9d/hypothesis-6.165.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:96d02928d1a0b7d59e39fd8ada75f0b7d0377ff29f91c94109f92fc2dab9af74", size = 1322998, upload-time = "2026-08-05T21:31:24.373Z" }, - { url = "https://files.pythonhosted.org/packages/f0/24/38752794eb821f5c77da08523602003c344f3498fce09f20741cd5b5e29c/hypothesis-6.165.2-cp310-abi3-win32.whl", hash = "sha256:0f2044093c8244d73893e755a7fa53154b7eca57b37e1427d9b0d9948f6c2b3e", size = 667506, upload-time = "2026-08-05T21:32:10.547Z" }, - { url = "https://files.pythonhosted.org/packages/5f/71/b28f714a127017750e450d152aa4fbff51bd144c6840090d28b529b408d3/hypothesis-6.165.2-cp310-abi3-win_amd64.whl", hash = "sha256:2aa30716066e5ee7750e56b8f90cefaf4ed28c12b4b1d66cef40014fd3f95196", size = 673650, upload-time = "2026-08-05T21:31:25.726Z" }, - { url = "https://files.pythonhosted.org/packages/67/43/ba4e9140480326c517e46f6d16fb2efd423a579f4f50ff79d0326226c563/hypothesis-6.165.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a49889d19484ccfe1977f448a9c0ad27078232a707fa9f9c8667e9d7ffbe792d", size = 782176, upload-time = "2026-08-05T21:32:08.821Z" }, - { url = "https://files.pythonhosted.org/packages/bf/10/f0ed8d6906a959f015bb6140413e5be7965ff50f965478316cc3a7469254/hypothesis-6.165.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67560ca111bcad4e07dc8390a499d9b5c6b5488bab0b3c801a4c31a02238e199", size = 777969, upload-time = "2026-08-05T21:32:31.397Z" }, - { url = "https://files.pythonhosted.org/packages/8a/1e/137cc1cc254c4b00eb41ddebccf0a33cc2ad99f6bb531e68525c2561c475/hypothesis-6.165.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54e5afbdf7c973ced6984265a7ffbcdd3b072349c6e9f9d92af889a5af8d2a08", size = 1106856, upload-time = "2026-08-05T21:30:59.63Z" }, - { url = "https://files.pythonhosted.org/packages/f2/16/52e37b42dfa7579176aa9c8ada2f94eadcd27659b71c85fb477df0d57ad8/hypothesis-6.165.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3aed6b889f493d75b7f734e931d10dc003d7277af63956f8d2f131326aaf297", size = 1156324, upload-time = "2026-08-05T21:31:51.917Z" }, - { url = "https://files.pythonhosted.org/packages/6c/31/467ccd787de1cd77c9a23b963580bbe0ec00e8163ee0df224b3bfcbc2c94/hypothesis-6.165.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:929e47581a4a20b81a67deecd48f6589151fada7c23a418d8505c4595de14577", size = 1280680, upload-time = "2026-08-05T21:31:32.452Z" }, - { url = "https://files.pythonhosted.org/packages/39/9b/6edfb53334df96a0889978374a115fbc1569ffdef7786fe256ed71aa72a1/hypothesis-6.165.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:020a889e548e81514132fe215cca664ee3961a8d1343262c2aa6ea7e5f31370a", size = 1323331, upload-time = "2026-08-05T21:31:13.393Z" }, - { url = "https://files.pythonhosted.org/packages/67/83/92a460ee8811360b5f95cc547d6f43e632457be0bd1a28e497c84e46fa7a/hypothesis-6.165.2-cp311-cp311-win_amd64.whl", hash = "sha256:1bd8955bd5dd72bb9bd6f7243cef8a2bf8a264e23edbd29eecebe41de9348eb9", size = 673376, upload-time = "2026-08-05T21:31:22.637Z" }, - { url = "https://files.pythonhosted.org/packages/98/81/a9039e7eee38523e2ad13e9e4e70c508f25d4205fb4c6ceb98632547ca82/hypothesis-6.165.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b24f1b238deb97fda828a939931de3210f5cef21e87fe0b941fafbeb55ead676", size = 783294, upload-time = "2026-08-05T21:31:56.881Z" }, - { url = "https://files.pythonhosted.org/packages/8d/e5/120642320291d8d117a83491d93527149ddbd15e56399274741fc4bd3a9a/hypothesis-6.165.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:306763ce7186e08ee30dba409b320873d1afc54adf76b44c6bf83b5867d17359", size = 774867, upload-time = "2026-08-05T21:32:05.489Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ef/251607eb2446fb44e8faa83e30aa1b6cc281b6eca5d0ecaabe7527e3395d/hypothesis-6.165.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f878ebc5c33e4e8e8a90bd3d7ccc3f3a7370847aa22243536e673047f0f4c37", size = 1105307, upload-time = "2026-08-05T21:31:53.719Z" }, - { url = "https://files.pythonhosted.org/packages/54/f1/de30869d83f00137a664319a4acb0ba8b1a9e2c879afdc12abb1b4c703f7/hypothesis-6.165.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d726513b32cc6407667ac0812fa3517408f933b89b16b6b84f296335eec18432", size = 1155348, upload-time = "2026-08-05T21:31:58.536Z" }, - { url = "https://files.pythonhosted.org/packages/fa/2c/8925c2bddf6e105d947a04511cd5d536eabc8d4ed926518a0d1672ede007/hypothesis-6.165.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a700c0e193707e3b6f1b23f1d5f534896dd9f79bb2a2340582579bad5f5b59a4", size = 1278124, upload-time = "2026-08-05T21:32:14.049Z" }, - { url = "https://files.pythonhosted.org/packages/b9/00/e285e7987e96d74e229fcb94c58dd8e85290966fc2040fbac4b081fc49c9/hypothesis-6.165.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:50d50e313dfff2e79c754b92a4c88c479dd9e102a56c60caa5b3d6263caa1b02", size = 1322340, upload-time = "2026-08-05T21:31:11.671Z" }, - { url = "https://files.pythonhosted.org/packages/51/7a/990e802222b3a88a284a872fc41339e39d8b619e5266aae45ef1c5da231a/hypothesis-6.165.2-cp312-cp312-win_amd64.whl", hash = "sha256:e2493b71a6e75dbd9ab33f8ab3920a6850a7965de55baf9725738a227ef3bfd2", size = 670805, upload-time = "2026-08-05T21:32:19.278Z" }, - { url = "https://files.pythonhosted.org/packages/22/01/add18f19d5e5f084a59709f0dcebf3cb1edeca475ce8a31573dc33891e66/hypothesis-6.165.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e2bf15d05264ec9da8d55c3843902f906ced5e84fb3da924eafe165697ab4638", size = 783183, upload-time = "2026-08-05T21:31:55.305Z" }, - { url = "https://files.pythonhosted.org/packages/8e/4b/df2e4c24d208518a6a3dab7acabad7f5ec6c5bb0f4d0bf1701d2fb7206ce/hypothesis-6.165.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3748153d4f64d347f8c988dd41fbef3513b66819e73e9209b1c501bf0d716a13", size = 774829, upload-time = "2026-08-05T21:32:01.891Z" }, - { url = "https://files.pythonhosted.org/packages/72/a0/b75a001efbde704ff2188924a4d4bb3cdf7a22924dc51c155115af64858c/hypothesis-6.165.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f689976e0eb578afbe8ce37669cb637f00f7c545ad303412947ee4abe2f29ce6", size = 1105224, upload-time = "2026-08-05T21:32:35.189Z" }, - { url = "https://files.pythonhosted.org/packages/71/f2/4942f510c6441b5d60dc7f0d8d2af74fe444b62d3af565bdf42d9b6b803d/hypothesis-6.165.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7c68a5684b2e2ad3c33500198a6073b3d04493fd7b1ef34937645ad092b797d", size = 1155166, upload-time = "2026-08-05T21:31:46.408Z" }, - { url = "https://files.pythonhosted.org/packages/48/11/405ebff50c6949518d34f487017412d5b8f8ee9239e50ecdfdd99a745f4b/hypothesis-6.165.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b40db922ccb53fb77c68d944748a3eb5b945402967b64093d9ba970d028cf1af", size = 1278170, upload-time = "2026-08-05T21:31:43.116Z" }, - { url = "https://files.pythonhosted.org/packages/73/ff/93ad0f4b55100876604d2c316a8c6e0ee037cb0da925caaa478709e504b0/hypothesis-6.165.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d2fd48ec969b2dbe1c8e25dc86d199c6820b9222846469449b997f5546383378", size = 1322061, upload-time = "2026-08-05T21:32:07.217Z" }, - { url = "https://files.pythonhosted.org/packages/14/37/14b655c664a957e44c7f59d9498e53d79b55f1b752a1bb38fee9da403e9c/hypothesis-6.165.2-cp313-cp313-win_amd64.whl", hash = "sha256:9cf13225121280036ea5a8ff8babb82ec27a4736aea669bbe0bc9839d254575f", size = 670824, upload-time = "2026-08-05T21:32:21.25Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ce/a2de75f1a12b6670edfca890794daab685a63ab1a2e36f28dc2c4d8e831d/hypothesis-6.165.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:66be9b848bdcb29132b18de6f574b89b392024c7de442acb393edaa1540cb548", size = 783398, upload-time = "2026-08-05T21:31:37.916Z" }, - { url = "https://files.pythonhosted.org/packages/02/8b/bf01b5f356f64a8af28be2718674e23ff7c0a4dbc5f476295be624b1a5ad/hypothesis-6.165.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e51742efe8466cf89e26cb94843db8854bd0673a6d80da7e3d1ff6bd9dc006db", size = 774963, upload-time = "2026-08-05T21:31:10.053Z" }, - { url = "https://files.pythonhosted.org/packages/a8/4b/9ad06c5a5613b6d175a7fd1a518a9732bcc4772c0cb24f6d7e5fd63f7fed/hypothesis-6.165.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c648ee54cd734261e1615d80c2ebbd679d6dfbba6ef5aa672354c6ca62b6f446", size = 1105721, upload-time = "2026-08-05T21:32:29.252Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f1/04c8ebe621c8b832106859f9425f91d049a96ccf99c3501f2905f3e1291a/hypothesis-6.165.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:032292cbffc0743b2fe4337a30c21ea9703a70a0021d39bf0c3e68bce7baab18", size = 1155349, upload-time = "2026-08-05T21:31:39.636Z" }, - { url = "https://files.pythonhosted.org/packages/6e/23/41fe5e805638dcb6a1b70c147e909d254d9f09db5ade7a3e790c94ec926e/hypothesis-6.165.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5dc64171b06472f0b6c2e54bdc25687987e560e15133cf52f55d9ef4c747ebe1", size = 1278502, upload-time = "2026-08-05T21:32:23.405Z" }, - { url = "https://files.pythonhosted.org/packages/ce/42/54fdfc954314980d2b0eabbe57ef2960d85f3b1acb95f3326ff931ed349f/hypothesis-6.165.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8e5a823ba918641af8177c121f122964d471db2ab1d07c1fe229c77b3a5ab7e8", size = 1322379, upload-time = "2026-08-05T21:31:05.39Z" }, - { url = "https://files.pythonhosted.org/packages/68/db/3667633b31b2b423b320fec4b7ac154e74d6b4a122fadd8e06f9d9afbef1/hypothesis-6.165.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:70966ab7dbe0ea9644eaed8324e037f05ac6a8141646b977da9e77813dac6ed7", size = 614909, upload-time = "2026-08-05T21:31:41.464Z" }, - { url = "https://files.pythonhosted.org/packages/2c/d9/0168c0d6ea32c195225b0673ee8cb7b61de6841d62c87d614d26add35770/hypothesis-6.165.2-cp314-cp314-win_amd64.whl", hash = "sha256:a5b913acd896f4f80597dd38966cd13984a1cfd45512498e8cf054aa7c92ffb9", size = 670684, upload-time = "2026-08-05T21:32:37.076Z" }, - { url = "https://files.pythonhosted.org/packages/21/e4/61cea938488b6958f07b8249bf37fcfb2802367e2a6af65afe82b05ca18c/hypothesis-6.165.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:6fa46589088966083ce653f908560cdd3330274cfafaaa65d1fb29b5f6681644", size = 781982, upload-time = "2026-08-05T21:32:15.899Z" }, - { url = "https://files.pythonhosted.org/packages/15/73/b4f9ba3e4b988b567d887b0857962e841b227a81a02ea83ae520e2001c31/hypothesis-6.165.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6b5b922603879b4788447583928eb1cf2e1aafb9ce27f3a7234b7a4557d089a8", size = 773430, upload-time = "2026-08-05T21:31:28.913Z" }, - { url = "https://files.pythonhosted.org/packages/9c/e2/6fb4a2edbc7775dfa4d3950e3537239c6d957eeb6c571275edfd79a2b981/hypothesis-6.165.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ab3a6b5bb3302f7dcd65b07dcdc0ca353c8c151567dffeda826977e765caaf9", size = 1104317, upload-time = "2026-08-05T21:30:57.765Z" }, - { url = "https://files.pythonhosted.org/packages/5d/06/9479b2acc58996ae18300becbaf910d7c542b5054a47ba05c28bdacd08f1/hypothesis-6.165.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09f1c626023b68968d2cc5fe1e31548109f4406d81a2c3017b3de9fdd3a02e7d", size = 1154232, upload-time = "2026-08-05T21:31:30.368Z" }, - { url = "https://files.pythonhosted.org/packages/e3/9b/5b5cfce5445a807d042ca5a1a470606613835842d99488a199d994584cae/hypothesis-6.165.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5c95808ab851498513192268e25f40bccd1c3719384c91535bbec3eedea39760", size = 1276739, upload-time = "2026-08-05T21:32:12.324Z" }, - { url = "https://files.pythonhosted.org/packages/2e/aa/6a731776ccaee13dba0624a73f01935fa174f39647ad463a495dcb2206a8/hypothesis-6.165.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a1c8bec789f21dc10620ce99e15fcd4f7737b9b4cfa571cdd93e01a17b7b06b", size = 1321119, upload-time = "2026-08-05T21:31:21.113Z" }, - { url = "https://files.pythonhosted.org/packages/43/a1/5d2c7c1346a0089908a3974c9e40ce223c22dd291dfe5df69a8c8cc64b98/hypothesis-6.165.2-cp314-cp314t-win_amd64.whl", hash = "sha256:458c891dfc00133bc4ce2e6c9716838f80f2fd96caf306f5eef42ad02aa4d972", size = 670831, upload-time = "2026-08-05T21:31:17.998Z" }, - { url = "https://files.pythonhosted.org/packages/35/51/745695dde335122e447dd8f762ee1c2f44d5f73dd3268a6df7f5d4339c1d/hypothesis-6.165.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:210955bdb78ce0e3279fd7ed47fa77fdd9f6004368a099c933c456f4c6ea358f", size = 783104, upload-time = "2026-08-05T21:31:02.564Z" }, - { url = "https://files.pythonhosted.org/packages/a4/8a/26f37eb2265ec3d6d59fcac7d93d554231ad2db7cef4e9e78113fa39ac36/hypothesis-6.165.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:024ddf78d150825210b4e41169ad1f3d40f8819bee0304890b851904d9f97541", size = 778956, upload-time = "2026-08-05T21:31:08.731Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ef/72d586c2c5094410569a4e62792f0069dc0b6113979dd7754e80d03a800b/hypothesis-6.165.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49e9aa39b21589e64b25ed8a452014fb546f2533cd5dbc0be16c35f94a4a9f6c", size = 1107828, upload-time = "2026-08-05T21:31:19.457Z" }, - { url = "https://files.pythonhosted.org/packages/38/1b/292fd8f7552d624ed29abab7f17935009cae3de14e50991951a73cff2a25/hypothesis-6.165.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:568038c8c9de3b22087fe1536d8d512e23c5a5eca3aa4c692e8fa46592afa267", size = 1157603, upload-time = "2026-08-05T21:32:39.041Z" }, - { url = "https://files.pythonhosted.org/packages/47/10/005eff3fba214bac14df88fdb78502d4990a135b128837dc826b215aa746/hypothesis-6.165.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5a9c92f193b14bb0e69038cb4b31f3c0c7497cd3f2f43be5bb8a8fb6a036362d", size = 674478, upload-time = "2026-08-05T21:32:41.203Z" }, + { url = "https://files.pythonhosted.org/packages/05/c1/9a9538e6d185baf5cc7f15bc3b76e08efbb3de4b3c782f234356449c0dd7/hypothesis-6.165.10-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f839d29d0cc12048cf073d88ca4fdf94d420bc2b8afd69641ff6d496422ccd4f", size = 783243, upload-time = "2026-08-16T22:55:44.058Z" }, + { url = "https://files.pythonhosted.org/packages/a1/30/b70d9d79e871a75cbdeccd9067f20ecdb9eb2a1dfa03c630be3ad13b8b30/hypothesis-6.165.10-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e10858f57ed0e74baa04393845f469fe8ad502c16ece4499bef7700c575611bd", size = 778815, upload-time = "2026-08-16T22:55:46.948Z" }, + { url = "https://files.pythonhosted.org/packages/db/52/6f0a9b7aab24b0635e2238f3fbddea5b54b17879ac813df42a3cc3384c5c/hypothesis-6.165.10-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76a7be86d986223b9f1bdb7e7cbcdb048649901fdb956c598ef73bdab1786cd5", size = 1108009, upload-time = "2026-08-16T22:54:53.082Z" }, + { url = "https://files.pythonhosted.org/packages/f6/06/8d0d4e11ff02350d09ec9f9e90af354158e59e16a8907ba5199a4ff2d7e8/hypothesis-6.165.10-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:717aea574e0e5edba2868aa66b1caae335d8f1ad3fb29f01dd6502953fa823a1", size = 1136596, upload-time = "2026-08-16T22:54:54.443Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/01a1e440f2e38dc1ccf5d597af5b8a0bee5f21b674c99c123b5554de9690/hypothesis-6.165.10-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4334058033e0214475f019e15492a50f3854fe8728cf51fe25c6191a2c3f8e52", size = 1135234, upload-time = "2026-08-16T22:55:08.911Z" }, + { url = "https://files.pythonhosted.org/packages/7d/18/8a26c24d3d9db20265f39df341ab265858c094e209571e3179cf237935f4/hypothesis-6.165.10-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2abb50cf1cf77d721de0a24c3f99d9c4ffdeb2cbd1e12aebb5a7a93e2b6b6d1f", size = 1157528, upload-time = "2026-08-16T22:56:02.159Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8e/ce3c829b1937402d7944420ca26a05a0c8563e894dcff03d34ffa279d306/hypothesis-6.165.10-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:3de69aa8b924b400291a3cc42aaf78e6ab65c905a3e7e1a5dc39d95ef1b428cb", size = 1112870, upload-time = "2026-08-16T22:54:55.919Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1b/4c4926d6c9a2b5d7cc090cc1e91219d6796102aa2a2c4b8f961c939e60b5/hypothesis-6.165.10-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5841331c504e02d7c334591681cb8587cdd59dee7e149db6d3db8e3f9e9f02eb", size = 1149683, upload-time = "2026-08-16T22:55:30.567Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f9/df24eb28412f82465e2b7707f0ff1ec274d580bce389d4d9156617dc7bba/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2d0e0f8263d34dd8fa3b39eaa9a50bba56a8470b3dd9ebf6672d10840abe063e", size = 1283402, upload-time = "2026-08-16T22:54:18.054Z" }, + { url = "https://files.pythonhosted.org/packages/4d/07/c2b2a761300cf60b90ccebba4328175331e67d34f4fbd39429a7ddcdce49/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0c4e6869817c3cfdf5a2b4d348497b95159bdecb3365be732c9b8570e36a4eef", size = 1409948, upload-time = "2026-08-16T22:54:22.343Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ec/1c2bf1acdd0e273d81f833f85caf0ae5423db68a783554992fca36e6c541/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:9f07ae36c3b093e13687a894e79fe69e98a94c0b67fef656c575247682218143", size = 1265023, upload-time = "2026-08-16T22:54:41.402Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a8/7f984908b7391160c7801b84e51ca8e4ba88c89e8d8811aa1aa7c03de73c/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:aff1f584c9538e8979cd180b1d70bf99bc16be19d4666414f49e5942b21a4f2c", size = 1282698, upload-time = "2026-08-16T22:56:06.998Z" }, + { url = "https://files.pythonhosted.org/packages/48/78/3a5d91c2d0250521736c42dfa2402b75049bc5fe2fb716c10bc84bb91ed1/hypothesis-6.165.10-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1f2c4db25fb8ec1a16a8dba580666337b8ffb1887c4cf1750cc954313897cef7", size = 1324816, upload-time = "2026-08-16T22:54:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/6f/99/27450763853a034bca1574d3e0a315164b33ff49c3862df6872dda45e25e/hypothesis-6.165.10-cp310-abi3-win32.whl", hash = "sha256:b33dc30170a7402e03c180f2c5ef69dc077152f35b91621e9cebcde9c7d71746", size = 669039, upload-time = "2026-08-16T22:55:11.962Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fc/ff2988b72b5705ad9ca500444bf3f43e3c2f41edfa034bbfeb23b215791a/hypothesis-6.165.10-cp310-abi3-win_amd64.whl", hash = "sha256:e9f924aa610c0618445e1e8738c822c3190ce2a2699a0cb48ec3a351a96761f2", size = 675213, upload-time = "2026-08-16T22:55:01.697Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8b/821810d36f78d9d9421cd2c5d9d36983b45bb3575c3086276cc5c76f9f73/hypothesis-6.165.10-cp310-abi3-win_arm64.whl", hash = "sha256:1d305448e9bd8e2f4f3cea0eafd809efdaab4e998a0019bc615650c8463e42f1", size = 673537, upload-time = "2026-08-16T22:54:47.898Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c2/b9546ace11f241c9c02d389f258cb80c14447a8c885771c9f1f0bc1d85ca/hypothesis-6.165.10-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:592107a0faf6c9c3a63a8dbf13dfb1cbda1cf599b0bc11c953221b00204b9ce1", size = 783716, upload-time = "2026-08-16T22:55:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/37/10/27c2fdd574fd798caf5e91eb51f7834b098f5d840ce733efb3fba79ef86e/hypothesis-6.165.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9180c362bde06fd05380298ded4e234fbc0d6ede0a864835bfd91c1e24283d5", size = 779507, upload-time = "2026-08-16T22:55:07.633Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b6/70bc23695f3783c4b0486b6cad47b08a20f791db4a3c1b25250add9659fa/hypothesis-6.165.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d623801ae3dcd97b77b983400ef3d48bf976648e4efff19929175322eaae074d", size = 1108406, upload-time = "2026-08-16T22:55:39.653Z" }, + { url = "https://files.pythonhosted.org/packages/71/4c/32e200bd7a352af4b7f4e3729aaa4cd002cb5fe8c4c6aef5599d0019f152/hypothesis-6.165.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20f6236cfb90b7817bb1a6a087589ca4aa46d73170f0dd62963952ed5dadc589", size = 1157850, upload-time = "2026-08-16T22:55:24.394Z" }, + { url = "https://files.pythonhosted.org/packages/03/a5/8efc2a9a484822efc0d0da466f50094e0f2c068187faaf33831fc905873e/hypothesis-6.165.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad0764730e8e3421601c2cc7e1f054a9206c60ea0917165d8d9193dc453f34f1", size = 1283704, upload-time = "2026-08-16T22:54:27.279Z" }, + { url = "https://files.pythonhosted.org/packages/46/2a/90cc8d7463929c04786f29600de45f3227c12fa9bed1d5b7ce319b05e1c9/hypothesis-6.165.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:10d9a650a4666b0914831f769703d36140ed8039fd19bf9b71f615b8541eccf2", size = 1325077, upload-time = "2026-08-16T22:55:16.561Z" }, + { url = "https://files.pythonhosted.org/packages/82/ac/bc16faba4b42883e3d290bfaceff51e258b63fbbdf789bf9fe88df1ce537/hypothesis-6.165.10-cp311-cp311-win_amd64.whl", hash = "sha256:5671d2b2bf83bd4b6f02e55b32d432506eff5358c82f39b460a849ce19a2666e", size = 674920, upload-time = "2026-08-16T22:55:42.613Z" }, + { url = "https://files.pythonhosted.org/packages/e9/45/cde4f78afe2b9e29caecf38319eedc1deb76aebcacbdd128e03cbb2511c3/hypothesis-6.165.10-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:637445c1593a2a9d1024fda50082f07bb56baedda78d90a25f64b8111727ef94", size = 784835, upload-time = "2026-08-16T22:54:45.429Z" }, + { url = "https://files.pythonhosted.org/packages/7f/81/847f30b81cbfd07607296b3ce43067cf4f80799bd9244167f587de9c8081/hypothesis-6.165.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:713f4ce4e82c26b53031f139de959bc9e8b54d3995aa824b89bbdf8229df2a45", size = 776419, upload-time = "2026-08-16T22:55:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/04/66/4c71c5be7a49d84b8c3a9278c1807c4c81181ab5474beb27df9d4c40dc0e/hypothesis-6.165.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9ff356e97e3ab09db07c8b675efa67340103874a0bae7465acb83dad7a35f7f", size = 1106830, upload-time = "2026-08-16T22:55:10.389Z" }, + { url = "https://files.pythonhosted.org/packages/e3/c4/e2cbd2810e79f7a452a8ea9f6c6438ee718ce938d8cc12252cf0b36a81d3/hypothesis-6.165.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a380bc99aa3b035e6a95a2201bf792d4082a04ca75babcc21849c2d0914bb28", size = 1156952, upload-time = "2026-08-16T22:55:53.35Z" }, + { url = "https://files.pythonhosted.org/packages/a8/8b/794ced36864825492ac3712d5acab5a257b4601e6a9dc2ccdd3937198f87/hypothesis-6.165.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9acb2c4d9cb532c3fedea74159f7b923c8c036328c9239b4049e7aa073bdd81", size = 1280780, upload-time = "2026-08-16T22:54:34.983Z" }, + { url = "https://files.pythonhosted.org/packages/5d/2d/550525442cdbcc2daf1f9bdd8ba35bcbde63db7c7a22f2ef137fbb49df2f/hypothesis-6.165.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8660572b2d424bf5369ea8990985225f70bd1615b76ecd9c25588a3b9307009f", size = 1324130, upload-time = "2026-08-16T22:55:48.659Z" }, + { url = "https://files.pythonhosted.org/packages/74/59/6caf69dd5fe03499ada94c9cec016bffcc164511c6b93fe680f01209b9ff/hypothesis-6.165.10-cp312-cp312-win_amd64.whl", hash = "sha256:3376f2594763aef14faa519b0fb27cae7ce9eeaab4c69efa07777499110306c9", size = 672337, upload-time = "2026-08-16T22:54:49.11Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fb/c82c5bd92864ffcf319772fedc8c9bf2dbe4ca14baa0fee6e49e67b5ba1c/hypothesis-6.165.10-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9d77c3be7b429875036ad0f0597c6e5cc6bb17894a4da005e3807de64d2673ad", size = 784726, upload-time = "2026-08-16T22:54:32.371Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b9/3d7acd08506da85557e65147b7f3fca8c47684e33be90bee0acb523920db/hypothesis-6.165.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:490c56b830772b0eca3b4b2cecb3741a1ed26b1d7206a279e1525dbf0aa95ee4", size = 776375, upload-time = "2026-08-16T22:55:13.303Z" }, + { url = "https://files.pythonhosted.org/packages/38/6b/922e8b3f9a706dd89d440b9545d2c6231c65e74da1c1fee3ff36c251b9c4/hypothesis-6.165.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed68e27b8a61e57a3ccdc7c5a14499e00b54dfe223087204d5d40b3b5ef58b6d", size = 1106763, upload-time = "2026-08-16T22:55:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/01/39/f5b9a5d390d4edd1ad472334493ac442963ebeb4daaa74ff4bdac6ef292f/hypothesis-6.165.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6caadcd1afb62630ff5c5ff353626eaa616553a5971295ad6dc2b19ca8a39620", size = 1156778, upload-time = "2026-08-16T22:54:33.824Z" }, + { url = "https://files.pythonhosted.org/packages/b5/5f/5fbe1be4326337fd6acefe2d18ed44007ee1dc1f98fe5b3c0eb22942364d/hypothesis-6.165.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9145fe43ebb22e66672967c3fab411793b226ed776e4fe282271bca6ad3c0bb", size = 1280756, upload-time = "2026-08-16T22:55:54.834Z" }, + { url = "https://files.pythonhosted.org/packages/25/c0/cf6f9e1ef632a1a75694eed0db3a02e6fc75c367a363e94acee52f043c64/hypothesis-6.165.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79900a9920a0b1d3a626c03a90ac6bf7042e78d46906a565b86a0dbe926f1d96", size = 1323889, upload-time = "2026-08-16T22:55:56.567Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cc/662b94880f260b0a88de1fdcf60fc9984f6e2a796da549542adc10a7bc83/hypothesis-6.165.10-cp313-cp313-win_amd64.whl", hash = "sha256:c01dd04044c472e47193b54f68e84e08d6ebf4f29551885aa959b015f7cd9747", size = 672346, upload-time = "2026-08-16T22:56:03.792Z" }, + { url = "https://files.pythonhosted.org/packages/3f/77/55e020c9c576532ff7d20bf8b1dfa052ecbd5ada1949b02f76c44c966f7e/hypothesis-6.165.10-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9ccac776b2ca93b324806facd526ccb45da0fd035001c899a35b02c44431e209", size = 784833, upload-time = "2026-08-16T22:55:21.255Z" }, + { url = "https://files.pythonhosted.org/packages/4f/f2/01da2adf829cf549eaddcabb8e8072077fb3d26da4275f4c1e89b2c0af74/hypothesis-6.165.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e5f95f7b622e4171096d92175dda0a560f0955ade9b8a3a07bdcf151f7359611", size = 776545, upload-time = "2026-08-16T22:56:10.159Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/58d4f842895220b793c53fc94a6489705b3665bb4d0ae4d338ce03fdf9fb/hypothesis-6.165.10-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f76d1562643693b8a40066f1f96af795b93fd9bcfc9690a1af2ff4c5867ee29e", size = 1107271, upload-time = "2026-08-16T22:54:50.266Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b8/206468912d2153306bb8a41afdfc59e45b7a73a0495bbe4b9cb4f0e79c1d/hypothesis-6.165.10-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60cab3ab4ea468d31a33739ffd7e94ec3e37dea891d65a6582ecc8a477175191", size = 1156915, upload-time = "2026-08-16T22:54:25.89Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d3/bf5a22929b70a4cfd3edf69c5642b029b27ddb5cfda48fa295d384b01abb/hypothesis-6.165.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:22cf19388f0ff6ced8eb3e49c903d14938e4ed909d93bf28383eef451511e424", size = 1281205, upload-time = "2026-08-16T22:54:44.083Z" }, + { url = "https://files.pythonhosted.org/packages/07/a2/d7b2ba444d36fc84d4779f4431e74dd9b023dc63bcf282199f6e48ad39f4/hypothesis-6.165.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:057d0232f1224dcd0b7698902551a4341a7399f90670b036db6c4376715fe889", size = 1324243, upload-time = "2026-08-16T22:55:41.123Z" }, + { url = "https://files.pythonhosted.org/packages/d1/95/afe6b531fd01928c6f63d394ee413fa2338d088b2b44efcc23596b54477e/hypothesis-6.165.10-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:ab0f2e9d7d7d4db257f7cf53de3706c2baf124269571f20ffc2bcd6781f03063", size = 616382, upload-time = "2026-08-16T22:55:18.449Z" }, + { url = "https://files.pythonhosted.org/packages/48/86/9b4fb75f520a028edec50ffc904a94d724180395d71feb6d7a0ce7bb6f00/hypothesis-6.165.10-cp314-cp314-win_amd64.whl", hash = "sha256:d1ea02fa8ab3d33eb1125eade81f7136341eb429152c6dbe2ae6f8bc33b3fbdd", size = 672145, upload-time = "2026-08-16T22:54:24.831Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ba/f7bbaae0c789bab7ddb764d2056ee1a463cc95a8acbccc90d4184e48b242/hypothesis-6.165.10-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ed1a5891e59472884a03cb9875483e8fc131c80a275c60967f8afc5458a0c8ff", size = 783287, upload-time = "2026-08-16T22:54:23.751Z" }, + { url = "https://files.pythonhosted.org/packages/3a/83/01ef80772b4abd335c49405576dc503cede94fb5da30ba2643a119013aea/hypothesis-6.165.10-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:09772e328a26e50486ac572be34f9887f9aa185efe7ebb16bde4e8f6038db1f4", size = 774991, upload-time = "2026-08-16T22:55:25.987Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0b/f47506241f9d5a5a2efe4c65b6bf4830e9d9576e5d3779007a260699e608/hypothesis-6.165.10-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cf3b612542ba174c9da4000b59a4f4c81e8d66f87509be85d3a1b71b5c36413", size = 1105499, upload-time = "2026-08-16T22:54:51.864Z" }, + { url = "https://files.pythonhosted.org/packages/84/fe/abb3909b7089835112fbe75bf00d817d733b3a8032759783db0a24ff1e56/hypothesis-6.165.10-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69ec5be85ef508e206153bed8eafd03f7995dc464356c8bbb279a1e2b7d56f3", size = 1155685, upload-time = "2026-08-16T22:54:30.94Z" }, + { url = "https://files.pythonhosted.org/packages/73/2f/1964738921640184067121ae77414522fc3f0463fc26c6e25a4f3b8e42ca/hypothesis-6.165.10-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:dd207497bb985918409a1bb5db85d1875f74e1269487332113b73d1ee7c77647", size = 1279177, upload-time = "2026-08-16T22:54:40.179Z" }, + { url = "https://files.pythonhosted.org/packages/34/c5/312af8ae038d3af9cf3f7f1021c1abfe31c0d9035e4cf63519e0a7dc983e/hypothesis-6.165.10-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:00de0abdcf8c05c9d0eab735a3c49a276376b55151e6fcb903c2b39a90e5e5c3", size = 1322921, upload-time = "2026-08-16T22:54:42.7Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e7/b0a2fde7570c090a1b914026266a421c751ef10138fffe37fe0ef9e675c0/hypothesis-6.165.10-cp314-cp314t-win_amd64.whl", hash = "sha256:cc2da5aa4edf14743fa9257e5ba3513963999f01211635702479d8e92b8207c8", size = 672147, upload-time = "2026-08-16T22:55:27.527Z" }, + { url = "https://files.pythonhosted.org/packages/47/fd/985aa564d6ffd06483d45a62b40d319df0a703cd8bc1d041de17d102fbaa/hypothesis-6.165.10-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:eeab73050ea58c13dd56e329f594c1dfe32ebd7bb169bbdf4f8ceefbc31ec6b5", size = 782882, upload-time = "2026-08-16T22:55:37.93Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2c/6cc11151e450f72353a490940cd0db704680d07b78dc75dcc9f480e0d0e1/hypothesis-6.165.10-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:4c68e983d0007d014bb01ad4bcbba78bc432c73a1755ff36d5102ceefa18299a", size = 774584, upload-time = "2026-08-16T22:55:51.822Z" }, + { url = "https://files.pythonhosted.org/packages/10/39/ef26fa79c1738dfe9cdb1a3584fb6717d26429ca6c9d011cc4fdf08130c2/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7730d8197086f65d8969a991d6728a1d420a51b19fea06535c896cb43a1e05d0", size = 1104876, upload-time = "2026-08-16T22:54:58.937Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f4/3fcc84e7637f42bf00d987093b9418083ac8db81b87392608a60f4b7c5fd/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7a7980a898a3e6ebe4de1896a0507e3d519edb53fb9b4bda478c9fbeb6514558", size = 1133353, upload-time = "2026-08-16T22:54:28.635Z" }, + { url = "https://files.pythonhosted.org/packages/35/59/21c5c14179c38f8d0de3560e7f1825c083311b3013b63f817d7dc78dfcbd/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5820d009aedb7ae9cfd32f98b1ab0c0bbd6268379c4fab042218b6b655c63f8", size = 1132300, upload-time = "2026-08-16T22:56:08.539Z" }, + { url = "https://files.pythonhosted.org/packages/14/af/fbb56059961e416b2de7b9dc5352db2e8572bd5ea46892957e4c1e5548ab/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37a7ac3d34220800e1107871cc391bca1b00439875925d7d821878b8b791f245", size = 1155175, upload-time = "2026-08-16T22:55:19.824Z" }, + { url = "https://files.pythonhosted.org/packages/0f/53/77fb0c2dad445858555429c4e06cf94a59ae8d2407dd6426b5af97c84828/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:dafa7c9dbe3d802f9bcdf261b29c8a70700fb22839947f06e471f62c46b6257f", size = 1109881, upload-time = "2026-08-16T22:55:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/a8/7b/d187f673ff30e6ada640953636f978ffe64a6332f756b64163c2277f8d0c/hypothesis-6.165.10-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:90915635b9648071129b0f72c0673cf8eac9eb84cfd445c5bedef30c714b1ec2", size = 1144963, upload-time = "2026-08-16T22:56:13.428Z" }, + { url = "https://files.pythonhosted.org/packages/e0/60/31d504e364134d60af23e5f6365db0da3cf4a51b3ed3d4836e5a2cff12cf/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:e1bbeb7c506b07ee0422cf9b2f7212fefa4240957f03526d38d27bc6743a0a48", size = 1278684, upload-time = "2026-08-16T22:55:22.971Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e6/89d26834a08c02f8da149e541dd40d7a96f68d9722f43146e69a77436ed7/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:2b36aaffc88625a44f91074c5bbedfdefb9b376c38d1b3c342edcd2e4c8ed16c", size = 1407202, upload-time = "2026-08-16T22:55:14.949Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/20d1e72246867ea195440092e8bb422c7ddc2f271b87b5b65679d5532719/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:18a3ea838ddea183388f8788750afa8494d79abb5358823be9782585f34445d3", size = 1261395, upload-time = "2026-08-16T22:56:05.448Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9b/ebab6c3c2b90a16abb4119198178652d12aff83cc8ec2cfde5276c69fb1e/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:2a2567b3a03a4a5a7c575c191cfcce321a967df3727803817e75bffbbeaecabe", size = 1279213, upload-time = "2026-08-16T22:55:35.066Z" }, + { url = "https://files.pythonhosted.org/packages/23/78/69b219b524231d36eb20c792e1f01e7cb037e02bd0af1c29f77ed9a969c0/hypothesis-6.165.10-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:8001925fa3dde51cb574e4c9de4c7efe77c4e4d64bd2fd2ef61d5651f9d04f3d", size = 1322367, upload-time = "2026-08-16T22:54:21.279Z" }, + { url = "https://files.pythonhosted.org/packages/55/63/ad5cc153dcc72ae5e7905fb9b3585f3e48ce892a2d6366f90163e867a69d/hypothesis-6.165.10-cp315-abi3.abi3t-win32.whl", hash = "sha256:c6559380469295c4009215fe1cab561301591a3bee2e2fb3f4f96d2273a3affc", size = 666038, upload-time = "2026-08-16T22:56:11.797Z" }, + { url = "https://files.pythonhosted.org/packages/80/32/b62307b73fbc99f0a4381d6f9456df76fbcbb7a27ef7256e26f0376f48ea/hypothesis-6.165.10-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:30797f20ca45e57f526d2df872f63ba453cb4e1091ad542184a7a951af8da79d", size = 671941, upload-time = "2026-08-16T22:55:00.235Z" }, + { url = "https://files.pythonhosted.org/packages/c2/dd/e0f98add0548ef73ea7afac45da1fb8efc854d7f9931db568754d0f963f3/hypothesis-6.165.10-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:c53e9b1c36350df9965ec44d6c0d4e0bbbb38f720dd2b0e1256dc6524d411015", size = 669931, upload-time = "2026-08-16T22:55:50.205Z" }, + { url = "https://files.pythonhosted.org/packages/0b/6a/880d6eeed5c451fb40a66733dadec4a5d498628a4a7f6a8a5f633f4c6dcb/hypothesis-6.165.10-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:34ee6402df6f31274d89119f1561b5f7489c97866afc5b7a3ed3a13d7e762802", size = 784644, upload-time = "2026-08-16T22:54:20.127Z" }, + { url = "https://files.pythonhosted.org/packages/27/e0/9e942bd3c3cf5ea0d5c0fd0905893bbfb6cefb7284c70fcc8033f8fdec38/hypothesis-6.165.10-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:277f41801e88dad2eba082f91a75632b7584ff64044ba2cf9dadf511b0d19cd0", size = 780515, upload-time = "2026-08-16T22:55:04.676Z" }, + { url = "https://files.pythonhosted.org/packages/19/32/f11a618415dc5fa9cdde41fea56c489f0814759527ae1ecd11a75a4558b9/hypothesis-6.165.10-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72df95fb1db41755b155c5f02106e0036a339250555c8d351d488704fd112cf9", size = 1109374, upload-time = "2026-08-16T22:56:00.241Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6f/db49b719842297c2b71e0d81e5b8967d31215fb7389421abcb465ce7ed3f/hypothesis-6.165.10-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e20a02775eb3cf0ffb4f0219b6d7c1f240336663d4e5d7028675ec247c790c4", size = 1159092, upload-time = "2026-08-16T22:55:58.57Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2a/bf0bae84ba1cb3923d295973f1fe38ee867eaf90119e0d559116083be300/hypothesis-6.165.10-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1ec53f08732e3cfd0342cbbd75dbd1b193c8f19390660466e536a748bb81f757", size = 676045, upload-time = "2026-08-16T22:55:45.514Z" }, ] [[package]] @@ -786,7 +805,7 @@ wheels = [ [[package]] name = "mining-dashboard" -version = "1.19.2" +version = "1.19.3" source = { editable = "." } dependencies = [ { name = "aiofiles" }, @@ -817,8 +836,8 @@ requires-dist = [ { name = "aiohttp", specifier = ">=3.14.3" }, { name = "diff-cover", marker = "extra == 'test'", specifier = ">=10.5.0" }, { name = "grpcio", specifier = ">=1.83.0" }, - { name = "hypothesis", marker = "extra == 'test'", specifier = ">=6.165.2" }, - { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.6.1" }, + { name = "hypothesis", marker = "extra == 'test'", specifier = ">=6.165.9" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.6.2" }, { name = "protobuf", specifier = ">=6.31.1,<7" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=9.1.1" }, { name = "pytest-aiohttp", marker = "extra == 'test'", specifier = ">=1.0" }, @@ -826,7 +845,7 @@ requires-dist = [ { name = "pytest-cov", marker = "extra == 'test'", specifier = ">=5" }, { name = "pytest-mock", marker = "extra == 'test'", specifier = ">=3.14" }, { name = "requests", extras = ["socks"], specifier = ">=2.34.2" }, - { name = "ruff", marker = "extra == 'dev'", specifier = "==0.16.2" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.16.3" }, ] provides-extras = ["test", "dev"] @@ -985,7 +1004,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.6.1" +version = "4.6.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -994,9 +1013,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/3a/ddb78f32a0814e66b18a099377a106a2dcdce92d86a034d69d65df9b256e/pre_commit-4.6.1.tar.gz", hash = "sha256:03e809865c7d178b9979d06c761fcbfe6808fdaded8581a745bb110e52050421", size = 198646, upload-time = "2026-07-21T20:56:58.225Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/89/1f3e8e1fc3e97de0fa963495832f581f025f29471602a309e48808244292/pre_commit-4.6.2.tar.gz", hash = "sha256:8f5d7bfb021ecdbcd9d49d89847082dd24172ccde534390081a679ad046e2441", size = 198670, upload-time = "2026-08-10T22:07:18.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/49/bc925106abcdac498074f2cbe6137e94e09f418dd2b7775df5b577dc0313/pre_commit-4.6.1-py2.py3-none-any.whl", hash = "sha256:0e3b2942510d1fb34eec167a3ec57331bf8442122f1153a9fb8b58f5c49b2717", size = 226186, upload-time = "2026-07-21T20:56:57.064Z" }, + { url = "https://files.pythonhosted.org/packages/45/e2/bbb7129c9e7999a6b8ee9cca3b66486c25c423ab5a75f34071798b74ce94/pre_commit-4.6.2-py2.py3-none-any.whl", hash = "sha256:e2dde9a75d3bce11bd3831c26d134df00a2803c1d818be6a0383c3dcda25dc4e", size = 226202, upload-time = "2026-08-10T22:07:16.942Z" }, ] [[package]] @@ -1302,27 +1321,27 @@ socks = [ [[package]] name = "ruff" -version = "0.16.2" +version = "0.16.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/e1/4508a569211b35599016e84ba65c1a992b7a4004b4b6c4bea02a851cba1b/ruff-0.16.2.tar.gz", hash = "sha256:c3d7828d12e8927a6fc65fe38e2c2541b9e762d360a1786d752cb1b8883b3c9c", size = 4885811, upload-time = "2026-08-07T13:31:01.432Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/b3/3213589383f8f1b3938781bd1278713f6d18621a14992b3e81fefb8a5ef9/ruff-0.16.3.tar.gz", hash = "sha256:e76d33a347661a84b5be6d043d0347fdc745dfdcf825a8f4fed64b5e26eebdf2", size = 4891904, upload-time = "2026-08-13T15:17:13.381Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/57/db19951540f98859c956b50bdb4d31089b4d91e9f15e2968e7d5193806d5/ruff-0.16.2-py3-none-linux_armv6l.whl", hash = "sha256:3c8de4cf2181f01d57946d87d777aa52916976fc09942aed89938fab5e013318", size = 10847925, upload-time = "2026-08-07T13:30:14.468Z" }, - { url = "https://files.pythonhosted.org/packages/13/5a/995fe85a8470d3e391ac0f7fa8054bb454eaf33ee138196d6172ed1079c0/ruff-0.16.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a48cc05c6fbc811ca81b5d7ba95375affea6582d1b8024e455e41afbbf55344", size = 11072662, upload-time = "2026-08-07T13:30:18.143Z" }, - { url = "https://files.pythonhosted.org/packages/32/53/370d767c61c71a971a4ace36703a7ecd8c393956349a7325d7fab2b56827/ruff-0.16.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a2c0d14fcbb26c91f0f867a6dc9bd71bbc30b1b6151829c884f23faeab2e5700", size = 10566771, upload-time = "2026-08-07T13:30:20.899Z" }, - { url = "https://files.pythonhosted.org/packages/85/d6/9d96948caf5a632be62d62202d5ec914d6856f204fd79eb036e5915e79ea/ruff-0.16.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:335c621622c4650330be50842561c6586ac6971bb8ab5407fe34dcc9efb16bbe", size = 10975825, upload-time = "2026-08-07T13:30:23.517Z" }, - { url = "https://files.pythonhosted.org/packages/3b/92/ea87129b3414acb0b5770563779c51804d37ac67675c7ba35447ddb14773/ruff-0.16.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20e66910f2c37cc753f9ef6580c914a621b80c4fa3549d3e3521e29d0f5bfc3f", size = 10649437, upload-time = "2026-08-07T13:30:26.097Z" }, - { url = "https://files.pythonhosted.org/packages/ac/43/f8f291dcd4af5bb7872b74fdfa41a7cd7c856ca1d4069670971cf1b9f5cb/ruff-0.16.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7e36fbfba65510548156902bcf1350a979a958ce0347ce0f90d73894036b39f", size = 11446761, upload-time = "2026-08-07T13:30:28.752Z" }, - { url = "https://files.pythonhosted.org/packages/71/4a/ef991fb2fcf516ab71f0808adcdd8da5e18c8cde447f4ceaf5f47a5132a5/ruff-0.16.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0eab35f80df8f134aae5d1630e751901321d317cc8e50dc39e36fa3ed34cd12", size = 12336364, upload-time = "2026-08-07T13:30:31.468Z" }, - { url = "https://files.pythonhosted.org/packages/f3/24/f615e74f307e6ca0e56a482872477b856c70d530aa356abfb6dfe5ca8a80/ruff-0.16.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ea8c0594feb894e89c8c61ab9c103d38b0ea72dfde6c594107147ca31b1140", size = 11630720, upload-time = "2026-08-07T13:30:34.426Z" }, - { url = "https://files.pythonhosted.org/packages/c5/d3/8ef50149e8412a77f7ab409efdef0e2b23803707a3863da4fc64cb23d459/ruff-0.16.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab3d62dde0b19facdd632008cc4827fc28ada7736c6bd35ab6f1050f0bfed53f", size = 11466130, upload-time = "2026-08-07T13:30:36.958Z" }, - { url = "https://files.pythonhosted.org/packages/dd/a7/a19334985c4dea8c381981fa252cd854c7ee52dc4b1686dc16f4a911c702/ruff-0.16.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e43e1f5b8388da9eca1b9e88328d47a5cec794633ccf6f7484ac2dd15eee92c0", size = 11523634, upload-time = "2026-08-07T13:30:39.822Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6c/96d192b0e742412ceda08c0a50f9669b253dde9fd6a60ea1a10c9fa79a63/ruff-0.16.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c24788a980581e1d7ea3a0cbe4344c4fbeb0a6a9b1f4713aa46bb104f8294690", size = 10949807, upload-time = "2026-08-07T13:30:42.745Z" }, - { url = "https://files.pythonhosted.org/packages/fa/51/e26599ceca11e79ee255c7df515995561edf87e9ca1893284e44d98f5a86/ruff-0.16.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:81806b08329130005dd4a8a8394a0c9da8c6f4cafb16ba438d2a2ee6a18bedf1", size = 10646891, upload-time = "2026-08-07T13:30:45.522Z" }, - { url = "https://files.pythonhosted.org/packages/68/01/800c4b1f97bc8d7c6029e06b1f20473a3cf1e13c4933d8f3342add83fc55/ruff-0.16.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4ce4e02bad779bef557f541a1b31f20d6abeae1cc05ed1b1ac019d4ffd1044c8", size = 11162063, upload-time = "2026-08-07T13:30:48.131Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d0/1477ea50fc5a0d4b0b71d1d63d50770bdd794d90b43e37a7618e63ec9894/ruff-0.16.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e0422abdf70070255fc4073ce9dfc814cc03db577013761ddd09bc1e4a9a4fbd", size = 11556038, upload-time = "2026-08-07T13:30:50.686Z" }, - { url = "https://files.pythonhosted.org/packages/b8/76/a7776f32048d991e16d4fa8ff91790b877342d3596cc3ed04acdbf1aaedc/ruff-0.16.2-py3-none-win32.whl", hash = "sha256:bf3a63d78fb39f4bf5ac8ae52051c5520505301abe19ba4e204c453b3f09bb0b", size = 10872850, upload-time = "2026-08-07T13:30:53.471Z" }, - { url = "https://files.pythonhosted.org/packages/00/0d/929c800d920e61397d82a01b60bffc68da3052c17d31de59efaad2e4ed75/ruff-0.16.2-py3-none-win_amd64.whl", hash = "sha256:bcabe2f6d0fc7819f1431793005af4e4de7371927d037345bf941252b195b9fa", size = 12023338, upload-time = "2026-08-07T13:30:56.193Z" }, - { url = "https://files.pythonhosted.org/packages/5b/6c/93e26c22c5f78ff87363e07da49c84955affbeb1098bd1936bf3b3f293bf/ruff-0.16.2-py3-none-win_arm64.whl", hash = "sha256:d614e95cedf38a2053fd351c55b103ba30d017d61688fdbfd40ee0412852a99f", size = 11374065, upload-time = "2026-08-07T13:30:58.775Z" }, + { url = "https://files.pythonhosted.org/packages/bf/96/493770daebd68c0a67f1549fdf519f53be51fc435186c0585bcc272fd76c/ruff-0.16.3-py3-none-linux_armv6l.whl", hash = "sha256:0c5710e247a58a4521e66e124ba9a74655b414f61ba3a2e9e3811e11098f48f7", size = 10902799, upload-time = "2026-08-13T15:16:27.382Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e6/2becf3942fddc29a29b8df47691d456fb1085391a694f74d84513251418c/ruff-0.16.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fe155130631a2471fd2e14a7a664a4dfbd7194b8229c3d7b2a40b21178639081", size = 11135539, upload-time = "2026-08-13T15:16:30.87Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1e/4b8b72f0d006dbf19326aa99f9ca0ee2ff374187c4d301cf529a51aa06fe/ruff-0.16.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e2ed719e14aa64d895c2ee922594a90a43c861a93f0575a95ff8c47cdbd13eb9", size = 10475095, upload-time = "2026-08-13T15:16:33.259Z" }, + { url = "https://files.pythonhosted.org/packages/92/32/2201fa49ba1f6c101ee321e83f051ac7a4b8d07b0ef6b4d3f2772b302275/ruff-0.16.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e0b1da805eb043654645d74d5de1e5ce2edc686e40790d2b86f56d71cc06a84", size = 10668771, upload-time = "2026-08-13T15:16:35.65Z" }, + { url = "https://files.pythonhosted.org/packages/c3/66/4afc5c8363bd04d45effce1b7c8713ca037d7a6740b7451a2403a6e3a972/ruff-0.16.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a37bdea0bbe21780f590bf437d6412c8c4e1b6cd010f91a65c2c40c5e5f5f870", size = 10699568, upload-time = "2026-08-13T15:16:38.195Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/c67d246bf36bf1698551c56de39e95cd07f70e64433e0098e6267d77061b/ruff-0.16.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09571e6d1288ed9be475207a3ac04ada404f1cd898104be0f6ab8d7df438575b", size = 11499365, upload-time = "2026-08-13T15:16:40.623Z" }, + { url = "https://files.pythonhosted.org/packages/67/0b/00ecbceb99a263af7b12f6f05ac3c92bc47b905e91adc3f207a836e3bc01/ruff-0.16.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c18c5a101eb540010638cc1ff3c84944d3adb3df62b8d98ca8f22ba484d3413", size = 12311728, upload-time = "2026-08-13T15:16:43.564Z" }, + { url = "https://files.pythonhosted.org/packages/54/b2/b7b3bb54f4d3f7db504e476ad4ab8de530dceebe2c061384b2757ee419e8/ruff-0.16.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8457c44f15033c85ddbb77b15d451df9e24e4bd03b628396dd3610cedc3b8f82", size = 11699896, upload-time = "2026-08-13T15:16:46.209Z" }, + { url = "https://files.pythonhosted.org/packages/c7/30/4c468429ac195addc5ee1b717b6ab1b66632786737ca3b2ed3443fb0c26a/ruff-0.16.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:294b95c4ae0cda9388525c2047778aa758d6b8d4bb876fd4e9eaa3ebc92343eb", size = 11058736, upload-time = "2026-08-13T15:16:48.823Z" }, + { url = "https://files.pythonhosted.org/packages/43/67/7a113cdaddf24b64d7f75b1242a99d04c82fcef4f6921fdbb832beaffb5f/ruff-0.16.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:3d0c7c40c87c2a820509c31ba007968da6e1306468c067b2d82fbfdbcd0e8474", size = 11586911, upload-time = "2026-08-13T15:16:51.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c1/2e66f24c0f3ead25a5e660111778685e505e5da353c82802bf49f0cbe7b9/ruff-0.16.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9f738c0fdfa8eed0b2ce7fb27ee7258208a92a68d7949e62aa15164bc7b389da", size = 10954265, upload-time = "2026-08-13T15:16:54.763Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ba/4cee23bf52cba9a058d3726de623624daf50ef9638868edd86f4126157f6/ruff-0.16.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fb785f0be25abe69d320415cd4f833b59e17ba7613d9ba6a958023b6bceb0a50", size = 10709886, upload-time = "2026-08-13T15:16:57.339Z" }, + { url = "https://files.pythonhosted.org/packages/82/df/7da7194fa5d9dc0a285f7e6fa5a4722e7c63faac0b45b614ded9314363a1/ruff-0.16.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c5536e3acfbf9563085aa2be7b13c629c3077e902afc5b941ac44024dbb9f506", size = 11210392, upload-time = "2026-08-13T15:17:00.171Z" }, + { url = "https://files.pythonhosted.org/packages/35/85/7795f6e817af050e7517bf3e7aa9b061cce70ef33d280aad902c956c1ecf/ruff-0.16.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a2d85c02f9b8e165d85e6779184d38c4132de12603dab59c51c28e22584f9e4d", size = 11626910, upload-time = "2026-08-13T15:17:03.299Z" }, + { url = "https://files.pythonhosted.org/packages/78/9b/475b927cf27a5cbbda3c7bafb69ed6ff77e1d7923d5d85f17c2749d7ae32/ruff-0.16.3-py3-none-win32.whl", hash = "sha256:388cdf2166642bd9b13d52b5932d3170f34f8abed7e8d9a855f1d84b83645a0a", size = 10931415, upload-time = "2026-08-13T15:17:05.726Z" }, + { url = "https://files.pythonhosted.org/packages/b2/99/e2a2bfc4fbf0a1e8a916bc9ebe6fe6c58cc34c28e0ffc6ce281d572d1c2e/ruff-0.16.3-py3-none-win_amd64.whl", hash = "sha256:e80a7d69ca2a6d1c4d352ec91458cdca6e56c83cdbcabd93e4abe1e53591d948", size = 11445993, upload-time = "2026-08-13T15:17:08.353Z" }, + { url = "https://files.pythonhosted.org/packages/69/3e/4132e539aed78c148854d4997a2685b0ed4dc4e87110b59ce528564e184e/ruff-0.16.3-py3-none-win_arm64.whl", hash = "sha256:b8ca152da82c1acc1fa8d5874b15951935f0eef46f10e6954c83859011b6178a", size = 11399302, upload-time = "2026-08-13T15:17:10.908Z" }, ] [[package]] diff --git a/build/monero/Dockerfile b/build/monero/Dockerfile index 54db6c4b..1d09f48a 100644 --- a/build/monero/Dockerfile +++ b/build/monero/Dockerfile @@ -1,5 +1,5 @@ # Pinned by digest (#135) so the ubuntu:24.04 tag can't be silently re-pointed. -FROM ubuntu:24.04@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea +FROM ubuntu:24.04@sha256:33ceb71981b602c1a7443a53469e4dba065f7503eab3078a2d7a57a2ab987517 # Install system dependencies required for downloading, verifying, and configuring the node # 'gettext-base' provides 'envsubst' for template processing in entrypoint.sh @@ -9,8 +9,8 @@ RUN apt-get update && apt-get install -y \ # Define Monero version and SHA256 hash for binary verification # Refer to https://www.getmonero.org/downloads/hashes.txt for validation -ARG MONERO_VERSION=v0.18.5.0 -ARG MONERO_HASH=166ad93036f95f5abeba24c8670061be022c9238dba2e6a7587611a1d759e294 +ARG MONERO_VERSION=v0.18.5.1 +ARG MONERO_HASH=22a7dda7b0cb699fdd6b7674c3b4a4465b337cc98a54983523b759e1e7cc9958 # Download, verify integrity, and install the Monero daemon (monerod) plus monero-wallet-rpc. # monero-wallet-rpc ships in the SAME CLI tarball (already verified by the hash above), so the diff --git a/build/monero/bitmonero.conf.template b/build/monero/bitmonero.conf.template index 30d156c1..e07d4d3f 100644 --- a/build/monero/bitmonero.conf.template +++ b/build/monero/bitmonero.conf.template @@ -24,7 +24,7 @@ public-node=1 # ZeroMQ publisher for block notifications (Required for P2Pool) zmq-pub=tcp://0.0.0.0:18083 -# Peer Management. P2Pool v4.16 recommends out-peers=32 / in-peers=64 for a clearnet node, where +# Peer Management. P2Pool v4.18 recommends out-peers=32 / in-peers=64 for a clearnet node, where # in-peers=64 is the one that matters: it caps INBOUND connections so they can't grow past ~1000 and # hit Linux's open-files limit — we honor it exactly. out-peers defaults to 48 (monero.out_peers) # because each peer is roughly one bandwidth-capped Tor circuit, so more peers = more aggregate diff --git a/build/monero/entrypoint.sh b/build/monero/entrypoint.sh index 484c059d..322baf69 100644 --- a/build/monero/entrypoint.sh +++ b/build/monero/entrypoint.sh @@ -10,11 +10,11 @@ CONFIG_PATH="${CONFIG_PATH:-/home/ubuntu/.bitmonero/bitmonero.conf}" CLEARNET_MARKER="${CLEARNET_MARKER:-/clearnet-state/monero.synced}" # Optional clearnet initial sync (#183). DEFAULT OFF. For the fast clearnet sync window only, this -# makes monerod match the connectivity P2Pool v4.16 recommends for a clearnet node: +# makes monerod match the connectivity P2Pool v4.18 recommends for a clearnet node: # - strip the single `proxy=` line that forces ALL P2P over Tor → monerod dials its compiled-in # clearnet seed nodes (and the priority nodes below) directly, at clearnet speed instead of # crawling over bandwidth-capped Tor circuits. -# - out-peers → 32 (P2Pool v4.16's clearnet recommendation; the Tor render uses 48, a circuit- +# - out-peers → 32 (P2Pool v4.18's clearnet recommendation; the Tor render uses 48, a circuit- # bandwidth workaround). in-peers stays 64 — the open-files cap, which is the rec we always honor. # - add P2Pool's recommended priority nodes for guaranteed-good peers + block templates. These are # CLEARNET hostnames, so they're added ONLY here: in Tor mode their resolution would leak a @@ -32,7 +32,7 @@ apply_clearnet_initial_sync() { sed -e '/^proxy=/d' -e 's/^out-peers=.*/out-peers=32/' "$cfg" >"$tmp" && mv "$tmp" "$cfg" cat >>"$cfg" <<'PRIONODES' -# P2Pool v4.16 recommended priority nodes (clearnet sync window only — removed when flipped to Tor). +# P2Pool v4.18 recommended priority nodes (clearnet sync window only — removed when flipped to Tor). add-priority-node=p2pmd.xmrvsbeast.com:18080 add-priority-node=nodes.hashvault.pro:18080 PRIONODES diff --git a/build/p2pool/Dockerfile b/build/p2pool/Dockerfile index 1da78dac..f98491cc 100644 --- a/build/p2pool/Dockerfile +++ b/build/p2pool/Dockerfile @@ -1,8 +1,8 @@ # Pinned by digest (#135) so the ubuntu:24.04 tag can't be silently re-pointed. -FROM ubuntu:24.04@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea +FROM ubuntu:24.04@sha256:33ceb71981b602c1a7443a53469e4dba065f7503eab3078a2d7a57a2ab987517 -ARG P2POOL_VERSION=v4.16 -ARG P2POOL_HASH=1b03b2e4d4adfe488b2867eb85b57366fbaf8594a7f84cdbf13a3c8519159280 +ARG P2POOL_VERSION=v4.18 +ARG P2POOL_HASH=893691726b0218fe1883a7a326e2c69db4eb228fc72ba00c8adfa6be85b8a415 # Install runtime dependencies. socat (#278): the entrypoint bridges 127.0.0.1 -> the real monerod # (node RPC/ZMQ) and the Tari base node (merge-mine gRPC) so those intra-stack legs stay DIRECT while diff --git a/build/p2pool/entrypoint.sh b/build/p2pool/entrypoint.sh index 1217b152..f084770d 100644 --- a/build/p2pool/entrypoint.sh +++ b/build/p2pool/entrypoint.sh @@ -8,15 +8,18 @@ set -euo pipefail # routing — e.g. "--mini --socks5 172.28.0.25:9050 --socks5-proxy-type tor"). We word-split it HERE # because Docker Compose passes a `- ${VAR}` command item as ONE argument (no word-splitting), which # would hand p2pool a single mangled flag. An empty value expands to nothing (no stray empty arg). -# #278: p2pool's --socks5 (#165 Tor sidechain routing) ALSO proxies the monerod RPC/ZMQ connection — -# unless the node address is LOOPBACK. p2pool exempts only 127.0.0.1/::1/localhost from the SOCKS5 -# proxy (verified vs p2pool v4.16: json_rpc_request.cpp / util.cpp is_localhost), so a private Docker -# node IP (e.g. 172.28.0.26) gets dialled THROUGH Tor — which can't reach a private IP → "get_info ... -# empty response", no block template, no mining. There is no per-host proxy-bypass flag. So when the +# #278: p2pool's --socks5 (#165 Tor sidechain routing) ALSO proxies the monerod RPC/ZMQ connection +# unless the node address is exempt. Up to v4.16 only LOOPBACK was exempt (json_rpc_request.cpp / +# util.cpp is_localhost), so a private Docker node IP (e.g. 172.28.0.26) got dialled THROUGH Tor — +# which can't reach a private IP → "get_info ... empty response", no block template, no mining. +# v4.18 widened the RPC-leg exemption to any private address (json_rpc_request.cpp:250 +# is_private_address, verified vs p2pool v4.18), which makes this bridge belt-and-braces for the +# node RPC/ZMQ — kept because the merge-mine leg below still needs its twin, and loopback stays +# exempt in every version, so the bridge costs nothing and survives an upstream narrowing. When the # Tor proxy is on, bridge 127.0.0.1 -> the real node with socat and repoint --host at loopback: the -# node RPC/ZMQ then stay DIRECT (socat is a plain TCP forward, not p2pool's proxy) while the sidechain -# P2P still rides --socks5 over Tor. The socat hops (loopback -> node) are intra-stack, allowed by the -# #270 firewall (subnet -> 172.16/12). +# node RPC/ZMQ then stay DIRECT (socat is a plain TCP forward, not p2pool's proxy) while the +# sidechain P2P still rides --socks5 over Tor. The socat hops (loopback -> node) are intra-stack, +# allowed by the #270 firewall (subnet -> 172.16/12). if printf '%s' "${P2POOL_FLAGS:-}" | grep -q -- '--socks5'; then _node="" _rpc="18081" _zmq="18083" _prev="" for _a in "$@"; do @@ -45,9 +48,10 @@ if printf '%s' "${P2POOL_FLAGS:-}" | grep -q -- '--socks5'; then esac # Same trap, one leg further (#278 covered monerod only): p2pool's MergeMiningClientTari reaches - # the Tari base node via TCPServer::connect_to_peer, which — like the node RPC — only skips the - # SOCKS5 proxy for a LOOPBACK IP literal (no_proxy = m_addressType != DomainName && is_localhost(); - # verified vs p2pool v4.16 src/tcp_server.cpp). So `--merge-mine tari://:18142` + # the Tari base node via TCPServer::connect_to_peer, which only skips the SOCKS5 proxy for a + # LOOPBACK IP literal (no_proxy = m_addressType != DomainName && is_localhost(); verified vs + # p2pool v4.18 src/tcp_server.cpp:425 — v4.18's private-address exemption covers the node RPC + # leg only, NOT this one). So `--merge-mine tari://:18142` # is dialled THROUGH Tor, which rejects RFC1918 ("Rejecting SOCKS request ... to private address") # → the gRPC channel_state sticks at TRANSIENT_FAILURE and no Tari is merge-mined. Same remedy: # bridge 127.0.0.1 -> the real node and rewrite the URL host to the 127.0.0.1 IP literal (NOT diff --git a/build/tor/entrypoint.sh b/build/tor/entrypoint.sh index 7e27a4d3..9d4666bf 100755 --- a/build/tor/entrypoint.sh +++ b/build/tor/entrypoint.sh @@ -9,8 +9,15 @@ set -eu # TORRC_TEMPLATE is a test seam so the shell suite can render against build/tor/torrc.template; the # container always uses the baked-in default. # ponytail: default unchanged, only overridden in tests : "${TORRC_TEMPLATE:=/etc/tor/torrc.template}" +# TORRC_OUT is the matching seam for the RENDERED file (#1104). Without it the shell suite's tor +# stub reads a host-global /tmp/torrc, which is the only unsandboxed path in an ~8000-line suite +# where every other fixture gets its own mktemp -d — so two concurrent runs race on one file and +# produce a false RED in the hidden-service assertions, whose natural remedy is "re-run until green". +# The DEFAULT MUST STAY /tmp/torrc: tier-3 assertions in tests/integration/run.sh read this path +# inside the running container. # ponytail: default unchanged, only overridden in tests +: "${TORRC_OUT:=/tmp/torrc}" -sed "s/__NETWORK_PREFIX__/${NETWORK_PREFIX}/g" "$TORRC_TEMPLATE" >/tmp/torrc +sed "s/__NETWORK_PREFIX__/${NETWORK_PREFIX}/g" "$TORRC_TEMPLATE" >"$TORRC_OUT" # Node inbound hidden services (#103): the bundled monerod and Tari containers only start under # their compose profiles, so publish each node's onion only then — in remote mode the onion would @@ -19,7 +26,7 @@ sed "s/__NETWORK_PREFIX__/${NETWORK_PREFIX}/g" "$TORRC_TEMPLATE" >/tmp/torrc # Unset (a bare `docker compose` with no .env) means no profiles, so no bundled node and no onion. case ",${COMPOSE_PROFILES:-}," in *,local_node,*) - cat >>/tmp/torrc <>"$TORRC_OUT" <