Tailrace
Guides

Govern tRPC procedures

Wrap tRPC procedures so input and output respect Tailrace policy at the tool boundary.

Use @tailrace/trpc to enforce data policy on procedure input and result data - in-process, with no proxy. This is procedure middleware at the tool boundary, not an OpenAI REST gateway.

Prerequisites

Step 1: Install

pnpm add @tailrace/core @tailrace/trpc @trpc/server

Step 2: Attach middleware to procedures

Standalone (recommended when you already have a Tailrace instance):

import { createTailrace } from "@tailrace/core";
import { createTailraceMiddleware } from "@tailrace/trpc";
import { initTRPC } from "@trpc/server";

const t = initTRPC.create();
const governed = createTailraceMiddleware(createTailrace(), {
  agent: "api",
  name: ({ path }) => path,
});

export const procedure = t.procedure.use(governed);

Fluent:

import { withTrpc } from "@tailrace/trpc";

const tr = withTrpc(createTailrace());
export const procedure = t.procedure.use(tr.middleware({ agent: "api" }));

v0.1 supports non-streaming queries and mutations only.

How it works

SurfaceBoundaryWhen
Input{ kind: "tool", name, direction: "out" }Before next
Output{ kind: "tool", name, direction: "in" }After next, on ok result data

name defaults to the procedure path (for example "chat.completions"). Match that string in definePolicy when you write tool-specific rules.

When policy resolves to block, the middleware throws a host-native TRPCError with code BAD_REQUEST and a value-free message derived from PolicyViolationError. Tokenize and mask rewrite the input or result data in place and continue.

Common variations

Scope agent, name, and workflow ID

createTailraceMiddleware(tailrace, {
  agent: ({ ctx }) => (ctx as { agentId?: string }).agentId ?? "api",
  name: ({ path }) => path,
  workflowId: ({ ctx }) => (ctx as { workflowId?: string }).workflowId ?? "default",
});

By default, workflowId scopes tokens per call. For multi-turn agents that need tokens to resolve consistently across turns, see Tokenization and the vault.

Verify with a synthetic key

Call a governed procedure whose input includes sk_test_…FAKE. Expect a BAD_REQUEST TRPCError whose message names api_key and the rule - never the raw key. An example.com email should tokenize under the default policy before the procedure handler runs.

Audit decisions

createTailraceMiddleware(tailrace, {
  onDecision: (decisions) => {
    // hashes + entity classes only - never log raw values
  },
});

See also

On this page