# `@wrnexus/rpc`

Define a service contract in a shared workspace package, then import that same contract from the caller and callee.

```ts
import {
  defineService,
  implement,
  inProcessTransport,
  procedure,
  serviceClient,
} from "@wrnexus/rpc";
import { v } from "@wrnexus/validation";

const greeter = defineService({
  name: "greeter",
  procedures: {
    greet: procedure
      .input(v.object({ name: v.string() }))
      .output<{ message: string }>()
      .build(),
  },
});

const service = implement(
  greeter,
  { greet: async ({ name }) => ({ message: `Hello, ${name}` }) },
  { selfApp: "greeter" },
);

const client = serviceClient(greeter, {
  app: "greeter",
  transport: inProcessTransport({
    "greeter/greet": (input, identity) => service.invoke("greet", input, identity),
  }),
});
await client.greet({ name: "Ada" });
```

Service files default-export `implement(...)` from `app/services`. The development server mounts them under the private `/__wrnexus/rpc` prefix.

Pass `{ as: ctx }` to `serviceClient` to propagate the subject. The signed token contains only subject and tenant identifiers; permissions are always checked by the callee. Set `WRNEXUS_RPC_SECRET` in every app, use at least 32 characters, and never reuse the session secret.

Calls time out by default. Retrying is intentionally deferred; when introduced, only procedures marked `.idempotent()` may be retried.

## Deployment requirement: apps must be unreachable except through the gateway

`/__wrnexus/rpc/*` is authenticated by TWO signals together: a marker header
(`x-wrnexus-internal: 1`) AND the absence of any `X-Forwarded-*` header. The
WrNexus gateway satisfies this by construction — it strips any inbound
marker header from the public request, and it always adds `X-Forwarded-*`
when proxying to an app. A direct loopback call from a sibling app process
carries the marker and no forwarded headers, so it passes; anything that
came through the gateway carries forwarded headers, so it's rejected even if
it also carries the marker.

**This check only works if the app process is unreachable except through the
gateway.** If an app's port is exposed directly, or if a reverse proxy sits
in front of it WITHOUT setting `X-Forwarded-*` (a bare `proxy_pass` with no
`proxy_set_header X-Forwarded-For`/`X-Forwarded-Host`/`X-Forwarded-Proto`),
then an external caller can set the marker header itself, arrive with no
forwarded headers, and reach `/__wrnexus/rpc/*` as if it were an internal
call — bypassing the gateway's edge block entirely.

Requirements for any deployment:

- App processes must bind to a private/loopback interface and be reachable
  ONLY through the gateway (or an equivalent trusted front door) — never
  exposed directly to the internet or an untrusted network.
- Any reverse proxy placed in front of an app (nginx, a load balancer, etc.)
  MUST set `X-Forwarded-For`, `X-Forwarded-Host`, and `X-Forwarded-Proto` on
  every request it forwards. Omitting these silently reopens the private RPC
  namespace to anyone who can reach the proxy.
