Merging capabilities

67 views
Skip to first unread message

Alan Karp

unread,
Sep 8, 2026, 7:45:13 PMSep 8
to <friam@googlegroups.com>, cap-talk
Say that Dave has a service that takes a read/write capability on some object.  Alice delegates a read capability on some object to Bob.  Carol delegates a write capability on that same object to Bob.  Bob has all the permissions needed to invoke Dave's service but no way to invoke it.

Are there any capability systems where Bob can create a read/write capability on an object given separate read and write capabilities on that object?

Asking for a friend.

--------------
Alan Karp

Matt Rice

unread,
Sep 8, 2026, 8:42:45 PMSep 8
to cap-...@googlegroups.com, <friam@googlegroups.com>
It never came with the base system, and 1-ml was never much more than
a prototype compiler (That is to say that the prototype lacked many
features but it's lack of features made it something of a capability
system, i'm not sure if it was intended to gain non-capability
features),

Anyhow I did make an implementation of this sort of reference in that
language. There were 3 levels permissions, read, write, and ptr_eq,
with prt_eq being the weakest (neither readable nor writable, but able
to compare two references).
Anyhow a pseudocode description of the interface was something to the
effect of `(r: read + ptr_eq, w: write + ptr_eq) where prt_eq(r) ==
ptr_eq(w) -> { rw: read + write + ptr_eq }`

David Nicol

unread,
Sep 9, 2026, 3:10:23 AMSep 9
to cap-...@googlegroups.com, <friam@googlegroups.com>
I'd think the answer to "is this pattern supported" rather than "which existing system supports this pattern" would depend on how Dave's system determines that the capability that is supposed to be "a read/write capability on Object Oscar" is such or not. 

--
You received this message because you are subscribed to the Google Groups "cap-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cap-talk+u...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/cap-talk/CANpA1Z3Q_ODz6NLqeDDk2X5S%2Bhx_8DUNwFC6%3DAGLtyNwi9A4Hg%40mail.gmail.com.


--
"The profit motive is often in conflict with the aims of art." -- Ursula K. Le Guin

Alan Karp

unread,
Sep 9, 2026, 12:28:05 PMSep 9
to cap-...@googlegroups.com
On Wed, Sep 9, 2026 at 12:10 AM David Nicol <david...@gmail.com> wrote:
I'd think the answer to "is this pattern supported" rather than "which existing system supports this pattern" would depend on how Dave's system determines that the capability that is supposed to be "a read/write capability on Object Oscar" is such or not. 

My question is more, has anyone thought to see if this pattern is supported?  It's clearly not for object references as capabilities.  It could be for certificate capabilities or systems that use token exchange for delegation.  I've just never heard of one that does.

In my example, Dave's service takes a single argument.  In a capability system, that argument is a single capability that authorizes read and write on the resource.  The service does not "determine" what the capability authorizes.  It will attempt to use that capability to do both read and write operations, which will fail if the operation isn't authorized.
The question also raises a theoretical concern.  Delegations form a tree if there is no way to merge capabilities in this way.  They form a more complex structure (directed graph?) if capabilities can be merged.  Does that difference affect the kind of analysis capability systems support? 

--------------
Alan Karp

Matt Rice

unread,
Sep 9, 2026, 1:06:51 PMSep 9
to cap-...@googlegroups.com
There were two ways in 1ml this could be done, the first did have a
cyclic directed graph kind of structure, where when you construct a
reference you get back a
structure for a read-write ref, which you can take apart into a read
ref, and a write ref, (each read/write ref containing a ptr_eq), and
then have a separate function to recombine them.

There is also a bottom-up construction, where instead of being given a
read-write ref, you get individual read/write handles or facets to a
reference.
To get a read/write reference you must construct them just like Bob would.

The way that this worked with 1ml was via Applicative functors, we can
generate a type hierarchy for the reference type.
This contained 3 distinct types (ptr_eq, read, write), and an
amalgamation of the two which contained the functions from all 3
types.

The only way I can simply explain how these types work is to compare
them to traits in rust that have an associated type.
So if we consider a ReadPtr trait with an associated type and a read function:

struct Ptr<T>(T);

trait <T> PtrEq<T> {
type T = T;
fn eq(T, T) -> bool;
}

trait<T> ReadPtr<T>: PtrEq<T> {
type T = T;
fn read(&Ptr<T>) -> T;
}

trait<T> WritePtr<T>: PtrEq<T> {
type T = T;
fn write(T, Ptr<T>) -> ();
}

trait ReadWritePtr<T>: ReadPtr<T> + WritePtr<T> {}

In 1ml because these were applicative functors you could prove that
`ReadPtr<T>::T == WritePtr<T>::T`
that is to say there is an equivalence relation between each of the `T` types.
The functor is basically a function that takes types as parameters and
returns another type.
To give a silly example the `dup_tuple(t: Type) => (t, t)` returns a
type that is a tuple containing two t instances,

