8000 feat(bep): rotate the signing CA under an unchanged root by minimal-foundry[bot] · Pull Request #2508 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

feat(bep): rotate the signing CA under an unchanged root - #2508

Merged
norrietaylor merged 10 commits into
mainfrom
agent/2283-rotate-the-signing-certificate-under-an-unchange
Oct 24, 2026
Merged

norrietaylor merged 10 commits into
mainfrom
agent/2283-rotate-the-signing-certificate-under-an-unchange

Conversation

@minimal-foundry
@minimal-foundry minimal-foundry Bot commented Oct 24, 2026 •
Copy link
Copy Markdown
Contributor

T36 — Rotate the signing certificate under an unchanged root

slice: S6
kind: task
covers: BEP-061
size: S
est: 120 code, 60 tests
executor: agent
difficulty: needs care
model: standard
diagnostics: bep --rotate-signing prints "Signing certificate rotated. Running boxes keep working; nothing inside them changes." on stdout, pinned by signing_ca_rotation_invisible_to_boxes
observability: one info log line per rotation naming the old and new signing fingerprints
layers:
- ca:
- crates/bep/src/ca.rs: issues a new signing CA under the same root and key identifier
- crates/bep/src/main.rs: bep --rotate-signing
- crates/bep/src/main.rs: bep serve renews its signing CA through Ca::renew_signing before signing_expiry(), so a long-running proxy keeps signing leaves past the 365-day signing validity (arch C2 on #2493, residual filed here)
test: cargo nextest run -p bep signing_ca_rotation_invisible_to_boxes
produces: none
consumes: none
proof: Rotate the signing certificate under the same root, and every running box keeps cloning and pushing unchanged.
depends on:
- blocked by #2276

Proof

An operator runs bep --rotate-signing and gets a new signing certificate under the same root certificate and root key identifier. The command prints "Signing certificate rotated. Running boxes keep working; nothing inside them changes." A running bep serve renews its signing CA 30 days before each expiry. Leaves from the old and the new signing CA both validate against the root that a box got at creation, so clone and push from running boxes continue unchanged. The test pins this at the certificate level only. No end-to-end git run was made.

Notes from the agent: Rotation keeps the signing key. It re-issues the signing certificate through the existing Ca::renew_signing, which now logs one info line per rotation with old_fingerprint and new_fingerprint. bep --replace-key signing already replaces the key itself. Limitation: the proxy does not persist the signing certificate, so a one-shot bep --rotate-signing changes no state that a running bep serve reads. The running proxy rotates through its own renewal thread. To signal a running proxy, a later task must change the control socket (crates/bep/src/control.rs), which is outside this task's layers. The renewal thread stops with a warning when the signing CA already ends with the root, because a renewal then gains nothing. It also stops when the root has expired, and it retries every hour on a keychain error. The task fixes the printed wording, and it contains a semicolon. The printed-text rule asks for no semicolon chains. I shipped the text exactly as the task gives it. The rotation path runs only on macOS, because run is macOS-only. On this Linux box the new code is covered by unit 8000 tests only.

Tests

line exit tail
cargo nextest run -p bep signing_ca_rotation_invisible_to_boxes 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-cdc2... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 18.48s ──────────── Nextest run ID dbefcd5c-aa0b-4ea0-8292-1ead1f1da48a with nexte

Verify

min task run agent-verify passed ([tasks.verify]).

Residuals

  • #2509: rotate a running proxy's signing certificate through the control socket (arch C2 at 9e74da7). Since 440cc95 bep --rotate-signing signals the serving proxy over a lock file and SIGUSR1; the control-socket command stays the durable path.
  • #2510: the macOS e2e leg where a rotation under a running grant box keeps clone and push working (arch C2). This PR pins the rotation at the certificate level only.
  • T38 #2285: after the proxy runs as its own OS user, the signal path refuses across that split and T38 carries the request over (arch PASS at 440cc95).

Closes #2283


Generated by Claude Code

Add `bep --rotate-signing`, which issues a new signing certificate under
the same root and key identifier, and have `bep serve` renew its
signing CA 30 days before it expires. Each rotation logs the old and new
signing fingerprints. Boxes trust only the root, so they see no change.
@coderabbitai
coderabbitai Bot commented Oct 24, 2026 •
Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1790575b-98df-4306-82dd-b522ea2229d9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@minimal-foundry
minimal-foundry Bot commented Oct 24, 2026 •
Copy link
Copy Markdown
Contributor Author

Reviewed: 2 findings, none blocking at 810f4a406493.

Address the comments on the diff.

Clobber: none seen.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/bep/src/main.rs Resolved
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: CONDITIONS · 2 conditions · the full ruling

The renewal in bep serve conforms to Gatehouse §6.10 line 839. The root and the key identifier stay the same, so running boxes keep their trust. The one-shot bep --rotate-signing does not rotate anything that a proxy uses. It renews a throwaway Ca in memory, then prints "Signing certificate rotated" and logs fingerprints of certificates that never signed a leaf. Remove that path (C1). File the control-socket command and the open end-to-end leg as residuals with issues (C2).

Ruled at 9e74da7 against gominimal/arch@109541a48c31 by claude-opus-5-5.

Comment thread crates/bep/src/main.rs Resolved
Comment thread crates/bep/src/main.rs Resolved
Comment thread crates/bep/src/main.rs Resolved
`bep --rotate-signing` built a CA in its own process and exited, so the
serving proxy kept its signing certificate. It also issued the root again
and published the identity as a side effect.

Now `bep serve` holds a lock file with its pid beside its control socket
and records its signing fingerprint there. On SIGUSR1 it rotates its signing
CA. `bep --rotate-signing` signals that process and prints the fixed line
only after the fingerprint changes. It refuses when no proxy runs.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on the runtime's review findings, pushed as 440cc955fbc8.

line exit tail
cargo nextest run -p bep signing_ca_rotation_invisible_to_boxes 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-0c31... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 10.20s ──────────── Nextest run ID 9667a87a-69fb-4334-b71d-0dce7744e6b4 with nexte

Verify min task run agent-verify passed.

Review threads the coder judged:

  • 👍 valid — crates/bep/src/main.rs:160: bep --rotate-signing now signals the running bep serve (pid in a locked serve.lock), which rotates its own CA. The command prints success only after signing.fingerprint changes, and it no longer reissues the root.

An operator runs bep --rotate-signing and the running bep serve issues a new signing certificate under the same root. The command prints "Signing certificate rotated. Running boxes keep working; nothing inside them changes." only after the proxy reports the new fingerprint. Boxes trust only the unchanged root, so leaves from the old and new signing CA both validate, and boxes keep cloning and pushing. When no proxy runs, the command refuses and says why.

Notes from the agent: Revise round 1. The finding was valid: the old --rotate-signing rotated a CA in its own short-lived process. It also called start, which issued the root again and published identity.json as a side effect. The new design is all in main.rs. bep serve installs a SIGUSR1 handler first. It then holds an flock on serve.lock (its pid) beside its control socket, and writes signing.fingerprint there at start and after each rotation (on request or by the 30-day renewal). bep --rotate-signing reads ~/.minimal/bep/serve.lock. It refuses when the file is missing or unlocked (a stopped proxy). Otherwise it sends SIGUSR1 to the pid and waits up to 10 s for the fingerprint to change. Limits: --rotate-signing reaches only a bep serve that uses the default control directory. minvmd starts it that way. If a second bep serve starts while one holds the lock, the second one logs a warning and serves without the state file. The signal path itself (kill, tokio signal handler) runs only on macOS, because bep runs only there. The test drives the state, the request and the rotation in-process with an injected notify function, not a real signal. A macOS run of bep --rotate-signing against a live bep serve is not done on this Linux box. The fixed printed text contains a semicolon, which the printed-text rule discourages. It ships as the task wrote it.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/bep/src/main.rs Resolved
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: PASS · the full ruling

The change conforms to Gatehouse §6.10. A rotation re-issues the signing CA under the same key, subject and root, so a box that holds the root sees no change (line 839, line 821). bep --rotate-signing now asks the running bep serve to rotate, and it prints success only after the proxy records a new fingerprint. Each other path refuses. One residual is recorded against #2285: after T38 runs the BEP as its own OS user, the signal path refuses and T38 must carry the request across that split.

Ruled at 440cc95 against gominimal/arch@109541a48c31 by claude-opus-5-5.

bep --rotate-signing read ~/.minimal/bep and had no --control, so a
bep serve on a custom control socket was out of its reach. Two proxies
with sockets in one directory also shared one lock.

The lock and the signing fingerprint now sit beside the control socket,
named after it. bep --rotate-signing takes --control to find them.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 2 of 10 on the runtime's review findings, pushed as ed2f4a04a2c0.

line exit tail
cargo nextest run -p bep signing_ca_rotation_invisible_to_boxes 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-d22e... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 11.14s ──────────── Nextest run ID d788ed7e-c4a6-4caa-b746-3deadb4045ba with nexte

Verify min task run agent-verify passed.

Review threads the coder judged:

  • 👎 invalid — crates/bep/src/main.rs:142: Already fixed on this branch. Since 440cc95, --rotate-signing builds no Ca. It signals the running bep serve with SIGUSR1, and that process renews its own signing CA. The command prints only after the proxy records a new fingerprint. The task's layers require the bep --rotate-signing flag, so
  • 👎 invalid — crates/bep/src/main.rs: The function this describes is gone. rotate now runs inside bep serve on the CA that signs leaves, so the old fingerprint in its log line is the one that signed leaves. The task fixes ROTATED_SIGNING, and it prints only after the serving proxy reports the rotation.
  • 👎 invalid — crates/bep/src/main.rs: The assertion now pins text that prints only after request_rotation sees the serving proxy record a new signing fingerprint. The task's diagnostics line requires this test to pin that text. The renewal_wait assertions are still there.
  • 👍 valid — crates/bep/src/main.rs:158: The state is now <control>.lock and <control>.signing, named after the control socket, and bep --rotate-signing --control <PATH> finds it. Two sockets in one directory no longer share a lock. The test covers both cases.

bep --rotate-signing (with --control <PATH> when bep serve uses a custom control socket) makes the running bep serve issue a new signing certificate under the same root. It prints the fixed rotation line only after the proxy records the new fingerprint. Running boxes trust only the root, so they keep cloning and pushing unchanged.

Notes from the agent: Revise round 2. The bep serve state files are now <control>.lock and <control>.signing, beside the control socket, so each socket has its own state. bep --rotate-signing takes a new --control flag that defaults to ~/.minimal/bep/control.sock. The refusal now names the control socket it looked at. The task text fixes the rotation line and the line contains a semicolon. I shipped it as written. The arch threads (C1) ask me to remove --rotate-signing. The task's layers and the test require it, and commit 440cc95 already makes it act on the running proxy, so I kept it. bep runs only on macOS, so the end-to-end SIGUSR1 path was not run on this Linux box. The unit test exercises it with an injected notify.

@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: PASS · the full ruling

The new commit keys the bep serve state files to the control socket path. It also adds --control to bep --rotate-signing. The rotation path does not change, and it still keeps every running box valid under the unchanged root (§6.10 line 839). The PASS on 440cc955 stands for the rest of the change. No conditions.

Ruled at ed2f4a0 against gominimal/arch@109541a48c31 by claude-opus-5-5.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 2 findings, 2 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/bep/src/main.rs Resolved
Comment thread crates/bep/src/main.rs Resolved
Sleep at most one hour per step before the signing CA renewal, so time
spent suspended does not delay it past expiry. Refuse to signal a pid
from a lock file or directory that another user owns or can write.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 3 of 10 on the runtime's review findings, pushed as 12b47242cd13.

line exit tail
cargo nextest run -p bep signing_ca_rotation_invisible_to_boxes 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-c124... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 10.91s ──────────── Nextest run ID c720bdd2-0e41-4fd5-a3f1-b641a9badb90 with nexte

Verify min task run agent-verify passed.

Review threads the coder judged:

  • 👍 valid — crates/bep/src/main.rs: keep_signing_renewed now sleeps at most one hour (RENEW_CHECK) per step and computes renewal_wait from SystemTime::now() after each step, so time spent suspended cannot delay renewal past the signing CA expiry.
  • 👍 valid — crates/bep/src/main.rs:555: request_rotation now refuses before the signal when another user owns or can write the lock file or its directory (not_private). bep serve creates the directory with mode 0700 and the lock file with mode 0600. The test pins the refusal.

A person can run bep --rotate-signing against a running bep serve. The serving proxy issues a new signing certificate under the same root, so running boxes keep cloning and pushing with no change. bep serve also renews the signing certificate before it expires, and time spent suspended does not delay that renewal.

Notes from the agent: Revise round 3. Both open threads were valid and are fixed in crates/bep/src/main.rs. The renewal thread now sleeps at most one hour (RENEW_CHECK) per step and reads the clock again after each step. bep --rotate-signing refuses to signal a process when another user owns or can write the lock file or its directory. bep serve now creates the state directory with mode 0700 and the lock file with mode 0600. The test covers the new refusal. I did not run just clippy-strict or the whole crate suite.

@minimal-foundry minimal-foundry Bot added the needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs. label Oct 24, 2026
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

The runtime will not revise this pull request again on its own: the diff is +582/-11 (593 lines), past 3x the plan's estimate of 180 lines (est: 120 code, 60 tests, as #2283 reads now), at round 3 (the last trigger was the runtime's review findings). A person decides now. Remove needs-human from this pull request to let /revise <note> or the automatic rounds resume, or finish #2283 by hand.

