release: v1.4.0#473
Merged
Merged
Conversation
Keeps main an ancestor of develop after the patch cut. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Back-merge main (v1.3.1) into develop
…ation docs (#430) Two automations collided on miner-0 (2026-07-10) because nothing on a shared box said it was claimed. This is the pithead half of the fix; rigforge#183 is the canonical mechanism and every decision here follows it. - lib.sh: rig_lock() copied verbatim from rigforge#183 (env-overridable RIG_LOCK_FILE/RIG_LOCK_HOLDER per its note 6) — the same lock path on every box IS the protocol. Plus rig_lock_remote(): hold the lock on a remote box via a long-lived ssh whose stdin pipe (local FD 8) dies with this process, so the kernel drops the remote lock on any exit, kill -9 included. - run.sh: take the lock on the target before preflight — in-process for --local, over ssh for --host. Read-only modes (--check/--readiness) take it shared; everything else exclusive. Busy exits 75 naming the holder; RIG_LOCK_WAIT=1 queues. - e2e.sh: take the loaner rig's exclusive lock in preflight, before the borrow mutates its xmrig — released by the kernel after the EXIT-trap restore runs. - selftest.sh (tier 1, sandboxed paths, no root): second exclusive acquire exits 75 naming the holder; shared+shared coexist; shared vs exclusive excludes both ways; RIG_LOCK_WAIT=1 blocks then acquires; the sidecar is written on acquire and removed on normal exit. Skips only where flock doesn't exist. - docs/release-server.md: the static allocation table (miner-0=rigforge, miner-1/2=pithead loaners, miner-3..7=prod, gouda=bench+release, pithead-prod=prod), the run-lock contract, and the off-box check line. bench-lease retirement on the boxes is post-merge ops, not this PR. Closes #430 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test: shared-bench rig flock in the tier-4 harness + allocation docs (#430)
PyPI yanked grpcio 1.82.0 (bad protobuf constraint), so every `uv lock` warned. Move the floor to 1.82.1, the first non-yanked release above it. grpcio core has no protobuf dependency, so protobuf stays on 6.x and the checked-in Tari gRPC stubs (floor GRPC_GENERATED_VERSION 1.78.0) import unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
build(deps): move grpcio floor off yanked 1.82.0 (#419)
`version` (plus `-V`/`--version`) prints one line identifying the build from the provenance export_build_provenance already computes each run — no re-read, no git call, no network. `doctor` repeats it in its header so every pasted diagnostics report carries the version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#371: fix CONTRIBUTING's `make test` description — it needs Docker (lint-proto shells out to buf via docker), and add lint-docs-voice to the lint-surface list. The test-inventory-check item was already resolved by #414 (target no longer runs it), so nothing to do there. #372: add CODE_OF_CONDUCT.md (verbatim Contributor Covenant v2.1), filling the last empty GitHub community-profile slot. Enforcement reports route through the same private channel as SECURITY.md. Link it from CONTRIBUTING. Excluded from lint-docs-voice (verbatim upstream) and markdownlint (file-level inline disable) like other verbatim files. #373: pin build/tor/Dockerfile to alpine:3.24 (the digest's minor line) instead of floating `latest`, and drop the follow-up comment; make build-pruned-chain.sh's SRC_DIR a required var instead of a maintainer's personal, pre-rename-repo path default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…#426) The release pipeline needed a manual relaunch on most recent cuts and died early on a reimaged box. Two release.sh robustness fixes: - #429: wrap the registry reads (manifest_digest digest capture + the smoke stage's raw manifest read) in a bounded retry-with-backoff via a shared retry_registry_read helper, so GHCR's read-after-push consistency lag no longer kills stage 4. Defaults to 5 tries / 3s backoff, tunable via PITHEAD_REGISTRY_READ_RETRIES / _BACKOFF; a genuinely-missing image still dies after the retries exhaust. The inspect call is a stubbable buildx_inspect function so the retry path is unit-testable. - #426: preflight now verifies the lint/test toolchain (shellcheck, shfmt, node/npx, uv/uvx) is on PATH before building, failing fast with the fix and a pointer to docs/release-server.md instead of dying mid-gate with a bare "shellcheck: not found". Only enforced on the paths that run the test gate. Adds a "provision the lint/release toolchain" snippet to docs/release-server.md (pinned versions matching CI), a CHANGELOG [Unreleased] entry, and tier-1 shell tests for both the retry (stubbed inspect fails N times then succeeds, plus the exhaustion path) and the preflight check (PATH missing a tool). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat: add `pithead version` and version in doctor's header (#386)
`pithead status` now re-renders the dashboard's own per-chain initial-sync progress from /api/state (127.0.0.1:8000, host-local, no auth): while a chain is syncing it prints the percent and blocks remaining inline, instead of only "check the dashboard". No ETA — block rate isn't sampled, so blocks-remaining is the honest figure. Lines are skipped once both chains are synced or when the dashboard app isn't answering yet, so status degrades quietly. `stack_up` now ends with a one-time "what happens next" epilogue explaining the sync-then-mine hold (#35) and where to watch progress, keyed on a marker file beside .env so it shows once, not on every restart. Tier-1 shell tests stub curl with a canned /api/state body (real jq parses it); docs (operations.md, getting-started.md) and CHANGELOG updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-robustness # Conflicts: # CHANGELOG.md
…ogress-384 # Conflicts: # CHANGELOG.md
…gress-384 status: per-chain sync progress + first-run epilogue (#384)
The v1.4 spine: edit config.json from the dashboard, with the host — never the container — validating and applying. - pithead: dashboard.control.enabled (default off, fails closed without a dashboard password), ./data/control spool (requests/ container-rw; staged/results/audit host-owned), `apply --dry-run [--porcelain]` + PITHEAD_CONFIG_FILE, `control-run-pending` runner dispatching only apply --dry-run/--porcelain and apply -y on UUID-validated JSON intents, systemd path-unit trigger, Caddy X-Auth-User actor header. - dashboard: control_service (secret-masking round trip, atomic spool writes), routes registered only when enabled (404 off; X-Pithead-Control CSRF header), Configuration view with preview → typed-confirm → commit. - compose: spool mounts with the rw/ro trust-boundary split; config.json prefill read-only. - tests: tier-1 shell (dry-run, runner claim/validate/commit/expiry, fail-closed flag), pytest (masking, routes, polling; patch coverage 100%), compose mount invariants, node tests for the form logic. - docs: configuration/dashboard/operations/SECURITY + changelog. Refs #33. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Security-critical fixes for the internet-facing (Tor onion), root-privileged control channel before it leaves draft. Each ships with a test at the lowest honest tier and a house-voice CHANGELOG/SECURITY/doc update. Must-fix (security): - CRITICAL: reject control characters/newlines in EVERY config string leaf, centrally in parse_and_validate_config (the chokepoint preview + commit both run), closing the .env line-injection → root image-substitution RCE. - HIGH: fail closed — the control channel on a published onion now requires dashboard.onion.client_auth (mirrors the onion-without-password refusal). - HIGH: control_approval_gate fails closed on destructive commits (re-derived host-side, not trusted from the container), pointing at #338; hook left for #338 to drop in out-of-band Telegram approval. Non-destructive commits pass. - MEDIUM: chmod 600 the staged copy carrying merged secrets; SECURITY.md records the config.json bind mount (not the API masking) as the real secret boundary. - MEDIUM: bound the root-runner DoS — refuse >64 KB intents before jq parses, cap 50 intents/run, sweep staged/ + requests/ older than an hour. Grafts from #437 / #438: - Refuse symlinked/non-regular claimed request files (never follow them). - Merge config.reference.json under the sparse config so the form shows the full schema; secrets masked after the merge; missing reference degrades gracefully. - Poll loop rides out the transient fetch failure of a mid-apply container recreate instead of dropping to an error state. - Mask healthchecks.ping_url in the config API (capability secret). - Strict canonical uuid4 id gate (version + variant nibbles) on the runner. Housekeeping: corrected the PR-body test counts. Refs #33. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…close test gaps The commit approval gate re-derived destructiveness from describe_change's DEST rows, but that flag labels only the enable/change direction — disabling the dashboard login, downgrading onion client-auth, clearing the stratum password or repointing the Telegram bot produced zero DEST rows and applied as root without approval. The gate now default-denies an explicit security-sensitive env-key set (CONTROL_SENSITIVE_KEYS: auth, onion, the control channel itself, payout wallets, Tor egress firewall, clearnet sync, stratum password, RPC/stratum binds, Telegram credentials) in EVERY direction, computed from the same host-side dry-run a preview runs — the security decision no longer shares describe_change's cosmetic INFO/DEST list, and no container-supplied flag is consulted. The DEST refusal stays as the second, independent check; #338 remains the single plug point. Also: - stage the preview copy under umask 077 (no create-then-chmod window) and assert mode 600 in the tier-1 suite - fix provision_onion_client_auth aborting the whole apply on hosts without GNU stat (BSD stat fallback + if-form for the trailing chown guard), surfaced by the new onion-baseline test - tests: security-control disable/downgrade commits are refused with config.json untouched (auth off, client-auth downgrade, stratum-password clear, bot-token repoint); forged destructive:false in the request AND a tampered result file are both still refused; unchanged sensitive keys do not trip the gate; per-run 50-intent cap processes exactly 50 and leaves the overflow; non-v4 uuid ids are discarded - correct the CI invocation comment in configview.test.mjs (*.test.mjs glob) - SECURITY.md + docs/dashboard.md describe the default-deny gate Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The security re-review found the "default-deny" gate was a blocklist: P2POOL_CLEARNET, XVB_TOR_ENABLED and HEALTHCHECKS_PING_URL were absent from the sensitive set (a container could deanonymize the host IP or repoint the dead-man's switch), and any future env key would silently pass until someone remembered to enumerate it. The gate now refuses any commit that changes an env key NOT in CONTROL_DASHBOARD_EDITABLE_KEYS — operational tuning only, and only keys derived from validated enums, booleans or numbers. Wallets, auth, onion exposure, the control channel, Tor egress/clearnet toggles, node endpoints, the XvB pool URL and donor id, credentials, and the WALLET_CHANGED/CLEARNET_EXPOSED tamper-evidence alert toggles all stay host-CLI-only, as does any key added later. Refusal keys off a violation count so a blank porcelain row cannot slip past an emptiness test. Docs updated; new refusal tests verified red under a neutered gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CHANGELOG [Unreleased] unioned by hand; read-back verified all 18 entries from both parents survive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#33 config editor + host-mutation control channel (candidate A)
…374) The backup archive holds the stack's full secret material (.env, onion private keys, the dashboard DB); chmod 600 protects it on the local disk only. Encrypt it by default: - `backup` prompts for a passphrase (twice, read -rs) and streams tar | openssl enc -aes-256-cbc -pbkdf2 -iter 600000 -salt into a .tar.gz.enc — no plaintext archive ever exists on disk, and the passphrase travels over fd 3, never argv or a file. A failed pipeline removes the partial ciphertext. - PITHEAD_BACKUP_PASSPHRASE covers unattended (--yes/cron) runs; --yes with no env var, an empty passphrase, or --no-encrypt falls back to the old plaintext tar.gz with a loud warning — never a silent lockout of the operator's own onion keys. - `restore` detects the format by magic bytes (Salted__ vs gzip), so every pre-v1.4 plaintext archive restores unchanged with no flag. A cheap pre-flight decrypts only the first bytes and checks the gzip magic, so a wrong passphrase fails before tar writes anything to /. AES-256-CBC because `openssl enc` has no portable AEAD mode (GCM is refused by the enc utility); PBKDF2 at 600k iterations instead of the one-round EVP_BytesToKey default. Verified against OpenSSL 3.x (Ubuntu LTS) and LibreSSL 3.3 (macOS). Tests (tier 1, tests/stack/run.sh): env-var and prompted encrypt → restore round-trips, wrong passphrase fails loudly with live files untouched, passphrase mismatch aborts, --no-encrypt + legacy plaintext round-trip, failed encryption leaves no file behind, garbage archives refused, encrypted restore without a passphrase fails clean. Closes #374 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…crypt-374 CHANGELOG [Unreleased] unioned by hand; read-back verified all entries from both parents survive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extend read_only: true + minimal tmpfs scratch from the three already- hardened services (docker-proxy, docker-control, caddy) to the six that matter most: tor, monerod, tari, p2pool, xmrig-proxy and the dashboard. Each service keeps exactly its verified write paths — the bind-mounted data dir plus a size-capped, noexec /tmp tmpfs (xmrig-proxy also gets a uid-1000 tmpfs over its image-only home in case the binary persists an API-driven config change). A compromised process can no longer stage tools in, or persist changes to, its container image. Write paths verified per service against build/*/ entrypoints, the rendered configs, and (for tari) the pinned upstream image's start_tari_app.sh. tmpfs mount modes/ownership are explicit because a bare tmpfs mounts root-owned (verified against Docker 29). Tests: tests/stack/test_compose.sh now asserts read_only + the exact tmpfs target set per service for all nine (red-checked: removing read_only from one service fails two assertions). The tier-3 mini-stack dashboard mirrors the production posture, so the control-plane e2e now proves the real dashboard code under a read-only rootfs. tor was additionally boot-tested locally under read_only to a 100%-bootstrapped healthcheck. Full runtime validation of monerod/tari/p2pool/xmrig-proxy is tier-4 (gouda) before release. Closes #377 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…als in one command One command rotates the secrets that are otherwise generated once and live forever: the local Monero RPC password (skipped in remote mode), the stratum access-password when p2pool.stratum_password is "auto" (announced loudly — every rig must update its 'pass'), and PROXY_AUTH_TOKEN (always). Before anything changes, owner-only .bak-<timestamp> copies of config.json and .env keep the old values recoverable; the #125 apply-incomplete marker makes a failed recreate retryable via 'pithead apply'. Containers are recreated via compose up (never restart, which would reuse p2pool's old --rpc-login args). Closes #378 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Chaining: when every argument is a bare subcommand name, pithead runs them left-to-right, validating the whole chain before anything executes. Rejected up front (nothing runs): non-chainable commands (setup, logs, restore, reset-dashboard, info commands), duplicates, more than one of up/down/restart, and down anywhere but last. Fails fast: the first non-zero step stops the chain, the report names what ran and what didn't, and that step's exit code propagates. Any flag/service/path argument keeps the invocation on the single-command path, unchanged. Completion: pithead-completion.bash (repo root, shipped in the release bundle) completes subcommands and `logs <service>` (service names read from the adjacent docker-compose.yml). bash sources it directly; zsh via compinit + bashcompinit. The command list lives in one place (PITHEAD_COMMANDS); tests pin the completion list AND the dispatch case labels to it, so none of the three can drift. Tier 1 coverage in tests/stack/run.sh: validation rules, execution order, fail-fast + exit-code propagation, reject-runs-nothing (stubbed docker log stays empty), single-command regression, completion sourcing/suggestions, and the drift guard. Closes #94 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bility (#349) Two logs, one operator-security story: spot an attack on the Tor-published dashboard and rotate the onion/password before it lands. Config-change audit (extends #33): control_audit now records the changed env-key NAMES (from the same host-side dry-run the approval gate re-derives) — never values, several near-allowlist keys are secrets — and trims the log to its newest 2000 entries past 512 KiB. Access log: every generated Caddy vhost writes a JSON access log (4 MiB x 3 files via Caddy's native rolling, mode 0644, credential headers redacted by Caddy's default) to a host dir the dashboard mounts read-only. The dashboard's Configuration view surfaces both: recent config changes (only with the control channel on; /api/audit registered conditionally like the other #33 routes) and recent accesses with a 24 h failed-login count plus a rotate nudge at >=5 401s — over Tor there is no source IP, the rate of failures IS the signal. Every field read from either log is treated as hostile input and whitelisted to a safe ASCII charset server-side before it can reach the browser (log injection rendered into the dashboard would be stored XSS against the operator). Tests at tier 1: shell black-box (keys-not-values, bounded growth, Caddyfile log block), pytest (routes + sanitizer, hostile <script> content served inert — shown red with the guard removed), node --test (panel states). Caddyfile log block validated against the pinned caddy:2.11.4 image. Closes #349 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Security review findings: - MEDIUM (rollback replay): a cosign signature binds bytes, not a version, so an attacker controlling the release response could serve an OLDER genuinely-signed bundle at the requested tag's URL and silently downgrade the stack to a patched-vulnerable release — the signature still verifies. The dashboard upgrade path now reads the bundle's own pithead/VERSION (without extracting) and refuses unless it matches the host-derived tag. Verified: without the guard the older bundle extracts and the downgrade succeeds. - LOW (ANSI injection): cosign stderr echoes registry-supplied bytes and error() prints via echo -e, so a hostile registry could inject terminal escapes; the snippet is now control-char-stripped. - Aligned the test fake bundle to the real pithead/ top-dir layout. - SECURITY.md: state the rollback guard; note the CLI verify-then-pull window (filed #451) honestly. Deferred as follow-ups: #451 (verify-then-pull TOCTOU on the image path, MEDIUM), #452 (verify on first install, LOW). make lint + make test green (933 stack), rollback guard red-checked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d-heal-424 Tor guard self-heal: restart tor when clearnet egress is stuck (#424)
Sign release images and bundle with cosign; verify fail-closed in upgrade (#376)
VERSION 1.3.1 -> 1.4.0, dashboard pyproject in lockstep, uv.lock regenerated, CHANGELOG [Unreleased] promoted to [1.4.0] with the release lede. Date 2026-07-12 is a placeholder — confirm at cut. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#374 made `pithead backup` refuse an unattended run without a passphrase (no silent plaintext archive of onion keys). The tier-4 harness takes throwaway rollback backups via `pithead backup -y` — correct code now refuses them, failing "safety backup created" + "backup succeeded". These bench backups are ephemeral, so pass the explicit --no-encrypt opt-out (the documented non-interactive path). Not shipped code (tests/ isn't in the bundle); pure harness compat. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…up-374-compat test(e2e): --no-encrypt on the tier-4 harness throwaway backups (#374 compat)
The automated tier-4 matrix had zero coverage of any v1.4 feature — the tier-4-only behaviors lived only in the hand-run #390 checklist, which regresses silently between releases. Add a --hardening phase (local mode only), wired into --mode matrix: - #377: read-only rootfs is LIVE — each hardened service must reject a write to / (the writable /tmp tmpfs is excluded). - #33: the control channel end-to-end through the REAL systemd path unit (tier-1 runs control-run-pending by hand) — enable control, let apply install pithead-control.path, drop a spooled allowlisted change and assert the UNIT applies + audits it, then a sensitive (wallet) change and assert it's refused host-side with .env untouched. - #424: the heal ACTION (restart tor) brings the stack back healthy (the stuck-guard trigger is guard-selection luck, not reproducible). Restores the baseline itself (re-apply with control off uninstalls the path unit) so the root systemd unit never outlives the phase. Static-checked (shellcheck clean, bash -n, selftest 120/0). LIVE-VALIDATION PENDING on gouda — the path-unit timing + spool round-trips are only proven against a real box; this commit is the draft under review, not a passed run. Refs #457. Folds toward the #454 harness-robustness work. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The heal action's whole point is restoring clearnet egress through fresh circuits — asserting only "stack healthy after restart" doesn't prove that. Probe egress with the stack's OWN mechanism (the doctor/healer curl: a generate_204 through the tor container's SOCKS via --socks5-hostname, which also resolves DNS through Tor). Gated on a pre-restart baseline so live-Tor-network flakiness can't false-fail the gate: a genuinely-bad Tor exit (outside our code — the exact #424 condition) makes the baseline probe fail, and we then skip-with-warn rather than fail; we assert RECOVERY only when egress was working to begin with. Post-restart polls up to 120s for circuits to rebuild. Probe confirmed live on gouda (generate_204 -> HTTP 204). shellcheck clean, bash -n OK. Full phase still pending live validation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ot the stack's SOCKS Testing egress by curling the stack's OWN tor SOCKS is testing from the inside with the stack's plumbing. Replace it with a black-box probe: a dedicated, isolated Tor-client image (tests/integration/tor-client/, pinned Alpine + tor + curl, non-root) that reaches the dashboard onion over the REAL Tor network exactly as a remote user's Tor Browser would — sharing no network, volume, or SOCKS with the stack. The #424 heal test now asserts the onion is reachable from this external client BEFORE and AFTER the tor restart (proving tor rebuilt circuits + republished its descriptor), gated on the baseline so a bad live Tor network can't false-fail. Doubles as the #343/#360 from-outside privacy-surface test and automates the onion-client-auth item off the #390 manual checklist. Secret handling: the client-auth key is piped pithead->container over stdin entirely on the box — never an argv, an ssh arg, `docker inspect`, or a shared-fs file. Validated live on gouda: the external client reached the running dashboard onion -> HTTP 401 (Caddy's auth challenge = full inbound path works from outside). shellcheck clean, bash -n, probe.sh shellcheck clean. Honest scope: onion reachability is INBOUND; the clearnet-EXIT half of #424 isn't externally observable and stays with the doctor egress check (the stack self-checking its own egress). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live validation caught a false pass: containers run non-root (#255), so `touch /` on a WRITABLE rootfs already fails with EACCES ("Permission denied"). The probe treated any failure as a pass — so it would green-light a service that silently lost read_only. Now it asserts the error is specifically "Read-only file system" (EROFS), which only a read-only mount returns. Verified on gouda: caddy (read_only) -> EROFS -> pass; dashboard on the v1.3.1 baseline (writable) -> Permission denied -> correctly NOT a pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…454) Two harness-reliability fixes so the gate stops crying wolf: - pool type: p2pool classifies by peer port, so nano (tiny sidechain, slow to find peers over Tor) can stay "Unknown" past wait_pool_ready's window. Unknown is a peer-discovery-timing state, not a misclassification — warn on it instead of failing; a WRONG determinate type (Main when we set Mini) is a real bug and still fails. This is the single flaky failure that survived the v1.4 gate re-runs (different scenario each time). - results dir: wipe stale per-scenario artifacts at run start; e2e.sh preserves results/ across runs, so an old capture (a different branch) lingered and read as this run's state while diagnosing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This reverts commit d9b9e12.
…remature-bump Revert the premature v1.4.0 version bump from develop
The first-party images pull by mutable :vX.Y.Z tag, so a re-pointed tag or leaked GHCR token could substitute a root-running image. Close that vector the cheap way — no off-repo secret to manage: make_bundle now rewrites the 5 first-party image lines in the BUNDLED compose to @sha256:<digest> (the immutable digest promote just published), so installs pull content-addressed. A post-condition refuses to ship a partially-pinned bundle. cosign signing becomes OPT-IN defense-in-depth rather than a release blocker: preflight computes COSIGN_ENABLED (cosign binary + COSIGN_KEY + committed cosign.pub all present) and warns+skips instead of die-ing when absent; sign_images/sign_bundle and the .sig upload are gated on it. A normal cut needs nothing off-repo; an operator who wants signatures too can add a key. Validated: the digest-pin sed applies cleanly to the real compose (all 5 lines pinned, post-condition passes); shellcheck clean; bash -n. Full exercise is at real release time (needs promote's digests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#33) Live validation: 10/11 hardening assertions passed on real v1.4 code (EROFS on all 6 services, external-Tor-client onion reachability before/after the #424 restart, the #33 path unit firing + sensitive-key refusal). The one miss was test-only: the allowlisted-change check read TELEGRAM_EVENT_NODE_DOWN, which only renders when telegram is configured, so it was empty on the telegram-off baseline. Switch to DASHBOARD_CHECK_UPDATES (allowlisted, rendered unconditionally, cosmetic). Also bump the phase's baseline-restore wait 180s->240s: that apply follows a control enable/disable + tor restart, so it needs more settle time (180s timed out live). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…#376) CI caught two things my first cut broke: 1. Digest bug: get_digest stores a FULL ref (repo@sha256:...), so appending it produced pithead-tor:tag@ghcr.io/.../pithead-tor@sha256:... — a broken double-repo ref at real release time. Now extract the bare @sha256 and validate it's a sha256 digest before pinning. 2. Existing release tests assumed the old behavior: make_bundle now needs digests set (it digest-pins), and sign_images is opt-in (skips without COSIGN_ENABLED). Updated the make_bundle test to seed digests + assert the bundle is digest-pinned with the bare-sha256 format, and the sign test to set COSIGN_ENABLED plus a new signing-off no-op assertion. make lint (shellcheck+shfmt) + the release-tooling suite green locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ptin-digest-pin release: digest-pin bundle images + make cosign opt-in (#376)
…ening-phase-457 test(e2e): tier-4 hardening phase for the v1.4 surfaces (#457)
VijitSingh97
force-pushed
the
release/v1.4.0
branch
from
July 11, 2026 22:15
8b3e9d3 to
dc7e1f2
Compare
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
develop-v1.5's wallet-healthcheck.sh uses env-var interpolation (`-u
"${VAR:-wallet}:..."`), not a hardcoded credential; the all-branches
fetch-depth:0 scan flagged it against the old empty-default-only regex. Both
sides must still be ${VAR:-...} expansions, so a real -u "admin:hunter2" is
still caught. (Same fix as #462 on develop-v1.5.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
VijitSingh97
force-pushed
the
release/v1.4.0
branch
from
July 11, 2026 22:21
dc7e1f2 to
2b59cff
Compare
Surface the headline v1.4 capabilities from the top-level entry points so they're reachable without reading the CHANGELOG: - README "What it does": the dashboard bullet now names the opt-in control channel (edit config, one-click upgrade, audit/access logs, gated host-side). - README "Common commands": add `version` and `rotate-secrets` rows; note the backup archive is passphrase-encrypted by default (`--no-encrypt` opt-out). - README + docs/README index: dashboard/operations rows point at config editing, one-click upgrades, audit logs, secret rotation, and intruder watch. - docs/README quick links: jump to the Configuration view and Watching for intruders. - dashboard.md Sync Mode: mention `./pithead status` per-chain progress (#384) alongside the log tail, matching getting-started. The deep docs (dashboard.md control channel + audit panels, operations.md rotate-secrets/chaining/completion/intruders, configuration.md control keys, releasing.md signed releases, architecture.md read-only roots, SECURITY.md control-channel model) already covered these; this makes them discoverable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rage v1.4 documentation coverage + discoverability
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cuts v1.4.0. Bumps
VERSION1.3.1 → 1.4.0 and dates the CHANGELOG ([Unreleased]→[1.4.0] - 2026-07-11).Contains the full v1.4 feature set already merged + security-reviewed on
develop(roadmap #390). The #423 XvB-overshoot gate is resolved (validated on live dwell-mechanics + the fixed sim — see #423).Release mechanics after merge: real merge (not squash) →
make releaseon gouda (digest-pinned, cosign opt-in/skipped) → back-merge main→develop → deploy gouda then prod viapithead upgrade.🤖 Generated with Claude Code