Anyhow, what I am trying to explain is that it *can* be a tree,
it just has to be a bottom-up constructed tree, where ReadPtr and
WritePtr are at the leaves,
and then you get a ReadWritePtr by bringing the leaves together...

They aren't really traits though, they are a bit different. For
instance there are no orphan rules, or external impls.
Basically like functions that return struct declarations and module instances.

Matt Rice

unread,
Sep 9, 2026, 4:39:02 PMSep 9
to cap-...@googlegroups.com
There are probably some important bits I left out here, which is that
1ml has both generative functors and applicative functors,
while applicative functors were important for each facet to have a
shared underlying type for the primitive reference.
Applicative functors generate a new type (like the rust newtype
pattern) each time the function is called with a type argument
it wraps it up in an opaque freshly generated type. There are multiple
ways to provide the protection over top the underlying reference,
But the easiest way i thought was to just generate 4 types one for
each facet (eq, ro, wo, rw),
and when combining them into the rw facet rely on runtime eq check, so
promotion to rw could fail if you passed different ro and wo refs.

Alan Karp

unread,
Sep 9, 2026, 8:32:53 PMSep 9
to cap-...@googlegroups.com
On Wed, Sep 9, 2026 at 10:06 AM Matt Rice <rat...@gmail.com> wrote:

Anyhow, what I am trying to explain is that it *can* be a tree,
it just has to be a bottom-up constructed tree, where ReadPtr and
WritePtr are at the leaves,
and then you get a ReadWritePtr by bringing the leaves together...

I don't know how to start at the leaves.  When you create a resource, you also create a root certificate authorizing all methods on the resource.  You then make one or more delegations from it.  Each of those delegations can be used to make further delegations.  The result is a top down (upside down) tree.

--------------
Alan Karp

Matt Rice

unread,
Sep 9, 2026, 9:18:41 PMSep 9
to cap-...@googlegroups.com
Maybe it's better if I just explain it via kind of translating it into
rust, rust and 1ml share a similar heritage (with the original rust
compiler bootstrapping from ML),
they are similar enough that we can mangle the high level concepts
into something representative enough. However 1ml did not have rusts
borrow checker, or ownership
rules... The implementation I wrote in 1ml would not satisfy the rust
borrow checker, the underlying machine pointer representation didn't
have mut and immutable variants.

Maybe we should step away from the notion of a tree representation,
and basically I'm going to make 3 different types for each permission,
but they'll all share a underlying machine representation. From the
*inside* when these have the same generic parameters, they share the
same underlying
machine representation. Thus, these types can convert between
themselves, and the only differences between them is the permissions
that we expose to the outside.

For simplicity i'm just going to use a generic type `T` as the machine rep...

```rust
struct PtrEq<T>(*T);
struct PtrRead<T>(*T);
// the pseudocode translation falls apart in the borrow checker
because we have both a *mut T for the `PtrWrite` and the `PtrRead` in
scope
struct PtrWrite<T>(*mut T);
struct PtrReadWrite<T>(*mut T);

impl PtrEq<T> {
fn eq(self, other: Self) -> bool;
}

impl PtrRead<T> {
fn modus_ponents<Q>(self, f: T -> Q) -> Q;
fn to_eq(self) -> PtrEq<T> {
PtrEq(self.0)
}
}

impl PtrWrite<T> {
fn write(self, t: T) -> ();
fn modus_ponents<Q>(self, f: T -> Q) -> Q;
fn eq(self, other: Self) -> bool;
fn to_eq(self) -> PtrEq<T> {
PtrEq(self.0.as_ptr())
}
}

impl PtrReadWrite<T> {
fn modus_ponents<Q>(self, f: T -> Q) -> Q;
fn write(self, t: T) -> ();
fn from_read_write(r: PtrRead<T>, w: PtrWrite<T>) -> Option<Self> {
if self.to_eq().eq(other.to_eq()) {
Some(PtrReadWrite(other.0))
} else {
None
}
}
}
```

This has a lot more boilerplate in rust, in 1ml we can copy the
read/write methods from the PtrRead/PtrWrite implementations, and we
don't have to manually
generate a bunch of newtypes, it's more like working in rusts macro
language, anyhow that is roughly how it worked...

Matt Rice

unread,
Sep 9, 2026, 9:42:12 PMSep 9
to cap-...@googlegroups.com
Apologies Alan, I forgot to give you the constructor,

```
fn ptr_new<T>() -> (PtrRead<T>, PtrWrite<T>);
```

In that system, the constructor generated the leaf types, it's up to
you to form voltron by calling `from_read_write`

Alan Karp