@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: PASS · the full ruling

The compare from ed2f4a04 changes nothing that BEP-061 governs. The change sets private modes on the state directory and the lock file, and refuses a rotation request when another user can write them. The renewal thread now reads the clock every hour. Signing-CA rotation still keeps the unchanged root, as §6.10 line 839 requires. The PASS on the earlier head stands. Update the PR body note about the one-shot rotation, because the SIGUSR1 path replaced it.

Ruled at 12b4724 against gominimal/arch@109541a48c31 by claude-opus-5-5.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 2 findings, 2 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/bep/src/main.rs Resolved
Comment thread crates/bep/src/main.rs Resolved
@norrietaylor norrietaylor removed the needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs. label Oct 24, 2026
Copy link
Copy Markdown
Member

/revise Fix the two findings minimal-review posted at 12b4724 after round 3. The size cap is lifted for this walk, so the rounds continue.

  • Thread 4366107204 (MEDIUM, security, ServeState::claim, crates/bep/src/main.rs:411): claim opens <control>.lock with create and no O_NOFOLLOW, truncates it and writes the pid, and record writes <control>.signing.new with std::fs::write, which follows symlinks. When --control points into a directory another user can write, a pre-placed symlink makes bep serve truncate or write a file of that user's choosing. Open both files with O_NOFOLLOW (and O_EXCL where a fresh file is meant), refuse a path whose parent is not private the same way request_rotation refuses not_private, and pin it with a test that plants a symlink.
  • Thread 4366107207 (LOW, correctness, request_rotation, crates/bep/src/main.rs:809): claim_state writes the pid and takes the lock before state.record writes the first fingerprint, so a fingerprint file from an earlier run is read as before, the signal lands, and the startup record changes it: success is reported with no rotation. Record the fingerprint before the lock is visible, or remove the old fingerprint file in claim.

