@nodii/telemetry 0.0.2 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +223 -1
  2. package/dist/active-span.d.ts +12 -0
  3. package/dist/active-span.d.ts.map +1 -0
  4. package/dist/active-span.js +20 -0
  5. package/dist/active-span.js.map +1 -0
  6. package/dist/agent-invocable.d.ts +24 -0
  7. package/dist/agent-invocable.d.ts.map +1 -0
  8. package/dist/agent-invocable.js +64 -0
  9. package/dist/agent-invocable.js.map +1 -0
  10. package/dist/audit.d.ts +47 -0
  11. package/dist/audit.d.ts.map +1 -0
  12. package/dist/audit.js +78 -0
  13. package/dist/audit.js.map +1 -0
  14. package/dist/context.d.ts +40 -0
  15. package/dist/context.d.ts.map +1 -0
  16. package/dist/context.js +70 -0
  17. package/dist/context.js.map +1 -0
  18. package/dist/index.d.ts +18 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +29 -5
  21. package/dist/index.js.map +1 -1
  22. package/dist/init.d.ts +39 -0
  23. package/dist/init.d.ts.map +1 -0
  24. package/dist/init.js +114 -0
  25. package/dist/init.js.map +1 -0
  26. package/dist/intent.d.ts +21 -0
  27. package/dist/intent.d.ts.map +1 -0
  28. package/dist/intent.js +123 -0
  29. package/dist/intent.js.map +1 -0
  30. package/dist/logger.d.ts +19 -0
  31. package/dist/logger.d.ts.map +1 -0
  32. package/dist/logger.js +99 -0
  33. package/dist/logger.js.map +1 -0
  34. package/dist/metrics.d.ts +53 -0
  35. package/dist/metrics.d.ts.map +1 -0
  36. package/dist/metrics.js +152 -0
  37. package/dist/metrics.js.map +1 -0
  38. package/dist/otlp-adapter.d.ts +44 -0
  39. package/dist/otlp-adapter.d.ts.map +1 -0
  40. package/dist/otlp-adapter.js +351 -0
  41. package/dist/otlp-adapter.js.map +1 -0
  42. package/dist/outbox-emit.d.ts +34 -0
  43. package/dist/outbox-emit.d.ts.map +1 -0
  44. package/dist/outbox-emit.js +58 -0
  45. package/dist/outbox-emit.js.map +1 -0
  46. package/dist/redaction.d.ts +51 -0
  47. package/dist/redaction.d.ts.map +1 -0
  48. package/dist/redaction.js +173 -0
  49. package/dist/redaction.js.map +1 -0
  50. package/dist/sdk-counters.d.ts +14 -0
  51. package/dist/sdk-counters.d.ts.map +1 -0
  52. package/dist/sdk-counters.js +42 -0
  53. package/dist/sdk-counters.js.map +1 -0
  54. package/dist/tracer.d.ts +63 -0
  55. package/dist/tracer.d.ts.map +1 -0
  56. package/dist/tracer.js +214 -0
  57. package/dist/tracer.js.map +1 -0
  58. package/dist/types.d.ts +230 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +66 -0
  61. package/dist/types.js.map +1 -0
  62. package/dist/uuid.d.ts +5 -0
  63. package/dist/uuid.d.ts.map +1 -0
  64. package/dist/uuid.js +34 -0
  65. package/dist/uuid.js.map +1 -0
  66. package/package.json +28 -6
package/README.md CHANGED
@@ -1,5 +1,227 @@
1
1
  # @nodii/telemetry
2
2
 
3
- Placeholder package at v0.0.1. Implementation lands at v0.1.0 per the locked feature_doc.
3
+ Substrate observability library for the Nodii microservice stack. v0.1.0
4
+ ships the SDK surface — bootstrap, log envelope, tracer, meter, audit
5
+ signal, intent lifecycle, agent registry, context propagation — without
6
+ the OTel-SDK wiring (deferred to a v0.1.x follow-up; see
7
+ `.agent-todo.md` TS-12).
4
8
 
