@pathrule/signals
v0.2.0
Published
Pathrule Signals: a small, failure-isolated runtime instrumentation SDK. Send runtime errors and events to your own HTTP collector, Pathrule Local or Pathrule Cloud.
Maintainers
Readme
Quickstart
npm install @pathrule/signalsimport { pathrule, httpCollector } from "@pathrule/signals";
pathrule.init({
destination: httpCollector({
url: "https://collector.example.com/signals",
headers: { Authorization: `Bearer ${process.env.SIGNALS_TOKEN}` },
}),
environment: "production",
release: { version: "2.4.0", commit: process.env.GIT_SHA },
});
try {
await checkout(cart);
pathrule.event("checkout_completed", { flow: "checkout", meta: { outcome: "success" } });
} catch (error) {
pathrule.error(error, { node: "/apps/web/checkout", flow: "checkout" });
throw error;
}That is the whole integration. With no destination, the SDK does nothing at all.
Pick a destination
Your data goes where you send it, and nowhere else.
| Destination | What you need | Where the data goes |
| --- | --- | --- |
| httpCollector({ url }) | an HTTP endpoint of your own | your server, nothing else |
| pathruleLocal() (Node) | the open-source Pathrule host on the same machine | a SQLite file on that machine |
| pathruleCloud({ key }) | a Pathrule workspace ingest key | Pathrule Cloud |
Your own collector
Your collector receives POST requests with a JSON envelope (docs/protocol.md). A complete collector in 40 lines is in examples/http-collector; it reads envelopes with the same function every Pathrule collector uses:
import { normalizeEnvelope, MAX_BODY_BYTES } from "@pathrule/signals/protocol";Pathrule Local: no account, nothing leaves the machine
import { pathrule } from "@pathrule/signals";
import { pathruleLocal } from "@pathrule/signals/local";
pathrule.init({ destination: pathruleLocal(), environment: "development", release: "1.4.0" });Install the open-source Pathrule CLI and run pathrule init --local in the project once. Events land in that workspace, and your coding agent can read them.
pathruleLocal is Node only. A browser application sends to its own server, which forwards with pathruleLocal() or httpCollector().
Pathrule Cloud
import { pathrule } from "@pathrule/signals";
pathrule.init({ key: "prs_your_public_ingest_key", release: "2.4.0" });The ingest key is public by design: it ships in your application. Revoke it from Pathrule to stop a deployed application from reporting.
What your agent gets
With Pathrule Local or Pathrule Cloud, the issues reach your coding agent where it works:
- A one-line count where issues are. Working under
/checkout, the agent seesRuntime: 1 active issue under /checkout. Inspect with pathrule_runtime_events.It sees the count, never the reported content. - The groups, on request. The
pathrule_runtime_eventsMCP tool returns each group with its kind, path, flow, release, count and verification. Every value your application sent arrives as one bounded line, with markup neutralised and marked untrusted. - A fix it can stand behind. Once you confirm the fix shipped,
pathrule_runtime_events_resolverecords the correction against the release it was made for, and Pathrule starts watching the next one.
From the terminal, pathrule instrument events shows the same groups, with --path, --kind, --verification and --json (docs/consuming.md).
Verified, not just quiet
A fix is proven by evidence, never by silence:
| State | What it means |
| --- | --- |
| unobserved | No fix recorded, or no newer release seen yet. |
| observing | A release other than the one the fix was made against is sending; evidence accumulates. |
| quiet | A day on that release without the issue. Silence only: not proof. |
| verified | That release sent for an hour and the group's flow succeeded there three times. |
| recurred | The issue happened again on a newer release. The agent's indicator says so. |
Two things make verification possible: mark a flow's successful end with meta: { outcome: "success" }, and give the SDK your release identity. Without a release, nothing can be verified, and the group says so.
What to report
pathrule.error(error, { node, flow, meta }): an exception. Grouped by message template and top frame; the stack (application frames, scrubbed) and a boundedcausechain go along.pathrule.event(name, { node, flow, text, meta }): something that happened.meta: { outcome: "success" }marks a flow's successful end.node: where in your codebase it belongs, as a path (/apps/web/checkout).flow: the structural flow it belongs to (checkout), never a user or session identifier.
Private by design
It is not analytics: no users, no sessions, no funnels, no raw event stream.
- Tracking and secret-like metadata keys (
email,user_id,token,cookie,session, ...) are dropped. - In text, bearer tokens, common API key formats (
sk-,pk-,ghp_,ghs_, Slackxox*-) and email addresses are redacted before anything is sent. - Anything else specific to your application (JWTs, card numbers, internal identifiers) is yours to redact with the
scruboption, which runs on every text value.
Release identity
Verification compares releases, so give the SDK the build's identity: a string, or { version, commit, deployment } with what you have. Left out in Node, the SDK reads the build variables of common platforms (Vercel, Netlify, Cloudflare Pages, Render, Railway, Heroku, Fly, GitHub Actions; see releaseFromEnvironment). It never invents one: pathrule.status().verification says no_release when there is none.
Delivery
- Batches of at most 50 items and 32 KiB; at most 500 distinct fingerprints waiting.
- A failed envelope is kept (at most 20) and retried byte for byte under the same envelope
id, so a collector can drop duplicates: after 1, 5 and 15 minutes, then dropped and counted. 429andRetry-Afterare honoured;401/403stop the SDK for good (a revoked key); other4xxdrop the batch;408,5xx, timeouts and network errors retry.await pathrule.flush()delivers what is waiting within a bound (default 5 s, at most 30 s) and says what happened:{ outcome, delivered, pending, dropped }. It never rejects.- Browsers: on
pagehidethe SDK hands the batch tonavigator.sendBeacon(only for a collector without custom headers). Node: it delivers onbeforeExit; callflush()yourself beforeprocess.exit()or at the end of a serverless invocation.
Documentation
- API reference
- Reading what you collected: your collector, Pathrule Local, Pathrule Cloud
- Wire protocol and compatibility vectors
- Writing a collector (CORS, serverless, auth)
- Security and privacy
- Examples
Node 18 or later; current browsers (ES2022, fetch). TypeScript types ship with the package.
