Repository navigation
feat(minimal): mint store handles and keep client keys registered across proxy restarts - #2520
Conversation
The control socket writes each new client key to a file beside the socket before it replies, and the next bind restores every key in it. A box created before a restart keeps redeeming its store references after it (BEP-063).
mint_store_handle signs a gatehouse-store-handle+jws under the client key and registers that key before it returns the handle (BEP-063). local_client_key replaces a key the keychain refuses. A mint under an expired root is refused and names `bep --replace-key root`. A refused key registration prints the message the task fixes.
|
Important Review skippedBot user detected. To trigger a single review, invoke the
|
|
Reviewed: 1 finding, none blocking at Address the comments on the diff. Clobber: not checked in depth. If that was not intentional, restore the earlier change. |
There was a problem hiding this comment.
arch-review: CONDITIONS · 2 conditions · the full ruling
The proxy half conforms with §6.10. Key registrations persist before the reply, they are restored at bind, and the files are owner-only. The store handle's claims and expiry follow lines 839 and 842. But box creation still makes a new key for each creation (create.rs:526). Line 842 asks for the client's local key. This PR deletes the interim note for that gap and closes its issue. Declare the interim again and correct the docs that claim one key (C1). File the follow-up work as residuals instead of closing #2288 (C2).
Ruled at bbf883e against gominimal/arch@109541a48c31 by claude-opus-5-5.
Open the client key registrations file once with O_NOFOLLOW and check owner and mode on the handle, in restore and in append. Start a new key on its own line after a torn last line. Write and sync the file before the registry write lock, so handle checks do not wait on the disk.
There was a problem hiding this comment.
arch-review: CONDITIONS · 2 conditions · the full ruling
The store handle and the restart-safe key file conform to Gatehouse §6.10 lines 839 and 842. The new control-file checks fail closed. Box creation still signs under a new key for each box, at create.rs mint_grants. This PR removes the interim note that said so, and it closes #2288. C1 restores the note with an open flip issue. The PR body also lists four undone items with no issue. C2 files them and links them from the dead_code reasons.
Ruled at 34ead5e against gominimal/arch@109541a48c31 by claude-opus-5-5.
|
Round 1 of 10 on the runtime's review findings, pushed as
Verify Review threads the coder judged:
A box created before a proxy restart keeps redeeming its store references after it. The proxy restores every registered client key from the file beside its control socket. This round makes that file harder to lose or swap: a torn last line no longer swallows the next key, and a symlinked or foreign file is refused. Notes from the agent: Revise round 1. Only crates/bep/src/control.rs changed. The key file write and its fsync now happen before the registry write lock, but they still run on the async worker thread. A new key is rare, so this round does not add spawn_blocking. Two connections that race on one new key can both append it. The restore registers it once, because ClientKeys::register is idempotent. No change to mint.rs this round. |
The daemon-connection inventory now lists the two control-socket connects in the BEP-063 mint test, which failed the tests lane. The mint and control socket docs now say that box creation still makes a new client key per box, that each key stays at the proxy, and that the flip to one kept key is a residual of #2288.
There was a problem hiding this comment.
minimal-review — approval withheld
Verdict COMMENT — APPROVE withheld: 10 unresolved threads — minimal-foundry[bot] crates/bep/src/control.rs, minimal-foundry[bot] crates/bep/src/control.rs, minimal-arch[bot] crates/minimal/src/mint.rs:203, minimal-arch[bot] crates/minimal/src/mint.rs, minimal-arch[bot] crates/minimal/src/mint.rs, …. No findings.
Panel: 3 of 3 personas ran (correctness, security, supply-chain).
There was a problem hiding this comment.
arch-review: CONDITIONS · 2 conditions · the full ruling
The proxy half of BEP-063 conforms. The client still makes a new key for each box, and §6.10 line 842 requires one local key that is registered at first use. The head calls this an interim, but it links the flip to #2288, which this PR closes. File an open issue for the flip, and put its URL in each interim note (C1). List the four residuals in the PR body, each with an open issue (C2). No code change is necessary.
Ruled at 0682030 against gominimal/arch@109541a48c31 by claude-opus-5-5.
|
Round 2 of 10 on a failed check suite, pushed as
Verify Outside the task's
Converted to a draft: something above needs a person before review. Review threads the coder judged:
The proxy restores every registered client key from its key file at bind, so a box created before a proxy restart keeps redeeming handles signed under its key after the restart (client_key_registrations_survive_restart). Box creation does not mint store references yet, so the store-reference half of the proof stays a residual of #2288. Notes from the agent: CI failure: minimal tests::every_daemon_connection_is_classified failed because the BEP-063 mint test in mint.rs added two ControlClient::connect sites that the inventory did not list. I added the two entries to the inventory in crates/minimal/src/tests.rs. That file is outside the layers, but the inventory test requires each new connect site to be listed there. ci-linux-native-success is the aggregator and failed only because of the tests job. Residuals the reviewer asked to file as issues: (1) mint_grants in cmd/boxes/create.rs reads local_client_key in place of ClientKey::generate; (2) a keychain ClientKeyStore backend; (3) a |
|
/revise Round 3 of #2520, on the arch ruling at 0682030. No round is in flight. The epic owner ruled on 2026-10-25 that this PR keeps
One commit, Conventional Commits subject, body lines at most 100 characters. Generated by Claude Code |
Issue 2288 ends with this branch, so the interim notes now link the open issues that own the residual work. The mint and control socket docs and each dead_code expect reason name #2524 for the mint_grants flip to one kept client key. The mint module doc also names #2525 for the store member kind and #2526 for the min box spec store references.
|
Round 3 of 10 on a /revise note, pushed as
Verify Outside the task's
Converted to a draft: something above needs a person before review. Review threads the coder judged:
A box created before a proxy restart keeps redeeming its store references after it: the proxy reloads the client key registrations from its owner-only key file at start, and client_key_registrations_survive_restart shows this. This round changes only docs and expect reasons. Every interim note now links #2524 in place of #2288. Notes from the agent: Round 3 changes only comments. Directive item 1 is done. Every #2288 reference in crates/bep/src/control.rs and crates/minimal/src/mint.rs now names #2524. This includes the module docs, the ClientKey Interim paragraph, the ProxyRegistration doc and all nine dead_code expect reasons. No expect covers the store handle's sealing, so no expect names 10BC8 #2525. The mint module doc also links #2525 (store member kind) and #2526 (min box spec store references). Directive item 2 (## Residuals in the PR body) and the replies or resolutions in items 3 and 5 are PR-body and thread actions. I have no credential for them, so the runner or the owner must do them. Thread 4369172514 is not in the Unresolved threads list, so I owe it no verdict here. Its interim stays as documented, and #2524 owns the flip. crates/minimal/src/tests.rs is unchanged this round (item 4). Clippy on -p minimal -p bep --all-targets is clean for these crates. It printed warnings only in minimald and minimald-rpc, which this round does not touch. I did not run just clippy-strict. |
There was a problem hiding this comment.
arch-review: PASS_WITH_CONDITIONS · 3 conditions · the full ruling
The interim key notes now link the open issue #2524, so the baseline's C1 is met. The proxy half and the store handle conform with Gatehouse §6.10 lines 839 and 842. Two parts of line 842 stay open as filed residuals: one local client key (C1, #2524) and the sealed store member kind (C2, #2525). The change can merge with them open. Edit the PR body so that notes 1 to 4 name #2524, #2525 and #2526 (C3).
Ruled at 5aefe94 against gominimal/arch@109541a48c31 by claude-opus-5-5.
|
Round 4 of 10 on the runtime's review findings: the coder changed nothing. Revise round 4: the one unresolved thread repeats the finding answered invalid in PRRT_kwDOPdfcxM6v88Fg. I judged it invalid again and changed no code. The branch head already satisfies the task, so this round adds no commit. The interim growth is documented in the control.rs module doc and in mint.rs, and #2524 owns the flip to one kept key. Review threads the coder judged:
|
There was a problem hiding this comment.
Babysitter approval at 5aefe94: every merge gate passes at this head (arch PASS with its two conditions held on the filed residuals #2524 and #2525, the key-trust finding held on #2528, zero open threads, every lane aggregator green, bot resolutions re-verified; the accepted Layer exception covers the two inventory lines in tests.rs). Squash title feat(minimal): mint store handles and keep client keys registered across proxy restarts and the squashed message pass commitlint. Queued for squash merge.
Generated by Claude Code
T27 — Mint store handles and keep the client's key registered across proxy restarts
slice: S3
kind: task
covers: BEP-063
size: M
est: 250 code, 240 tests
executor: agent
difficulty: needs care
model: standard
diagnostics:
min box speclists each store reference with its authorities; a refused registration prints "The proxy did not accept this client's signing key, so the box was not created." on stderr, pinned by store_handle_signed_and_key_registeredobservability: one info log line per key registration with its fingerprint, and one at start with the count restored
layers:
- mint:
- crates/minimal/src/mint.rs: the BEP-063 store handle; a refused key replaced and registered before minting
- crates/minimal/src/mint.rs: refuses a mint whose
root_not_afteris not later than now with an error namingbep --replace-key root, so an expired root never mints already-expired members (review MEDIUM 4347249409 on #2490, residual filed here)test: cargo nextest run -p minimal store_handle_signed_and_key_registered
- proxy:
- crates/bep/src/control.rs: registrations persisted across restarts; the socket owned by the operator's user at mode 0600
test: cargo nextest run -p bep client_key_registrations_survive_restart
test: cargo nextest run -p bep a_client_key_this_program_is_refused_is_replaced
test: cargo nextest run -p bep control_socket_is_owner_only_and_separate_from_listener
produces: none
consumes:
- crates/minimal/src/mint.rs
- crates/bep/src/control.rs
proof: A box created before a proxy restart keeps redeeming its store references after it.
depends on:
- blocked by #2271
- blocked by #2273
- blocked by #2280
- assumes OQ3 (the mint reads the root certificate's own expiry, and the root is generated with a fixed default validity in one constant that gominimal/arch#85's ruling replaces)
Proof
After a
bep serverestart, the proxy verifies a store handle signed under a client key registered before the restart. A box created before the restart keeps redeeming its store references after it. The control socket writes each new key to<control socket>.client-keys(mode 0600) before it replies, and the next start restores every key in that file. client_key_registrations_survive_restart shows this. On the client side, mint_store_handle returns a signed gatehouse-store-handle+jws only after the proxy accepts the client key.Notes from the agent: 1. The store handle is minted and signed, but not sealed. bep::seal::MemberKind has only SignIn, and seal.rs is outside the layers. A later task must add a store member kind before a store handle can travel in a sealed envelope.
2. Nothing in box creation calls mint_store_handle or local_client_key yet. The callers are in crates/minimal/src/cmd/boxes/create.rs, which is outside the layers. The new items carry cfg_attr(not(test), expect(dead_code, reason = ...)) until a later task wires them in.
3. ClientKeyStore is a trait with no host keychain backend. A macOS keychain backend that holds the key needs its own task. The replace-on-refusal logic is in local_client_key, and store_handle_signed_and_key_registered tests it.
4. Diagnostics:
min box specdoes not list store references with their authorities in this change. That code is not in mint.rs or control.rs.5. Printed text: I copied the fixed message "The proxy did not accept this client's signing key, so the box was not created." exactly. It says "the proxy". Every other MintError message says "the Box Egress Proxy", so the message breaks the one-name rule. It also gives no next step. The proxy's reason for the refusal goes to the log at warn.
6. The expired-root refusal reads: "Cannot start the box: the Box Egress Proxy's root certificate is expired. Run
bep --replace-key rootto make a new one." mint_and_register (sign-in references) now has this …[truncated]Tests
cargo nextest run -p minimal store_handle_signed_and_key_registeredcargo nextest run -p bep client_key_registrations_survive_restartcargo nextest run -p bep a_client_key_this_program_is_refused_is_replacedcargo nextest run -p bep control_socket_is_owner_only_and_separate_from_listenerVerify
min task run agent-verifyexited 101 under rustc 1.98.1, which the repository does not pin (1.97.0 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.Residuals
mint_grantsincrates/minimal/src/cmd/boxes/create.rsreadslocal_client_keyover a host-keychainClientKeyStoreinstead ofClientKey::generate()per box; this also covers the keychain backend (agent notes 2 and 3) and flips every interim inmint.rsandcontrol.rs.storemember kind inbep::seal::MemberKind, so a store handle travels sealed as{kind: "store", jws}(agent note 1).min box speclists each store reference with its authorities (agent note 4).Closes #2288
Layer exception
Undrafted 2026-10-25. The one edit outside the task's layers is
crates/minimal/src/tests.rs: two inventory lines (and their comment) inevery_daemon_connection_is_classified, which the repository requires for every new connect site, here the two test-only connects ofstore_handle_signed_and_key_registeredto the proxy control socket the test serves itself. The native lane fails without them. The babysitter accepted it under the epic owner's 2026-10-19 layer-exception rule.Generated by Claude Code