|
| 1 | +# What a sheaf is |
| 2 | + |
| 3 | +`@metamask/sheaves` lets you stitch a single dispatch surface from a |
| 4 | +collection of capabilities that _ought to_ do the same thing — even when |
| 5 | +they don't literally share an implementation. |
| 6 | + |
| 7 | +This doc explains the problem the sheaf solves and when you'd reach for one. |
| 8 | + |
| 9 | +## Attenuation: the ocap baseline |
| 10 | + |
| 11 | +In object-capability programming, you restrict authority by **attenuating** a |
| 12 | +capability: wrapping it in a proxy that exposes a strict subset of its powers. |
| 13 | +A `FileSystem` capability becomes a `Read("/home/alice")` capability by |
| 14 | +wrapping it in something that forwards only read operations under |
| 15 | +`/home/alice` and refuses everything else. |
| 16 | + |
| 17 | +The attenuator never adds authority. An attenuated capability is a narrower |
| 18 | +projection of the same underlying object. |
| 19 | + |
| 20 | +## Strict attenuations compose for free |
| 21 | + |
| 22 | +When two capabilities are strict proxy attenuations of the **same** base, |
| 23 | +their overlapping surfaces necessarily agree — both forward to the same |
| 24 | +underlying implementation, so behavior is identical wherever their scopes |
| 25 | +intersect. |
| 26 | + |
| 27 | +Composition is then a matter of bookkeeping. If `aliceCap = Read("foo/bar")` |
| 28 | +and `bobCap = Read("foo/baz")` are both attenuations of the same |
| 29 | +`FileSystem`, their union is `Read("foo/{bar,baz}")`. Where the scopes |
| 30 | +overlap (here: the `foo/` prefix), the shared base ensures coherent behavior |
| 31 | +— there is nothing to reconcile. |
| 32 | + |
| 33 | +This is the easy case, and the one ocap programming is built around. |
| 34 | + |
| 35 | +## Sheaves: alignment without a shared base |
| 36 | + |
| 37 | +Often you want to behave _as if_ you had a common base when you don't. Two |
| 38 | +implementations of a wallet API; a local exo and a remote capability over |
| 39 | +CapTP; replicas with different cost profiles. No shared origin to inherit |
| 40 | +alignment from — but the surfaces are supposed to mean the same thing, and |
| 41 | +the caller wants a single capability that routes invocations to whichever |
| 42 | +provider is right. |
| 43 | + |
| 44 | +A **sheaf** is the construction that lets you assert this alignment by |
| 45 | +contract instead of proving it by attenuation. You hand `sheafify` a set |
| 46 | +of providers — each a capability with a guard describing the open set of |
| 47 | +invocations it supports, plus optional metadata distinguishing it from its |
| 48 | +peers — and you get back an authority manager that glues these pieces into |
| 49 | +a single dispatch surface. |
| 50 | + |
| 51 | +The alignment is the load-bearing assumption (the **sheaf condition**): |
| 52 | +two providers that both cover the same `(method, args)` point are presumed |
| 53 | +to produce equivalent observable effects. The system trusts that contract; |
| 54 | +that trust is what makes the framework work without a literal shared base. |
| 55 | + |
| 56 | +## The dispatch surface is a section |
| 57 | + |
| 58 | +What you get back from the sheaf is a **section** — a capability covering |
| 59 | +some open set of the combined surface, restricted by an explicit guard: |
| 60 | + |
| 61 | +```ts |
| 62 | +const sheaf = sheafify({ name: 'Wallet', providers }); |
| 63 | +const userFacing = sheaf.getSection({ guard: userGuard, lift: policy }); |
| 64 | +``` |
| 65 | + |
| 66 | +`getSection` is itself attenuation: it takes the full combined surface that |
| 67 | +the sheaf has glued together and hands back a narrower view restricted by |
| 68 | +`userGuard`. The sheaf has done the hard part — asserting alignment so the |
| 69 | +providers can be treated as one — and `getSection` carves a slice out of |
| 70 | +that unified surface for the caller. The result is that you can attenuate |
| 71 | +a composition of capabilities the same way you would attenuate a single |
| 72 | +one. |
| 73 | + |
| 74 | +The guard determines what is invokable through `userFacing`. Anything |
| 75 | +outside the guard is simply not in the interface — there is no extra |
| 76 | +authorization step, no access check. Unauthorized invocations are |
| 77 | +unsupported in the same flat sense that calling a missing method on any |
| 78 | +ocap is unsupported. |
| 79 | + |
| 80 | +Where multiple providers cover the same invocation, a caller-supplied |
| 81 | +**policy** selects which one runs (see [POLICY.md](./POLICY.md)). Where |
| 82 | +exactly one covers it, the choice is forced. And because the returned |
| 83 | +section is itself a capability, it can be a provider to another sheaf — |
| 84 | +the construction composes with itself. See [USAGE.md](./USAGE.md) for |
| 85 | +worked examples. |
0 commit comments