> ## Documentation Index
> Fetch the complete documentation index at: https://docs.parmanasystems.com/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK in Production

> Configuration, fail-closed error handling, retries, audit logging, and a deployment checklist for the TypeScript SDK.

<Note>
  The patterns on this page — the audit-log schema, the metric names, the rate
  limiter — are illustrative starting points for your own integration, not
  claims about what this repository's own infrastructure runs or has test
  coverage for. The SDK behavior itself (the `Configuration`/`RetryPolicy`
  shape, the error classes, the retry semantics) is real and verified — see
  [TypeScript SDK](/sdks/typescript) for what's actually tested.
</Note>

## Deployment architecture

```
Your service
  → @parmana/sdk (ParmanaClient)
    → Parmana Runtime (Execution Gateway, Policy Engine)
      → registered Connector
        → downstream system (Paytm, HubSpot, your own API, ...)
```

The SDK never talks to a downstream system directly — it talks only to your Parmana
Runtime deployment. See [Deploy patterns](/guides/deploy-patterns) for running that Runtime
itself in production (Fly.io/Docker, or any Node-compatible platform).

## Environment variables

| Variable                | Required               | Purpose                                                                                                                                                                                       |
| ----------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PARMANA_ENDPOINT`      | Yes                    | Your Runtime's base URL.                                                                                                                                                                      |
| `PARMANA_API_KEY`       | Yes, outside local dev | Bearer key minted by `scripts/generate-api-key.ts`. Omitting it only works against a Runtime started with `PARMANA_AUTH_DISABLED=true` — see [Authentication](/api-reference/authentication). |
| `PARMANA_TIMEOUT_MS`    | No                     | Request timeout; SDK default is 30000ms if unset.                                                                                                                                             |
| `PARMANA_RETRY_ENABLED` | No                     | Whether your wrapper enables the SDK's retry policy (see below).                                                                                                                              |

## A production-safe client wrapper

```typescript theme={null}
import {
  ParmanaClient,
  HttpTransport,
  RetryStrategy,
  type Configuration,
} from "@parmana/sdk";

function createParmanaClient(): ParmanaClient {
  const endpoint = requireEnv("PARMANA_ENDPOINT");
  const apiKey = requireEnv("PARMANA_API_KEY");

  const configuration: Configuration = {
    endpoint,
    apiKey,
    timeout: Number(process.env.PARMANA_TIMEOUT_MS ?? 30_000),
    retryPolicy: {
      enabled: true,
      maxAttempts: 3,
      initialDelayMs: 1_000,
      maxDelayMs: 10_000,
      strategy: RetryStrategy.EXPONENTIAL,
    },
    transport: new HttpTransport({ endpoint, apiKey }),
  };

  return new ParmanaClient(configuration);
}

function requireEnv(name: string): string {
  const value = process.env[name];
  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }
  return value;
}

export const parmana = createParmanaClient();
```

`retryPolicy` is a real, typed field on `Configuration` (`typescript/src/config/RetryPolicy.ts`)
— retries apply to the SDK's own transient-failure handling (connection failures, retryable
statuses on idempotent requests), not to policy rejections, which are a real, final answer
and should never be retried.

## Error handling: fail closed

Every non-2xx response the Runtime returns maps to a specific, typed error — there is no
"unknown error, assume success" path:

```typescript theme={null}
import {
  ValidationError,
  AuthenticationError,
  AuthorizationError,
  ExecutionRejectedError,
  NotFoundError,
  ConflictError,
  InternalServerError,
  NetworkError,
  TimeoutError,
} from "@parmana/sdk";

