// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.24; import {Owned} from "./Owned.sol"; /// @notice Admin-governed Epoche workload versions. This registry has no customer-artifact API. contract EpocheVersionRegistry is Owned { struct Version { bytes32 releaseId; bytes32 ccePolicyHash; bytes32 imageDigest; bytes32 sourceCommitment; bytes32 protocolCompatibilityHash; bytes32 evidenceHash; bytes32 predecessor; bytes32 proofImageId; uint64 approvedAt; bool revoked; } bool public approvalsPaused; mapping(bytes32 purpose => mapping(bytes32 releaseId => Version)) public versions; mapping(bytes32 purpose => mapping(bytes32 proofImageId => bytes32 releaseId)) public releaseForProofImage; mapping(bytes32 purpose => mapping(bytes32 ccePolicyHash => bytes32 releaseId)) public releaseForPolicy; mapping(bytes32 purpose => mapping(bytes32 releaseId => bytes32 reasonCode)) public versionRevocationReason; /// @dev A proof program is assigned once, globally, so one image cannot silently acquire a /// different purpose through a second approval. mapping(bytes32 proofImageId => bytes32 purpose) public purposeForProofImage; error ApprovalsPaused(); error InvalidVersion(); error VersionExists(); error InvalidPredecessor(); error ProofImageAlreadyAssigned(); error VersionAlreadyRevoked(); event VersionApproved( bytes32 indexed purpose, bytes32 indexed releaseId, bytes32 ccePolicyHash, bytes32 imageDigest, bytes32 evidenceHash, bytes32 proofImageId ); event VersionRevoked(bytes32 indexed purpose, bytes32 indexed releaseId, bytes32 reasonCode); event ApprovalsPauseChanged(bool paused); constructor(address initialOwner) Owned(initialOwner) {} function setApprovalsPaused(bool paused) external onlyOwner { approvalsPaused = paused; emit ApprovalsPauseChanged(paused); } function approveVersion(bytes32 purpose, Version calldata version) external onlyOwner { if (approvalsPaused) revert ApprovalsPaused(); if ( purpose == bytes32(0) || version.releaseId == bytes32(0) || version.ccePolicyHash == bytes32(0) || version.imageDigest == bytes32(0) || version.sourceCommitment == bytes32(0) || version.protocolCompatibilityHash == bytes32(0) || version.evidenceHash == bytes32(0) ) revert InvalidVersion(); if (versions[purpose][version.releaseId].releaseId != bytes32(0)) revert VersionExists(); if (version.predecessor != bytes32(0)) { if ( version.predecessor == version.releaseId || versions[purpose][version.predecessor].releaseId == bytes32(0) ) revert InvalidPredecessor(); } if ( version.proofImageId != bytes32(0) && purposeForProofImage[version.proofImageId] != bytes32(0) ) revert ProofImageAlreadyAssigned(); if (releaseForPolicy[purpose][version.ccePolicyHash] != bytes32(0)) { revert VersionExists(); } Version memory stored = version; stored.approvedAt = uint64(block.timestamp); stored.revoked = false; versions[purpose][version.releaseId] = stored; releaseForPolicy[purpose][version.ccePolicyHash] = version.releaseId; if (version.proofImageId != bytes32(0)) { releaseForProofImage[purpose][version.proofImageId] = version.releaseId; purposeForProofImage[version.proofImageId] = purpose; } emit VersionApproved( purpose, version.releaseId, version.ccePolicyHash, version.imageDigest, version.evidenceHash, version.proofImageId ); } function revokeVersion(bytes32 purpose, bytes32 releaseId, bytes32 reasonCode) external onlyOwner { Version storage version = versions[purpose][releaseId]; if (version.releaseId == bytes32(0) || reasonCode == bytes32(0)) revert InvalidVersion(); if (version.revoked) revert VersionAlreadyRevoked(); version.revoked = true; versionRevocationReason[purpose][releaseId] = reasonCode; emit VersionRevoked(purpose, releaseId, reasonCode); } function isApproved(bytes32 purpose, bytes32 releaseId) public view returns (bool) { Version storage version = versions[purpose][releaseId]; return version.releaseId != bytes32(0) && !version.revoked; } function isProofImageApproved(bytes32 purpose, bytes32 proofImageId) external view returns (bool) { return isApproved(purpose, releaseForProofImage[purpose][proofImageId]); } function isPolicyApproved(bytes32 purpose, bytes32 ccePolicyHash) external view returns (bool) { return isApproved(purpose, releaseForPolicy[purpose][ccePolicyHash]); } /// @notice Resolves all immutable release coordinates, not just a policy alias. function matchesApprovedVersion( bytes32 purpose, bytes32 releaseId, bytes32 ccePolicyHash, bytes32 imageDigest ) external view returns (bool) { Version storage version = versions[purpose][releaseId]; return version.releaseId == releaseId && !version.revoked && version.ccePolicyHash == ccePolicyHash && version.imageDigest == imageDigest && releaseForPolicy[purpose][ccePolicyHash] == releaseId; } }