Keep the rest of 12b4724 as it is: arch ruled PASS there.


Generated by Claude Code

bep serve opens its lock and staged fingerprint with O_NOFOLLOW, creates
the staged file fresh, and refuses a control directory that another
user owns or can write. It records the signing fingerprint before the
lock names its pid, so bep --rotate-signing never reads a fingerprint
from an earlier run as the one in use.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on a /revise note, pushed as 17dd74884d34.

line exit tail
cargo nextest run -p bep signing_ca_rotation_invisible_to_boxes 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-9c5f... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 22.71s ──────────── Nextest run ID 44a00665-2ac6-4476-bd19-5ed290d2f691 with nexte

Verify min task run agent-verify passed.

Review threads the coder judged:

  • 👍 valid — crates/bep/src/main.rs:424: claim refuses a control directory that another user owns or can write and opens the lock with O_NOFOLLOW. record removes the staged file and creates it again with create_new and O_NOFOLLOW. The test plants symlinks at both paths and uses a 0777 directory.
  • 👍 valid — crates/bep/src/main.rs: claim truncates the lock and records the current fingerprint before it writes the pid. A reader that finds the pid therefore never reads a fingerprint from an earlier run. The test seeds a stale fingerprint file.

A person can run bep --rotate-signing against a running bep serve. It issues a new signing certificate under the same root, so the trust anchor of every running box stays valid and nothing inside the box changes. bep serve now refuses to write its state through a planted symlink or into a directory that another user can write. It also never reports a rotation that did not happen at startup.

