Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
# `instruments` — `api.rbac.denials_total`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Owns the single OpenTelemetry counter `api.rbac.denials_total` and the
factory that constructs the `RbacInstruments` registry against a
caller-supplied `MeterProvider`. The middleware itself never touches
the global OTel API directly — every emission flows through
`recordRbacDenial(instruments, { plugin, route, reason })`, which
guarantees the three label keys are populated on every call and the
base attributes from the host (host-id, host-version, etc.) are
merged in.
Centralising emission here lets the spec-conformance test pin the
exact counter name, label set, and reason vocabulary against a real
`MeterProvider` from `@opentelemetry/sdk-metrics` — empirically the
class of regression that broke production dashboards in two of three
prior incidents was a label-key rename slipping past lint+typecheck.
## Features
- Single counter `api.rbac.denials_total` with description, created
on the `@suaveplan/api-rbac` meter.
- Closed `RbacDenialReason` union — `"missing-scope" | "rule-deny" |
"no-principal"` — so dashboard authors can rely on a finite label
set.
- Frozen `RBAC_DENIAL_REASONS` and `RBAC_DENIAL_LABEL_KEYS` tuples
for tests and metric-discovery tooling.
- Base attributes merged on every emission; host injects host-id /
host-version at boot.
- `MeterProvider` is injectable — tests use a real
`MeterProvider` from `@opentelemetry/sdk-metrics`, production
resolves through `metrics.getMeterProvider()`.
## Usage: Basic
```ts
import {
createRbacInstruments,
recordRbacDenial,
} from "@suaveplan/api-rbac/instruments";
const instruments = createRbacInstruments();
recordRbacDenial(instruments, {
plugin: "billing",
route: "/invoices",
reason: "missing-scope",
});
```
## Usage: Advanced
Inject a real `MeterProvider` and a `PeriodicExportingMetricReader`
with an `InMemoryMetricExporter` to assert the exact counter
emissions in a conformance test:
```ts
import { metrics } from "@opentelemetry/api";
import {
InMemoryMetricExporter,
MeterProvider,
PeriodicExportingMetricReader,
} from "@opentelemetry/sdk-metrics";
import {
createRbacInstruments,
RBAC_DENIALS_TOTAL_NAME,
recordRbacDenial,
} from "@suaveplan/api-rbac/instruments";
const exporter = new InMemoryMetricExporter(0);
const reader = new PeriodicExportingMetricReader({ exporter });
const provider = new MeterProvider({ readers: [reader] });
const instruments = createRbacInstruments({
meterProvider: provider,
baseAttributes: { host: "h-1" },
});
recordRbacDenial(instruments, {
plugin: "billing",
route: "/x",
reason: "rule-deny",
});
await provider.forceFlush();
const dump = exporter.getMetrics();
// dump[0].scopeMetrics[0].metrics carries RBAC_DENIALS_TOTAL_NAME …
```
## API Reference
- `RBAC_DENIALS_TOTAL_NAME` — string literal `"api.rbac.denials_total"`.
- `RBAC_METER_NAME` — string literal `"@suaveplan/api-rbac"`.
- `RBAC_DENIAL_REASONS` — frozen tuple of legal reason labels.
- `RBAC_DENIAL_LABEL_KEYS` — frozen tuple `["plugin", "route", "reason"]`.
- `RbacDenialReason` — `"missing-scope" | "rule-deny" | "no-principal"`.
- `RbacInstruments` — `{ denials, baseAttributes }`.
- `RbacInstrumentsOptions` — `{ meterProvider?, baseAttributes? }`.
- `createRbacInstruments(options?)` — factory.
- `recordRbacDenial(instruments, attrs)` — emission helper.
## Implementation Notes
The factory falls back to `metrics.getMeterProvider()` only when no
provider is supplied; production hosts always pass their own
provider so SDK installation is opt-in. The counter description is
load-bearing for the OTel collector's `view` / `attribute_filter`
configuration — operators rely on it to scope the metric to
particular pipelines without re-discovering it. `Object.freeze` on
the registry plus `Object.freeze` on the base attribute bag makes
accidental in-place mutation throw in strict mode, which is the
class of bug `tslib` + `verbatimModuleSyntax` cannot catch.