Node.js Runtime Enforcement Guide

A complete Node.js-first manual for implementing hard runtime control with explicit validation boundaries.

Companion to External Control Plane, Implementation Guide, and Operational Guarantees.
Audience: Node services, workers, serverless Goal: explicit stop boundaries Critical: async/await safety
What this is

This page is a complete Node.js manual for implementing MachineID runtime enforcement without relying on any specific agent framework.

Use this guide if you have
  • Express / Fastify / HTTP services
  • Workers that consume queues
  • Cron jobs and scheduled tasks
  • Serverless functions (Lambda, Vercel, etc.)
  • Any runtime that can incur cost or side effects
What this is not
  • A “soft guardrail” tutorial
  • A feature-flag system
  • A best-effort policy guide
MachineID provides external authority. Your runtime must validate before it executes.
Core invariant

Everything reduces to one invariant:

Register. Validate. Work.

If validation fails, work does not begin.
In Node, the most common way to accidentally violate this invariant is async “fire-and-forget.” Fix that first (next section).
Fast start
1) Get a key

Generate a free org key (supports up to 3 devices): machineid.io

2) Decide your stop boundaries

In Node, boundary placement determines stop latency: validate at the points where work begins, and immediately before cost and side effects.

3) Choose one integration path
  • Path A (recommended): Use a local guard.js helper (provided below)
  • Path B: Direct HTTP to canonical endpoints (POST register + validate)
Async warning (critical)
You must await validation

In Node.js, if you call validate and forget await, your runtime can execute work before the authority decision completes. That is a fail-open bug.

Rule: validation is a hard gate. It must be awaited. It must complete before work begins.
Identity model

MachineID enforces permission on an identity (a “device”) that represents an execution surface. In Node, an execution surface is usually one of:

  • One service instance (one container / one process)
  • One queue worker process (one consumer replica)
  • One scheduled job identity
  • One tool runner / side-effect executor
  • One serverless function invocation surface
Correct mapping
  • Good: one worker replica = one device
  • Good: one serverless function identity per function name + env
  • Risky: one entire fleet = one device (loses surgical control)
Device IDs are identifiers (auditable labels). Your org key is the credential.
Device ID strategy

Device IDs should be stable enough to audit, but specific enough to revoke. A practical format:

{service}:{env}:{role}:{instance}

Examples:

  • api:prod:http:01
  • worker:prod:queue-consumer:07
  • job:prod:nightly-reindex:01
  • tool:prod:payments-runner:02
  • fn:prod:invoice-webhook:01
Important: do not embed secrets in device IDs. Treat IDs as identifiers, not credentials.
Endpoints

MachineID uses canonical POST endpoints. Send your org key via x-org-key.

Register (idempotent)
POST https://machineid.io/api/v1/devices/register
Headers:
  x-org-key: org_...
  Content-Type: application/json
Body:
  {"deviceId":"worker:prod:queue-consumer:07"}
Validate (hard gate)
POST https://machineid.io/api/v1/devices/validate
Headers:
  x-org-key: org_...
  Content-Type: application/json
Body:
  {"deviceId":"worker:prod:queue-consumer:07"}
Decision semantics

Validation returns a decision including allowed, a stable code, and a request_id. Your runtime must treat allowed:false as an immediate stop condition.

Required behavior
  • If allowed is false: do not begin work
  • Log code + request_id for auditability
  • Stop the loop / stop the handler / terminate the worker
Revocation and org-wide disable take effect on the next validate. Boundary placement determines stop latency.
Where to validate

Validation belongs at execution boundaries: points where work begins or commits side effects. Most production Node systems already have these boundaries; they are simply not treated as enforcement points.

  • Startup boundary: before an instance begins processing work
  • Request/job boundary: before each request or unit of work
  • Tool boundary: before external calls that incur cost
  • Side-effect boundary: before irreversible actions (writes, payments, emails)
  • Fan-out boundary: before batch loops and parallel dispatch
In Node, the safest “stop points” are tool-call and side-effect boundaries because they occur during real work.
Node boundary patterns

Pattern A: Startup gate (required)

Register once, validate once, then start serving/consuming work. If validation denies, terminate the process.

Pattern B: Per-request / per-job gate (recommended)

Validate before each request/job. This makes revocation effective at predictable points.

