74 lines
2.5 KiB
Python
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))
|