npm package: @incidentary/sdk-node
Description: Incidentary Node.js SDK
Latest version: 0.2.0

# @incidentary/sdk-node

Production-focused Node.js SDK for Incidentary.

## Quick Start

```ts
import {
  IncidentaryClient,
  createExpressMiddleware,
} from "@incidentary/sdk-node";

const incidentary = new IncidentaryClient({
  apiKey: process.env.INCIDENTARY_API_KEY!,
  serviceName: "my-service",
});

app.use(createExpressMiddleware(incidentary));
```

## Event Vocabulary Helpers (Queue + Job + Webhook)

The SDK keeps `kind` as the causal contract and adds `event_type` for richer vocabulary.

```ts
incidentary.recordQueuePublish({ eventAttrs: { topic: "payments.jobs" } });
incidentary.recordQueueConsume();
incidentary.recordJobStart({ parentCeId: parentCeId });
incidentary.recordJobEnd();
incidentary.recordWebhookIn();
incidentary.recordWebhookOut({ status: 202 });
```

You can also use the generic emitter:

```ts
incidentary.recordEvent("job_start", {
  eventAttrs: { worker: "invoice-sync" },
});
```

## Environment Variables

| Variable                   | Required | Default                       | Description                                      |
| -------------------------- | -------- | ----------------------------- | ------------------------------------------------ |
| `INCIDENTARY_API_KEY`      | yes      | —                             | Workspace API key for ingest auth                |
| `INCIDENTARY_API_URL`      | no       | `https://api.incidentary.com` | Incidentary API base URL                         |
| `INCIDENTARY_SERVICE_NAME` | no       | —                             | Optional convenience for app-level config wiring |
| `INCIDENTARY_ENVIRONMENT`  | no       | `production`                  | Environment label sent in CE batches             |

## Local Pre-Arm Triggers

The SDK can silently move from `NORMAL` to `PRE_ARMED` before an external alert arrives.  
This only escalates capture depth. It does not page humans, and it does not create incidents locally.

Implemented local triggers:

- `slow_success`: requests still succeed, but a growing fraction is slow.
- `in_flight_pileup`: concurrent in-flight work keeps growing.
- `retry_onset`: outbound retry behavior starts increasing.
- Existing `5xx` trigger is still supported and participates in the same pre-arm controller.

Pre-arm enters when:

- any trigger fires at `severe`, or
- two distinct triggers fire at `mild` within 10 seconds.

Pre-arm reasons are attached to debug state and backend pre-arm start metadata with observed values and thresholds.

## Retry Identity Quality

Retry onset uses the most stable downstream identity available, in this order:

1. explicit retry metadata (`retryGroupId`, idempotency key, operation key)
2. canonical downstream route template
3. logical downstream edge (`downstreamService` + operation name)
4. normalized URL fallback

The SDK tracks key quality usage (`explicit`, `route_template`, `logical_edge`, `normalized_url`, `unknown`) and exposes normalized URL fallback rate in pre-arm debug status.

You can supply stronger retry identity context on outbound calls:

```ts
await instrumentedFetch(
  client,
  ctx,
  "https://billing.internal/charges/123/capture",
  {
    method: "POST",
    incidentary: {
      retryAttempt: 2,
      retryGroupId: "capture-req-9b4f",
      routeTemplate: "/charges/:id/capture",
      downstreamService: "billing",
      operationName: "CaptureCharge",
    },
  },
);
```

## Enhanced Detail Capture in PRE_ARMED/INCIDENT

Node SDK keeps one CE envelope and adds optional `detail` only in elevated modes.

- `NORMAL`: base CE only (skeleton)
- `PRE_ARMED` / `INCIDENT`: base CE + optional `detail` metadata

Current detail fields include method, route/route template, retry metadata, downstream identity, request/response byte estimates, selected safe headers, and timeout/cancel classification.
Payload snippets are optional and disabled by default.

## Pre-Arm Tuning (Node Config Keys)

All settings are constructor config fields on `IncidentaryClient`.
Env var names are shown for operational conventions.

### Arbiter + state machine

| Config key                | Env convention                           | Default  |
| ------------------------- | ---------------------------------------- | -------- |
| `preArmThresholdHigh`     | `INCIDENTARY_PREARM_THRESHOLD_HIGH_5XX`  | `10`     |
| `preArmThresholdLow`      | `INCIDENTARY_PREARM_THRESHOLD_LOW_5XX`   | `2`      |
| `preArmMinDurationMs`     | `INCIDENTARY_PREARM_MIN_DURATION_SECS`   | `60000`  |
| `preArmTtlMs`             | `INCIDENTARY_PREARM_TTL_SECS`            | `300000` |
| `preArmCooldownMs`        | `INCIDENTARY_PREARM_COOLDOWN_SECS`       | `30000`  |
| `preArmEnableSlowSuccess` | `INCIDENTARY_PREARM_ENABLE_SLOW_SUCCESS` | `true`   |
| `preArmEnableInFlight`    | `INCIDENTARY_PREARM_ENABLE_INFLIGHT`     | `true`   |
| `preArmEnableRetry`       | `INCIDENTARY_PREARM_ENABLE_RETRY`        | `true`   |

