R5: consent-gated recruitment + per-hop X25519 sealed credentials
Add the in-band, signature-gated consent handshake that governs whether a
node may be recruited into a measured SOR circuit, plus a per-recipient
sealed box so a hop credential decrypts only with the host's X25519 key.
- crypto.rs: seal_to_pubkey/open_sealed (ephemeral X25519 -> HKDF-SHA256 ->
Fernet AEAD), reusing the existing fernet crate (no new symmetric primitive).
- sor/consent.rs: signed ConsentRequest (Ed25519 persona), node_evaluate
(reject unless the signature verifies), CircuitBuilder that recruits a hop
only on an accept it can open; parse_sor_frame with never-panic proptests.
- net.rs: {"_sor":...} control frames are live-only and classified out-of-band
by the SOR layer (parse_sor), never surfaced as chat/app events.
- cmd_chat/sor/consent.py + bridge _sor case: bit-compatible Python mirror;
the agent only observes/classifies consent frames — it never auto-accepts
and stands up no forwarder (forwarding is R4, isolated-engine-only).
Acceptance check (roadmap R5) green both languages: host recruits a hop only
after an explicit accept; a reject leaves no entry; the credential opens only
with the host key (third party cannot); unsigned/forged request -> rejected.
Cross-language KDF parity pinned by a shared known-answer vector.
Rust 75 passed; Python SOR suite 37 passed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -394,6 +394,18 @@ class AgentBridge(Client):
|
||||
self.sbx_name = ""
|
||||
self.sbx_backend = None
|
||||
return
|
||||
# R5 in-band SOR consent frames: classify only. Recruitment into a SOR
|
||||
# circuit is opt-in + signature-gated (cmd_chat/sor/consent.py); the
|
||||
# actual forwarder is R4 and runs isolated-engine-only. The bridge never
|
||||
# auto-accepts and stands up no forwarder here — it only recognizes the
|
||||
# frame so it is not mistaken for an addressed message.
|
||||
if "_sor" in frame:
|
||||
from cmd_chat.sor.consent import parse_sor_frame
|
||||
|
||||
parsed = parse_sor_frame(text)
|
||||
if parsed is not None:
|
||||
self.info(f"observed SOR consent frame ({parsed['kind']})")
|
||||
return
|
||||
if frame.get("_perm") != "acl":
|
||||
return
|
||||
was = self.granted
|
||||
|
||||
Reference in New Issue
Block a user