Documentation home

TypeScript SDK

Use TypeScript for Node services, web backends, internal tools, and guarded business-action wrappers.

Reference install

npm install @haltstate/sdk

First-party TypeScript SDK
Package: @haltstate/sdk
Published: 1.0.2
Replay-safe source candidate: 2.0.0-dev.0 (not yet published). The candidate builds ESM/CJS/types and has focused replay tests.

SDK guard call

import {
  HaltStateClient,
  ApprovalPending,
  ActionDenied,
  OutcomeReportError,
  ActionOutcomeReportError,
  OutcomeReportRecovery,
} from '@haltstate/sdk';

const client = new HaltStateClient({
  tenantId: process.env.HALTSTATE_TENANT_ID!,
  apiKey: process.env.HALTSTATE_API_KEY!,
  baseUrl: 'https://haltstate.ai',
  agentId: 'retail-refund-agent',
  failOpen: false,
});
const operatorEpoch = '11111111-2222-4333-8444-555555555555';
const destinationKey = `refund:${ledgerEntryId}`;
const guardKey = `hsr1:${operatorEpoch}:${destinationKey}`;
const reportId = crypto.randomUUID();

try {
  await client.withGuard(
    'refund.create',
    {
      params: { amount: 126, currency: 'USD' },
      idempotencyKey: guardKey,
      reportId,
      resource: `refund/${ledgerEntryId}`,
      riskClass: 'low',
    },
    async () => executeRefundOnce(destinationKey),
  );
} catch (error) {
  if (error instanceof ApprovalPending) return;
  if (error instanceof ActionDenied) return;
  if (
    error instanceof OutcomeReportError ||
    error instanceof ActionOutcomeReportError
  ) {
    // Explicit export includes the permit token: persist only as a secret.
    await secureRecoveryStore.put(
      reportId,
      error.recovery.exportForSecurePersistence(),
    );
    const recovery = OutcomeReportRecovery.fromSecurePersistence(
      await secureRecoveryStore.get(reportId),
    );
    const receipt = await client.resumeGuardOutcome(recovery);
    await secureRecoveryStore.markReceived(reportId, receipt);
    return; // executeRefundOnce ran once; never rerun it to repair a receipt.
  }
  throw error;
}

withGuard reports both success and error. On retry exhaustion its redacted recovery envelope can be explicitly exported to a secret store, reconstructed after restart, and passed to resumeGuardOutcome. That public method returns the durable receipt without rerunning the side effect.

Raw HTTP fallback

Prefer the SDK for normal Node workers. If a constrained runtime cannot use the package, call the branded governance namespace directly: POST /api/haltstate/sentinel/action/guard. The older /api/sentinel/* path remains a compatibility alias, not the lead public contract.

Browser boundary

Do not place privileged HaltState keys in public frontend code. Browser dashboards should call your backend, and your backend should call HaltState with server-side credentials.

Client configuration

const client = new HaltStateClient({
  tenantId: process.env.HALTSTATE_TENANT_ID!,
  apiKey: process.env.HALTSTATE_API_KEY!,
  baseUrl: 'https://haltstate.ai',
  timeout: 30,
  failOpen: false,
  retryCount: 3,
  agentId: 'retail-refund-agent',
});

Guard and report

import { HaltStateClient, ApprovalPending, ActionDenied, ActionExpired } from '@haltstate/sdk';

const operatorEpoch = '11111111-2222-4333-8444-555555555555';
const destinationKey = `deployment-${buildId}`;
const guardKey = `hsr1:${operatorEpoch}:${destinationKey}`;
const reportId = crypto.randomUUID();
try {
  const permit = await client.guard('deploy_to_production', {
    params: { version: '2.0.0', environment: 'prod' },
    idempotencyKey: guardKey,
    agentId: 'deploy-agent',
  });

  try {
    await deploy(permit);
  } catch (actionError) {
    await client.reportGuardOutcome(permit, {
      reportId,
      outcome: 'error',
      error: String(actionError),
    });
    throw actionError;
  }
  await client.reportGuardOutcome(permit, {
    reportId,
    outcome: 'success',
    result: { destinationKey },
  });
} catch (error) {
  if (error instanceof ApprovalPending) return;
  if (error instanceof ActionDenied) return;
  if (error instanceof ActionExpired) return;
  throw error;
}

withGuard helper

const operatorEpoch = '11111111-2222-4333-8444-555555555555';
const destinationKey = `email:${message.id}`;
const result = await client.withGuard(
  'customer.email.send',
  {
    params: { template: 'refund-followup', customerRisk: 'medium' },
    idempotencyKey: `hsr1:${operatorEpoch}:${destinationKey}`,
    reportId: crypto.randomUUID(),
  },
  async (permit) => sendCustomerEmail(message, permit)
);

Error hierarchy and type guards

import { isControlFlowSignal, isHaltStateError } from '@haltstate/sdk';

const operatorEpoch = '11111111-2222-4333-8444-555555555555';
const destinationKey = `refund:${id}`;
try {
  await client.guard('refund.create', {
    idempotencyKey: `hsr1:${operatorEpoch}:${destinationKey}`,
  });
} catch (error) {
  if (isControlFlowSignal(error)) {
    // ApprovalPending, ActionDenied, or ActionExpired.
    return;
  }
  if (isHaltStateError(error)) {
    console.error('HaltState error:', error.message);
  }
  throw error;
}

Kill switch client option

Node agents can enable background kill-switch checks when they are long-running. Keep that capability server-side and paired with explicit shutdown behavior so a remote kill signal cannot leave a partial business action half-written.

Replay-safe source candidate contract

The stable retry identity binds the exact agent, action or tool, resource, normalized parameters, risk class, and immutable policy version. With an operator-issued activation epoch, the SDK may create epoch-qualified generated keys only when the caller omitted a key; explicit keys are never changed. Approval expiry blocks a stale permit, while already_started and completed remain terminal. After the side effect, receipt validation requires the durable event ID, receipt hash, received flag, and duplicate marker; retry the same report identity without rerunning the action.

This is a source candidate contract, not evidence that the candidate package is registry-published or production-deployed. Published package baselines and language-specific executable verification status are listed separately above.

Implementation notes

Keep the HaltState call as close as possible to the side effect. The agent may plan and draft freely, but the wrapper around the actual action should be the place where authority is checked. That wrapper should send only the context required for policy evaluation: safe identifiers, normalized amounts, action names, risk flags, schedule windows, and redaction status. Raw customer payloads and secrets should stay in the business system or protected operator tooling.

Operational evidence

For each action, preserve the decision, the worker outcome, the idempotency key, safe resource references, latency, proof status, and redaction status. This evidence supports incident response and control narratives because it shows what the system did at runtime rather than only describing what the policy document intended. HaltState supports alignment work; it is not a substitute for legal advice or a compliance certification.