async function executeOrFailClosed(transaction: BusinessTransaction) {
  try {
    return await parmana.execute(transaction);
  } catch (error) {
    if (error instanceof ExecutionRejectedError) {
      // A real, final policy decision — do not retry, do not proceed.
      logDenial(transaction, error.message);
      throw error;
    }
    if (
      error instanceof AuthenticationError ||
      error instanceof AuthorizationError
    ) {
      // A credentials/permissions problem, not a policy decision — page someone.
      alertOnCallEngineer("Parmana auth failure", error);
      throw error;
    }
    if (error instanceof ValidationError) {
      // Your request was malformed — a bug in your own code, not the Runtime's.
      logBug("Malformed BusinessTransaction", error);
      throw error;
    }
    // NetworkError / TimeoutError / InternalServerError / ConflictError / NotFoundError:
    // the Runtime's answer is unknown. Treat as not-approved. Never treat "we couldn't
    // reach Parmana" as "the action is authorized."
    logUnknownState(transaction, error);
    throw error;
  }
}
```

| Status               | Exception                | Retry?                                  |
| -------------------- | ------------------------ | --------------------------------------- |
| 400                  | `ValidationError`        | No — fix the request                    |
| 401                  | `AuthenticationError`    | No — fix the key                        |
| 403, `POLICY_DENIED` | `ExecutionRejectedError` | No — final decision                     |
| 403, no code         | `AuthorizationError`     | No — fix caller permissions             |
| 404                  | `NotFoundError`          | No                                      |
| 409                  | `ConflictError`          | No — duplicate `businessTransactionId`  |
| any other non-2xx    | `InternalServerError`    | Idempotent GETs only, via `retryPolicy` |
| connection failure   | `NetworkError`           | Idempotent GETs only, via `retryPolicy` |
| timeout              | `TimeoutError`           | Idempotent GETs only, via `retryPolicy` |

`POST /execute` is not itself retried by the SDK's retry policy — see
[TypeScript SDK](/sdks/typescript#errors-they-actually-throw-now) for exactly which
operations are retry-eligible. If you need your own retry around `execute()` (for a
`NetworkError`/`TimeoutError` only, never for `ExecutionRejectedError`), reuse
`businessTransactionId` on the retry — it's the server's idempotency key, so a retried
`execute()` against a transaction that actually succeeded returns the original result
instead of executing twice.

## Audit logging pattern

Every `ExecutionTrustRecord` is already Parmana's own durable, signed audit record — but
most teams also want a local, queryable copy alongside their own application logs:

```typescript theme={null}
interface AuditLogEntry {
  businessTransactionId: string;
  principalId: string;
  action: string;
  outcome: "approved" | "denied" | "error";
  trustRecordId?: string;
  policyName: string;
  policyVersion: string;
  timestamp: string;
}