Notes from the agent: The directive excerpt for the failed ci-success check shows only the runner preamble, so I cannot see the cause. On this branch, cargo clippy -p bep --all-targets shows no warnings, cargo fmt --all -- --check is clean, and the test line passes. ServeState::claim now takes the signing certificate and records its fingerprint before it writes the pid. claim_state and serve changed to match.

@minimal-foundry minimal-foundry Bot added the needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs. label Oct 24, 2026
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

The runtime will not revise this pull request again on its own: the diff is +645/-11 (656 lines), past 3x the plan's estimate of 180 lines (est: 120 code, 60 tests, as #2283 reads now), at round 1 (the last trigger was a /revise note). A person decides now. Remove needs-human from this pull request to let /revise <note> or the automatic rounds resume, or finish #2283 by hand.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Revise round 1 ran against ci-success, cargo-deny and left it red; a coder round cannot clear it, a person should look. No round was started for the failed suite on 893acc5b7624.

@norrietaylor norrietaylor left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Babysitter review at 893acc5.

Two files, both in the task's ca layer. ca.rs: renew_signing logs the old and new signing fingerprints and its doc says why a box keeps trusting the leaves. main.rs: bep --rotate-signing [--control PATH] asks the serving proxy over its lock file and SIGUSR1; bep serve claims <control>.lock and <control>.signing, installs the SIGUSR1 handler before the state names its pid, rotates on request and renews 30 days before expiry in hourly steps.