unread,
Sep 9, 2026, 10:05:50 PMSep 9
to cap-...@googlegroups.com
I have no idea how that code relates to delegating capabilities, nor how you can build the delegation tree from the leaves.  I do know how you can enumerate an existing tree starting from the leaves, but that's not what I'm talking about.

--------------
Alan Karp


--
You received this message because you are subscribed to the Google Groups "cap-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cap-talk+u...@googlegroups.com.

Matt Rice

unread,
Sep 9, 2026, 10:27:39 PMSep 9
to cap-...@googlegroups.com
I'm just saying it's a reference which constructing it gives you a
read endpoint, and a write endpoint, and it can rights amplify those
two into a read-write capability by combining the two with synergy. If
that isn't what you were asking about, I do not understand what you
were asking about, sorry.
> To view this discussion visit https://groups.google.com/d/msgid/cap-talk/CANpA1Z1y990eZ5wteCb9MtN89DYcUOJrY9bDBFVq%3DX1UynGfmw%40mail.gmail.com.

David Nicol

unread,
Sep 9, 2026, 10:47:47 PMSep 9
to cap-...@googlegroups.com
On Wed, Sep 9, 2026, 11:28 Alan Karp <alan...@gmail.com> wrote:
On Wed, Sep 9, 2026 at 12:10 AM David Nicol <david...@gmail.com> wrote:
I'd think the answer to "is this pattern supported" rather than "which existing system supports this pattern" would depend on how Dave's system determines that the capability that is supposed to be "a read/write capability on Object Oscar" is such or not. 
In my example, Dave's service takes a single argument.  In a capability system, that argument is a single capability that authorizes read and write on the resource.  The service does not "determine" what the capability authorizes.  It will attempt to use that capability to do both read and write operations, which will fail if the operation isn't authorized.

--------------
Alan Karp

In a try-or-fail system, Bob may be able to craft a capability that forwards the read method through one and forwards the write method through the other. That seems tautological.

Alan Karp

unread,
Sep 9, 2026, 11:42:33 PMSep 9
to cap-...@googlegroups.com
On Wed, Sep 9, 2026 at 7:47 PM David Nicol <david...@gmail.com> wrote:

In my example, Dave's service takes a single argument.  In a capability system, that argument is a single capability that authorizes read and write on the resource.  The service does not "determine" what the capability authorizes.  It will attempt to use that capability to do both read and write operations, which will fail if the operation isn't authorized.

In a try-or-fail system, Bob may be able to craft a capability that forwards the read method through one and forwards the write method through the other. That seems tautological.

I think that works.  Bob creates an object that holds the two capabilities and passes a reference to it as the parameter when invoking Dave.  Dave's read and write requests go to that object, which uses the corresponding capability to perform the operation on the resource.

Has anyone implemented a feature like this?

--------------
Alan Karp

Alan Karp

unread,
Sep 9, 2026, 11:43:57 PMSep 9
to cap-...@googlegroups.com
Sorry.  I thought you were talking about something else.  See my latest email for what I think is the same solution.

--------------
Alan Karp


Matt Rice

unread,
Sep 10, 2026, 12:13:11 AMSep 10
to cap-...@googlegroups.com
On Wed, Sep 9, 2026 at 8:43 PM Alan Karp <alan...@gmail.com> wrote:
>
> Sorry. I thought you were talking about something else. See my latest email for what I think is the same solution.
>

Yeah, I believe it's basically the same except instead of wrapping two
capabilities, it really is able to just tell from
the pointer equality that the read reference, and the write reference
are the same reference. So it doesn't actually need
to store two references in the wrapper object. It can just store one
and use that as a read/write ref.
> To view this discussion visit https://groups.google.com/d/msgid/cap-talk/CANpA1Z2mDxGDh2%3DGrRvOk3%3DEfK%3DKnUZRvW%3D7REcxYGH8at8VZw%40mail.gmail.com.

Chris Hibbert

unread,
Sep 10, 2026, 8:30:04 PMSep 10
to cap-...@googlegroups.com, <friam@googlegroups.com>
When I was working in JavaScript at Agoric, with their distributed OCap
system, I could have written a wrapper class that behaved like the
interface that Dave's service expected, and was implemented in terms of
the read cap from Alice and the write cap from Carol. If you have
polymorphism and duck-typing, that's all you need.

To answer your direct question, I don't recall any systems that
implemented this natively, and I don't recall any discussions where
someone proposed a plausible direct implementation.

Chris
--
C. J. Cherryh, "Invader", on why we visit very old buildings:
"A sense of age, of profound truths. Respect for something
hands made, that's stood through storms and wars and time.
It persuades us that things we do may last and matter."

Chris Hibbert
hib...@mydruthers.com
Blog: http://www.pancrit.org
http://mydruthers.com




Reply all
Reply to author
Forward
0 new messages