async function logExecution(
  transaction: BusinessTransaction,
  outcome: AuditLogEntry["outcome"],
  trustRecordId?: string,
) {
  const entry: AuditLogEntry = {
    businessTransactionId: transaction.businessTransactionId,
    principalId: transaction.authority.principalId,
    action: transaction.intent.action,
    outcome,
    trustRecordId,
    policyName: transaction.policy.name,
    policyVersion: transaction.policy.version,
    timestamp: new Date().toISOString(),
  };

  await db.insertInto("agent_audit_log").values(entry).execute();
}
```

An illustrative schema for the table above:

```sql theme={null}
CREATE TABLE agent_audit_log (
  id                     BIGSERIAL PRIMARY KEY,
  business_transaction_id TEXT NOT NULL UNIQUE,
  principal_id           TEXT NOT NULL,
  action                 TEXT NOT NULL,
  outcome                TEXT NOT NULL,
  trust_record_id        TEXT,
  policy_name            TEXT NOT NULL,
  policy_version         TEXT NOT NULL,
  created_at             TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

This is a convenience index into your own systems, not a replacement for the signed record
itself — for anything compliance-sensitive, verify against the `ExecutionTrustRecord`'s
`signature` (see [Verify independently](/guides/verify-independently)), which doesn't depend
on this table being correct.

## Monitoring pattern

Track, at minimum, the shape of decisions your service is producing — a spike in denials or
a rising Parmana-call latency is usually the first signal something is wrong upstream of
Parmana (a bad policy deploy, a misconfigured signal), not inside the SDK itself:

```typescript theme={null}
function recordExecutionMetric(
  outcome: "approved" | "denied" | "error",
  durationMs: number,
) {
  metrics.increment(`parmana.execution.${outcome}`);
  metrics.histogram("parmana.execution.duration_ms", durationMs);
}
```

Reasonable alert thresholds to start from: denial rate crossing an unusual baseline for a
given policy, `error` outcomes (network/timeout/internal, as opposed to `denied`) above
zero for more than a few minutes, and `p99` latency on `execute()` growing past your
`PARMANA_TIMEOUT_MS`.

## Rate limiting pattern

The Runtime itself does not currently return a `429`/rate-limit response the SDK maps to a
dedicated error (see [TypeScript SDK](/sdks/typescript#errors-defined-but-never-thrown) for
error classes that are defined but not yet thrown by any real condition) — if your own
call volume needs shaping, do it client-side:

```typescript theme={null}
class TokenBucketLimiter {
  private tokens: number;
  private readonly capacity: number;
  private readonly refillPerMs: number;
  private lastRefill = Date.now();

  constructor(capacity: number, refillPerSecond: number) {
    this.capacity = capacity;
    this.tokens = capacity;
    this.refillPerMs = refillPerSecond / 1000;
  }

  async acquire(): Promise<void> {
    this.refill();
    while (this.tokens < 1) {
      await new Promise((resolve) => setTimeout(resolve, 50));
      this.refill();
    }
    this.tokens -= 1;
  }

  private refill() {
    const now = Date.now();
    const elapsed = now - this.lastRefill;
    this.tokens = Math.min(
      this.capacity,
      this.tokens + elapsed * this.refillPerMs,
    );
    this.lastRefill = now;
  }
}

const limiter = new TokenBucketLimiter(10, 5); // burst of 10, 5/sec sustained

async function executeRateLimited(transaction: BusinessTransaction) {
  await limiter.acquire();
  return parmana.execute(transaction);
}
```

## Testing in production

A smoke test to run right after every deploy, against the real endpoint you just deployed:

```typescript theme={null}
async function smokeTest() {
  const health = await parmana.health();
  if (health.status !== "UP") {
    throw new Error(`Parmana health check failed: ${JSON.stringify(health)}`);
  }

  const version = await parmana.version();
  console.log("Deployed against Parmana version:", version);
}
```

For a full execution smoke test (not just health), run it against a staging Runtime with a
dedicated, obviously-named test policy and principal — never against a production policy
your own smoke test could pollute with real audit entries.

## Deployment checklist

* [ ] `PARMANA_ENDPOINT` and `PARMANA_API_KEY` set from your secrets manager, not committed
  to source (see [Deploy patterns](/guides/deploy-patterns)).
* [ ] `retryPolicy` configured deliberately — confirm what's idempotent-safe to retry versus
  what isn't (see the table above).
* [ ] Every `client.execute()` / `client.createTransaction()` call site fails closed: a
  caught, unrecognized error is treated as not-approved, never as approved.
* [ ] `ExecutionRejectedError` is never silently swallowed — it's a real policy decision
  your caller needs to see.
* [ ] `businessTransactionId` reuse on retry is intentional (idempotency), not accidental.
* [ ] A signature-verification path exists independent of this SDK
  (see [Verify independently](/guides/verify-independently)) for anything you'll need to
  prove later without trusting your own logs.
* [ ] Smoke test wired into your deploy pipeline, hitting `health()`/`version()` at minimum.
* [ ] Monitoring on denial rate and error rate, not just uptime.

## Next

<CardGroup cols={2}>
  <Card title="TypeScript SDK reference" icon="js" href="/sdks/typescript">
    The full, verified error taxonomy, model types, and test suite this page
    builds on.
  </Card>

  <Card title="Deploy patterns" icon="server" href="/guides/deploy-patterns">
    Running the Parmana Runtime itself in production, not just the SDK client.
  </Card>

  <Card title="Verify independently" icon="check-double" href="/guides/verify-independently">
    Re-verify a trust record's signature without trusting this SDK or your own
    logs.
  </Card>

  <Card title="Limitations" icon="triangle-exclamation" href="/security/limitations">
    What Parmana does and does not guarantee — read before writing an incident
    runbook.
  </Card>
</CardGroup>
