// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; /** * @title PolicyProfileRegistry * @notice Minimal on-chain anchor for URA `policyProfileId` rows (content hash + version + effective time). * Pair with off-chain `config/universal-resource-activation/policy-profiles.json` and * `scripts/ura/policy-profiles-content-hash.mjs` in the proxmox repo. Not a full GRU M00 diamond. */ contract PolicyProfileRegistry is AccessControl { bytes32 public constant PUBLISHER_ROLE = keccak256("PUBLISHER_ROLE"); struct Record { bytes32 contentHash; uint64 version; uint256 effectiveFrom; bool exists; } mapping(bytes32 => Record) private _records; event ProfilePublished( bytes32 indexed profileKey, string policyProfileId, bytes32 contentHash, uint64 version, uint256 effectiveFrom ); constructor(address admin) { require(admin != address(0), "PolicyProfileRegistry: admin"); _grantRole(DEFAULT_ADMIN_ROLE, admin); _grantRole(PUBLISHER_ROLE, admin); } function profileKey(string calldata policyProfileId) public pure returns (bytes32) { return keccak256(bytes(policyProfileId)); } /** * @param contentHash keccak256 of canonical JSON for this profile row (see proxmox hash script) */ function publishProfile( string calldata policyProfileId, bytes32 contentHash, uint64 version, uint256 effectiveFrom ) external onlyRole(PUBLISHER_ROLE) { bytes32 key = profileKey(policyProfileId); _records[key] = Record({ contentHash: contentHash, version: version, effectiveFrom: effectiveFrom, exists: true }); emit ProfilePublished(key, policyProfileId, contentHash, version, effectiveFrom); } function getRecord(string calldata policyProfileId) external view returns (Record memory) { return _records[profileKey(policyProfileId)]; } }