Problem
Evidence (nest_core/types.py:299) has no structured payload field:
class Evidence(BaseModel):
reporter: AgentId
subject: AgentId
kind: str
detail: str = ""
timestamp: float | None = None
Trust plugins that work from signed receipts have to smuggle them through the string field. agent_receipts (merged in #26) says it outright in its module docstring:
NEST's Evidence has no receipt field, so a cross-signed receipt rides as JSON in evidence.detail.
and its report() does json.loads(evidence.detail), then falls back to the plain-string heuristic on decode failure (agent_receipts.py:616-644). The parc plugin (#63) inherits the same convention. Every producer has to json.dumps a dict into a string field and every consumer has to try-parse it back, with a silent semantic fork between "string that happens to be JSON" and "string that is prose".
The same shape keeps being reinvented per layer because there is no shared primitive:
- payments has its own
Receipt model (types.py:126) — unrelated to trust evidence;
- datafacts'
cid_facts defines a signed FreshnessProof carrying a Signature;
- trust receipts are untyped dicts with
issuer_did / action / signature / evidence.witness_signatures keys, spec'd only by the code that verifies them.
Proposal
Add an optional structured payload to Evidence, non-breaking (defaults keep every existing caller valid):
Option A — minimal (my preference):
class Evidence(BaseModel):
...
receipt: dict[str, Any] | None = None
Consumers prefer receipt when present and keep the detail JSON fallback for one release. No schema commitment beyond "a receipt-shaped dict"; the verifying plugin still owns validation, exactly as today.
Option B — typed: promote the receipt dict convention from agent_receipts into a SignedReceipt model in nest_core.types (receipt_id, issuer_did, action, signature, witness co-signatures). Stronger contract, but it freezes a wire format that currently belongs to one plugin family, and pydantic validation on hostile receipts would need care to keep "invalid receipt" a scoring decision rather than a parse exception.
Option C — generic: attachments: list[dict[str, Any]] for arbitrary structured evidence. Most flexible, least discoverable.
Option A is the smallest change that removes the string-smuggling without deciding a universal receipt schema prematurely; B can follow later if more plugins converge on one shape.
Scope if accepted
Evidence.receipt field + docstring/example update in nest_core/types.py
agent_receipts.report() and parc.report() prefer the structured field, keep the detail fallback
receipt_reputation / parc_migration scenarios switch their auditors to the structured field
- tests for both paths (structured, fallback, and structured-but-invalid)
Happy to send the PR if the direction (and which option) is agreed.
Problem
Evidence(nest_core/types.py:299) has no structured payload field:Trust plugins that work from signed receipts have to smuggle them through the string field.
agent_receipts(merged in #26) says it outright in its module docstring:and its
report()doesjson.loads(evidence.detail), then falls back to the plain-string heuristic on decode failure (agent_receipts.py:616-644). Theparcplugin (#63) inherits the same convention. Every producer has tojson.dumpsa dict into a string field and every consumer has to try-parse it back, with a silent semantic fork between "string that happens to be JSON" and "string that is prose".The same shape keeps being reinvented per layer because there is no shared primitive:
Receiptmodel (types.py:126) — unrelated to trust evidence;cid_factsdefines a signedFreshnessProofcarrying aSignature;issuer_did/action/signature/evidence.witness_signatureskeys, spec'd only by the code that verifies them.Proposal
Add an optional structured payload to
Evidence, non-breaking (defaults keep every existing caller valid):Option A — minimal (my preference):
Consumers prefer
receiptwhen present and keep thedetailJSON fallback for one release. No schema commitment beyond "a receipt-shaped dict"; the verifying plugin still owns validation, exactly as today.Option B — typed: promote the receipt dict convention from
agent_receiptsinto aSignedReceiptmodel innest_core.types(receipt_id,issuer_did,action,signature, witness co-signatures). Stronger contract, but it freezes a wire format that currently belongs to one plugin family, and pydantic validation on hostile receipts would need care to keep "invalid receipt" a scoring decision rather than a parse exception.Option C — generic:
attachments: list[dict[str, Any]]for arbitrary structured evidence. Most flexible, least discoverable.Option A is the smallest change that removes the string-smuggling without deciding a universal receipt schema prematurely; B can follow later if more plugins converge on one shape.
Scope if accepted
Evidence.receiptfield + docstring/example update innest_core/types.pyagent_receipts.report()andparc.report()prefer the structured field, keep thedetailfallbackreceipt_reputation/parc_migrationscenarios switch their auditors to the structured fieldHappy to send the PR if the direction (and which option) is agreed.