Skip to content

Commit 01db469

Browse files
committed
docs(sheaves): add INTRODUCTION.md and fix doc links
INTRODUCTION.md frames the sheaf as the construction that asserts alignment of overlapping ocap surfaces — the case where you want attenuation-style coherence but don't share a common base. README is updated to point at it and to use the correct documents/ subpath for USAGE.md and POLICY.md (the LIFT.md link was stale after the earlier rename).
1 parent 1c1261f commit 01db469

2 files changed

Lines changed: 92 additions & 5 deletions

File tree

packages/sheaves/README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ Runtime capability routing adapted from sheaf theory in algebraic topology.
66
over a collection of capability providers. The sheaf produces dispatch sections via
77
`getSection`, each of which routes invocations through the provider set.
88

9-
See [USAGE.md](./USAGE.md) for annotated examples and [POLICY.md](./POLICY.md) for
10-
the policy coroutine protocol and semantic equivalence assumption.
9+
See [INTRODUCTION.md](./documents/INTRODUCTION.md) for what a sheaf is and when to
10+
reach for one, [USAGE.md](./documents/USAGE.md) for annotated examples, and
11+
[POLICY.md](./documents/POLICY.md) for the policy coroutine protocol and semantic
12+
equivalence assumption.
1113

1214
## Install
1315

@@ -46,9 +48,9 @@ entries.
4648
> stalk at `("transfer", ...)` might contain one.
4749
4850
**Policy** — An `async function*` coroutine that yields candidates from a
49-
multi-candidate stalk in preference order. See [LIFT.md](./LIFT.md) for the
50-
coroutine protocol, `PolicyContext`, and the semantic equivalence assumption
51-
required of all policies.
51+
multi-candidate stalk in preference order. See [POLICY.md](./documents/POLICY.md)
52+
for the coroutine protocol, `PolicyContext`, and the semantic equivalence
53+
assumption required of all policies.
5254

5355
At dispatch time, metadata is decomposed into **constraints** (keys with the
5456
same value across every candidate — topologically determined, not a choice) and
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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

Comments
 (0)