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

@n50/safety-gates

v0.1.0

Published

Three decoupling patterns for autonomous-system safety gates that don't decay into paralysis. Implements the fix archetypes from PAT-039 (safety-mechanism-without-unlock-criteria) in the ALEF Pattern Catalog.

Readme

@alef/safety-gates

Three decoupling patterns for autonomous-system safety gates that don't decay into paralysis.

Implements the fix archetypes from PAT-039: safety-mechanism-without-unlock-criteria in the ALEF Pattern Catalog.

The problem this solves

A safety mechanism gets installed in response to a real threat (cease-and-desist, prompt-injection, chaos-test finding) but ships without a retirement condition. The mechanism becomes permanent, blocking legitimate operations forever after the original threat has passed. Defense decays into paralysis.

Symptoms in production:

  • Hardcoded if (CHAOS_MODE) return false flags that nobody remembers installing.
  • Circuit breakers stuck open for hours despite the upstream having recovered.
  • Observer-only modes that silently drop legitimate work.
  • Gates whose only documentation is the original incident PR, retrieved by archaeology.

This package gives three composable primitives that bake retirement, decoupling, and verification into the gate itself.

Install

npm install @alef/safety-gates

Node ≥ 18, ESM-only.

Quick reference

import {
  withTTLGate,
  withProcessBoundary,
  adversarialGateTester,
} from "@alef/safety-gates";

1. withTTLGate — retirement clock

Wraps a safety-check function with an explicit retireBy. After that date, the function refuses to execute (or invokes an explicit renewal handler that returns true to grant one more pass).

const safeCheck = withTTLGate(myGateFunction, {
  installedAt: new Date("2026-05-19T11:00:00Z"),
  retireBy:    new Date("2026-06-02T11:00:00Z"),
  onExpired: async () => {
    return await promptOperatorForRenewal();  // true → granted, false → leave retired
  },
});

await safeCheck(input);       // works
// ... 14 days later ...
await safeCheck(input);       // throws TTLGateExpiredError unless operator renews

Inspect operational state via _status:

console.log(safeCheck._status);
// {
//   installedAt: Date,
//   retireBy:    Date,
//   lastTrigger: Date | null,
//   renewalCount: number,
//   isExpired: boolean
// }

Why this matters: Hardcoded if (CHAOS_MODE) lasts as long as the file does. withTTLGate makes the retirement decision an active, dated act — visible to the operator, requiring confirmation, with a paper trail.


2. withProcessBoundary — fate-separated check

Wraps a safety-check with an explicit failMode ("fail-open" or "fail-closed"). When the check times out or throws, the wrapper returns the configured fail-mode value — never a silent "skip because the check didn't return."

const guarded = withProcessBoundary(safetyCheck, {
  timeoutMs: 500,
  failMode:  "fail-closed",
  logRejection: (err, input) => metrics.recordBoundaryFailure(err, input),
});

const verdict = await guarded(request);
// Always a verdict. Never a silent miss.

For full process-isolation (separate OS process, separate trust domain), supply an adapter that owns the inter-process channel:

const httpAdapter = {
  send: async (input) => {
    const res = await fetch("http://localhost:9876/check", {
      method: "POST",
      body: JSON.stringify(input),
    });
    if (!res.ok) throw new Error(`adapter HTTP ${res.status}`);
    return (await res.json()).allowed;
  },
};

const guarded = withProcessBoundary(/* unused */ () => null, {
  timeoutMs: 500,
  failMode:  "fail-closed",
  adapter:   httpAdapter,
});

Why this matters: In-process safety checks share fate with the orchestrator. A worker pool exhaustion or a hung promise stalls the check and the orchestrator continues believing the check succeeded. The process boundary makes the failure mode explicit and the verdict deterministic even under partial failure.


3. adversarialGateTester — verifier-of-the-verifier

Periodically synthesizes legitimate inputs and confirms the gate ALLOWS them. If the gate starts blocking synthetic-legit inputs, the tester reports a dysfunction. This is the recursive application of fate-separation: a verifier whose only job is to verify that the verifier still works.

const tester = adversarialGateTester({
  gate: safeCheck,
  synthesize: () => [
    { user: "synthetic-1", action: "read",  resource: "public-doc" },
    { user: "synthetic-2", action: "list",  resource: "public-feed" },
  ],
  onDysfunction: (err) => {
    alertOperator(err);            // err.input, err.verdict, err.runIndex
  },
  intervalMs: 60_000,
});

tester.start();
// ... later ...
console.log(tester.stats());
// { totalRuns, dysfunctionCount, lastRunAt, lastDysfunctionAt, isRunning }
tester.stop();

Why this matters: Gates can silently drift to "always-block" — the PAT-039 paralysis decay — and the orchestrator never knows. With adversarialGateTester, drift becomes a periodic, surfaced signal. The first synthetic-legit input that gets blocked is the moment the operator finds out, not the day the production user complains.


Composition

The three patterns compose:

import {
  withTTLGate,
  withProcessBoundary,
  adversarialGateTester,
} from "@alef/safety-gates";

// Layered: process boundary on top of TTL on top of the raw check.
const rawCheck = (input) => { /* original safety logic */ };

const ttlChecked  = withTTLGate(rawCheck, {
  installedAt: new Date(),
  retireBy:    new Date(Date.now() + 30 * 86400_000),  // 30 days
  onExpired:   async () => false,                       // require manual renewal
});

const guarded = withProcessBoundary(ttlChecked, {
  timeoutMs: 200,
  failMode:  "fail-closed",
});

// Self-verifying: adversarial tester runs against the composed stack.
const tester = adversarialGateTester({
  gate: guarded,
  synthesize: () => SYNTHETIC_LEGIT_INPUTS,
  onDysfunction: alertOperator,
  intervalMs: 5 * 60_000,  // every 5 minutes
});
tester.start();

This gives you:

  • A check that retires after 30 days unless explicitly renewed.
  • A check that always returns a verdict (never silently stalls).
  • A check whose silent decay-into-paralysis is detected within 5 minutes.

Pattern catalog reference

This library is the implementation arm of PAT-039 in the ALEF Pattern Catalog — a public corpus of named failure modes in autonomous and agentic systems.

Related patterns:

  • PAT-038 prompt-injection-via-issue-comment
  • PAT-040 bounded-iteration-without-progressive-state-preservation
  • PAT-041 self-metric-calibration-lag-blinds-to-success

Browse all patterns: n50.io/patterns

License

MIT