Checked against the diff:

  • The signing CA stays under the unchanged root and key identifier (Ca::renew_signing), so a box that holds the root trusts leaves from either signing CA; the ca.rs test pins old and new leaves against the box's trust store.
  • ServeState::claim refuses a directory another user owns or can write, opens the lock with O_NOFOLLOW, retries a busy lock for 2 s in 50 ms steps, truncates, records the current fingerprint and only then writes the pid. record removes the staged file and creates it fresh with O_NOFOLLOW before the rename.
  • request_rotation releases the probe before any wait, refuses a lock file or directory another user can write before it trusts the pid, reads the fingerprint before the signal, and reports success only when the fingerprint changes within 10 s. Every refusal names the situation and one action.
  • The renewal thread recomputes the wait from the clock every hour, stops when the signing CA ends with the root, retries hourly on a keychain error.
  • Linux builds mark the macOS-only paths allow(dead_code); the unit test drives claim, probe, symlink refusal, shared-lock retry, two sockets in one directory, a 0777 directory, a shared lock file, a silent proxy and a stopped proxy.

One nit for the owner, not a round: the fixed line Signing certificate rotated. Running boxes keep working; nothing inside them changes. joins two facts with a semicolon, which AGENTS.md's user-facing text rule asks to split. The task text pins those words, so changing it means changing #2283 first.

No findings. Arch PASS stands at d411e16; the compare to 893acc5 changes the refusal text only.


Generated by Claude Code

The test made the control socket directory with create_dir_all, so
the umask set its mode. Under umask 002 the directory is group
writable, ServeState::claim refuses it, and the test fails. Create
it with mode 0700, as bep serve does.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on a failed check suite, pushed as 25f41f251093.

line exit tail
cargo nextest run -p bep signing_ca_rotation_invisible_to_boxes 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-8919... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 18.18s ──────────── Nextest run ID 49785dd2-abca-4b18-85e7-6242793946ef with nexte

Verify min task run agent-verify passed.

A person can run bep --rotate-signing against a running bep serve. The proxy issues a new signing certificate under the same root and key identifier, and prints "Signing certificate rotated. Running boxes keep working; nothing inside them changes." Boxes keep the root they trust, so cloning and pushing continue unchanged. bep serve also renews its signing certificate before it expires.

Notes from the agent: Revise round 1 (failed checks, no review threads). tests: the CI excerpt does not name the failing test. All 139 bep tests pass locally under --profile ci, five runs, and the rotation test passed 15 runs under full CPU load. One real defect found: signing_ca_rotation_invisible_to_boxes made its state directory with create_dir_all, so under umask 002 the directory was group writable and ServeState::claim refused it (panic at main.rs:1131). The test now creates the directory with mode 0700. This is not proved to be the CI failure. Under umask 002, publish::tests::a_replacement_publishes_the_new_key_beside_a_refused_one and a_value_sealed_to_the_published_identity_unseals_under_the_keys also fail, and that code comes from main and this branch does not touch it. So CI probably does not use umask 002. If the tests check fails again, the full job log must name the failing test. cargo-deny: the advisories failure is a vulnerability in the ecdsa/rfc6979/primeorder tree under russh 0.64.1, plus an unmatched RUSTSEC-2026-0009 entry in deny.toml. This branch does not change Cargo.lock or deny.toml. The fix belongs in deny.toml or Cargo.lock, outside this task's layers, so this round leaves it alone. Doctests were not run: this box has no rustdoc. The diff adds no doc examples.

