feat(remote): implement proxy from-substitution and outbound ID routing

Inbound wire messages are now delivered FROM the proxy representing the
remote sender, so local actors see a replyable `from`. This enables full
two-way communication across the wire:

- endpoint: deliver send_named/send via proxy (deliver_named/deliver_pid)
  instead of sending raw; from_id=0 bypasses proxy for anonymous sends

- proxy: handle deliver_named and deliver_pid to send from within actor
  scope (providing from-substitution); cache one owned pid clone per sender
  keyed by stable instance_id() to avoid use-after-free when forwarding
  reply handles to the endpoint asynchronously

- test: add remote_endpoint_id_test covering the full inbound proxy table /
  from-substitution / outbound ID table / send-by-ID round-trip

- test: extend remote_child_endpoint with echo_id actor and send_wire_by_id
  to support the new test
This commit is contained in:
CJ van den Berg 2026-03-10 12:59:13 +01:00
parent be7aeefd21
commit bf5a80ef05
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
5 changed files with 290 additions and 36 deletions

View file

@ -140,18 +140,25 @@ const Endpoint = struct {
switch (msg) {
.send => |s| {
// Ensure a local proxy exists for the sender.
_ = try self.get_or_create_proxy(s.from_id);
// Route to the local actor that owns this wire ID.
if (self.local_actors.get(s.to_id)) |actor|
try actor.send_raw(tp.message{ .buf = s.payload })
const prx = try self.get_or_create_proxy(s.from_id);
// Route to the local actor that owns this wire ID, delivering
// FROM the proxy so the recipient can reply naturally.
if (self.local_actors.getPtr(s.to_id)) |actor_ptr|
try prx.send(.{ "deliver_pid", @as(u64, @intFromPtr(actor_ptr.h)), protocol.RawCbor{ .bytes = s.payload } })
else
return tp.exit_error(error.UnknownLocalActor, null);
},
.send_named => |s| {
// Ensure a local proxy exists for the sender so it can receive replies.
_ = try self.get_or_create_proxy(s.from_id);
const actor = tp.env.get().proc(s.to_name);
try actor.send_raw(tp.message{ .buf = s.payload });
if (s.from_id != 0) {
// Deliver FROM the proxy so the recipient sees the proxy as
// `from` and can reply by sending back through it.
const prx = try self.get_or_create_proxy(s.from_id);
try prx.send(.{ "deliver_named", s.to_name, protocol.RawCbor{ .bytes = s.payload } });
} else {
// No sender identity; deliver directly without from substitution.
const actor = tp.env.get().proc(s.to_name);
try actor.send_raw(tp.message{ .buf = s.payload });
}
},
.exit => |e| {
// Remote actor has exited; notify its local proxy.