"""Database models for on-chain governance attestations. ``RiskAttestation.rationale_hash`` (see arf-onchain's ``AttestationLib.sol`` and enterprise's ``arf_enterprise.onchain.attestation``) is a ``keccak256`` digest anchored on Monad -- the chain deliberately never stores the rationale text itself, only its hash, to keep operational detail about a customer's infrastructure off a public ledger. That means the text has to live somewhere off-chain, keyed by the same hash, or the anchored hash proves nothing: nobody could ever produce the preimage to check it against. This table is that store. It is intentionally separate from ``DecisionAuditLogDB`` (``models_intents.py``) rather than an extension of it: that table is written for every governance decision, on-chain or not, and already has its own signature column for a different purpose (Ed25519 intent-signing, not the secp256k1 EIP-712 signature the guard verifies). Conflating the two would mean a column that is only sometimes meaningful depending on whether the decision was ever attested on-chain. """ import uuid import datetime from sqlalchemy import Column, String, DateTime, Text from .base import Base class OnchainRationaleDB(Base): """The plaintext preimage of an anchored ``rationale_hash``. Keyed by the hash itself (unique, indexed) rather than by an auto-incrementing id: a lookup always starts from a hash read off-chain (from `DecisionAnchored` or `AttestationIssued`), never from a row id nothing on-chain knows about. No ``tenant_id`` / foreign key to ``tenants``: an on-chain agent is identified by its wallet address, not by this service's tenant concept, and the two are not yet bridged. `evaluator_address` and `agent_address` are recorded instead so a row can still be attributed and audited without assuming a tenant relationship that may not exist. """ __tablename__ = "onchain_rationales" id = Column(String(64), primary_key=True, default=lambda: str(uuid.uuid4())) rationale_hash = Column( String(66), nullable=False, unique=True, index=True ) # "0x" + 64 hex chars rationale = Column(Text, nullable=False) agent_address = Column(String(42), nullable=True) evaluator_address = Column(String(42), nullable=True) created_at = Column( DateTime, default=datetime.datetime.utcnow, nullable=False, index=True )