@minimal-foundry minimal-foundry Bot added the needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs. label Oct 24, 2026
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

The runtime will not revise this pull request again on its own: the diff is +727/-11 (738 lines), past 3x the plan's estimate of 180 lines (est: 120 code, 60 tests, as #2283 reads now), at round 1 (the last trigger was a failed check suite). A person decides now. Remove needs-human from this pull request to let /revise <note> or the automatic rounds resume, or finish #2283 by hand.

@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: PASS · the full ruling

This push changes only the setup of one test. The test now makes the state directory private, as bep serve does. The privacy check in ServeState::claim is not changed, so the proof still tests the gate. The PASS on 893acc5 stays for the rest of the change, under §6.10 line 839. The three residuals keep their issues: #2509, #2510 and #2285.

Ruled at 25f41f2 against gominimal/arch@109541a48c31 by claude-opus-5-5.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 2 findings, 2 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/bep/src/main.rs Resolved
Comment thread crates/bep/src/main.rs Resolved
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Revise round 1 ran against ci-success, cargo-deny and left it red; a coder round cannot clear it, a person should look. No round was started for the failed suite on 25f41f251093.

@norrietaylor norrietaylor removed the needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs. label Oct 24, 2026
Copy link
Copy Markdown
Member

/revise Fix the one MEDIUM minimal-review posted at 25f41f2 (thread 4366256381, correctness, serve, crates/bep/src/main.rs:878-889). The size cap is lifted for this walk, so the rounds continue.

The SIGUSR1 task calls rotate on a tokio worker. rotate holds the std Mutex<Ca> that Proxy issues leaves through (proxy.rs:162) while it runs renew_signing, a synchronous keychain signing call that can block or prompt on macOS, and then writes the fingerprint file. A slow keychain call stalls that worker and every leaf issue until it returns. Move the rotation off the runtime: tokio::task::spawn_blocking around rotate in the SIGUSR1 loop, and the same for claim_state, which sleeps up to CLAIM_RETRY on the runtime thread at start. Keep the Mutex<Ca> hold as short as it is now (renew, then record), and keep the log lines. Pin it with a test that drives the signal path through spawn_blocking and checks the result, or state in the body why the existing test covers it.

Thread 4366256387 is the pid-reuse finding ruled not worth its code on thread 4366222826; leave it. Arch PASS stands at d411e16 and the compare since changes the refusal text and a test directory mode only.


Generated by Claude Code

The SIGUSR1 handler and the state claim at start now run in
spawn_blocking. A slow or prompting keychain signature no longer
stalls a tokio worker, and the claim retry no longer sleeps on one.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on a /revise note, pushed as 091a4ad9870e.

line exit tail
cargo nextest run -p bep signing_ca_rotation_invisible_to_boxes 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-6842... Compiling bep v0.0.1 (/workbench/crates/bep) Finished 'test' profile [unoptimized] target(s) in 24.12s ──────────── Nextest run ID 38f8d505-0c75-46f6-9ff5-80ef2abbce27 with nexte

Verify min task run agent-verify passed.

Review threads the coder judged:

  • 👍 valid — crates/bep/src/main.rs:913: The SIGUSR1 handler now runs rotate through spawn_blocking in a new rotate_on_request, and claim_state runs through spawn_blocking at start. A new test, signing_ca_rotation_invisible_to_boxes_off_the_runtime, checks that no CA signature runs on the runtime thread.

A person can rotate the signing certificate under the same root with bep --rotate-signing, and running boxes keep cloning and pushing unchanged. The rotation now runs on a blocking thread, so a slow or prompting keychain signature does not stall the proxy's runtime threads.

