ExSandbox.Proxy (ExSandbox v1.0.0)

Copy Markdown View Source

Forwards a request to a running sandbox's address (012 T041, contracts/boundary.md).

What this is not

It is not an HTTP client, and it does not know what a "request" is. FR-001 forbids ex_sandbox any dependency, which rules out Finch, Mint, Req, and Plug alike — so this library cannot ship the forwarding itself.

What it can own, and what a host would otherwise reimplement per mechanism, is deciding where a request goes and whether it may go at all. That decision has three failure modes worth centralising:

  • forwarding to a sandbox that is not running,
  • forwarding to a stale address, and
  • treating "we could not tell" as "go ahead".

So target/2 resolves an address and refuses on anything but a positive answer. The host performs the transport with whatever client it already has.

Address resolution is the mechanism's, not this library's

A mechanism knows where its sandboxes listen; nothing else does. A BEAM sandbox is a node name, a container is an IP and port, and a future mechanism may be a unix socket. The mechanism puts the address in the sandbox's context under :address, and this module only decides whether to hand it back.

No caching, ever (006 R7)

target/2 re-resolves on every call. 006 R7 is explicit that a stale hostname→sandbox route is a cross-tenant data breach rather than a correctness bug — requests for one tenant's address reaching another tenant's sandbox — and that caching this particular lookup is the optimisation someone adds later without recognising what its failure mode is.

There is no cache here and no option to add one. If profiling ever shows the resolution is a bottleneck, the admissible fix is a cache invalidated in the same transaction as the state change, which is a host concern with access to the transaction — not something this library can do correctly.

Summary

Types

Where a running sandbox can be reached.

Why a forward was refused. Each is distinguishable (006 R4).

Functions

The address a mechanism recorded for this sandbox, without a status check.

Resolves where to forward a request for sandbox, or refuses.

Types

address()

@type address() :: term()

Where a running sandbox can be reached.

Deliberately loose: a {host, port} for a container, a node name for a BEAM sandbox, a path for a unix socket. This library stores and returns it without interpretation (FR-007).

refusal()

@type refusal() ::
  {:not_running, ExSandbox.Mechanism.status()}
  | :no_address
  | {:status_unknown, term()}

Why a forward was refused. Each is distinguishable (006 R4).

Functions

address(sandbox)

@spec address(ExSandbox.Sandbox.t()) :: {:ok, address()} | {:error, :no_address}

The address a mechanism recorded for this sandbox, without a status check.

Public because a mechanism's own start path needs it before the sandbox is observably running. Not a substitute for target/2 on a request path: it answers "where would this be" rather than "may this be reached now".

target(mechanism, sandbox)

@spec target(module(), ExSandbox.Sandbox.t()) ::
  {:ok, address()} | {:error, refusal()}

Resolves where to forward a request for sandbox, or refuses.

Refuses on every outcome but a positive one — 006 R4's decision table applied here. The row that matters is the last: a mechanism that cannot report status refuses rather than forwarding, because "we could not tell" arriving at a live sandbox is how a request reaches the wrong tenant.

case ExSandbox.Proxy.target(MyMechanism, sandbox) do
  {:ok, address} -> MyClient.forward(address, conn)
  {:error, reason} -> refuse(conn, reason)
end