Vote for your favorite SkillMD. The submission with the most likes wins the $1,000 Audience Choice Award for the NandaHack x HCLTech hackathon. Voting is open through September 25.Vote now →
mergedhumanPR #155Auth

Hackathon/parsa noah mesh revocable auth

delegatable (#138) solved delegation, but its revocation set is one in-process

Author

parsaayoubi avatar

@parsaayoubi

github profile →
Status
Merged
Merged on
Jul 10
Branch
hackathon/parsa-noah-mesh-revocable-auth

Description

The pitch.

# auth: `mesh_revocable` — revocation that survives a network partition

## Problem

`delegatable` (#138) solved delegation, but its revocation set is one in-process
`set[str]`, and `delegated_auth` hands **a single plugin instance to all sixteen
agents** — so the cascade is global for free. It's one Python object.

Give each agent its own replica, the only shape a real network permits, and the
guarantee vanishes: a revocation at the issuer is invisible to every other
verifier, forever.

## Solution

`MeshRevocableAuth` subclasses `DelegatableAuth` — token format, HMAC chain,
attenuation, exceptions all inherited verbatim, zero overrides — and reinterprets
the inherited revocation set as a replica of a **grow-only set CRDT**.
`export_revocations()` / `merge_revocations()` are the gossip channel. Union is
commutative, associative and idempotent, so replicas converge under any delivery
order (Hypothesis enforces the laws). Malformed gossip raises
`RevocationStateError` and leaves the replica untouched. 150 lines.

## Evidence

`nest run delegated_auth_partition` — 10 agents, seed 42, byte-identical across
runs. Split at t=10; revoke the far subtree's grant at t=20 while it's
unreachable; heal at t=55; gossip every 5 ticks. Far-side gateway on that lineage:

```
t= 3..51   accept   (split open from t=10 — it cannot know)
t=57..87   DENY     (healed t=55; converged within one gossip round)
```

Same scenario, swapped plugin:

| Wiring | liveness | converges |
|---|---|---|
| `delegatable`, one shared instance | ✗ denies impossibly fast | ✓ vacuously |
| `delegatable`, one replica per agent | ✓ | **✗ stale forever** |
| `mesh_revocable`, one replica per agent | ✓ | ✓ |

That middle row is the gap. An end-to-end test drives the real simulator under
both plugins and locks the flip into CI.

Two validators encode both halves of the CAP position, so neither can rot:
`check_partition_liveness` (an isolated verifier **must** keep accepting — denying
instantly claims knowledge the network never delivered) and
`check_revocation_converges` (after heal + a gossip bound nobody accepts, **and** a
verifier other than the revoker explicitly denied — no vacuous pass).
`check_no_stale_ancestor_use` is deliberately not asserted: unattainable under
partition. The merged escalation and audience validators run green here.

## Supporting core change

Partitions activated at the first event, so no scenario could establish
cross-boundary state before the split. Adds `partition_start_at_time` /
`partition_heal_at_time`, gating on **simulation time** — a gossiping plugin burns
more events per tick, so `t=55` is event 702 under `mesh_revocable` and 339 under
`delegatable`; an event-count window would put the two under *different*
partitions and void the comparison. `partition_heal_at_tick` keeps its meaning and
its test; `None` preserves today's behavior.

## Verification

`make ci-local` green: ruff + format clean, pyright strict **0 errors**, **1192
passed** (`main` collects 1151; +42 here). `nest doctor` 7/7. `nest run
delegated_auth` unchanged. Registered in `_BUILTINS` and as a `nest.plugins.auth`
entry point. Docs: [`docs/layers/mesh_revocable_auth.md`](docs/layers/mesh_revocable_auth.md).

## Limits

- **Revocation window** = partition duration + up to one gossip interval. The CAP
  price, bounded and explicit.
- **Grow-only**, matching the base plugin's no-unrevoke semantics.
- **Gossip is the deployment's job** — the plugin exposes the channel, not a
  transport.
- **Symmetric secret, inherited** — any holder can also mint.
- **Couples to the base plugin's private `_revoked`** — regression tests make an
  upstream refactor fail loudly, not silently.

Try it

Open PR on GitHubView diff

Checkout locally

git fetch origin pull/155/head:pr-155
git checkout pr-155