// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { IAccountabilityCanonicalSourceV3 } from "./IAccountabilityCanonicalSourceV3.sol";

/// @title AccountabilityCanonicalSourceV3
/// @notice Minimal append-only on-chain proof that the obligated account
///         performed the exact protocol action before a deadline.
/// @dev The obligation identifier is fixed to keccak256(account, sourceId).
/// No oracle, owner, proxy, score, evidence blob, or upgrade hook exists.
contract AccountabilityCanonicalSourceV3 is IAccountabilityCanonicalSourceV3 {
    bytes32 public constant SOURCE_SCHEMA = keccak256("Vaultfire.ObjectiveEvidence.CanonicalRecordedAt.v1");
    mapping(bytes32 sourceId => mapping(bytes32 obligationId => uint64 timestamp)) public recordedAt;

    error InvalidObligation();
    error AlreadyRecorded();

    event CanonicalActionRecordedV3(
        bytes32 indexed sourceId, bytes32 indexed obligationId, address indexed account, uint64 recordedAt
    );

    function record(bytes32 sourceId, bytes32 obligationId) external {
        if (sourceId == bytes32(0) || obligationId != keccak256(abi.encode(msg.sender, sourceId))) {
            revert InvalidObligation();
        }
        if (recordedAt[sourceId][obligationId] != 0) revert AlreadyRecorded();
        uint64 timestamp = uint64(block.timestamp);
        recordedAt[sourceId][obligationId] = timestamp;
        emit CanonicalActionRecordedV3(sourceId, obligationId, msg.sender, timestamp);
    }
}
