@armoriq/sdk-dev 0.8.5 → 0.8.7

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 (55) hide show
  1. package/README.md +2 -30
  2. package/dist/_version.d.ts +1 -1
  3. package/dist/_version.js +1 -1
  4. package/dist/client.d.ts +0 -1
  5. package/dist/client.d.ts.map +1 -1
  6. package/dist/client.js +15 -39
  7. package/dist/client.js.map +1 -1
  8. package/dist/index.d.ts +2 -3
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +4 -5
  11. package/dist/index.js.map +1 -1
  12. package/dist/observability/index.d.ts +5 -3
  13. package/dist/observability/index.d.ts.map +1 -1
  14. package/dist/observability/index.js.map +1 -1
  15. package/dist/observability/otel-runtime.d.ts +54 -4
  16. package/dist/observability/otel-runtime.d.ts.map +1 -1
  17. package/dist/observability/otel-runtime.js +244 -52
  18. package/dist/observability/otel-runtime.js.map +1 -1
  19. package/dist/observability/otel-session.d.ts +23 -4
  20. package/dist/observability/otel-session.d.ts.map +1 -1
  21. package/dist/observability/otel-session.js +51 -24
  22. package/dist/observability/otel-session.js.map +1 -1
  23. package/dist/observability/otel-shutdown.d.ts +1 -1
  24. package/dist/observability/otel-shutdown.d.ts.map +1 -1
  25. package/dist/observability/otel-shutdown.js +1 -13
  26. package/dist/observability/otel-shutdown.js.map +1 -1
  27. package/dist/observability/otel-span-spool.d.ts +55 -0
  28. package/dist/observability/otel-span-spool.d.ts.map +1 -0
  29. package/dist/observability/otel-span-spool.js +94 -0
  30. package/dist/observability/otel-span-spool.js.map +1 -0
  31. package/dist/observability/otel-spool-export.d.ts +64 -0
  32. package/dist/observability/otel-spool-export.d.ts.map +1 -0
  33. package/dist/observability/otel-spool-export.js +279 -0
  34. package/dist/observability/otel-spool-export.js.map +1 -0
  35. package/dist/observability/otlp-http-transport.d.ts +20 -0
  36. package/dist/observability/otlp-http-transport.d.ts.map +1 -0
  37. package/dist/observability/otlp-http-transport.js +141 -0
  38. package/dist/observability/otlp-http-transport.js.map +1 -0
  39. package/dist/observability/policy-lease.d.ts +25 -0
  40. package/dist/observability/policy-lease.d.ts.map +1 -1
  41. package/dist/observability/policy-lease.js +49 -0
  42. package/dist/observability/policy-lease.js.map +1 -1
  43. package/dist/session.d.ts +4 -3
  44. package/dist/session.d.ts.map +1 -1
  45. package/dist/session.js +5 -4
  46. package/dist/session.js.map +1 -1
  47. package/dist/token_usage.d.ts +10 -33
  48. package/dist/token_usage.d.ts.map +1 -1
  49. package/dist/token_usage.js +38 -85
  50. package/dist/token_usage.js.map +1 -1
  51. package/package.json +10 -10
  52. package/dist/session_transcripts.d.ts +0 -8
  53. package/dist/session_transcripts.d.ts.map +0 -1
  54. package/dist/session_transcripts.js +0 -72
  55. package/dist/session_transcripts.js.map +0 -1
@@ -1,8 +1,10 @@
1
1
  /** SDK-owned OpenTelemetry runtime. It never registers a global provider. */
2
- import type { Tracer } from '@opentelemetry/api';
2
+ import { type Span, type SpanOptions, type Tracer } from '@opentelemetry/api';
3
3
  import { type ObservabilityOptions, type ResolvedOtelConfig } from './otel-config';
4
- import { type PolicyLease } from './policy-lease';
4
+ import { type PolicyLease, type PolicyLeaseStore } from './policy-lease';
5
5
  import { type ContentCaptureAuthority } from './content-capture';
6
+ import { type SpanSink } from './otel-span-spool';
7
+ import { type SpooledExportResult } from './otel-spool-export';
6
8
  export type TelemetryAttributeValue = string | number | boolean | string[];
7
9
  export type TelemetryAttributes = Readonly<Record<string, unknown>>;