Notes from the agent: Revise round 1. The SIGUSR1 loop in serve now calls a new rotate_on_request. That function runs rotate in tokio::task::spawn_blocking and keeps the same log lines. claim_state at start also runs in spawn_blocking. The Mutex<Ca> hold did not change: rotate still renews and then records under one lock. The new test signing_ca_rotation_invisible_to_boxes_off_the_runtime runs the SIGUSR1 loop body, fed from a channel through request_rotation, on a current_thread runtime. It checks the printed text, the unchanged root, the new signing CA, the fingerprint file, and that no CA signature ran on the runtime thread. I checked that the test fails when rotate is called inline, and then restored the fix. The new test matches the test line by name, so the line runs it. The cargo-deny failure is a RUSTSEC advisory in the russh dependency tree and deny.toml. Both are outside this task's layers, so I did not change them. ci-success fails only because it aggregates cargo-deny. Thread 4366256387 was left alone, as the note asked. It was not listed under Unresolved threads.

@minimal-foundry minimal-foundry Bot added the needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs. label Oct 24, 2026
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

The runtime will not revise this pull request again on its own: the diff is +865/-11 (876 lines), past 3x the plan's estimate of 180 lines (est: 120 code, 60 tests, as #2283 reads now), at round 1 (the last trigger was a /revise note). A person decides now. Remove needs-human from this pull request to let /revise <note> or the automatic rounds resume, or finish #2283 by hand.

@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: PASS · the full ruling

This push moves the keychain rotation and the state claim off the runtime threads. The rotation itself does not change. The root stays the same, so running boxes see no change, as Gatehouse §6.10 line 839 requires. The new test checks the root, the new signing CA, and the recorded fingerprint, and it does not relax a gate. The earlier PASS stands, and the three residuals keep their issues.

Ruled at 091a4ad against gominimal/arch@109541a48c31 by claude-opus-5-5.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 2 findings, 2 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/bep/src/main.rs Resolved
Comment thread crates/bep/src/main.rs Resolved
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Revise round 1 ran against ci-success, cargo-deny and left it red; a coder round cannot clear it, a person should look. No round was started for the failed suite on 091a4ad9870e.

@norrietaylor norrietaylor removed the needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs. label Oct 24, 2026
@norrietaylor norrietaylor changed the title T36 — Rotate the signing certificate under an unchanged root feat(bep): rotate the signing CA under an unchanged root Oct 24, 2026
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: PASS · the full ruling

This head merges main into the branch. It does not change the bep code. The crates/bep diff is identical to the diff on 091a4ad9, which passed against Gatehouse §6.10 lines 821 and 839. The ruling on that head stands for this head. The residuals #2509, #2510 and #2285 stay open as filed. The e2e leg in #2510 is open, not passed.

Ruled at 810f4a4 against gominimal/arch@109541a48c31 by claude-opus-5-5.

@norrietaylor norrietaylor left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Babysitter verification at 810f4a4. This head is GitHub's merge of main fa31776 (crypto-bigint 0.7.7, the macOS reap) onto 091a4ad, which the earlier review covered; arch re-ruled PASS here and minimal-review confirms the head only merged the base. Zero open threads, cargo-deny green, ci-success green. Squash title feat(bep): rotate the signing CA under an unchanged root passes commitlint. Residuals #2509 and #2510 are scheduled as T51 and T52 in slice S9.


Generated by Claude Code

@norrietaylor norrietaylor left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Babysitter approval at 810f4a4: every merge gate passes at this head (arch PASS, zero open threads, every lane aggregator green, bot resolutions re-verified). Squash title feat(bep): rotate the signing CA under an unchanged root and the squashed message pass commitlint. Queued for squash merge.


Generated by Claude Code

@norrietaylor
norrietaylor added this pull request to the merge queue Oct 24, 2026
Merged via the queue into main with commit 99ade7d Oct 24, 2026
37 checks passed
@norrietaylor
norrietaylor deleted the agent/2283-rotate-the-signing-certificate-under-an-unchange branch October 24, 2026 22:51
norrietaylor added a commit that referenced this pull request Oct 25, 2026
…reap

#2508 moved the control socket's default into default_control(). The
stdin lifeline watch stays ahead of it in serve().

Claude-Session: https://claude.ai/code/session_01WhCC5TEBNzPeWgytRsU4rc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

T36 — Rotate the signing certificate under an unchanged root

1 participant

0