// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; /// @title AeonoraRegistry /// @notice Minimal, chain-neutral anchor for agent identity, constitution lineage, and receipts. contract AeonoraRegistry { uint64 public constant RECOVERY_DELAY = 2 days; struct Agent { address controller; bytes32 constitutionCommitment; bytes32 previousCommitment; uint64 version; bool active; } struct BrainPointer { string cid; bytes32 ciphertextCommitment; uint64 version; } mapping(bytes32 => Agent) public agents; mapping(bytes32 => bool) public receiptExists; mapping(bytes32 => address) public recoveryAddresses; mapping(bytes32 => address) public pendingControllers; mapping(bytes32 => uint64) public recoveryReadyAt; mapping(bytes32 => BrainPointer) public brainPointers; event AgentRegistered(bytes32 indexed agentId, address indexed controller, bytes32 constitutionCommitment); event ConstitutionUpdated(bytes32 indexed agentId, bytes32 previousCommitment, bytes32 constitutionCommitment, uint64 version); event AgentSuspended(bytes32 indexed agentId); event AgentReactivated(bytes32 indexed agentId); event ActionReceiptCommitted(bytes32 indexed agentId, bytes32 indexed actionHash, bytes32 receiptHash); event ControllerTransferProposed(bytes32 indexed agentId, address indexed controller); event ControllerChanged(bytes32 indexed agentId, address indexed previousController, address indexed controller); event RecoveryAddressChanged(bytes32 indexed agentId, address indexed recoveryAddress); event RecoveryStarted(bytes32 indexed agentId, address indexed controller, uint64 readyAt); event RecoveryCancelled(bytes32 indexed agentId); event BrainPointerUpdated(bytes32 indexed agentId, string cid, bytes32 ciphertextCommitment, uint64 version); modifier controlled(bytes32 agentId) { require(agents[agentId].controller == msg.sender, "not controller"); _; } function register(bytes32 constitutionCommitment, bytes32 salt) external returns (bytes32 agentId) { require(constitutionCommitment != bytes32(0), "empty commitment"); agentId = keccak256(abi.encode(msg.sender, constitutionCommitment, salt)); require(agents[agentId].controller == address(0), "agent exists"); agents[agentId] = Agent(msg.sender, constitutionCommitment, bytes32(0), 1, true); emit AgentRegistered(agentId, msg.sender, constitutionCommitment); } function update(bytes32 agentId, bytes32 constitutionCommitment) external controlled(agentId) { require(constitutionCommitment != bytes32(0), "empty commitment"); Agent storage agent = agents[agentId]; bytes32 previous = agent.constitutionCommitment; agent.previousCommitment = previous; agent.constitutionCommitment = constitutionCommitment; agent.version += 1; emit ConstitutionUpdated(agentId, previous, constitutionCommitment, agent.version); } function proposeController(bytes32 agentId, address controller) external controlled(agentId) { require(controller != address(0), "empty controller"); pendingControllers[agentId] = controller; recoveryReadyAt[agentId] = 0; emit ControllerTransferProposed(agentId, controller); } function acceptController(bytes32 agentId) external { require(pendingControllers[agentId] == msg.sender, "not pending controller"); _changeController(agentId, msg.sender); } function setRecoveryAddress(bytes32 agentId, address recoveryAddress) external controlled(agentId) { require(recoveryAddress != address(0), "empty recovery"); recoveryAddresses[agentId] = recoveryAddress; emit RecoveryAddressChanged(agentId, recoveryAddress); } function startRecovery(bytes32 agentId, address controller) external { require(recoveryAddresses[agentId] == msg.sender, "not recovery address"); require(controller != address(0), "empty controller"); uint64 readyAt = uint64(block.timestamp + RECOVERY_DELAY); pendingControllers[agentId] = controller; recoveryReadyAt[agentId] = readyAt; emit RecoveryStarted(agentId, controller, readyAt); } function cancelRecovery(bytes32 agentId) external controlled(agentId) { require(recoveryReadyAt[agentId] != 0, "no recovery"); delete pendingControllers[agentId]; delete recoveryReadyAt[agentId]; emit RecoveryCancelled(agentId); } function finishRecovery(bytes32 agentId) external { uint64 readyAt = recoveryReadyAt[agentId]; require(readyAt != 0 && block.timestamp >= readyAt, "recovery delay"); address controller = pendingControllers[agentId]; require(controller != address(0), "empty controller"); _changeController(agentId, controller); } function suspend(bytes32 agentId) external controlled(agentId) { agents[agentId].active = false; emit AgentSuspended(agentId); } function reactivate(bytes32 agentId) external controlled(agentId) { agents[agentId].active = true; emit AgentReactivated(agentId); } function commitReceipt(bytes32 agentId, bytes32 actionHash, bytes32 receiptHash) external controlled(agentId) { require(agents[agentId].active, "agent suspended"); require(receiptHash != bytes32(0), "empty receipt"); require(!receiptExists[receiptHash], "receipt exists"); receiptExists[receiptHash] = true; emit ActionReceiptCommitted(agentId, actionHash, receiptHash); } function setBrainPointer(bytes32 agentId, string calldata cid, bytes32 ciphertextCommitment) external controlled(agentId) { require(agents[agentId].active, "agent suspended"); bytes memory cidBytes = bytes(cid); require(cidBytes.length >= 8 && cidBytes.length <= 128, "invalid cid"); require(ciphertextCommitment != bytes32(0), "empty commitment"); BrainPointer storage pointer = brainPointers[agentId]; pointer.cid = cid; pointer.ciphertextCommitment = ciphertextCommitment; pointer.version += 1; emit BrainPointerUpdated(agentId, cid, ciphertextCommitment, pointer.version); } function _changeController(bytes32 agentId, address controller) internal { address previous = agents[agentId].controller; agents[agentId].controller = controller; delete pendingControllers[agentId]; delete recoveryReadyAt[agentId]; emit ControllerChanged(agentId, previous, controller); } }