Skip to content

[Hackathon] stellarminds-ai: PARC — portable reputation credentials with a recomputing admission gate#63

Open
Sharathvc23 wants to merge 1 commit into
projnanda:mainfrom
Sharathvc23:hackathon/stellarminds-ai-parc-portable-reputation
Open

[Hackathon] stellarminds-ai: PARC — portable reputation credentials with a recomputing admission gate#63
Sharathvc23 wants to merge 1 commit into
projnanda:mainfrom
Sharathvc23:hackathon/stellarminds-ai-parc-portable-reputation

Conversation

@Sharathvc23

Copy link
Copy Markdown
Contributor

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.pyParcTrust 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 recomputationscore 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 rewriting the merged receipt format.
  • Scores as strings. Fixed 6-decimal embedding sidesteps JCS float canonicalization entirely; the gate compares within score_tolerance (default 1e-6).
  • No revocation registry / selective disclosure in this PR: freshness (max_age_ticks) + rotation windows cover staleness; Merkle inclusion proofs are the natural follow-up and listed in docs/layers/trust.md.

What would invalidate this

  • A corrupt issuer that fabricates receipts wholesale (not just inflates scores) with counterparties it controls defeats inline recomputation; only anchor-aware published-ledger analysis catches it, and a publisher that censors its ledger weakens that.
  • Ring severance inherits agent_receipts' thresholds (dense rings ≥3 or mutual pairs, isolated from the anchor): a ring that buys one genuine corroborated edge to an honest anchor agent evades severance.
  • The as-of check anchors to validFrom supplied by the issuer; a compromised current key can backdate validFrom into its own window. Rotation bounds the damage window but does not eliminate it.

Verify

uv sync
uv run pytest packages/nest-plugins-reference/tests/test_parc.py packages/nest-core/tests/test_parc_migration.py -v
uv run nest run scenarios/parc_migration.yaml   # then validate:
uv run python -c "
from pathlib import Path
from nest_core.validators import validate_trace
for r in validate_trace(Path('traces/parc_migration.jsonl'), 'parc_migration'):
    print(r.passed, r.name, '-', r.detail)"

make ci-local: all 5 checks green (ruff check, ruff format, pyright strict, 777 tests).

…ssion gate

Reputation in NEST dies with the run: no plugin exports it, carries it, or
verifies it in another trust domain. This adds the parc trust plugin
(extending the merged agent_receipts receipt maths), the parc_migration
two-domain scenario, and six adversarial validators.

- Export: an agent's receipt ledger becomes a W3C-VC-shaped credential
  (behavioral_merkle_root + nanda-rep/0.2 scores as fixed 6-decimal strings),
  signed through the ed25519_rotating identity layer with key_id pinned in
  proof.verificationMethod (vendored did:key encoder, stdlib-only).
- Admission: the gate recomputes the root and scores from the carried
  receipts instead of trusting signed claims — an issuer-inflated,
  genuinely re-signed score is rejected (score_mismatch). A valid
  signature is not admission.
- Cross-layer: proofs are checked against the issuer's key-rotation
  window as-of the credential's validFrom tick (stale_key), using genuine
  rotation records replayed onto the gate.
- Ring severance at the border: an inline single-subject ledger is a star
  graph and cannot show an N-party ring, so the gate re-runs whole-graph
  collusion severance over the originating domain's published ledger
  (severed_below_threshold).
- Differential proof: with task.config.naive_gate (signed claims trusted)
  the inflation + ring validators FAIL; under trust: score_average no
  credentials exist and every validator fails.

Registered in _BUILTINS and as a real nest.plugins.trust entry point.
41 new tests (hostile paths per typed reason, Hypothesis properties,
5-seed e2e, byte-identical determinism). make ci-local green.
@shilpa-kulkarni-14

Copy link
Copy Markdown

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants