Hi Erin,
In Cloudflare Workers, we've had some success attaching certain kinds of metadata by having a wrapper object which is called first, like:
interface Endpoint {
startCall @0 (metadata :Metadata) -> (dispatcher :CallDispatcher);
}
interface CallDispatcher {
foo @0 (...) -> (...);
bar @1 (...) -> (...);
...
}
Here, the client always first calls startCall() to pass the metadata, then makes a pipelined call to the specific method. By convention, a `CallDispatcher` is expected to be used for exactly one call.
Of course, there's a limit to how far this pattern can go. It has worked well for us so far but it's definitely a bit clunky.
I'm really hesitant to attach a totally untyped int -> any map to every call. Having built several systems using such a pattern in the past, I know it can be very useful, but also tends to turn into a mess in certain ways.
For the specific use case of tracing, I can see a strong argument that this should be built into the system. You really do want trace IDs to pass down the call stack implicitly, without the business logic having to think about it (except in cases where the business logic wishes to explicitly declare a span). To make this work well in Cap'n Proto C++, we'd want to extend KJ's promise framework to implicitly propagate a trace context along the promise chain, so that the RPC system can pick it up.
I think authentication is at the opposite end of the spectrum. Implicitly propagating authentication credentials goes against the capability philosophy and gives rise to confused deputy problems. Authentication should be achieved by having the client exchange their credentials for an authenticated capability upfront, and then use that capability for further calls. A membrane should revoke the capability if the credentials become invalid. So I would not want to create a system that might be abused for authentication.
Lamport timers are interesting. I don't have a lot of intuition here as I haven't worked with them much. Naively it seems like you want to maintain a time counter in each vat, send the vector with each call/return, and merge the vectors received... however, this implies that the timer is ambient mutable state, which introduces lots of problems. Does it become a side channel through which it's possible to observe what else is going on within the vat? This makes me a bit uncomfortable.
Maybe you could implement Lamport clocks with membranes. You could wrap each group of objects that share a time counter in a single membrane. When a call exits the membrane, the membrane could first generate a preceding call to the same target object to some common propagateLamportTime() method. On the receiving end, the membrane intercepts this call and updates the receiver's clock. E-order guarantees that the propagateLamportTime() call is received immediately before the call that caused it. Alternatively, you could actually wrap outgoing calls in an envelope that include the timestamp, and have the receiving membrane unwrap the envelope.
-Kenton