npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sevra/guard

v0.1.0

Published

The Sevra guard client. Call the control layer before an agent moves money and get Allow, Require approval, or Block, with an execution token on allow and an immutable audit trail behind every decision.

Readme

@sevra/guard

The Sevra guard client. Sevra is the control layer for AI agents that move money: your agent calls guard() before a refund, payout, or ledger write, and Sevra returns one of three verdicts, each recorded in an immutable audit trail.

  • allow: proceed. The decision carries an executionToken; use it as your payment provider's idempotency key so the side effect fires at most once.
  • require_approval: the action is held for a human. The SDK can wait for the decision for you.
  • block: do not execute. Blocked, denied, timed out, and unevaluable actions all land here.

Sevra decides and audits; it never holds or moves funds.

Source of truth: this package is a copy of src/lib/sevra/sdk/index.ts in the Sevra repo, which is what the app itself runs. Changes land there first, with tests, and are synced here before a release.

Install

npm install @sevra/guard

Zero runtime dependencies. Requires a runtime with global fetch (Node 18+, Deno, Bun, browsers, edge runtimes).

Auth

Every call authenticates with your per-tenant guard key in the Authorization header:

Authorization: Bearer <your guard key>

The key both authenticates and resolves your tenant. Keep it server-side; treat it like any secret.

Quickstart: a refund agent

import { SevraGuard, SevraBlocked } from "@sevra/guard";

const sevra = new SevraGuard({
  apiKey: process.env.SEVRA_GUARD_KEY!,
  baseUrl: "https://sevra.dev",
});

async function refund(orderId: string, amountMinor: number, currency: string) {
  const decision = await sevra.guard({
    agent: "refund-bot",
    action: "refund",
    target: `order:${orderId}`,
    params: { orderId, amountMinor, currency },
    idempotencyKey: `refund:${orderId}`,
    amountMinor,
    currency,
    direction: "out",
  });

  if (decision.verdict !== "allow" || !decision.executionToken) {
    // Blocked, denied, timed out, or unevaluable. Never execute without a token.
    throw new SevraBlocked(decision);
  }

  // Execute exactly once: the token is your provider-side idempotency key.
  return stripe.refunds.create(
    { payment_intent: orderId, amount: amountMinor },
    { idempotencyKey: decision.executionToken },
  );
}

The one rule: only execute when verdict is "allow" and an executionToken is present. Anything else, including a thrown network error, means do not execute.

Verdicts

| verdict | status | What you do | |---|---|---| | allow | approved or executed | Execute, using executionToken as your provider idempotency key | | require_approval | held | Wait for a human decision (see below) | | block | blocked, denied, timed_out, collision | Do not execute; reasons says why |

score is the risk score as an integer from 0 to 1000 (or null when the action was unevaluable). reasons is a list of short machine-readable strings explaining the decision.

Waiting on a held action

When the verdict is require_approval, the server returns a held ticket immediately and a human decides in the Sevra console or Telegram. Three ways to handle it:

  1. Block until resolved (the default). With no mode (or mode: "block"), guard() polls the decision endpoint under the hood, using bounded server-side waits of up to 25 seconds per poll, until the hold resolves or overallTimeoutMs elapses (default 900000 ms). No single connection is ever held long; the state is durable server-side. If your client-side deadline passes first, guard() returns verdict: "block", status: "timed_out".

  2. Manual poll. Pass mode: "poll" and guard() returns the held ticket at once. Poll yourself with sevra.decision(requestId) until status leaves "held". When the hold is approved, the first poll claims the single execution and returns status: "executed" with the executionToken; polling again is safe and returns the same token.

  3. mode: "webhook" with callbackUrl. The ticket returns at once and the callbackUrl is recorded on the request. Callback delivery is not yet active on the server, so today you still learn the outcome by polling; use block or poll mode until delivery ships.

Note that the hold itself expires server-side (per-policy, default 900 seconds); an unresolved hold times out to block. See the timeout section.

Idempotency keys

idempotencyKey is required and must be non-empty. It is your exactly-once anchor:

  • Retrying the same key with the same params replays the stored decision verbatim. Safe to retry freely.
  • Reusing a key with different params is a collision: the server returns HTTP 409 and the SDK maps it to verdict: "block", status: "collision".

Use a key derived from the business action, for example refund:<orderId>, so a crashed-and-retried agent cannot double-move money.

Movement fields

amountMinor, currency, and direction describe the money movement and feed reversibility scoring, the case file, and the audit trail. The server validates them at the front door:

  • amountMinor: a safe non-negative integer, in minor units (cents).
  • currency: an ISO 4217 shaped code (3 letters; normalized to uppercase).
  • amountMinor and currency must be provided together or not at all. Half a movement is a 400.
  • direction: "out" or "in" (optional; anything else is a 400).

Timeouts and fail-closed

Two distinct timeouts:

  • Server hold timeout. A held action expires after a per-policy window (matched rule, then protected path, then org default, then the 900 second default; a caller-supplied timeoutSeconds can only shorten it, never extend, floor 30 seconds). An expired hold becomes status: "timed_out", which is a block.
  • Client overall timeout. In block mode, overallTimeoutMs (default 900000) bounds how long guard() waits. Past it, the SDK returns verdict: "block", status: "timed_out" locally.

Fail-closed semantics: if Sevra cannot evaluate an action (control layer unprovisioned, a dependency down, the audit write for an allow fails), the server returns a block, never an allow. If the network fails outright, guard() throws; since you only execute on an allow with a token, a thrown error is a block by construction. There is no path where uncertainty produces an execution.

Shadow mode

An agent not yet marked live runs in shadow mode. On this proactive guard endpoint, shadow is an audit annotation only: the response may carry mode: "shadow", but the real verdict is still enforced. A would-be block is blocked and a would-be hold is held; shadow never downgrades a decision to allow.

Errors and statuses

| Signal | Meaning | |---|---| | HTTP 401 | Missing or invalid guard key | | HTTP 400 | Missing action, params, or idempotencyKey; empty key; invalid movement fields | | HTTP 409 | Idempotency key reused with different params; SDK returns status: "collision" | | status: "held" | Awaiting a human decision | | status: "timed_out" | Hold expired (server) or client deadline passed; treat as block | | status: "blocked" / "denied" | Terminal block; reasons explains |

The SDK does not throw on HTTP error statuses other than by JSON parsing; it throws only on transport failure. Always gate execution on verdict === "allow" and the presence of executionToken, not on the absence of an error.

HTTP API

The SDK is a thin client over two endpoints, POST /api/app/gate/guard and GET /api/app/gate/requests/{id}/decision. The full language-agnostic reference lives at sevra.dev/docs.