mergedhumanPR #115Trust
hackathon/sam04-trust-gated-privacy
The merged hybrid_x25519 plugin (#28) answers *who* can read with a static, all-or-nothing audience — it is deliberately trust-blind. Meanwhile the trust layer computes reputation scores that no privacy plugin consumes.
Author
@Sam04-dev
github profile →- Status
- Merged
- Merged on
- Jul 9
- Branch
- hackathon/claude-trust-gated-privacy
Description
The pitch.
## Problem
The merged `hybrid_x25519` plugin (#28) answers *who* can read with a static, all-or-nothing audience — it is deliberately trust-blind. Meanwhile the trust layer computes reputation scores that no privacy plugin consumes. The two layers that most obviously belong together — *how much do I trust you* and *how much may you see* — are not connected anywhere in the stack. This PR connects them.
## Design
`("privacy", "trust_gated")` **composes** `hybrid_x25519` (re-implements **no** cryptography) with a live `Trust` feed. Per recipient, at encrypt time:
| Trust score | Tier | Recipient gets |
|---|---|---|
| >= 0.8 | full | complete plaintext (inner hybrid envelope #1) |
| 0.5 - 0.8 | partial | redacted view with salted-Merkle selective-disclosure proofs (inner envelope #2); opaque payloads get an honest SHA-256 commitment, never a pretend proof |
| < 0.5 | denied | no wrap entry anywhere — the cryptography enforces the gate; refusal carries a **signed denial receipt** naming score and threshold |
The gate table (agent → tier → score) is digest-sealed **inside** every AEAD-authenticated inner plaintext, so a doctored gate cannot be re-attached to real ciphertexts (`TamperError`). Poisoned trust scores (NaN/±inf/out-of-range) **fail closed** to denied. `deterministic=True` yields byte-identical Tier-1 traces.
## Adversarial validators (mandatory artifact)
`validators/trust_gate_validators.py` — four attack classes: **low-trust exfiltration, partial-tier overexposure, gate laundering** (via the `forge_tier_upgrade` helper), **silent denial**. Each check **fails against `noop` AND against `hybrid_x25519`** and passes against `trust_gated` — one notch above the charter bar, which only requires beating the default plugin. Scenario: `scenarios/trust_gated_exchange.yaml`, with a ScenarioRunner integration test proving byte-identical traces under seed replay.
## Results (measured, not simulated)
200 structured payloads (2 sensitive fields), audience trust 0.9 / 0.6 / 0.1:
| Plugin | Low-trust plaintext reads | Mid-trust hidden-field exposures | Auditable denials | Mean envelope | enc + 3×dec |
|---|---|---|---|---|---|
| `noop` | 200/200 | 200/200 | 0 | 82 B | ~0 ms |
| `hybrid_x25519` | 200/200 | 200/200 | 0 | 676 B | 0.76 ms |
| `trust_gated` | **0/200** | **0/200** | **200/200** | 3 558 B | 1.32 ms |
## Tradeoffs (full list in `docs/layers/trust_gated_privacy.md`)
- Gate-time trust, not read-time: a recipient whose score later collapses keeps envelopes it already received (same future-only stance as `hybrid_x25519` revocation; pair with `revoke()`).
- HMAC receipts are sender-verifiable only; supply an `Identity` for third-party verification.
- Merkle commitments, not ZK circuits (full zk-SNARKs are out of scope per the problem brief).
- ~5× envelope size vs plain `hybrid_x25519` for a 3-tier audience.
## Verification
Whole-repo `make ci-local` green: `uv sync`, `ruff check`, `ruff format --check`, `pyright` (strict), `pytest` — **977 passed**. 36 tests for this plugin: disclosure tiers (incl. inclusive boundaries and integration with the reference `score_average` trust plugin), discrimination invariant vs both reference plugins, Byzantine behaviour (poisoned scores, gate tamper, policy tamper, tier-blob swap, receipt forgery, replay, tampered proofs), byte-determinism, registry resolution via both `_BUILTINS` and the `nest.plugins.privacy` entry point, and full-simulator scenario integration.
```bash
uv run pytest packages/nest-plugins-reference/tests/test_trust_gated.py \
packages/nest-plugins-reference/tests/test_trust_gated_scenario.py -v
uv run nest run trust_gated_exchange
```Try it
Open PR on GitHubView diffCheckout locally
git fetch origin pull/115/head:pr-115
git checkout pr-115