8
10
  export interface AttributeContext {
@@ -18,7 +20,19 @@ export interface TelemetryRuntimeOptions {
18
20
  options?: ObservabilityOptions;
19
21
  /** Test/host hook; production defaults to the policy-lease HTTP endpoint. */
20
22
  leaseFetcher?: (signal?: AbortSignal) => Promise<PolicyLease>;
23
+ leaseStore?: PolicyLeaseStore;
24
+ /** Takes the place of the OTLP request for the `armoriq` exporter. */
25
+ spanSink?: SpanSink;
21
26
  }
27
+ export interface SpanIds {
28
+ traceId: string;
29
+ spanId: string;
30
+ }
31
+ /**
32
+ * Only a tracer from the runtime's own provider honors the ids; a
33
+ * caller-owned provider assigns its own.
34
+ */
35
+ export declare function startRootSpanWithIds(runtime: ArmorIQTelemetryRuntime, tracer: Tracer, name: string, options: SpanOptions, ids: SpanIds): Span;
22
36
  /**
23
37
  * Manages one private exporter, or borrows a caller-owned provider. A runtime
24
38
  * is request/session scoped and `close()` is idempotent and fail-open.
@@ -29,21 +43,38 @@ export declare class ArmorIQTelemetryRuntime {
29
43
  private readonly apiKey;
30
44
  private readonly sdkVersion;
31
45
  private readonly leaseFetcher?;
46
+ private readonly leaseStore?;
47
+ private readonly spanSink?;
48
+ private readonly leaseBinding;
49
+ private readonly spoolKey;
50
+ private readonly storeAbort;
32
51
  private lease;
33
52
  private refreshing;
34
53
  private firstExternalAuthorityAwaited;
35
54
  private externalAuthorityUnavailable;
36
55
  private nextRefreshAt;
37
56
  private provider;
57
+ private exporter;
38
58
  private policyLeaseAbort;
39
- private readonly ownedAgents;
59
+ private transport;
60
+ private readonly spoolCalls;
61
+ private readonly spoolSlots;
40
62
  private closed;
41
63
  constructor(options: TelemetryRuntimeOptions);
64
+ currentOrFreshSnapshot(): Promise<PolicyLease>;
42
65
  policySnapshot(options?: {
43
66
  awaitFresh?: boolean;
44
67
  }): Promise<PolicyLease>;
68
+ /**
69
+ * Resolves at once while a current lease is held. Otherwise it adopts a
70
+ * current lease from the store, or waits for a lease request: the real
71
+ * lease, or the fallback after a failure or the request's 5 s abort.
72
+ */
73
+ refreshPolicy(): Promise<void>;
74
+ private adoptStoredLease;
45
75
  /** Start at most one control-plane request; request callers never await it. */
46
76
  private refreshInBackground;
77
+ private startRefresh;
47
78
  private applyCeiling;
48
79
  /**
49
80
  * The ceiling-applied lease this runtime already knows about, computed
@@ -78,9 +109,14 @@ export declare class ArmorIQTelemetryRuntime {
78
109
  * time for a disallowed capture mode.
79
110
  */
80
111
  reapplyContentCeiling(snapshot: PolicyLease, attributes: TelemetryAttributes): Record<string, TelemetryAttributeValue>;
112
+ private storeLease;
81
113
  private fetchPolicyLease;
82
114
  tracer(snapshot: PolicyLease): Tracer | null;
83
115
  private privateProvider;
116
+ private resource;
117
+ private pipelineExporter;
118
+ private firstPartyTracesUrl;
119
+ private ownedTransport;
84
120
  /**
85
121
  * Construct the OTLP delegate from explicit values. The convenience
86
122
  * exporter reads process.env while merging defaults, which could leak a
@@ -98,6 +134,20 @@ export declare class ArmorIQTelemetryRuntime {
98
134
  /** Stable, externally safe root span names prevent identifier leakage. */
99
135
  externalTraceName(value: string): string;
100
136
  forceFlush(): Promise<void>;
101
- close(): Promise<void>;
137
+ /** Process-exit drain: a bounded flush that then fails what is still in flight; the runtime stays open. */
138
+ drainForExit(): Promise<void>;
139
+ private drainExports;
140
+ /** The `binding` of every batch this runtime's `spanSink` receives. */
141
+ get spoolBinding(): string;
142
+ /**
143
+ * Ships batches this runtime's `spanSink` wrote, one OTLP request each,
144
+ * under its current authoritative lease. Resolves to one result per entry of
145
+ * `batches`, in order.
146
+ */
147
+ exportSpooled(batches: readonly unknown[]): Promise<SpooledExportResult[]>;
148
+ private openSpoolCall;
149
+ private shipSpooled;
150
+ private shippedAttributes;
151
+ close(deadlineMs?: number): Promise<void>;
102
152
  }
