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

@parmanasystems/server

v1.42.0

Published

Fastify REST API server for parmanasystems governance runtime.

Readme

@parmanasystems/server

Fastify REST API server for the parmanasystems deterministic governance runtime.

npm


Overview

@parmanasystems/server is an HTTP wrapper over @parmanasystems/execution. It exposes the governance execution pipeline over a REST API so that any language or platform can execute and independently verify governance decisions without embedding the TypeScript SDK directly.


Installation

npm install @parmanasystems/server

Quick start

# Start with default ephemeral Ed25519 keypair (development)
npx Parmana-server

# Start with persistent keys from environment variables
Parmana_PRIVATE_KEY="$(cat private.pem)" Parmana_PUBLIC_KEY="$(cat public.pem)" npx Parmana-server

# Enable API key authentication
Parmana_API_KEY=my-secret-key npx Parmana-server

The server listens on http://0.0.0.0:3000 by default.


Environment variables

| Variable | Required | Description | |---|---|---| | PORT | No | HTTP port (default: 3000) | | HOST | No | Bind address (default: 0.0.0.0) | | Parmana_API_KEY | No | When set, all routes require Authorization: Bearer <key> | | Parmana_PRIVATE_KEY | No | PEM-encoded Ed25519 private key for signing | | Parmana_PUBLIC_KEY | No | PEM-encoded Ed25519 public key for verification |

When Parmana_PRIVATE_KEY and Parmana_PUBLIC_KEY are absent, the server generates an ephemeral Ed25519 keypair on startup. This is suitable for development but means attestations cannot be verified after a restart.


API routes

GET /health

Returns runtime status, version, and per-subsystem health checks.

curl http://localhost:3000/health
{
  "status": "ok",
  "version": "1.2.3",
  "timestamp": "2026-05-03T10:00:00.000Z",
  "checks": {
    "runtime_manifest": "ok",
    "signing_key": "ok",
    "audit_db": "unconfigured"
  }
}

status is "ok" when all checks are "ok" or "unconfigured". It is "degraded" if any check is "error" or "unavailable". The HTTP status code is always 200 — callers decide how to act on a degraded response.

Check values:

| Check | Values | Meaning | |---|---|---| | runtime_manifest | ok / error | Whether getRuntimeManifest() succeeds | | signing_key | ok / unconfigured | Parmana_PRIVATE_KEY env var set, or dev key on disk | | audit_db | ok / unavailable / unconfigured | DB reachable / unreachable / AUDIT_DATABASE_URL not set |


POST /execute

Runs the deterministic governance runtime and returns a signed ExecutionAttestation.

Request body validation:

| Field | Type | minLength | maxLength | Pattern | |---|---|---|---|---| | policy_id | string | 1 | 128 | ^[a-zA-Z0-9_-]+$ | | policy_version | string | 1 | 32 | ^v?\d+(\.\d+){0,2}([-+][\w.-]+)?$ | | decision_type | string | 1 | 64 | ^[a-zA-Z0-9_-]+$ | | signals_hash | string | 64 | 64 | ^[0-9a-f]{64}$ |

Extra fields not listed above are rejected with 400 Bad Request (additionalProperties: false).

curl -X POST http://localhost:3000/execute \
  -H "Content-Type: application/json" \
  -d '{
    "policy_id":      "loan-approval",
    "policy_version": "v1",
    "decision_type":  "approve",
    "signals_hash":   "a3f1e2d4b5c6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
  }'
{
  "result": {
    "execution_id":    "550e8400-e29b-41d4-a716-446655440000",
    "policy_id":       "loan-approval",
    "policy_version":  "v1",
    "schema_version":  "1.0.0",
    "runtime_version": "1.0.0",
    "runtime_hash":    "a1b2c3...",
    "decision":        "approve",
    "signals_hash":    "a3f1e2d4b5c6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
    "executed_at":     "2025-05-02T10:00:00.000Z"
  },
  "signature": "base64-encoded-Ed25519-signature"
}

Error responses:

| Status | Meaning | |---|---| | 400 | Missing required fields, failed pattern/length validation, or unexpected fields | | 422 | Execution failed (token expired, replay detected, etc.) |


POST /verify

Independently verifies an ExecutionAttestation. Pass the response from POST /execute directly.

curl -X POST http://localhost:3000/verify \
  -H "Content-Type: application/json" \
  -d '{ "result": { ... }, "signature": "..." }'
{
  "valid": true,
  "checks": {
    "signature_verified": true,
    "runtime_verified":   true,
    "schema_compatible":  true
  }
}

Error responses:

| Status | Meaning | |---|---| | 400 | Malformed attestation body | | 422 | Verification threw an unexpected error |


Audit routes (requires AUDIT_DATABASE_URL)

When AUDIT_DATABASE_URL is configured, five read-only audit query routes are registered:

| Route | Description | |---|---| | GET /audit/decisions | Decision timeline. Query params: limit (default 100), offset, policy_id, decision, from, to | | GET /audit/decisions/:executionId | Single decision row with full ExecutionAttestation JSONB | | GET /audit/security | Security event dashboard. Query params: from, to, limit | | GET /audit/stats | Aggregate counts: total decisions, decisions today, verifications, security events, API calls | | GET /audit/verifications/:executionId | All verification attempts for an execution, newest first |

All audit routes return 404 if the database is not configured.


Stub endpoints (501 Not Implemented)

These endpoints are defined in the OpenAPI spec and will be implemented in future releases:

| Endpoint | Description | |---|---| | GET /runtime/manifest | Returns the signed bundle manifest for the active runtime | | GET /runtime/capabilities | Lists runtime capabilities | | POST /evaluate | Dry-run policy evaluation without attestation | | POST /simulate | Full simulation mode — no side effects |


Rate limits

All routes are rate-limited per API key (when Parmana_API_KEY is set) or per client IP (in dev mode).

