"""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))