diff --git a/capiscio_sdk/simple_guard.py b/capiscio_sdk/simple_guard.py index b28bb44..386faab 100644 --- a/capiscio_sdk/simple_guard.py +++ b/capiscio_sdk/simple_guard.py @@ -300,6 +300,8 @@ def _load_or_generate_keys(self) -> None: else: raise ConfigurationError(f"capiscio_keys directory not found at {self.keys_dir}") + did_key_path = self.keys_dir / "did_key.txt" + if private_key_path.exists(): # Load existing key via gRPC key_info, error = self._client.simpleguard.load_key(str(private_key_path)) @@ -308,6 +310,11 @@ def _load_or_generate_keys(self) -> None: # Update signing kid to match the loaded key self.signing_kid = key_info["key_id"] logger.info(f"Loaded key: {self.signing_kid}") + + # Recover did:key identity from sidecar file if in dev mode + if self.dev_mode and not self._explicit_agent_id and did_key_path.exists(): + self.agent_id = did_key_path.read_text().strip() + logger.info(f"Dev Mode: Recovered did:key identity: {self.agent_id}") elif self.dev_mode: logger.info("Dev Mode: Generating Ed25519 keypair via gRPC") @@ -323,6 +330,8 @@ def _load_or_generate_keys(self) -> None: if did_key and not self._explicit_agent_id: self.agent_id = did_key logger.info(f"Dev Mode: Using did:key identity: {self.agent_id}") + # Persist did:key for recovery on subsequent loads + did_key_path.write_text(did_key) # Save private key with open(private_key_path, "w") as f: diff --git a/tests/unit/test_simple_guard.py b/tests/unit/test_simple_guard.py index c20477b..61e3fcf 100644 --- a/tests/unit/test_simple_guard.py +++ b/tests/unit/test_simple_guard.py @@ -121,6 +121,58 @@ def test_legacy_agent_card_with_deprecation_warning(self, temp_workspace, mock_r guard.close() +class TestSimpleGuardDidKeyRecovery: + """Tests for dev mode did:key persistence and recovery.""" + + def test_dev_mode_persists_did_key_on_generate(self, temp_workspace, mock_rpc_client): + """Test that dev_mode persists did:key to sidecar file on first generation.""" + mock_rpc_client.simpleguard.generate_key_pair.return_value = ({ + "key_id": "test-key", + "did_key": "did:key:z6MkTestGeneratedKey", + "private_key_pem": "-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----", + "public_key_pem": "-----BEGIN PUBLIC KEY-----\ntest\n-----END PUBLIC KEY-----", + }, None) + + guard = SimpleGuard(dev_mode=True) + + assert guard.agent_id == "did:key:z6MkTestGeneratedKey" + did_key_path = temp_workspace / "capiscio_keys" / "did_key.txt" + assert did_key_path.exists() + assert did_key_path.read_text() == "did:key:z6MkTestGeneratedKey" + + guard.close() + + def test_dev_mode_recovers_did_key_on_load(self, temp_workspace, mock_rpc_client): + """Test that dev_mode recovers did:key from sidecar file when key exists.""" + keys_dir = temp_workspace / "capiscio_keys" + keys_dir.mkdir(parents=True) + (keys_dir / "trusted").mkdir() + (keys_dir / "private.pem").write_text("mock key") + (keys_dir / "public.pem").write_text("mock pub") + (keys_dir / "did_key.txt").write_text("did:key:z6MkRecoveredKey") + + guard = SimpleGuard(dev_mode=True) + + assert guard.agent_id == "did:key:z6MkRecoveredKey" + mock_rpc_client.simpleguard.generate_key_pair.assert_not_called() + + guard.close() + + def test_dev_mode_explicit_agent_id_ignores_sidecar(self, temp_workspace, mock_rpc_client): + """Test that explicit agent_id is not overridden by sidecar did_key.txt.""" + keys_dir = temp_workspace / "capiscio_keys" + keys_dir.mkdir(parents=True) + (keys_dir / "trusted").mkdir() + (keys_dir / "private.pem").write_text("mock key") + (keys_dir / "did_key.txt").write_text("did:key:z6MkShouldBeIgnored") + + guard = SimpleGuard(dev_mode=True, agent_id="did:web:example.com:myagent") + + assert guard.agent_id == "did:web:example.com:myagent" + + guard.close() + + class TestSimpleGuardSigning: """Tests for SimpleGuard signing operations."""