| Route | Limit | |---|---| | POST /execute | 100 req/min | | POST /verify | 200 req/min | | GET /audit/* | 60 req/min | | GET /health | 300 req/min | | GET /runtime/* | 60 req/min | | POST /evaluate | 60 req/min | | POST /simulate | 60 req/min |

When authenticated, the rate limit key is sha256(Parmana_API_KEY). In dev mode (no Parmana_API_KEY), the key falls back to X-Forwarded-ForX-Real-IP → socket IP.

Every response includes rate limit headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1714640460

Payload size limits (enforced independently of rate limits):

| Route | Max body | |---|---| | POST /execute | 64 KB | | POST /verify | 64 KB | | POST /evaluate | 64 KB | | POST /simulate | 64 KB | | All other routes | 1 MB (global default) |

Requests exceeding the limit receive 413 Payload Too Large. GET routes carry no body so no limit applies.

When a rate limit is exceeded the server responds with 429 Too Many Requests:

{
  "error": "Rate limit exceeded",
  "limit": 100,
  "remaining": 0,
  "reset": 1714640460
}

CORS

Cross-origin requests are controlled via the CORS_ORIGIN environment variable.

| CORS_ORIGIN value | Behaviour | |---|---| | (not set) | Allow http://localhost:5173 and http://localhost:8080 (dev default) | | http://localhost:5173,https://app.example.com | Allow those two origins only | | * | Reflect any request origin (allows all — use with care in production) |

# Single origin
CORS_ORIGIN=https://app.example.com

# Multiple origins (comma-separated, no spaces)
CORS_ORIGIN=https://app.example.com,https://admin.example.com

credentials: true is set on all CORS responses. This means browsers will include cookies and Authorization headers on cross-origin requests. When using CORS_ORIGIN=*, the server reflects the specific request origin rather than sending a literal * so that credentials continue to work.

Allowed methods: GET, POST.
Allowed request headers: Content-Type, Authorization, X-Request-ID.
Exposed response headers: X-Request-ID, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.
Preflight cache (Access-Control-Max-Age): 86400 s (24 h).


Security headers

All responses include the following HTTP security headers, set via @fastify/helmet:

| Header | Value | Purpose | |---|---|---| | Content-Security-Policy | default-src 'none'; frame-ancestors 'none' | Blocks all resource loading and framing — this is a pure API, not a browser app | | Cross-Origin-Resource-Policy | cross-origin | Allows cross-origin reads (needed for browser clients consuming the API) | | Strict-Transport-Security | max-age=31536000; includeSubDomains; preload | Enforces HTTPS for 1 year across all subdomains; eligible for HSTS preload list | | X-Content-Type-Options | nosniff | Prevents MIME-type sniffing | | X-Frame-Options | DENY | Blocks the response from being embedded in a frame | | X-DNS-Prefetch-Control | off | Disables DNS prefetching | | Referrer-Policy | no-referrer | Suppresses the Referer header on all requests | | X-Download-Options | noopen | Prevents IE from executing downloaded files in the browser context | | X-XSS-Protection | 0 | Disables the legacy XSS auditor (modern browsers ignore it; CSP is the correct control) | | X-Powered-By | (removed) | Suppressed to avoid leaking server technology |

Cross-Origin-Embedder-Policy is intentionally disabled — it would block cross-origin API requests from browser clients that have not opted in to COEP.


Graceful shutdown

The server handles SIGTERM and SIGINT (Ctrl-C) with a clean, ordered shutdown:

  1. Stops accepting new connections (app.close()).
  2. Waits for in-flight requests to complete.
  3. Closes the PostgreSQL audit pool (auditDb.disconnect()), if configured.
  4. Logs "Server closed cleanly" and exits 0.

If shutdown takes longer than 10 seconds the process force-exits with code 1 and logs "Graceful shutdown timed out, forcing exit". The timeout is unref()-ed so it does not extend the process lifetime on its own.

SIGTERM/SIGINT
  └─ app.close()         — drain in-flight HTTP requests
  └─ auditDb.disconnect() — close postgres pool (if configured)
  └─ process.exit(0)

Logging

The server uses pino structured JSON logging via Fastify.

Log level

| LOG_LEVEL | Default | |---|---| | trace / debug / info / warn / error / fatal | debug in development, info in production |

Set NODE_ENV=production or LOG_LEVEL=info to suppress debug output. LOG_LEVEL takes priority over NODE_ENV.

Request ID (X-Request-ID)

Every request gets a unique ID. The server:

  • Reuses X-Request-ID from the incoming request if present.
  • Generates a crypto.randomUUID() otherwise.
  • Echoes the ID back in the X-Request-ID response header.
  • Includes reqId in every structured log line for that request.
curl -H "X-Request-ID: my-trace-id" http://localhost:3000/health
# Response header: X-Request-ID: my-trace-id

Redacted fields

The following fields are replaced with [REDACTED] in all log output:

| Field | Why | |---|---| | req.headers.authorization | Bearer token must not appear in logs | | req.body.signature | Ed25519 signature is key material | | req.body.attestation.signature | Nested signature in verify requests |

Structured log events

| Event | Level | Fields | |---|---|---| | Governance decision executed | info | reqId, policy_id, policy_version, decision_type | | Governance decision failed | warn | reqId, error | | Attestation verified | info | reqId, valid, checks | | Authentication failure | warn | reqId, reason: "auth_failure" |


Authentication

When Parmana_API_KEY is set, all requests must include:

Authorization: Bearer <your-api-key>

Requests without a valid bearer token receive 401 Unauthorized.

When Parmana_API_KEY is unset, the auth hook is disabled. This is the default development mode.


OpenAPI specification

The full OpenAPI 3.0.3 specification is available at openapi.json in the repository root.

To regenerate it:

npx tsx scripts/export-openapi.ts

Programmatic usage

import { createServer } from "@parmanasystems/server";

const { app, auditDb } = await createServer();
await app.listen({ port: 3000, host: "0.0.0.0" });

// On shutdown:
// await auditDb?.disconnect();
// await app.close();

License

Apache-2.0