mergedhumanPR #63Trust
stellarminds-ai: PARC — portable reputation credentials with a recomputing admission gate
Reputation in NEST dies with the run. Every trust plugin holds an in-memory ledger; nothing exports it, carries it, or verifies it in another trust domain — an agent that migrates starts from zero, and a domain that wants to admit a…
Author
@Sharathvc23
github profile →- Status
- Merged
- Merged on
- Jul 8
- Branch
- hackathon/stellarminds-ai-parc-portable-reputation
Description
The pitch.
## Motivation Reputation in NEST dies with the run. Every trust plugin holds an in-memory ledger; nothing exports it, carries it, or verifies it in another trust domain — an agent that migrates starts from zero, and a domain that wants to admit a stranger has nothing to check. The merged `agent_receipts` plugin (#26) made reputation *evidence-based*; this PR makes it **portable**, and does it by composing two merged submissions rather than adding a parallel scheme: the receipt maths of `agent_receipts` and the rotation windows of `ed25519_rotating` (#25). ## Design **`trust/parc.py` — `ParcTrust`** extends `AgentReceiptsTrust` (drop-in for the `Trust` Protocol) with: - **Export** (`build_credential`): wraps the subject's receipts as a W3C-VC-shaped document — `behavioral_merkle_root` committing to the carried receipts, `nanda-rep/0.2` scores embedded as fixed 6-decimal strings, `validFrom` as a logical tick — signed **through the identity layer**, with the signing `key_id` pinned in `proof.verificationMethod` as `did:key:z6Mk...#<key_id>` (base58btc/multicodec encoder vendored, ~25 lines, stdlib-only). - **Admission** (`admit`): ordered gates, each a typed reason: schema → scoring method → trusted issuer → presenter binding (`replay_presenter_mismatch`) → freshness → proof with **as-of key-window enforcement** (`proof_invalid` vs `stale_key`) → ledger size → subject binding (`foreign_receipts`) → **Merkle-root recomputation** → **score recomputation** (`score_mismatch`) → threshold → **published-ledger severance** (`severed_below_threshold`). - **Published-ledger severance** (`ingest_published_ledger`): an inline single-subject ledger is a star graph and structurally cannot show an N-party collusion ring, so the originating domain publishes its community ledger once and the gate re-runs `agent_receipts`' whole-graph Tarjan severance over it. Stated openly rather than papered over. **The headline property: a valid signature is not admission.** The scenario's `auditor-x` is a *trusted* issuer that inflates a client's score and genuinely re-signs. The proof verifies; recomputation over the carried receipts rejects it. **Scenario** (`parc_migration`, tier 1): two trust domains in one seeded run. 4 honest agents + a 3-agent wash ring earn receipts in domain A; at the border to domain B: a forger (tampered proof), a replayer (stolen genuine credential), a stale-key credential (signed with A's rotated-out key via the identity layer's adversarial `sign_with`), and the inflated credential. The factory performs a genuine key rotation and replays the rotation record onto the gate — the proof checks run against real key windows, not scenario-faked ones. **Adversarial validators** (6, in `VALIDATORS["parc_migration"]`): honest admitted (anti-deny-everything guard), forgery → `proof_invalid`, inflation → `score_mismatch`, ring → `severed_below_threshold`, replay → `replay_presenter_mismatch`, stale key → `stale_key`. Each asserts the *typed reason*, not just the denial. **Differential proof:** `task.config.naive_gate: true` makes the gate trust signed claims (proof still checked) — the inflation and ring validators FAIL while the rest hold. Under `trust: score_average` no credentials can be built and every validator fails. Both asserted in tests across a 5-seed bank. ## Tradeoffs - **Inline vs pointer ledgers.** Carrying receipts in the credential keeps verification fully offline but bloats the document and cannot reveal rings; the published-ledger path closes the ring hole at the cost of the origin domain publishing its graph. Both are shipped; the residual (a hostile publisher omitting receipts entirely) is documented in the plugin docstring. - **Two key namespaces.** Subjects keep `agent_receipts`' hex-pubkey identity (that is what the carried receipts use — recomputation depends on it); issuers get stable `did:nest:` URIs with per-key `did:key` pins, so issuer trust survives key rotation. Unifying them would have meant rewriti …
Try it
Open PR on GitHubView diffCheckout locally
git fetch origin pull/63/head:pr-63
git checkout pr-63