Skip to content

types: Evidence has no structured receipt field — receipts ride as JSON strings in detail #64

Description

@Sharathvc23

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions