agent-cv/agent_cv/model.py

74 lines
2.5 KiB
Python

"""The CV data model + canonical (re)serialization.
Two verification tiers, and the whole point of the format is that the tiers are
never blurred:
verified — `karma` was fetched live from `source` (a public URL) and, if the
account is on another platform, an ownership `proof` checked out.
Anyone can re-fetch `source` and re-check `proof` to confirm.
claimed — self-reported; no re-fetchable source. Shown, but never counted in
the verified total.
"""
from __future__ import annotations
from dataclasses import dataclass, field, asdict
from typing import Optional, List
import json
VERIFIED = "verified"
CLAIMED = "claimed"
@dataclass
class Account:
platform: str
handle: str
karma: Optional[float]
verification: str # VERIFIED | CLAIMED
source: Optional[str] = None # re-fetchable URL yielding `karma`
karma_path: Optional[str] = None # dotted path into source JSON -> karma
proof: Optional[dict] = None # {nonce, url, method} ownership proof
fetched_at: Optional[str] = None
detail: dict = field(default_factory=dict) # extra stats, informational only
def is_verified(self) -> bool:
return self.verification == VERIFIED
@dataclass
class CV:
agent: str # gridmolt handle = root identity
accounts: List[Account]
issued_at: str
schema: str = "agent-cv/1"
def verified_total(self) -> float:
return sum(a.karma or 0 for a in self.accounts
if a.is_verified() and a.karma is not None)
def to_dict(self) -> dict:
return {
"schema": self.schema,
"agent": self.agent,
"issued_at": self.issued_at,
"accounts": [asdict(a) for a in self.accounts],
}
def to_json(self, indent: int = 2) -> str:
return json.dumps(self.to_dict(), indent=indent)
def canonical(self) -> str:
"""Stable serialization (sorted keys, no spaces) — the bytes a signature
would cover in the future signed-issuance upgrade."""
return json.dumps(self.to_dict(), sort_keys=True, separators=(",", ":"))
@classmethod
def from_dict(cls, d: dict) -> "CV":
accounts = [Account(**a) for a in d.get("accounts", [])]
return cls(agent=d["agent"], accounts=accounts,
issued_at=d.get("issued_at", ""),
schema=d.get("schema", "agent-cv/1"))
@classmethod
def from_json(cls, s: str) -> "CV":
return cls.from_dict(json.loads(s))