5
9
  **Spec**: `https://planning.dev.nucleus-cloud.in/api/v1/feature-docs?serviceId=nodii-libs&docKey=telemetry`
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ bun add @nodii/telemetry
15
+ ```
16
+
17
+ ## Bootstrap (D152)
18
+
19
+ ```ts
20
+ import { initTelemetry } from "@nodii/telemetry";
21
+
22
+ initTelemetry({
23
+ serviceName: "nodii-billing-service",
24
+ env: process.env.APPLICATION_STATE!, // 'dev' | 'uat' | 'prod'
25
+ otlpEndpoint: process.env.OTLP_ENDPOINT,
26
+ vocabularyRegistry: {
27
+ billing_payments_total: {
28
+ family: "async_worker",
29
+ description: "Payments processed",
30
+ decision: "D23",
31
+ },
32
+ },
33
+ intentTypes: {
34
+ "billing.invoice.create": {
35
+ typeName: "billing.invoice.create",
36
+ server_mintable: true,
37
+ client_mintable: false,
38
+ timeout_seconds: 300,
39
+ },
40
+ },
41
+ });
42
+ ```
43
+
44
+ Subsequent `initTelemetry()` calls warn-log + no-op. Calling any public
45
+ API before init throws `TelemetryNotInitialized`.
46
+
47
+ ## Logger (D13)
48
+
49
+ ```ts
50
+ import { logger } from "@nodii/telemetry";
51
+
52
+ logger.info("payment.charged", { invoice_id, amount_minor_units });
53
+ logger.error("saga.compensation.failed", { saga_id, step, reason });
54
+ ```
55
+
56
+ The 11-field envelope (`level, ts, msg, service, env, tenant_id,
57
+ user_id, request_id, trace_id, span_id, intent_id`) is auto-attached
58
+ from the active `NodiiContext` + the init config + the active OTel
59
+ span. **Do not pass envelope fields yourself** — they get filled in.
60
+
61
+ D15 redaction runs on every line (structured-field keys against the D2
62
+ PII list + regex pass on the message string) and is **fail-closed**:
63
+ if redaction throws, the line is DROPPED and
64
+ `telemetry.sdk.redaction_failure_total{reason}` bumps.
65
+
66
+ ## Tracer (D26 / D27 / D28)
67
+
68
+ ```ts
69
+ import { tracer, withSpanAttributes } from "@nodii/telemetry";
70
+
71
+ await withSpanAttributes({ invoice_id }, async () => {
72
+ await tracer.span("billing.invoice.create", async (span) => {
73
+ span.setAttribute("payment.method", method);
74
+ // ... business logic; mandatory attrs auto-applied
75
+ });
76
+ });
77
+ ```
78
+
79
+ Mandatory attributes (`tenant_id, request_id, intent_id`) are stamped
80
+ on span start. 4 KB per-value cap with `_truncated=true` marker. D27
81
+ status mapping: `recordException` auto-pairs with `ERROR`; clean
82
+ return defaults to `OK`.
83
+
84
+ ## Metrics (D18 / D19 / D20 / D24)
85
+
86
+ ```ts
87
+ import { metrics } from "@nodii/telemetry";
88
+
89
+ const paymentsCounter = metrics.counter("billing_payments_total", {
90
+ family: "async_worker",
91
+ description: "Payments processed",
92
+ });
93
+ paymentsCounter.inc({ tenant_id, pgw, status });
94
+ ```
95
+
96
+ Names must exist in the vocabulary registry passed to
97
+ `initTelemetry({ vocabularyRegistry })` — D24 ADR-gated. Mismatched
98
+ family or off-vocab name → `MetricVocabularyError` + bumps
99
+ `telemetry.sdk.cardinality_rejected_total{family, reason}`.
100
+
101
+ D20 bare-label discipline: registration rejects `tenant.id` and
102
+ `tenantId` variants.
103
+
104
+ D19: exponential histograms, max 160 buckets.
105
+
106
+ ## Audit (D17 / D33)
107
+
108
+ ```ts
109
+ import { audit } from "@nodii/telemetry";
110
+
111
+ await audit.emit({
112
+ action: "payment.refund.issued",
113
+ target_kind: "payment",
114
+ target_id: payment.id,
115
+ payload: { amount_minor_units, reason },
116
+ });
117
+ ```
118
+
119
+ The library ships the adapter (write path); the emission policy
120
+ (method-level interceptor, saga lifecycle events, agent-layer
121
+ double-emission) and the `audit_log` schema live in the audit
122
+ doctrine + each consuming service.
123
+
124
+ ## Intent lifecycle (D46 / D155)
125
+
126
+ ```ts
127
+ import { intent } from "@nodii/telemetry";
128
+
129
+ const id = await intent.begin("billing.invoice.create", { invoice_id });
130
+ try {
131
+ // work
132
+ await intent.complete(id, { result: "ok" });
133
+ } catch (err) {
134
+ await intent.abandon(id, "error");
135
+ throw err;
136
+ }
137
+ ```
138
+
139
+ `intent.begin` HARD-validates the type name against the registry
140
+ (D155); unknown type → `IntentTypeNotRegistered`. Activates the intent
141
+ on the active `NodiiContext` (sets `intent_id`). Nested begins stack
142
+ by id match.
143
+
144
+ `intent.progress` is a no-op stub at v0.1.0 per D155 § M02
145
+ carry-forward.
146
+
147
+ ## @agentInvocable (D156)
148
+
149
+ ```ts
150
+ import { agentInvocable } from "@nodii/telemetry";
151
+
152
+ class PaymentService {
153
+ @agentInvocable({
154
+ mode: "composable",
155
+ authTier: "confirmation-required",
156
+ sideEffects: ["payment.refund"],
157
+ idempotency: "keyed-by-request-id",
158
+ description: "Refund a captured payment",
159
+ })
160
+ async refundPayment(req: RefundRequest): Promise<RefundResult> {
161
+ /* ... */
162
+ }
163
+ }
164
+ ```
165
+
166
+ Runtime is a no-op at v0.1.0 (D156). The decorator attaches the
167
+ config to the method + the class prototype under a symbol-keyed
168
+ property; the build-time codegen (separate tool, deferred) walks the
169
+ tsc AST + falls back to the runtime registry to emit
170
+ `agent-methods.generated.json`.
171
+
172
+ ## Context propagation (D40 / D41)
173
+
174
+ ```ts
175
+ import {
176
+ type NodiiContext,
177
+ withContext,
178
+ getContext,
179
+ requireContext,
180
+ } from "@nodii/telemetry";
181
+
182
+ const ctx: NodiiContext = {
183
+ tenant_id: "...",
184
+ user_id: "...",
185
+ actor_type: "user",
186
+ on_behalf_of: null,
187
+ role: "owner",
188
+ request_id: "...",
189
+ intent_id: null,
190
+ };
191
+ await withContext(ctx, async () => {
192
+ /* every log / span / metric / audit emit picks up this context */
193
+ });
194
+ ```
195
+
196
+ Read with `getContext()` (returns null when no scope active) or
197
+ `requireContext()` (throws). SDK middleware adapters (deferred to
198
+ TS-9) are the ONLY writers of the parent-scope context per D41 lock;
199
+ service code uses `withContext` for child scopes only.
200
+
201
+ ## SDK-internal counters
202
+
203
+ The library increments these on its own behaviors (do not register
204
+ them in your vocabulary):
205
+
206
+ - `telemetry.sdk.redaction_failure_total{reason}` — D15 fail-closed
207
+ drop.
208
+ - `telemetry.sdk.cardinality_rejected_total{family, reason}` — D18.
209
+ - `telemetry.sdk.export_failures_total{signal, reason}` — D153.
210
+ - `telemetry.sdk.trace_propagation_missing_total` — § 5.9 D25.
211
+
212
+ ## Deferred to v0.1.x
213
+
214
+ See `.agent-todo.md` at the repo root:
215
+
216
+ - **TS-9**: Subpath adapters (`/grpc`, `/bullmq`, `/outbox`,
217
+ `/system-process`, `/lambda`).
218
+ - **TS-12**: Real OTel SDK wiring — replaces the no-op
219
+ `SpanEmitter` + the default JSON log sink with the OTLP
220
+ exporters.
221
+ - **TS-13**: Smoke service per D157 at `ts/telemetry-smoke/`.
222
+ - **TS-14**: Telemetry-tax-budget benchmark (D28 ≤ 200µs SDK-side
223
+ per typical RPC).
224
+
225
+ ## License
226
+
227
+ MIT
@@ -0,0 +1,12 @@
1
+ export interface ActiveSpanRef {
2
+ trace_id: string;
3
+ span_id: string;
4
+ }
5
+ export type ActiveSpanGetter = () => ActiveSpanRef | null;
6
+ /** SDK-internal: the tracer module installs this on initialization. */
7
+ export declare function _setActiveSpanGetter(g: ActiveSpanGetter): void;
8
+ /** Read the active span ids. Returns null when no span is active. */
9
+ export declare function getActiveSpan(): ActiveSpanRef | null;
10
+ /** Test-only reset. */
11
+ export declare function _resetActiveSpanForTests(): void;
12
+ //# sourceMappingURL=active-span.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-span.d.ts","sourceRoot":"","sources":["../src/active-span.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,aAAa,GAAG,IAAI,CAAC;AAI1D,uEAAuE;AACvE,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAE9D;AAED,qEAAqE;AACrE,wBAAgB,aAAa,IAAI,aAAa,GAAG,IAAI,CAEpD;AAED,uBAAuB;AACvB,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C"}
@@ -0,0 +1,20 @@
1
+ // Active-span hooks. The tracer (§ 5.3) registers a getter that returns
2
+ // the current `{ trace_id, span_id }` pair, or null if no span is
3
+ // active. The logger reads this on every log line so envelope fields
4
+ // `trace_id` and `span_id` (D13) are auto-stamped without the logger
5
+ // importing the tracer (avoids a cycle and lets the OTel wiring land
6
+ // in TS-12 as a strict module-internal swap).
7
+ let getter = () => null;
8
+ /** SDK-internal: the tracer module installs this on initialization. */
9
+ export function _setActiveSpanGetter(g) {
10
+ getter = g;
11
+ }
12
+ /** Read the active span ids. Returns null when no span is active. */
13
+ export function getActiveSpan() {
14
+ return getter();
15
+ }
16
+ /** Test-only reset. */
17
+ export function _resetActiveSpanForTests() {
18
+ getter = () => null;
19
+ }
20
+ //# sourceMappingURL=active-span.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-span.js","sourceRoot":"","sources":["../src/active-span.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,kEAAkE;AAClE,qEAAqE;AACrE,qEAAqE;AACrE,qEAAqE;AACrE,8CAA8C;AAS9C,IAAI,MAAM,GAAqB,GAAG,EAAE,CAAC,IAAI,CAAC;AAE1C,uEAAuE;AACvE,MAAM,UAAU,oBAAoB,CAAC,CAAmB;IACtD,MAAM,GAAG,CAAC,CAAC;AACb,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,aAAa;IAC3B,OAAO,MAAM,EAAE,CAAC;AAClB,CAAC;AAED,uBAAuB;AACvB,MAAM,UAAU,wBAAwB;IACtC,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC;AACtB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { type AgentInvocableConfig } from "./types";
2
+ export declare const AGENT_INVOCABLE_METADATA: unique symbol;
3
+ /**
4
+ * Method decorator. TS legacy decorators are still the only widely
5
+ * deployed form for class methods; we attach metadata under a
6
+ * symbol-keyed property on the method itself + on the class prototype.
7
+ *
8
+ * Usage:
9
+ *
10
+ * class PaymentService {
11
+ * @agentInvocable({ mode: 'composable', authTier: 'confirmation-required',
12
+ * sideEffects: ['payment.refund'], idempotency: 'keyed-by-request-id',
13
+ * description: 'Refund a captured payment' })
14
+ * async refundPayment(req) { ... }
15
+ * }
16
+ */
17
+ export declare function agentInvocable(cfg: AgentInvocableConfig): (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => PropertyDescriptor;
18
+ /**
19
+ * Read the registry from a class. Used by tests + the codegen fallback
20
+ * (when AST discovery doesn't find a decorator, e.g. when the file was
21
+ * transformed by a different toolchain).
22
+ */
23
+ export declare function readAgentRegistry<T extends object>(ctor: T): Record<string | symbol, AgentInvocableConfig>;
24
+ //# sourceMappingURL=agent-invocable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-invocable.d.ts","sourceRoot":"","sources":["../src/agent-invocable.ts"],"names":[],"mappings":"AASA,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,wBAAwB,eAEpC,CAAC;AAoBF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,oBAAoB,IAGpD,QAAQ,MAAM,EACd,aAAa,MAAM,GAAG,MAAM,EAC5B,YAAY,kBAAkB,KAC7B,kBAAkB,CAuBtB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAChD,IAAI,EAAE,CAAC,GACN,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,oBAAoB,CAAC,CAM/C"}
@@ -0,0 +1,64 @@
1
+ // `@agentInvocable` decorator per D156. Runtime no-op at v0.1.0 — the
2
+ // agent dispatcher consumes the build-time codegen registry
3
+ // (`agent-methods.generated.json`), not the runtime metadata.
4
+ //
5
+ // At v0.1.0 we attach the supplied config to the method's prototype
6
+ // under a well-known symbol so the codegen step (a separate tsc AST
7
+ // walk in TS-8 wave 2) can also discover registrations via runtime
8
+ // import if it falls back from the AST path.
9
+ import { AGENT_INVOCABLE_SIDE_EFFECTS, } from "./types";
10
+ export const AGENT_INVOCABLE_METADATA = Symbol.for("@nodii/telemetry/agentInvocable");
11
+ function validate(cfg) {
12
+ if (cfg.mode === "strict-only" && !cfg.allowedPipelines?.length) {
13
+ throw new Error("@agentInvocable: `allowedPipelines` is required when `mode = 'strict-only'`");
14
+ }
15
+ for (const eff of cfg.sideEffects) {
16
+ if (!AGENT_INVOCABLE_SIDE_EFFECTS.includes(eff)) {
17
+ throw new Error(`@agentInvocable: sideEffect '${eff}' is not in the locked vocabulary (D156).`);
18
+ }
19
+ }
20
+ if (!cfg.description) {
21
+ throw new Error("@agentInvocable: `description` is required");
22
+ }
23
+ }
24
+ /**
25
+ * Method decorator. TS legacy decorators are still the only widely
26
+ * deployed form for class methods; we attach metadata under a
27
+ * symbol-keyed property on the method itself + on the class prototype.
28
+ *
29
+ * Usage:
30
+ *
31
+ * class PaymentService {
32
+ * @agentInvocable({ mode: 'composable', authTier: 'confirmation-required',
33
+ * sideEffects: ['payment.refund'], idempotency: 'keyed-by-request-id',
34
+ * description: 'Refund a captured payment' })
35
+ * async refundPayment(req) { ... }
36
+ * }
37
+ */
38
+ export function agentInvocable(cfg) {
39
+ validate(cfg);
40
+ return function decorate(target, propertyKey, descriptor) {
41
+ const original = descriptor.value;
42
+ if (typeof original !== "function") {
43
+ throw new Error("@agentInvocable can only be applied to methods (PropertyDescriptor.value must be a function).");
44
+ }
45
+ // Attach metadata to the method function itself.
46
+ original[AGENT_INVOCABLE_METADATA] =
47
+ cfg;
48
+ // Mirror onto a class-level registry under the same symbol.
49
+ const ctor = target.constructor;
50
+ const existing = ctor[AGENT_INVOCABLE_METADATA] ?? {};
51
+ existing[propertyKey] = cfg;
52
+ ctor[AGENT_INVOCABLE_METADATA] = existing;
53
+ return descriptor;
54
+ };
55
+ }
56
+ /**
57
+ * Read the registry from a class. Used by tests + the codegen fallback
58
+ * (when AST discovery doesn't find a decorator, e.g. when the file was
59
+ * transformed by a different toolchain).
60
+ */
61
+ export function readAgentRegistry(ctor) {
62
+ return (ctor[AGENT_INVOCABLE_METADATA] ?? {});
63
+ }
64
+ //# sourceMappingURL=agent-invocable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-invocable.js","sourceRoot":"","sources":["../src/agent-invocable.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,4DAA4D;AAC5D,8DAA8D;AAC9D,EAAE;AACF,oEAAoE;AACpE,oEAAoE;AACpE,mEAAmE;AACnE,6CAA6C;AAE7C,OAAO,EACL,4BAA4B,GAE7B,MAAM,SAAS,CAAC;AAEjB,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC,GAAG,CAChD,iCAAiC,CAClC,CAAC;AAEF,SAAS,QAAQ,CAAC,GAAyB;IACzC,IAAI,GAAG,CAAC,IAAI,KAAK,aAAa,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CACb,6EAA6E,CAC9E,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;QAClC,IAAI,CAAC,4BAA4B,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CACb,gCAAgC,GAAG,2CAA2C,CAC/E,CAAC;QACJ,CAAC;IACH,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,cAAc,CAAC,GAAyB;IACtD,QAAQ,CAAC,GAAG,CAAC,CAAC;IACd,OAAO,SAAS,QAAQ,CACtB,MAAc,EACd,WAA4B,EAC5B,UAA8B;QAE9B,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC;QAClC,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CACb,+FAA+F,CAChG,CAAC;QACJ,CAAC;QACD,iDAAiD;QAChD,QAA+C,CAAC,wBAAwB,CAAC;YACxE,GAAG,CAAC;QACN,4DAA4D;QAC5D,MAAM,IAAI,GAAI,MAAkC,CAAC,WAGhD,CAAC;QACF,MAAM,QAAQ,GACX,IAAI,CAAC,wBAAwB,CAEhB,IAAI,EAAE,CAAC;QACvB,QAAQ,CAAC,WAAW,CAAC,GAAG,GAAG,CAAC;QAC5B,IAAI,CAAC,wBAAwB,CAAC,GAAG,QAAQ,CAAC;QAC1C,OAAO,UAAU,CAAC;IACpB,CAAC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAO;IAEP,OAAO,CACH,IAA2C,CAC3C,wBAAwB,CACyB,IAAI,EAAE,CAC1D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,47 @@
1
+ export interface AuditEvent {
2
+ /** D33-locked vocabulary, e.g. `payment.refund.issued`. */
3
+ action: string;
4
+ /** D33: target_kind, e.g. `payment`, `service`, `feature_doc`. */
5
+ target_kind: string;
6
+ /** Stable id of the row being acted on. May be null for create-then-id. */
7
+ target_id: string | null;
8
+ /** Free-form payload; redaction happens in `audit.emit`. */
9
+ payload?: Record<string, unknown>;
10
+ }
11
+ /**
12
+ * The pluggable Postgres-write path. Services wire this on init by
13
+ * passing `auditWriter` to `initTelemetry({ ... })` (deferred) OR by
14
+ * calling `_setAuditWriter` from a `@nodii/db-rls` adapter. v0.1.0 ships
15
+ * a default no-op writer; consumers replace it.
16
+ */
17
+ export type AuditWriter = (row: {
18
+ actor: string;
19
+ action: string;
20
+ target_kind: string;
21
+ target_id: string | null;
22
+ payload: Record<string, unknown> | null;
23
+ tenant_id: string | null;
24
+ request_id: string | null;
25
+ intent_id: string | null;
26
+ ts: string;
27
+ }) => Promise<void> | void;
28
+ /**
29
+ * SDK-internal: services or the `@nodii/db-rls` adapter sets the
30
+ * writer. Public callers MUST NOT call this; the override exists for
31
+ * the adapter layer.
32
+ */
33
+ export declare function _setAuditWriter(w: AuditWriter): void;
34
+ /** Test-only restore. */
35
+ export declare function _resetAuditWriterForTests(): void;
36
+ /**
37
+ * The `audit.emit` API. Awaits the writer so the caller can await one
38
+ * write before responding; failures propagate to the caller (audit is
39
+ * NOT best-effort the way logs are — D33 requires durable emission for
40
+ * mutations).
41
+ */
42
+ declare function emit(event: AuditEvent): Promise<void>;
43
+ export declare const audit: {
44
+ readonly emit: typeof emit;
45
+ };
46
+ export {};
47
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,UAAU;IACzB,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,EAAE,EAAE,MAAM,CAAC;CACZ,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAQ3B;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,CAEpD;AAED,yBAAyB;AACzB,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAED;;;;;GAKG;AACH,iBAAe,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAepD;AAwBD,eAAO,MAAM,KAAK;;CAER,CAAC"}
package/dist/audit.js ADDED
@@ -0,0 +1,78 @@
1
+ // Audit signal class per D17 + D33 + D149. Separate from logs.
2
+ //
3
+ // "If a tenant could legitimately need to see this row, it's audit, not
4
+ // log." — D17 cultural rule.
5
+ //
6
+ // The library ships the adapter (the write path); the audit emission
7
+ // policy (D33 three paths: method-level interceptor, saga lifecycle,
8
+ // agent-layer double-emission) and the audit_log SCHEMA live in the
9
+ // audit doctrine, not here. `@nodii/telemetry`'s job is to provide
10
+ // `audit.emit(...)` that lands the row in:
11
+ // - per-service Postgres audit_log table (via the configured writer)
12
+ // - ClickHouse mirror (via OTLP audit signal; deferred to TS-12)
13
+ import { getContext } from "./context";
14
+ import { requireTelemetry } from "./init";
15
+ const noopWriter = () => {
16
+ /* no-op default; services wire a real writer */
17
+ };
18
+ let writer = noopWriter;
19
+ /**
20
+ * SDK-internal: services or the `@nodii/db-rls` adapter sets the
21
+ * writer. Public callers MUST NOT call this; the override exists for
22
+ * the adapter layer.
23
+ */
24
+ export function _setAuditWriter(w) {
25
+ writer = w;
26
+ }
27
+ /** Test-only restore. */
28
+ export function _resetAuditWriterForTests() {
29
+ writer = noopWriter;
30
+ }
31
+ /**
32
+ * The `audit.emit` API. Awaits the writer so the caller can await one
33
+ * write before responding; failures propagate to the caller (audit is
34
+ * NOT best-effort the way logs are — D33 requires durable emission for
35
+ * mutations).
36
+ */
37
+ async function emit(event) {
38
+ requireTelemetry(); // throws if SDK not init'd
39
+ const ctx = getContext();
40
+ const actor = formatActor(ctx);
41
+ await writer({
42
+ actor,
43
+ action: event.action,
44
+ target_kind: event.target_kind,
45
+ target_id: event.target_id,
46
+ payload: event.payload ?? null,
47
+ tenant_id: ctx?.tenant_id ?? null,
48
+ request_id: ctx?.request_id ?? null,
49
+ intent_id: ctx?.intent_id ?? null,
50
+ ts: new Date().toISOString(),
51
+ });
52
+ }
53
+ function formatActor(ctx) {
54
+ if (!ctx)
55
+ return "system";
56
+ switch (ctx.actor_type) {
57
+ case "user":
58
+ return ctx.user_id ? `user:${ctx.user_id}` : "user:unknown";
59
+ case "agent":
60
+ return ctx.on_behalf_of
61
+ ? `agent:on_behalf_of:${ctx.on_behalf_of}`
62
+ : "agent:unknown";
63
+ case "service":
64
+ return "service";
65
+ case "system":
66
+ return "system";
67
+ default: {
68
+ // Exhaustiveness check.
69
+ const _exhaustive = ctx.actor_type;
70
+ void _exhaustive;
71
+ return "unknown";
72
+ }
73
+ }
74
+ }
75
+ export const audit = {
76
+ emit,
77
+ };
78
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,EAAE;AACF,wEAAwE;AACxE,6BAA6B;AAC7B,EAAE;AACF,qEAAqE;AACrE,qEAAqE;AACrE,oEAAoE;AACpE,mEAAmE;AACnE,2CAA2C;AAC3C,uEAAuE;AACvE,mEAAmE;AAEnE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AA+B1C,MAAM,UAAU,GAAgB,GAAG,EAAE;IACnC,gDAAgD;AAClD,CAAC,CAAC;AAEF,IAAI,MAAM,GAAgB,UAAU,CAAC;AAErC;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,CAAc;IAC5C,MAAM,GAAG,CAAC,CAAC;AACb,CAAC;AAED,yBAAyB;AACzB,MAAM,UAAU,yBAAyB;IACvC,MAAM,GAAG,UAAU,CAAC;AACtB,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,IAAI,CAAC,KAAiB;IACnC,gBAAgB,EAAE,CAAC,CAAC,2BAA2B;IAC/C,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,MAAM,CAAC;QACX,KAAK;QACL,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,IAAI;QAC9B,SAAS,EAAE,GAAG,EAAE,SAAS,IAAI,IAAI;QACjC,UAAU,EAAE,GAAG,EAAE,UAAU,IAAI,IAAI;QACnC,SAAS,EAAE,GAAG,EAAE,SAAS,IAAI,IAAI;QACjC,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KAC7B,CAAC,CAAC;AACL,CAAC;AAED,SAAS,WAAW,CAAC,GAAkC;IACrD,IAAI,CAAC,GAAG;QAAE,OAAO,QAAQ,CAAC;IAC1B,QAAQ,GAAG,CAAC,UAAU,EAAE,CAAC;QACvB,KAAK,MAAM;YACT,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC;QAC9D,KAAK,OAAO;YACV,OAAO,GAAG,CAAC,YAAY;gBACrB,CAAC,CAAC,sBAAsB,GAAG,CAAC,YAAY,EAAE;gBAC1C,CAAC,CAAC,eAAe,CAAC;QACtB,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAC;QAClB,OAAO,CAAC,CAAC,CAAC;YACR,wBAAwB;YACxB,MAAM,WAAW,GAAU,GAAG,CAAC,UAAU,CAAC;YAC1C,KAAK,WAAW,CAAC;YACjB,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,IAAI;CACI,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { NodiiContext } from "./types";
2
+ /**
3
+ * Returns the active `NodiiContext`, or null if no middleware adapter
4
+ * has set one for this async scope (cron-flow before the
5
+ * `system-process` adapter runs; raw scripts; tests without harness).
6
+ */
7
+ export declare function getContext(): NodiiContext | null;
8
+ /**
9
+ * Like `getContext()` but throws if no context is active. Use this in
10
+ * code that is allowed to assume an adapter ran — typically inside a
11
+ * request handler.
12
+ */
13
+ export declare function requireContext(): NodiiContext;
14
+ /**
15
+ * Run `fn` inside a child scope where `ctx` is the active context. Per
16
+ * D41: never mutates the parent scope — child finishes and the parent
17
+ * scope's context is unaffected.
18
+ */
19
+ export declare function withContext<T>(ctx: NodiiContext, fn: () => T): T;
20
+ /**
21
+ * SDK-internal use only. The middleware adapters (§ 5.9) call this on
22
+ * request entry to set the active context. Service code MUST NOT call
23
+ * this directly — use `withContext()` for child scopes only. Not part
24
+ * of the public barrel; subpath adapters import it via the package
25
+ * internal path.
26
+ */
27
+ export declare function setContextForCurrentScope(ctx: NodiiContext): void;
28
+ /**
29
+ * Update the active context's `intent_id` only. Used by the intent
30
+ * lifecycle (§ 5.6) when `intent.begin()` / `intent.complete()` /
31
+ * `intent.abandon()` activate or deactivate an intent. Returns the new
32
+ * context so callers can verify; throws if no context is active (intent
33
+ * lifecycle must run inside an established `NodiiContext`).
34
+ *
35
+ * This is the ONLY mutation API per D41. It exists because intent
36
+ * activation is a per-scope assignment that the intent lifecycle owns,
37
+ * not a service-code mutation.
38
+ */
39
+ export declare function _setIntentIdInCurrentScope(intentId: string | null): NodiiContext;
40
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI5C;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,YAAY,GAAG,IAAI,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,YAAY,CAQ7C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEhE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAEjE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,YAAY,CAUd"}
@@ -0,0 +1,70 @@
1
+ // Context propagation per D40 / D41.
2
+ //
3
+ // SDK middleware (§ 5.9 of the feature_doc) is the ONLY writer of
4
+ // `NodiiContext`. Service code reads via `getContext()`; child scopes
5
+ // only via `withContext({...}, fn)`. There is no public mutate-in-place
6
+ // API — that's an invariant of D41.
7
+ //
8
+ // Implementation: Node's `AsyncLocalStorage`, which works under both
9
+ // Node and Bun. The store holds a `NodiiContext` directly.
10
+ import { AsyncLocalStorage } from "node:async_hooks";
11
+ const als = new AsyncLocalStorage();
12
+ /**
13
+ * Returns the active `NodiiContext`, or null if no middleware adapter
14
+ * has set one for this async scope (cron-flow before the
15
+ * `system-process` adapter runs; raw scripts; tests without harness).
16
+ */
17
+ export function getContext() {
18
+ return als.getStore() ?? null;
19
+ }
20
+ /**
21
+ * Like `getContext()` but throws if no context is active. Use this in
22
+ * code that is allowed to assume an adapter ran — typically inside a
23
+ * request handler.
24
+ */
25
+ export function requireContext() {
26
+ const ctx = als.getStore();
27
+ if (!ctx) {
28
+ throw new Error("No NodiiContext active. SDK middleware (or `withContext`) must run before this code path.");
29
+ }
30
+ return ctx;
31
+ }
32
+ /**
33
+ * Run `fn` inside a child scope where `ctx` is the active context. Per
34
+ * D41: never mutates the parent scope — child finishes and the parent
35
+ * scope's context is unaffected.
36
+ */
37
+ export function withContext(ctx, fn) {
38
+ return als.run(ctx, fn);
39
+ }
40
+ /**
41
+ * SDK-internal use only. The middleware adapters (§ 5.9) call this on
42
+ * request entry to set the active context. Service code MUST NOT call
43
+ * this directly — use `withContext()` for child scopes only. Not part
44
+ * of the public barrel; subpath adapters import it via the package
45
+ * internal path.
46
+ */
47
+ export function setContextForCurrentScope(ctx) {
48
+ als.enterWith(ctx);
49
+ }
50
+ /**
51
+ * Update the active context's `intent_id` only. Used by the intent
52
+ * lifecycle (§ 5.6) when `intent.begin()` / `intent.complete()` /
53
+ * `intent.abandon()` activate or deactivate an intent. Returns the new
54
+ * context so callers can verify; throws if no context is active (intent
55
+ * lifecycle must run inside an established `NodiiContext`).
56
+ *
57
+ * This is the ONLY mutation API per D41. It exists because intent
58
+ * activation is a per-scope assignment that the intent lifecycle owns,
59
+ * not a service-code mutation.
60
+ */
61
+ export function _setIntentIdInCurrentScope(intentId) {
62
+ const cur = als.getStore();
63
+ if (!cur) {
64
+ throw new Error("Cannot set intent_id: no NodiiContext active. SDK middleware must run before `intent.begin()`.");
65
+ }
66
+ const next = { ...cur, intent_id: intentId };
67
+ als.enterWith(next);
68
+ return next;
69
+ }
70
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,EAAE;AACF,kEAAkE;AAClE,sEAAsE;AACtE,wEAAwE;AACxE,oCAAoC;AACpC,EAAE;AACF,qEAAqE;AACrE,2DAA2D;AAE3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD,MAAM,GAAG,GAAG,IAAI,iBAAiB,EAAgB,CAAC;AAElD;;;;GAIG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,GAAG,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;IAC3B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,2FAA2F,CAC5F,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAI,GAAiB,EAAE,EAAW;IAC3D,OAAO,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAAiB;IACzD,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,0BAA0B,CACxC,QAAuB;IAEvB,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;IAC3B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,gGAAgG,CACjG,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAiB,EAAE,GAAG,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;IAC3D,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACpB,OAAO,IAAI,CAAC;AACd,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,19 @@
1
- export declare const LIB_NAME = "telemetry";
2
- export declare const VERSION = "0.0.1";
1
+ export { initTelemetry } from "./init";
2
+ export { forceFlush, getOtlpAdapterHandle, installOtlpAdapter, shutdownOtlpAdapter, } from "./otlp-adapter";
3
+ export type { OtlpAdapterHandle, OtlpAdapterConfig } from "./otlp-adapter";
4
+ export { logger } from "./logger";
5
+ export { tracer, withSpanAttributes, getSagaTracer } from "./tracer";
6
+ export type { Span, SpanKind, SpanRecord, SpanStatus } from "./tracer";
7
+ export { metrics, MetricVocabularyError } from "./metrics";
8
+ export type { CounterHandle, HistogramHandle, MetricRegistration, } from "./metrics";
9
+ export { audit } from "./audit";
10
+ export type { AuditEvent } from "./audit";
11
+ export { intent, IntentTypeNotRegistered } from "./intent";
12
+ export { agentInvocable, readAgentRegistry, AGENT_INVOCABLE_METADATA, } from "./agent-invocable";
13
+ export { getContext, requireContext, withContext, } from "./context";
14
+ export { isPiiFieldName, redactString, redactFields, PII_FIELD_NAMES, } from "./redaction";
15
+ export { uuidv7, isUuidv7 } from "./uuid";
16
+ export { TelemetryNotInitialized } from "./types";
17
+ export type { ActorType, AgentInvocableAuthTier, AgentInvocableConfig, AgentInvocableIdempotency, AgentInvocableMode, AgentInvocableRateLimit, AgentInvocableSideEffect, AutoInstrumentationConfig, ErrorHandlingConfig, ExportFailureReason, InitTelemetryConfig, IntentTypeDefinition, IntentTypeRegistry, LogEnvelope, LogLevel, MetricFamily, MetricVocabularyEntry, MetricVocabularyRegistry, NodiiContext, ResourceAttributes, SamplingConfig, } from "./types";
18
+ export { DEFAULT_SAMPLING_CONFIG, METRIC_FAMILIES, AGENT_INVOCABLE_SIDE_EFFECTS, } from "./types";
3
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,QAAQ,cAAc,CAAC;AACpC,eAAO,MAAM,OAAO,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACrE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAC3D,YAAY,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,UAAU,EACV,cAAc,EACd,WAAW,GACZ,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC1C,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAClD,YAAY,EACV,SAAS,EACT,sBAAsB,EACtB,oBAAoB,EACpB,yBAAyB,EACzB,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,EACxB,yBAAyB,EACzB,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,qBAAqB,EACrB,wBAAwB,EACxB,YAAY,EACZ,kBAAkB,EAClB,cAAc,GACf,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,uBAAuB,EACvB,eAAe,EACf,4BAA4B,GAC7B,MAAM,SAAS,CAAC"}