Pattern C: Tool-call gate (high leverage)

Validate immediately before each external call that incurs spend or cost.

Pattern D: Side-effect gate (most leverage)

Validate immediately before irreversible actions: writes, deletes, payments, emails, queue publishes.

Reality: Most systems use Startup + Per-unit-of-work, and add Tool + Side-effect gates for high-risk actions.
Serverless (Lambda / Vercel / Cloudflare)

In serverless environments, “startup” is ambiguous (cold start vs warm start). For strict enforcement: treat every invocation as a startup boundary.

Serverless rule
  • Validate at the top of the handler, every time
  • Validate again immediately before high-cost tools and side effects
  • Fail closed on timeout/network errors
Long-running loops

MachineID does not introspect internal loops. If a Node process runs for hours, you must define enforcement boundaries inside the loop.

Good boundaries are
  • Before each iteration that triggers a tool call
  • Before each external request that can incur cost
  • Before each write or irreversible side effect
  • Before entering high-cost fan-out loops
Rule of thumb: if an action can cost money or change state, put a validation boundary immediately before it.
Side-effect gates (the most leverage)

Side effects are where runtime mistakes become expensive or irreversible: payments, emails, writes, deletes, credential rotations, queue publishes.

Recommended practice
  • Treat side-effect functions as privileged surfaces
  • Validate immediately before the side effect
  • Fail closed: if you cannot confirm permission, do not execute
The guard.js pattern (recommended)

Node developers commonly place enforcement behind a small guard. The guard must: (1) best-effort register, (2) hard-gate validate, (3) fail closed on any uncertainty, and (4) require await.

Drop-in file: guard.js
// guard.js
const process = require("node:process");

const MACHINEID_BASE_URL = "https://machineid.io";
const REGISTER_URL = `${MACHINEID_BASE_URL}/api/v1/devices/register`;
const VALIDATE_URL = `${MACHINEID_BASE_URL}/api/v1/devices/validate`;

function die(msg) {
  // minimal ops visibility
  try { process.stderr.write(msg + "\n"); } catch (_) {}
  process.exit(1);
}

async function postJson(url, orgKey, body, timeoutMs) {
  const ctrl = new AbortController();
  const t = setTimeout(() => ctrl.abort(), timeoutMs);

  try {
    const resp = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-org-key": orgKey,
      },
      body: JSON.stringify(body),
      signal: ctrl.signal,
    });
    return resp;
  } finally {
    clearTimeout(t);
  }
}

async function enforce(deviceId, orgKey, { timeoutMs = 2000 } = {}) {
  if (!orgKey) die("authorization guard: missing org key");
  if (!deviceId) die("authorization guard: missing device id");

  // 1) REGISTER (best effort; does not authorize)
  try {
    await postJson(REGISTER_URL, orgKey, { deviceId }, timeoutMs);
  } catch (_) {
    // visibility but still continue to validate (validate is authoritative)
    try { process.stderr.write("authorization guard: registration failed (non-fatal; validate will gate)\n"); } catch (_) {}
  }

  // 2) VALIDATE (hard gate; MUST be awaited by the caller)
  let resp;
  try {
    resp = await postJson(VALIDATE_URL, orgKey, { deviceId }, timeoutMs);
  } catch (_) {
    die("authorization guard: validate unreachable");
  }

  if (!resp || !resp.ok) {
    die(`authorization guard: validate failed (http ${resp ? resp.status : "unknown"})`);
  }

  let data;
  try {
    data = await resp.json();
  } catch (_) {
    die("authorization guard: validate failed (non-json response)");
  }

  if (!data || data.allowed !== true) {
    const code = (data && data.code) ? data.code : "UNKNOWN";
    const requestId = (data && data.request_id) ? data.request_id : "UNKNOWN";
    die(`authorization guard: denied (code=${code}, request_id=${requestId})`);
  }

  return data;
}

module.exports = { enforce };
Required: await enforce(...) before work begins. Do not fire-and-forget.
Direct HTTP (canonical endpoints)

If you prefer to avoid a helper, call the endpoints directly. The rule stays the same: validate must be awaited and must complete before work.

Register + validate (fetch shape)
await fetch("https://machineid.io/api/v1/devices/register", {
  method: "POST",
  headers: { "Content-Type": "application/json", "x-org-key": "org_..." },
  body: JSON.stringify({ deviceId: "api:prod:http:01" })
});