103
153
  //# sourceMappingURL=otel-runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"otel-runtime.d.ts","sourceRoot":"","sources":["../../src/observability/otel-runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"otel-runtime.d.ts","sourceRoot":"","sources":["../../src/observability/otel-runtime.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,8 @@
2
2
  /** SDK-owned OpenTelemetry runtime. It never registers a global provider. */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.ArmorIQTelemetryRuntime = void 0;
5
+ exports.startRootSpanWithIds = startRootSpanWithIds;
6
+ const api_1 = require("@opentelemetry/api");
5
7
  const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
6
8
  const sdk_trace_node_1 = require("@opentelemetry/sdk-trace-node");
7
9
  const resources_1 = require("@opentelemetry/resources");
@@ -13,6 +15,9 @@ const policy_lease_1 = require("./policy-lease");
13
15
  const content_capture_1 = require("./content-capture");
14
16
  const otel_shutdown_1 = require("./otel-shutdown");
15
17
  const otel_export_ceiling_1 = require("./otel-export-ceiling");
18
+ const otlp_http_transport_1 = require("./otlp-http-transport");
19
+ const otel_span_spool_1 = require("./otel-span-spool");
20
+ const otel_spool_export_1 = require("./otel-spool-export");
16
21
  const SENSITIVE_KEY = /(?:api[_-]?key|auth(?:orization)?|bearer|cookie|credential|password|passphrase|secret|private[_-]?key|ssn|card(?:_number)?)/i;
17
22
  const SECRET_TOKEN_KEY = /(?:^|[_.-])token(?:$|[_.-])/i;
18
23
  const USAGE_TOKEN_KEY = /^gen_ai\.usage\.(?:input|output|cache_read|cache_write)_tokens$/;
@@ -37,7 +42,7 @@ const MAX_FINISH_REASONS = 16;
37
42
  const MAX_FINISH_REASON_CANDIDATES = 64;
38
43
  const SENSITIVE_VALUE = /(?:\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b|\b(?:ak|sk|pk|rk|ghp|github_pat)_[A-Za-z0-9_-]{12,}\b|-----BEGIN [A-Z0-9 ]+-----)/i;
39
44
  const EMAIL = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i;