### Slow-success trigger

| Config key                          | Env convention                              | Default |
| ----------------------------------- | ------------------------------------------- | ------- |
| `preArmSlowMinMs`                   | `INCIDENTARY_PREARM_SLOW_MIN_MS`            | `250`   |
| `preArmSlowMultiplier`              | `INCIDENTARY_PREARM_SLOW_MULTIPLIER`        | `2.0`   |
| `preArmSlowAlpha`                   | `INCIDENTARY_PREARM_SLOW_ALPHA`             | `0.1`   |
| `preArmSlowSuccessRateHigh`         | `INCIDENTARY_PREARM_SLOW_SUCCESS_RATE_HIGH` | `0.20`  |
| `preArmSlowSuccessRateMild`         | `INCIDENTARY_PREARM_SLOW_SUCCESS_RATE_MILD` | `0.10`  |
| `preArmSlowMinSamples`              | `INCIDENTARY_PREARM_SLOW_MIN_SAMPLES`       | `50`    |
| `preArmSlowInclude4xxAsSuccessLike` | —                                           | `true`  |

### In-flight pileup trigger

| Config key                   | Env convention                               | Default |
| ---------------------------- | -------------------------------------------- | ------- |
| `preArmInflightMinAbs`       | `INCIDENTARY_PREARM_INFLIGHT_MIN_ABS`        | `32`    |
| `preArmInflightMultiplier`   | `INCIDENTARY_PREARM_INFLIGHT_MULTIPLIER`     | `2.0`   |
| `preArmInflightNetGrowthMin` | `INCIDENTARY_PREARM_INFLIGHT_NET_GROWTH_MIN` | `16`    |
| `preArmInflightHoldSecs`     | `INCIDENTARY_PREARM_INFLIGHT_HOLD_SECS`      | `3`     |
| `preArmInflightMildHoldSecs` | `INCIDENTARY_PREARM_INFLIGHT_MILD_HOLD_SECS` | `2`     |

### Retry onset trigger

| Config key             | Env convention                        | Default |
| ---------------------- | ------------------------------------- | ------- |
| `preArmRetryWindowMs`  | `INCIDENTARY_PREARM_RETRY_WINDOW_MS`  | `5000`  |
| `preArmRetryRateHigh`  | `INCIDENTARY_PREARM_RETRY_RATE_HIGH`  | `0.10`  |
| `preArmRetryRateMild`  | `INCIDENTARY_PREARM_RETRY_RATE_MILD`  | `0.05`  |
| `preArmRetryMinTotal`  | `INCIDENTARY_PREARM_RETRY_MIN_TOTAL`  | `20`    |
| `preArmRetryTableSize` | `INCIDENTARY_PREARM_RETRY_TABLE_SIZE` | `4096`  |

### Enhanced detail capture

| Config key                            | Env convention                                      | Default                                                  |
| ------------------------------------- | --------------------------------------------------- | -------------------------------------------------------- |
| `preArmDetailCaptureEnabled`          | `INCIDENTARY_PREARM_DETAIL_CAPTURE_ENABLED`         | `true`                                                   |
| `preArmDetailCapturePayloadEnabled`   | `INCIDENTARY_PREARM_DETAIL_CAPTURE_PAYLOAD_ENABLED` | `false`                                                  |
| `preArmDetailMaxPayloadBytes`         | `INCIDENTARY_PREARM_DETAIL_MAX_PAYLOAD_BYTES`       | `4096`                                                   |
| `preArmDetailRequestHeaderAllowlist`  | —                                                   | `content-type, content-length, user-agent, x-request-id` |
| `preArmDetailResponseHeaderAllowlist` | —                                                   | `content-type, content-length, x-request-id`             |

### False positives and cost

- False positives only increase temporary local capture depth in `PRE_ARMED`.
- They do not page and do not create incident objects.
- Cost is mostly transient memory and upload volume during pre-arm windows.
- Value is richer lead-up evidence if an incident later binds.

## Flush Behavior

- Flush queue size cap: `5000` events.
- When the queue is full, the oldest event is dropped first.
- Batches are flushed in chunks of `100` events.
- Retry backoff on upload failure: `1s`, `4s`, `16s`.
- After 3 failed retries, the batch is dropped and a warning log is emitted.
- Upload failures are fail-open and never block the host application.

## Context Propagation

Use `incidentary.fetch(...)` to inject `x-incidentary-trace-id` automatically on outbound HTTP requests.

```ts
await incidentary.fetch("https://service-b.internal/checkout", {
  method: "POST",
  body: JSON.stringify(payload),
  headers: { "content-type": "application/json" },
});
```

## Troubleshooting

1. `401 Unauthorized` on ingest:
   Use a workspace API key (`sk_...`), not a user session token.

2. No events visible in dashboard:
   Ensure your process actually calls `flushToBackend()` after requests.

3. `426 SDK_VERSION_TOO_OLD` responses:
   Upgrade to `@incidentary/sdk-node@0.2.0` or newer.

4. High drop counts:
   Increase flush frequency and avoid long flush starvation windows.

5. Backend unreachable warnings:
   The SDK is fail-open by design; check API URL/network and observe retry logs.