const resp = await fetch("https://machineid.io/api/v1/devices/validate", {
  method: "POST",
  headers: { "Content-Type": "application/json", "x-org-key": "org_..." },
  body: JSON.stringify({ deviceId: "api:prod:http:01" })
});

const decision = await resp.json();
if (!decision.allowed) {
  process.exit(1);
}
Examples: up to 3 devices

The free tier is enough to implement a complete control loop and prove stop behavior end-to-end. A practical three-device model:

Suggested 3-device topology
  • api:dev:http:01 — web/API runner
  • worker:dev:queue-consumer:01 — consumes and processes jobs
  • tool:dev:external-call-runner:01 — tool-heavy or cost-heavy actions
Examples: up to 25 devices

This tier supports multiple concurrent workers and multiple execution surfaces for tools and side effects.

Example topology
  • 8 HTTP replicas: api:prod:http:01:08
  • 10 queue consumers: worker:prod:queue-consumer:01:10
  • 4 tool runners: tool:prod:external-call-runner:01:04
  • 3 schedulers: job:prod:nightly:01:03
Examples: up to 250 devices

At this scale, execution surfaces multiply under load: autoscaling, retries, event storms, and distributed consumers. The dominant requirement is consistent enforcement across replicas and across time.

Patterns that drive device count
  • Autoscaling worker pools (many replicas)
  • Multiple queues or topic partitions
  • Per-tenant or per-workflow identities
  • Dedicated side-effect runners
Examples: up to 1000 devices

Near the upper end of standard caps, the dominant failure mode is execution multiplication: autoscale, fan-out, retries, recursion, and delayed resumes.

Scale guidance
  • Prefer per-replica identities (avoid a single “fleet” identity)
  • Validate at boundaries that occur during real work (tool + side-effect)
  • Avoid fallback authority paths (no degraded enforcement)
Custom device limits

If you need device limits beyond standard tiers, MachineID supports custom device limits. This does not require changes to runtime code — the identity model and enforcement boundaries remain the same.

Dashboard controls

The console lives outside your runtime: machineid.io/dashboard

Controls available
  • Revoke / restore devices (including bulk)
  • Remove devices
  • Register devices
  • Rotate key
  • Lost key recovery via magic link
  • Org-wide disable
Org-wide disable (stop everything)

In addition to revoking individual devices, MachineID supports an org-wide disable control. This affects validate outcomes across the org.

Operational semantics
  • Org disable does not change device revoked/restored states
  • It causes validate to deny across the org
  • It takes effect at the next validation boundary you defined
Stop latency (what stops, and when)

Revoke/restore and org disable are effective at the next validate. Stop latency is determined by boundary placement.

Make stop control operationally useful
  • Validate before tool calls
  • Validate before side effects
  • Validate at loop re-entry points
  • Validate before resuming long-paused work
LLM implementation prompts

Replace bracketed placeholders and paste into your LLM of choice.

Prompt — Integrate MachineID into my Node.js runtime
I have a Node.js worker/service runtime and I want hard enforcement using MachineID.io.

Context:
- Org key: [PASTE ORG KEY]
- Device ID scheme: {service}:{env}:{role}:{instance}
- Fail-closed policy, short timeout (1–3s)
- Critical Node requirement: validate MUST be awaited (no fire-and-forget promises)

Validation boundaries required:
  1) Startup (register + validate)
  2) Before each unit of work (request/job/message)
  3) Before any external tool call that can incur cost
  4) Before any irreversible side effect (writes, payments, emails)
  5) Before entering high-cost fan-out loops or batches

Please provide:
1) Exact files/functions where validation should be added
2) Copy/paste code blocks (guard.js pattern + direct fetch pattern)
3) A recommended boundary map for my runtime
4) A test plan using the MachineID.io dashboard:
   - revoke a device
   - restore it
   - use org-wide disable
   - verify work stops at the next validate boundary
LLM checklist
A correct implementation should include
  • Stable device identity per execution surface
  • Startup gating (register + validate, fail closed)
  • At least one stop point during real work (tool-call or side-effect boundary)
  • Short timeout and consistent failure policy
  • Denials logged as operational events (include request_id)
  • Node-specific requirement: await validation