40
- const CONTENT_KEY = /^(?:armoriq\.(?:content\.|observability\.error\.summary$)|gen_ai\.(?:prompt|completion|input\.messages|output\.messages|tool\.(?:args|arguments|result|call\.(?:arguments|result))))$/;
45
+ const CONTENT_KEY = /^(?:armoriq\.(?:content\..*|observability\.error\.summary)|gen_ai\.(?:prompt|completion|input\.messages|output\.messages|tool\.(?:args|arguments|result|call\.(?:arguments|result))))$/;
41
46
  const EXTERNAL_METADATA_ATTRIBUTES = new Set([
42
47
  'gen_ai.request.model',
43
48
  'gen_ai.response.model',
@@ -118,7 +123,46 @@ const EXTERNAL_CONTENT_ATTRIBUTES = new Set([
118
123
  'gen_ai.tool.call.arguments', 'gen_ai.tool.call.result',
119
124
  ]);
120
125
  const CLOSE_DEADLINE_MS = 1_500;
126
+ const EXPORT_CONCURRENCY_LIMIT = 30;
127
+ const MAX_TIMER_MS = 2_147_483_647;
121
128
  const FIRST_EXTERNAL_AUTHORITY_WAIT_MS = 1_400;
129
+ const SPOOL_EXPORT_CONCURRENCY = 8;
130
+ const TRACER_NAME = 'armoriq-sdk';
131
+ class PresetIdGenerator {
132
+ random = new sdk_trace_base_1.RandomIdGenerator();
133
+ preset = null;
134
+ generateTraceId() {
135
+ return this.preset?.traceId ?? this.random.generateTraceId();
136
+ }
137
+ generateSpanId() {
138
+ return this.preset?.spanId ?? this.random.generateSpanId();
139
+ }
140
+ withIds(ids, start) {
141
+ this.preset = ids;
142
+ try {
143
+ return start();
144
+ }
145
+ finally {
146
+ this.preset = null;
147
+ }
148
+ }
149
+ }
150
+ const presetIdGenerators = new WeakMap();
151
+ function presetIdGenerator(runtime) {
152
+ const existing = presetIdGenerators.get(runtime);
153
+ if (existing)
154
+ return existing;
155
+ const created = new PresetIdGenerator();
156
+ presetIdGenerators.set(runtime, created);
157
+ return created;
158
+ }
159
+ /**
160
+ * Only a tracer from the runtime's own provider honors the ids; a
161
+ * caller-owned provider assigns its own.
162
+ */
163
+ function startRootSpanWithIds(runtime, tracer, name, options, ids) {
164
+ return presetIdGenerator(runtime).withIds(ids, () => tracer.startSpan(name, options, api_1.ROOT_CONTEXT));
165
+ }
122
166
  function bounded(promise, timeoutMillis = CLOSE_DEADLINE_MS) {
123
167
  return new Promise((resolve, reject) => {
124
168
  const timer = setTimeout(() => resolve(undefined), timeoutMillis);
@@ -131,6 +175,9 @@ function bounded(promise, timeoutMillis = CLOSE_DEADLINE_MS) {
131
175
  });
132
176
  });
133
177
  }
178
+ function validDeadline(value) {
179
+ return value > 0 ? Math.min(value, MAX_TIMER_MS) : CLOSE_DEADLINE_MS;
180
+ }
134
181
  function cleanString(value, length = 256) {
135
182
  if (SENSITIVE_VALUE.test(value))
136
183
  return '[REDACTED:CREDENTIAL]';
@@ -180,9 +227,27 @@ function safeValue(key, value, external) {
180
227
  function validExternalId(value) {
181
228
  return /^[A-Za-z0-9][A-Za-z0-9._:/@+-]{0,127}$/.test(value) && (0, otel_config_1.sanitizeExternalTelemetryValue)(value, 128) !== undefined;
182
229
  }
230
+ function sessionIdentity(context, external) {
231
+ const identity = {};
232
+ if (external) {
233
+ const sessionId = (0, otel_config_1.sanitizeExternalTelemetryValue)(context.sessionId, 128);
234
+ if (sessionId)
235
+ identity['session.id'] = sessionId;
236
+ if (context.userId && validExternalId(context.userId))
237
+ identity['user.id'] = context.userId;
238
+ return identity;
239
+ }
240
+ const sessionId = safeValue('armoriq.session_id', context.sessionId, false);
241
+ if (sessionId !== undefined)
242
+ identity['armoriq.session_id'] = sessionId;
243
+ return identity;
244
+ }
183
245
  function hasOwn(value, key) {
184
246
  return Object.prototype.hasOwnProperty.call(value, key);
185
247
  }
248
+ function holdsCurrentAuthority(lease) {
249
+ return Boolean(lease?.authoritative && (0, policy_lease_1.isLeaseCurrent)(lease));
250
+ }
186
251
  /**
187
252
  * Manages one private exporter, or borrows a caller-owned provider. A runtime
188
253
  * is request/session scoped and `close()` is idempotent and fail-open.
@@ -193,14 +258,22 @@ class ArmorIQTelemetryRuntime {
193
258
  apiKey;
194
259
  sdkVersion;
195
260
  leaseFetcher;
261
+ leaseStore;
262
+ spanSink;
263
+ leaseBinding;
264
+ spoolKey;
265
+ storeAbort = new AbortController();
196
266
  lease = null;
197
267
  refreshing = null;
198
268
  firstExternalAuthorityAwaited = false;
199
269
  externalAuthorityUnavailable = false;
200
270
  nextRefreshAt = 0;
201
271
  provider = null;
272
+ exporter = null;
202
273
  policyLeaseAbort = null;
203
- ownedAgents = new Set();
274
+ transport = null;
275
+ spoolCalls = new Set();
276
+ spoolSlots = new otel_spool_export_1.ExportSlots(SPOOL_EXPORT_CONCURRENCY);
204
277
  closed = false;
205
278
  constructor(options) {
206
279
  this.config = (0, otel_config_1.resolveOtelConfig)(options.options);
@@ -208,17 +281,24 @@ class ArmorIQTelemetryRuntime {
208
281
  this.apiKey = options.apiKey;
209
282
  this.sdkVersion = options.sdkVersion;
210
283
  this.leaseFetcher = options.leaseFetcher;
284
+ this.leaseStore = options.leaseStore;
285
+ this.spanSink = options.spanSink;
286
+ this.leaseBinding = (0, policy_lease_1.policyLeaseBinding)(this.backendEndpoint, options.apiKey);
287
+ this.spoolKey = { apiKey: options.apiKey, binding: this.leaseBinding };
211
288
  if (this.config.enabled && this.config.exporter !== 'provider') {
212
289
  (0, otel_shutdown_1.registerOwnedTelemetryRuntime)(this);
213
290
  }
214
291
  }
292
+ async currentOrFreshSnapshot() {
293
+ return this.policySnapshot({ awaitFresh: !holdsCurrentAuthority(this.lease) });
294
+ }
215
295
  async policySnapshot(options = {}) {
216
296
  if (!this.config.enabled || this.closed)
217
297
  return Object.freeze({ ...(0, policy_lease_1.fallbackPolicyLease)(), captureMode: 'off' });
218
298
  if (this.externalAuthorityUnavailable && this.config.exporter === 'external') {
219
299
  return Object.freeze({ ...(0, policy_lease_1.fallbackPolicyLease)(), captureMode: 'off' });
220
300
  }
221
- if (!options.awaitFresh && this.lease?.authoritative && (0, policy_lease_1.isLeaseCurrent)(this.lease))
301
+ if (!options.awaitFresh && holdsCurrentAuthority(this.lease))
222
302
  return this.applyCeiling(this.lease);
223
303
  const refresh = this.refreshInBackground(options.awaitFresh === true);
224
304
  const mustAwaitExternalAuthority = this.config.exporter === 'external' && !this.firstExternalAuthorityAwaited;
@@ -242,16 +322,40 @@ class ArmorIQTelemetryRuntime {
242
322
  }
243
323
  return this.applyCeiling(this.lease && (0, policy_lease_1.isLeaseCurrent)(this.lease) ? this.lease : (0, policy_lease_1.fallbackPolicyLease)());
244
324
  }
325
+ /**
326
+ * Resolves at once while a current lease is held. Otherwise it adopts a
327
+ * current lease from the store, or waits for a lease request: the real
328
+ * lease, or the fallback after a failure or the request's 5 s abort.
329
+ */
330
+ async refreshPolicy() {
331
+ if (!this.config.enabled || this.closed || holdsCurrentAuthority(this.lease))
332
+ return;
333
+ await this.adoptStoredLease();
334
+ if (this.closed || holdsCurrentAuthority(this.lease))
335
+ return;
336
+ await (this.refreshing ?? this.startRefresh());
337
+ }
338
+ async adoptStoredLease() {
339
+ if (!this.leaseStore)
340
+ return;
341
+ const stored = await (0, policy_lease_1.readStoredPolicyLease)(this.leaseStore, this.leaseBinding, this.storeAbort.signal);
342
+ if (stored && !holdsCurrentAuthority(this.lease))
343
+ this.lease = stored;
344
+ }
245
345
  /** Start at most one control-plane request; request callers never await it. */
246
346
  refreshInBackground(force = false) {
247
347
  if (this.refreshing)
248
348
  return this.refreshing;
249
349
  if (!force && Date.now() < this.nextRefreshAt)
250
350
  return null;
351
+ return this.startRefresh();
352
+ }
353
+ startRefresh() {
251
354
  this.nextRefreshAt = Date.now() + 1_000;
252
355
  this.refreshing = this.fetchPolicyLease()
253
356
  .then((lease) => {
254
357
  this.lease = lease;
358
+ this.storeLease(lease);
255
359
  return lease;
256
360
  })
257
361
  .finally(() => {
@@ -420,6 +524,11 @@ class ArmorIQTelemetryRuntime {
420
524
  next['armoriq.observability.policy.revision'] = snapshot.revision;
421
525
  return next;
422
526
  }
527
+ storeLease(lease) {
528
+ if (!lease.authoritative || !this.leaseStore)
529
+ return;
530
+ (0, policy_lease_1.writeStoredPolicyLease)(this.leaseStore, lease, this.leaseBinding, this.storeAbort.signal);
531
+ }
423
532
  async fetchPolicyLease() {
424
533
  const controller = new AbortController();
425
534
  this.policyLeaseAbort = controller;
@@ -450,9 +559,9 @@ class ArmorIQTelemetryRuntime {
450
559
  return null;
451
560
  try {
452
561
  if (this.config.exporter === 'provider') {
453
- return this.config.tracerProvider?.getTracer('armoriq-sdk', this.sdkVersion) ?? null;
562
+ return this.config.tracerProvider?.getTracer(TRACER_NAME, this.sdkVersion) ?? null;
454
563
  }
455
- return this.privateProvider()?.getTracer('armoriq-sdk', this.sdkVersion) ?? null;
564
+ return this.privateProvider()?.getTracer(TRACER_NAME, this.sdkVersion) ?? null;
456
565
  }
457
566
  catch {
458
567
  return null;
@@ -461,18 +570,14 @@ class ArmorIQTelemetryRuntime {
461
570
  privateProvider() {
462
571
  if (this.provider || this.closed || !this.config.enabled)
463
572
  return this.provider;
464
- const isExternal = this.config.exporter === 'external';
465
- const url = isExternal ? this.config.endpoint : `${this.backendEndpoint}/v1/traces`;
466
- if (!url)
467
- return null;
468
573
  try {
469
- const exporter = this.createExporter(url, isExternal ? this.config.headers : { 'x-api-key': this.apiKey });
574
+ const exporter = this.pipelineExporter();
575
+ if (!exporter)
576
+ return null;
577
+ this.exporter = exporter;
470
578
  this.provider = new sdk_trace_node_1.NodeTracerProvider({
471
- resource: (0, resources_1.resourceFromAttributes)({
472
- 'service.name': this.config.serviceName,
473
- 'service.version': this.sdkVersion,
474
- ...this.config.resourceAttributes,
475
- }),
579
+ idGenerator: presetIdGenerator(this),
580
+ resource: this.resource(),
476
581
  // A span queued under a permissive lease may still be sitting in
477
582
  // BatchSpanProcessor's in-memory buffer when the lease becomes
478
583
  // stricter (or is revoked). Wrap both the exporter -- the true
@@ -483,7 +588,7 @@ class ArmorIQTelemetryRuntime {
483
588
  // not whatever was authoritative at creation time, governs what
484
589
  // actually ships.
485
590
  spanProcessors: [
486
- new otel_export_ceiling_1.CeilingReapplyingSpanProcessor(new sdk_trace_base_1.BatchSpanProcessor(new otel_export_ceiling_1.CeilingReapplyingSpanExporter(exporter, this), { exportTimeoutMillis: Math.min(this.config.timeoutMillis, CLOSE_DEADLINE_MS) }), this),
591
+ new otel_export_ceiling_1.CeilingReapplyingSpanProcessor(new sdk_trace_base_1.BatchSpanProcessor(new otel_export_ceiling_1.CeilingReapplyingSpanExporter(exporter, this), { exportTimeoutMillis: this.config.timeoutMillis }), this),
487
592
  ],
488
593
  });
489
594
  return this.provider;
@@ -492,28 +597,50 @@ class ArmorIQTelemetryRuntime {
492
597
  return null;
493
598
  }
494
599
  }
600
+ resource() {
601
+ return (0, resources_1.resourceFromAttributes)({
602
+ 'service.name': this.config.serviceName,
603
+ 'service.version': this.sdkVersion,
604
+ ...this.config.resourceAttributes,
605
+ });
606
+ }
607
+ pipelineExporter() {
608
+ const isExternal = this.config.exporter === 'external';
609
+ if (this.spanSink && !isExternal) {
610
+ const sink = new otel_span_spool_1.SpanSinkExporter(this.spanSink, this.spoolKey);
611
+ this.transport = sink;
612
+ return sink;
613
+ }
614
+ const url = isExternal ? this.config.endpoint : this.firstPartyTracesUrl();
615
+ if (!url)
616
+ return null;
617
+ const headers = isExternal ? this.config.headers : { 'x-api-key': this.apiKey };
618
+ const transport = this.ownedTransport(url, headers);
619
+ this.transport = transport;
620
+ return this.createExporter(url, transport, this.config.timeoutMillis);
621
+ }
622
+ firstPartyTracesUrl() {
623
+ return `${this.backendEndpoint}/v1/traces`;
624
+ }
625
+ ownedTransport(url, headers) {
626
+ // The wire serializer is protobuf. Content type is SDK-owned and must
627
+ // be set after arbitrary caller headers.
628
+ return new otlp_http_transport_1.OwnedOtlpHttpTransport(new URL(url), {
629
+ ...headers,
630
+ 'content-type': 'application/x-protobuf',
631
+ });
632
+ }
495
633
  /**
496
634
  * Construct the OTLP delegate from explicit values. The convenience
497
635
  * exporter reads process.env while merging defaults, which could leak a
498
636
  * caller's external headers into an ArmorIQ-only exporter.
499
637
  */
500
- createExporter(url, headers) {
501
- const agentFactory = async (protocol) => {
502
- const { Agent } = protocol === 'http:' ? await import('node:http') : await import('node:https');
503
- const agent = new Agent({ keepAlive: false });
504
- this.ownedAgents.add(agent);
505
- return agent;
506
- };
507
- return new otlp_exporter_base_1.OTLPExporterBase((0, node_http_1.createOtlpHttpExportDelegate)({
508
- url,
509
- // The wire serializer is protobuf. Content type is SDK-owned and must
510
- // be set after arbitrary caller headers.
511
- headers: () => ({ ...headers, 'content-type': 'application/x-protobuf' }),
512
- timeoutMillis: Math.min(this.config.timeoutMillis, CLOSE_DEADLINE_MS),
513
- concurrencyLimit: 1,
638
+ createExporter(url, transport, timeoutMillis) {
639
+ return new otlp_exporter_base_1.OTLPExporterBase((0, otlp_exporter_base_1.createOtlpNetworkExportDelegate)({
640
+ timeoutMillis,
641
+ concurrencyLimit: EXPORT_CONCURRENCY_LIMIT,
514
642
  compression: 'none',
515
- agentFactory,
516
- }, otlp_transformer_1.ProtobufTraceSerializer));
643
+ }, otlp_transformer_1.ProtobufTraceSerializer, (0, node_http_1.createOtlpHttpExporterMetrics)('otlp_http_span_exporter', otlp_transformer_1.TraceExporterMetricsHelper, url, undefined), transport));
517
644
  }
518
645
  safeAttributes(snapshot, attributes, context) {
519
646
  return this.safeAttributesWithAuthority(snapshot, attributes, context, false);
@@ -566,14 +693,7 @@ class ArmorIQTelemetryRuntime {
566
693
  }
567
694
  result['armoriq.observability.capture.mode'] = snapshot.captureMode;
568
695
  result['armoriq.observability.policy.revision'] = snapshot.revision;
569
- if (external) {
570
- const sessionId = (0, otel_config_1.sanitizeExternalTelemetryValue)(context.sessionId, 128);
571
- if (sessionId)
572
- result['session.id'] = sessionId;
573
- if (context.userId && validExternalId(context.userId))
574
- result['user.id'] = context.userId;
575
- }
576
- return result;
696
+ return Object.assign(result, sessionIdentity(context, external));
577
697
  }
578
698
  externalAttributeAllowed(key, mode) {
579
699
  if (EXTERNAL_METADATA_ATTRIBUTES.has(key) || EXTERNAL_POLICY_METADATA_ATTRIBUTES.has(key))
@@ -640,39 +760,111 @@ class ArmorIQTelemetryRuntime {
640
760
  if (this.config.exporter === 'provider')
641
761
  return;
642
762
  try {
643
- await bounded(this.provider?.forceFlush() ?? Promise.resolve());
763
+ await bounded(this.drainExports(this.provider?.forceFlush(), this.exporter));
644
764
  }
645
765
  catch {
646
766
  // Export failure is intentionally fail-open.
647
767
  }
648
768
  }
649
- async close() {
769
+ /** Process-exit drain: a bounded flush that then fails what is still in flight; the runtime stays open. */
770
+ async drainForExit() {
771
+ await this.forceFlush();
772
+ this.transport?.abandon(false);
773
+ }
774
+ async drainExports(batches, exporter) {
775
+ await batches?.catch(() => undefined);
776
+ await exporter?.forceFlush?.();
777
+ }
778
+ /** The `binding` of every batch this runtime's `spanSink` receives. */
779
+ get spoolBinding() {
780
+ return this.leaseBinding;
781
+ }
782
+ /**
783
+ * Ships batches this runtime's `spanSink` wrote, one OTLP request each,
784
+ * under its current authoritative lease. Resolves to one result per entry of
785
+ * `batches`, in order.
786
+ */
787
+ async exportSpooled(batches) {
788
+ const parsed = await (0, otel_spool_export_1.parseSpooledSpanBatches)(batches, {
789
+ key: this.spoolKey,
790
+ resource: this.resource(),
791
+ scope: { name: TRACER_NAME, version: this.sdkVersion },
792
+ });
793
+ const available = this.config.enabled && !this.closed && this.config.exporter === 'armoriq';
794
+ const shippable = available && parsed.some((entry) => 'spans' in entry);
795
+ const call = shippable && this.openSpoolCall();
796
+ if (!call)
797
+ return (0, otel_spool_export_1.settleShippable)(parsed, otel_spool_export_1.SPOOL_UNAVAILABLE);
798
+ try {
799
+ await (0, otel_spool_export_1.untilAborted)(this.refreshPolicy(), call.signal);
800
+ const ship = (spans) => this.shipSpooled(spans, call);
801
+ return await (0, otel_spool_export_1.exportSpooledBatches)(parsed, ship, this.spoolSlots, call.signal);
802
+ }
803
+ finally {
804
+ call.end();
805
+ this.spoolCalls.delete(call);
806
+ }
807
+ }
808
+ openSpoolCall() {
809
+ const url = this.firstPartyTracesUrl();
810
+ let transport;
811
+ try {
812
+ transport = this.ownedTransport(url, { 'x-api-key': this.apiKey });
813
+ }
814
+ catch {
815
+ return null;
816
+ }
817
+ const exporter = this.createExporter(url, transport, this.config.timeoutMillis);
818
+ const call = new otel_spool_export_1.SpoolExportCall(transport, exporter);
819
+ this.spoolCalls.add(call);
820
+ return call;
821
+ }
822
+ shipSpooled(spans, call) {
823
+ if (!holdsCurrentAuthority(this.lease))
824
+ return Promise.resolve(otel_spool_export_1.SPOOL_POLICY_UNAVAILABLE);
825
+ const snapshot = this.applyCeiling(this.lease);
826
+ if (snapshot.captureMode === 'off')
827
+ return Promise.resolve(otel_spool_export_1.SPOOL_DISCARDED);
828
+ const project = (span) => ({
829
+ ...span,
830
+ attributes: this.shippedAttributes(snapshot, span.attributes),
831
+ });
832
+ return call.send(spans.map(project));
833
+ }
834
+ shippedAttributes(snapshot, attributes) {
835
+ const governed = this.reapplyContentCeiling(snapshot, attributes);
836
+ Object.assign(governed, (0, content_capture_1.trustedContentDiagnostics)(attributes, governed));
837
+ const shipped = {};
838
+ for (const key of Object.keys(attributes))
839
+ if (hasOwn(governed, key))
840
+ shipped[key] = governed[key];
841
+ return Object.assign(shipped, governed);
842
+ }
843
+ async close(deadlineMs = CLOSE_DEADLINE_MS) {
650
844
  if (this.closed)
651
845
  return;
652
846
  this.closed = true;
653
847
  (0, otel_shutdown_1.unregisterOwnedTelemetryRuntime)(this);
848
+ this.storeAbort.abort();
654
849
  this.policyLeaseAbort?.abort();
655
850
  this.policyLeaseAbort = null;
851
+ this.spoolCalls.forEach((call) => call.abort(otel_spool_export_1.SPOOL_UNAVAILABLE));
656
852
  if (this.config.exporter === 'provider')
657
853
  return;
658
854
  const provider = this.provider;
855
+ const exporter = this.exporter;
856
+ const transport = this.transport;
659
857
  this.provider = null;
858
+ this.exporter = null;
859
+ this.transport = null;
660
860
  try {
661
- await bounded(provider?.shutdown() ?? Promise.resolve());
861
+ await bounded(this.drainExports(provider?.shutdown(), exporter), validDeadline(deadlineMs));
662
862
  }
663
863
  catch {
664
864
  // Export failure is intentionally fail-open.
665
865
  }
666
866
  finally {
667
- for (const agent of this.ownedAgents) {
668
- try {
669
- agent.destroy();
670
- }
671
- catch {
672
- // Agents are SDK-owned and are best-effort only.
673
- }
674
- }
675
- this.ownedAgents.clear();
867
+ transport?.abandon(true);
676
868
  }
677
869
  }
678
870
  }