@armoriq/sdk-dev 0.6.10 → 0.8.0

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 (96) hide show
  1. package/README.md +168 -1
  2. package/dist/_version.d.ts +1 -1
  3. package/dist/_version.d.ts.map +1 -1
  4. package/dist/_version.js +1 -1
  5. package/dist/_version.js.map +1 -1
  6. package/dist/cli/commands/auth.d.ts +12 -0
  7. package/dist/cli/commands/auth.d.ts.map +1 -1
  8. package/dist/cli/commands/auth.js +522 -49
  9. package/dist/cli/commands/auth.js.map +1 -1
  10. package/dist/client.d.ts +8 -15
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +20 -18
  13. package/dist/client.js.map +1 -1
  14. package/dist/config.d.ts +0 -17
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +1 -19
  17. package/dist/config.js.map +1 -1
  18. package/dist/index.d.ts +3 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +8 -14
  21. package/dist/index.js.map +1 -1
  22. package/dist/integrations/google_adk.d.ts +155 -7
  23. package/dist/integrations/google_adk.d.ts.map +1 -1
  24. package/dist/integrations/google_adk.js +727 -46
  25. package/dist/integrations/google_adk.js.map +1 -1
  26. package/dist/integrations/langchain.d.ts +48 -2
  27. package/dist/integrations/langchain.d.ts.map +1 -1
  28. package/dist/integrations/langchain.js +528 -33
  29. package/dist/integrations/langchain.js.map +1 -1
  30. package/dist/integrations/strands.d.ts +65 -1
  31. package/dist/integrations/strands.d.ts.map +1 -1
  32. package/dist/integrations/strands.js +456 -36
  33. package/dist/integrations/strands.js.map +1 -1
  34. package/dist/models.d.ts +2 -2
  35. package/dist/models.d.ts.map +1 -1
  36. package/dist/observability/content-capture.d.ts +103 -0
  37. package/dist/observability/content-capture.d.ts.map +1 -0
  38. package/dist/observability/content-capture.js +423 -0
  39. package/dist/observability/content-capture.js.map +1 -0
  40. package/dist/observability/index.d.ts +6 -7
  41. package/dist/observability/index.d.ts.map +1 -1
  42. package/dist/observability/index.js +18 -27
  43. package/dist/observability/index.js.map +1 -1
  44. package/dist/observability/otel-config.d.ts +47 -0
  45. package/dist/observability/otel-config.d.ts.map +1 -0
  46. package/dist/observability/otel-config.js +268 -0
  47. package/dist/observability/otel-config.js.map +1 -0
  48. package/dist/observability/otel-export-ceiling.d.ts +96 -0
  49. package/dist/observability/otel-export-ceiling.d.ts.map +1 -0
  50. package/dist/observability/otel-export-ceiling.js +271 -0
  51. package/dist/observability/otel-export-ceiling.js.map +1 -0
  52. package/dist/observability/otel-runtime.d.ts +103 -0
  53. package/dist/observability/otel-runtime.d.ts.map +1 -0
  54. package/dist/observability/otel-runtime.js +680 -0
  55. package/dist/observability/otel-runtime.js.map +1 -0
  56. package/dist/observability/otel-session.d.ts +168 -0
  57. package/dist/observability/otel-session.d.ts.map +1 -0
  58. package/dist/observability/otel-session.js +630 -0
  59. package/dist/observability/otel-session.js.map +1 -0
  60. package/dist/observability/otel-shutdown.d.ts +17 -0
  61. package/dist/observability/otel-shutdown.d.ts.map +1 -0
  62. package/dist/observability/otel-shutdown.js +54 -0
  63. package/dist/observability/otel-shutdown.js.map +1 -0
  64. package/dist/observability/policy-lease.d.ts +22 -0
  65. package/dist/observability/policy-lease.d.ts.map +1 -0
  66. package/dist/observability/policy-lease.js +102 -0
  67. package/dist/observability/policy-lease.js.map +1 -0
  68. package/dist/plan_builder.d.ts +5 -4
  69. package/dist/plan_builder.d.ts.map +1 -1
  70. package/dist/plan_builder.js +14 -15
  71. package/dist/plan_builder.js.map +1 -1
  72. package/dist/session.d.ts +61 -93
  73. package/dist/session.d.ts.map +1 -1
  74. package/dist/session.js +388 -804
  75. package/dist/session.js.map +1 -1
  76. package/dist/token_usage.d.ts +11 -18
  77. package/dist/token_usage.d.ts.map +1 -1
  78. package/dist/token_usage.js +29 -94
  79. package/dist/token_usage.js.map +1 -1
  80. package/dist/tool_name.d.ts +18 -0
  81. package/dist/tool_name.d.ts.map +1 -0
  82. package/dist/tool_name.js +29 -0
  83. package/dist/tool_name.js.map +1 -0
  84. package/dist/tool_push.d.ts +28 -0
  85. package/dist/tool_push.d.ts.map +1 -0
  86. package/dist/tool_push.js +151 -0
  87. package/dist/tool_push.js.map +1 -0
  88. package/dist/tool_registry.d.ts +100 -0
  89. package/dist/tool_registry.d.ts.map +1 -0
  90. package/dist/tool_registry.js +440 -0
  91. package/dist/tool_registry.js.map +1 -0
  92. package/dist/tool_schema.d.ts +22 -0
  93. package/dist/tool_schema.d.ts.map +1 -0
  94. package/dist/tool_schema.js +163 -0
  95. package/dist/tool_schema.js.map +1 -0
  96. package/package.json +12 -7
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ /**
3
+ * Immutable, fail-safe OpenTelemetry configuration.
4
+ *
5
+ * The public contract intentionally follows standard OTLP environment
6
+ * variables. It never identifies or configures a specific external vendor.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.CAPTURE_RANK = void 0;
10
+ exports.sanitizeExternalTelemetryValue = sanitizeExternalTelemetryValue;
11
+ exports.resolveOtelConfig = resolveOtelConfig;
12
+ exports.lowerCaptureMode = lowerCaptureMode;
13
+ const CAPTURE_MODES = new Set(['off', 'metadata', 'enhanced', 'debug']);
14
+ const EXPORTERS = new Set(['armoriq', 'external', 'provider']);
15
+ const SAFE_RESOURCE_KEYS = new Set([
16
+ 'service.version',
17
+ 'service.namespace',
18
+ 'service.instance.id',
19
+ 'deployment.environment.name',
20
+ 'telemetry.sdk.name',
21
+ 'telemetry.sdk.version',
22
+ ]);
23
+ const HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
24
+ const RESERVED_TRANSPORT_HEADERS = new Set([
25
+ 'connection', 'content-length', 'content-type', 'host', 'keep-alive',
26
+ 'proxy-authenticate', 'proxy-authorization', 'te', 'trailer',
27
+ 'transfer-encoding', 'upgrade',
28
+ ]);
29
+ 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;
30
+ const EMAIL = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i;
31
+ const DEFAULT_TIMEOUT_MS = 10_000;
32
+ /**
33
+ * Numeric personal identifiers are unsafe even when their attribute name is
34
+ * harmless. Match phone-length runs, the SSN shape, and payment-card-length
35
+ * runs rather than relying on caller-provided field names.
36
+ */
37
+ function hasNumericPersonalIdentifier(value) {
38
+ if (/(?:^|\D)\d{3}[- ]?\d{2}[- ]?\d{4}(?:$|\D)/.test(value))
39
+ return true;
40
+ const candidates = value.match(/\+?\d(?:[ .()-]*\d){8,18}/g) ?? [];
41
+ return candidates.some((candidate) => {
42
+ const digits = candidate.replace(/\D/g, '');
43
+ return digits.length >= 10 && digits.length <= 15
44
+ || (digits.length >= 13 && digits.length <= 19 && passesLuhn(digits));
45
+ });
46
+ }
47
+ function passesLuhn(digits) {
48
+ let sum = 0;
49
+ let alternate = false;
50
+ for (let index = digits.length - 1; index >= 0; index -= 1) {
51
+ let digit = Number(digits[index]);
52
+ if (alternate && (digit *= 2) > 9)
53
+ digit -= 9;
54
+ sum += digit;
55
+ alternate = !alternate;
56
+ }
57
+ return sum % 10 === 0;
58
+ }
59
+ /** Returns an externally safe value, or no value when it could identify a person. */
60
+ function sanitizeExternalTelemetryValue(value, maxLength = 256) {
61
+ const trimmed = value.trim();
62
+ if (!trimmed || /[\r\n\0]/.test(trimmed) || SENSITIVE_VALUE.test(trimmed) || EMAIL.test(trimmed) || hasNumericPersonalIdentifier(trimmed))
63
+ return undefined;
64
+ return trimmed.slice(0, maxLength);
65
+ }
66
+ function env(name) {
67
+ const value = process.env[name]?.trim();
68
+ return value || undefined;
69
+ }
70
+ function isDisabledByEnvironment() {
71
+ return ['0', 'false', 'off', 'no', 'disabled'].includes((env('ARMORIQ_OBSERVABILITY') ?? '').toLowerCase())
72
+ || (env('OTEL_SDK_DISABLED') ?? '').toLowerCase() === 'true'
73
+ || (env('OTEL_TRACES_EXPORTER') ?? '').toLowerCase() === 'none';
74
+ }
75
+ function captureMode(value, fallback) {
76
+ const normalized = String(value ?? '').trim().toLowerCase();
77
+ return CAPTURE_MODES.has(normalized) ? normalized : fallback;
78
+ }
79
+ function endpointConfigured(options) {
80
+ return Boolean(options.endpoint?.trim() || options.baseEndpoint?.trim() || env('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT') || env('OTEL_EXPORTER_OTLP_ENDPOINT') || env('ARMORIQ_OTEL_ENDPOINT'));
81
+ }
82
+ function exporter(options) {
83
+ if (options.exporter)
84
+ return EXPORTERS.has(options.exporter) ? options.exporter : 'armoriq';
85
+ const configured = env('ARMORIQ_OTEL_EXPORTER')?.toLowerCase();
86
+ if (configured && EXPORTERS.has(configured))
87
+ return configured;
88
+ return endpointConfigured(options) ? 'external' : 'armoriq';
89
+ }
90
+ /**
91
+ * `http:` carries configured authorization headers and lease-authorized
92
+ * enhanced/debug content in plaintext, so it is only safe for a collector
93
+ * running on the same machine — never an arbitrary remote host. Remote
94
+ * endpoints must use `https:`.
95
+ */
96
+ function isLoopbackHostname(hostname) {
97
+ const normalized = hostname.replace(/^\[|\]$/g, '').toLowerCase();
98
+ return normalized === 'localhost' || normalized === '127.0.0.1' || normalized === '::1';
99
+ }
100
+ function validateUrl(value) {
101
+ try {
102
+ const url = new URL(value);
103
+ if ((url.protocol !== 'https:' && url.protocol !== 'http:') || url.username || url.password || url.search || url.hash)
104
+ return undefined;
105
+ if (url.protocol === 'http:' && !isLoopbackHostname(url.hostname))
106
+ return undefined;
107
+ return url.toString().replace(/\/$/, '');
108
+ }
109
+ catch {
110
+ return undefined;
111
+ }
112
+ }
113
+ function appendTracePath(value) {
114
+ const endpoint = validateUrl(value);
115
+ if (!endpoint)
116
+ return undefined;
117
+ const url = new URL(endpoint);
118
+ const pathname = url.pathname.replace(/\/+$/, '');
119
+ url.pathname = pathname.endsWith('/v1/traces') ? pathname : `${pathname}/v1/traces`;
120
+ return url.toString().replace(/\/$/, '');
121
+ }
122
+ function resolveEndpoint(options) {
123
+ if (options.endpoint?.trim())
124
+ return validateUrl(options.endpoint.trim());
125
+ if (options.baseEndpoint?.trim())
126
+ return appendTracePath(options.baseEndpoint.trim());
127
+ const tracesEndpoint = env('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT');
128
+ if (tracesEndpoint)
129
+ return validateUrl(tracesEndpoint);
130
+ const baseEndpoint = env('OTEL_EXPORTER_OTLP_ENDPOINT');
131
+ if (baseEndpoint)
132
+ return appendTracePath(baseEndpoint);
133
+ const convenienceEndpoint = env('ARMORIQ_OTEL_ENDPOINT');
134
+ return convenienceEndpoint ? validateUrl(convenienceEndpoint) : undefined;
135
+ }
136
+ function decodeHeaderValue(value) {
137
+ try {
138
+ const decoded = decodeURIComponent(value.trim());
139
+ return decoded && !/[\r\n\0]/.test(decoded) ? decoded : undefined;
140
+ }
141
+ catch {
142
+ return undefined;
143
+ }
144
+ }
145
+ function parseHeaderString(value) {
146
+ if (!value)
147
+ return {};
148
+ const parsed = {};
149
+ for (const pair of value.split(',')) {
150
+ const separator = pair.indexOf('=');
151
+ if (separator <= 0)
152
+ return undefined;
153
+ const key = pair.slice(0, separator).trim();
154
+ const decoded = decodeHeaderValue(pair.slice(separator + 1));
155
+ if (!HEADER_NAME.test(key) || decoded === undefined)
156
+ return undefined;
157
+ if (!RESERVED_TRANSPORT_HEADERS.has(key.toLowerCase()))
158
+ parsed[key.toLowerCase()] = decoded;
159
+ }
160
+ return parsed;
161
+ }
162
+ function parseHeaderObject(value) {
163
+ if (!value)
164
+ return {};
165
+ const parsed = {};
166
+ for (const [key, raw] of Object.entries(value)) {
167
+ if (!HEADER_NAME.test(key) || typeof raw !== 'string' || !raw || /[\r\n\0]/.test(raw))
168
+ return undefined;
169
+ if (!RESERVED_TRANSPORT_HEADERS.has(key.toLowerCase()))
170
+ parsed[key.toLowerCase()] = raw;
171
+ }
172
+ return parsed;
173
+ }
174
+ function resolveHeaders(options) {
175
+ const general = parseHeaderString(env('OTEL_EXPORTER_OTLP_HEADERS'));
176
+ const traces = parseHeaderString(env('OTEL_EXPORTER_OTLP_TRACES_HEADERS'));
177
+ const explicit = parseHeaderObject(options.headers);
178
+ if (!general || !traces || !explicit)
179
+ return undefined;
180
+ return Object.freeze({ ...general, ...traces, ...explicit });
181
+ }
182
+ function resolveProtocol(options) {
183
+ const value = options.protocol ?? env('OTEL_EXPORTER_OTLP_TRACES_PROTOCOL') ?? env('OTEL_EXPORTER_OTLP_PROTOCOL') ?? 'http/protobuf';
184
+ return String(value).trim().toLowerCase() === 'http/protobuf' ? 'http/protobuf' : undefined;
185
+ }
186
+ function parseTimeout(value) {
187
+ if (value === undefined || value === null || value === '')
188
+ return DEFAULT_TIMEOUT_MS;
189
+ const parsed = typeof value === 'number' ? value : Number(String(value).trim());
190
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
191
+ }
192
+ function resolveTimeout(options) {
193
+ return parseTimeout(options.timeoutMillis ?? env('OTEL_EXPORTER_OTLP_TRACES_TIMEOUT') ?? env('OTEL_EXPORTER_OTLP_TIMEOUT'));
194
+ }
195
+ function safeServiceName(value) {
196
+ const trimmed = value?.trim() ?? '';
197
+ return /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/.test(trimmed) && sanitizeExternalTelemetryValue(trimmed, 128)
198
+ ? trimmed
199
+ : 'armoriq-sdk';
200
+ }
201
+ function parseResources(value) {
202
+ if (!value)
203
+ return {};
204
+ const entries = typeof value === 'string'
205
+ ? value.split(',').map((pair) => {
206
+ const separator = pair.indexOf('=');
207
+ return separator <= 0 ? undefined : [pair.slice(0, separator).trim(), pair.slice(separator + 1).trim()];
208
+ })
209
+ : Object.entries(value);
210
+ const parsed = {};
211
+ for (const entry of entries) {
212
+ if (!entry)
213
+ continue;
214
+ const [key, raw] = entry;
215
+ const value = typeof raw === 'string' ? sanitizeExternalTelemetryValue(raw) : undefined;
216
+ if (!SAFE_RESOURCE_KEYS.has(key) || !value)
217
+ continue;
218
+ parsed[key] = value;
219
+ }
220
+ return parsed;
221
+ }
222
+ function resolveResources(options) {
223
+ const environmental = parseResources(env('OTEL_RESOURCE_ATTRIBUTES'));
224
+ const explicit = parseResources(options.resourceAttributes);
225
+ return Object.freeze(environmental && explicit ? { ...environmental, ...explicit } : {});
226
+ }
227
+ /** Resolve without network I/O. No returned object is mutable by a caller. */
228
+ function resolveOtelConfig(options = {}) {
229
+ const resolvedExporter = exporter(options);
230
+ const endpoint = resolvedExporter === 'external' ? resolveEndpoint(options) : undefined;
231
+ const headers = resolveHeaders(options);
232
+ const protocol = resolveProtocol(options);
233
+ const timeoutMillis = resolveTimeout(options);
234
+ const serviceName = safeServiceName(options.serviceName ?? env('OTEL_SERVICE_NAME'));
235
+ const base = {
236
+ enabled: options.enabled !== false && !isDisabledByEnvironment(),
237
+ exporter: resolvedExporter,
238
+ endpoint,
239
+ headers: headers ?? Object.freeze({}),
240
+ protocol: protocol ?? 'http/protobuf',
241
+ timeoutMillis: timeoutMillis ?? DEFAULT_TIMEOUT_MS,
242
+ resourceAttributes: resolveResources(options),
243
+ tracerProvider: options.tracerProvider,
244
+ captureMode: captureMode(options.captureMode ?? env('ARMORIQ_OTEL_CAPTURE_MODE') ?? 'debug', 'debug'),
245
+ serviceName,
246
+ };
247
+ if (resolvedExporter === 'external' && !endpoint)
248
+ return Object.freeze({ ...base, enabled: false, disabledReason: 'external endpoint is required' });
249
+ if (resolvedExporter === 'external' && !headers)
250
+ return Object.freeze({ ...base, enabled: false, disabledReason: 'invalid OTLP headers' });
251
+ if (resolvedExporter === 'external' && !protocol)
252
+ return Object.freeze({ ...base, enabled: false, disabledReason: 'only http/protobuf OTLP is supported' });
253
+ if (resolvedExporter === 'external' && !timeoutMillis)
254
+ return Object.freeze({ ...base, enabled: false, disabledReason: 'invalid OTLP timeout' });
255
+ if (resolvedExporter === 'provider' && !options.tracerProvider)
256
+ return Object.freeze({ ...base, enabled: false, disabledReason: 'tracerProvider is required' });
257
+ return Object.freeze(base);
258
+ }
259
+ exports.CAPTURE_RANK = Object.freeze({
260
+ off: 0,
261
+ metadata: 1,
262
+ enhanced: 2,
263
+ debug: 3,
264
+ });
265
+ function lowerCaptureMode(left, right) {
266
+ return exports.CAPTURE_RANK[left] <= exports.CAPTURE_RANK[right] ? left : right;
267
+ }
268
+ //# sourceMappingURL=otel-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel-config.js","sourceRoot":"","sources":["../../src/observability/otel-config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Export-time capture-mode ceiling re-application for buffered spans.
3
+ *
4
+ * `ArmorIQTelemetryRuntime.safeAttributes` (otel-runtime.ts) already governs
5
+ * what a span is *allowed to receive* at the moment its attributes are set,
6
+ * using whatever policy lease was current at that instant. That is not
7
+ * enough on its own: `BatchSpanProcessor` buffers ended spans in memory and
8
+ * ships them later, on a timer or when the queue fills. A span populated
9
+ * with rich content under an `enhanced`/`debug` lease can still be sitting
10
+ * in that buffer, un-exported, when the organization's lease becomes
11
+ * stricter -- because the policy changed, or because a refresh came back
12
+ * lower after the previous lease expired. Without a check at the moment
13
+ * spans actually leave the process, the stale, too-permissive attributes
14
+ * baked onto the span at creation time would still ship, even though the
15
+ * CURRENT lease no longer authorizes them.
16
+ *
17
+ * This module closes that gap at two points, both delegating to the exact
18
+ * same governance the runtime uses at span-creation time
19
+ * (`ArmorIQTelemetryRuntime.currentCeilingSnapshot` /
20
+ * `reapplyContentCeiling`, which in turn shares `contentAuthority` with
21
+ * `safeAttributesWithAuthority`), so there is exactly one place that defines
22
+ * "what counts as content" and "what survives a given capture mode":
23
+ *
24
+ * - `CeilingReapplyingSpanExporter` wraps the real span exporter. Its
25
+ * `export` re-reads the current policy snapshot immediately before
26
+ * handing spans to the wrapped exporter -- this runs whenever the
27
+ * batching processor actually flushes, so it catches a span regardless of
28
+ * how long it sat queued.
29
+ * - `CeilingReapplyingSpanProcessor` wraps the real span processor. Its
30
+ * `onEnd` performs the same re-check at the moment a span is handed off
31
+ * for export, closing the narrower window between when a long-running
32
+ * operation's attributes were set and when `span.end()` is finally
33
+ * called.
34
+ *
35
+ * Both are no-ops when the current snapshot still authorizes everything
36
+ * already on the span, so a session whose lease never changes exports
37
+ * exactly what it would have exported before this module existed.
38
+ *
39
+ * This is a privacy boundary, so re-governance fails CLOSED in two tiers
40
+ * rather than fail-open. A policy-lookup or re-governance failure used to
41
+ * leave a span exactly as it was, after which it would export with content
42
+ * the current lease may have revoked. Instead:
43
+ *
44
+ * 1. Replace the attributes with the same withheld-content markers
45
+ * `projectTelemetryContent` produces for a disallowed capture mode. This
46
+ * keeps the span itself -- name, timing, status, parent/child structure
47
+ * -- which is the point of the telemetry, while guaranteeing no governed
48
+ * content ships.
49
+ * 2. Only if even that replacement cannot be written is the span dropped
50
+ * outright: the exporter filters it out and the processor never queues
51
+ * it. A batch left empty by privacy drops reports `SUCCESS`, not
52
+ * `FAILED` -- the drop is a deliberate, permanent verdict, and `FAILED`
53
+ * would have `BatchSpanProcessor` treat it as a retryable transport
54
+ * problem.
55
+ */
56
+ import type { Context } from '@opentelemetry/api';
57
+ import type { ExportResult } from '@opentelemetry/core';
58
+ import type { ReadableSpan, Span, SpanExporter, SpanProcessor } from '@opentelemetry/sdk-trace-base';
59
+ import type { ArmorIQTelemetryRuntime } from './otel-runtime';
60
+ /**
61
+ * Re-govern span attributes against the CURRENT lease before shipping.
62
+ *
63
+ * This is the true export-time gate: `export` runs whenever the wrapped
64
+ * batching processor actually flushes its buffer, so a span that has been
65
+ * queued for any length of time is re-evaluated against the lease that is
66
+ * authoritative right now, not the one that was authoritative when it was
67
+ * created or ended.
68
+ */
69
+ export declare class CeilingReapplyingSpanExporter implements SpanExporter {
70
+ private readonly exporter;
71
+ private readonly runtime;
72
+ constructor(exporter: SpanExporter, runtime: ArmorIQTelemetryRuntime);
73
+ export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
74
+ shutdown(): Promise<void>;
75
+ forceFlush(): Promise<void>;
76
+ }
77
+ /**
78
+ * Re-govern span attributes at the `onEnd` hand-off boundary.
79
+ *
80
+ * Complements `CeilingReapplyingSpanExporter`: this closes the window
81
+ * between when a span's attributes were last set (which can be well before
82
+ * a long-running operation actually calls `span.end()`) and when it is
83
+ * handed to the wrapped processor for batching. `shutdown`/`forceFlush`
84
+ * delegate to the wrapped processor unchanged -- this class only adds a
85
+ * re-governance step, it does not change close/flush lifecycle semantics.
86
+ */
87
+ export declare class CeilingReapplyingSpanProcessor implements SpanProcessor {
88
+ private readonly processor;
89
+ private readonly runtime;
90
+ constructor(processor: SpanProcessor, runtime: ArmorIQTelemetryRuntime);
91
+ onStart(span: Span, parentContext: Context): void;
92
+ onEnd(span: ReadableSpan): void;
93
+ shutdown(): Promise<void>;
94
+ forceFlush(): Promise<void>;
95
+ }
96
+ //# sourceMappingURL=otel-export-ceiling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel-export-ceiling.d.ts","sourceRoot":"","sources":["../../src/observability/otel-export-ceiling.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAErG,OAAO,KAAK,EAAE,uBAAuB,EAA2B,MAAM,gBAAgB,CAAC;AAiIvF;;;;;;;;GAQG;AACH,qBAAa,6BAA8B,YAAW,YAAY;IAE9D,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,uBAAuB;IAGnD,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE,cAAc,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI;IAcnF,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAG5B;AAED;;;;;;;;;GASG;AACH,qBAAa,8BAA+B,YAAW,aAAa;IAEhE,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,uBAAuB;IAGnD,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,GAAG,IAAI;IAIjD,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAU/B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAG5B"}
@@ -0,0 +1,271 @@
1
+ "use strict";
2
+ /**
3
+ * Export-time capture-mode ceiling re-application for buffered spans.
4
+ *
5
+ * `ArmorIQTelemetryRuntime.safeAttributes` (otel-runtime.ts) already governs
6
+ * what a span is *allowed to receive* at the moment its attributes are set,
7
+ * using whatever policy lease was current at that instant. That is not
8
+ * enough on its own: `BatchSpanProcessor` buffers ended spans in memory and
9
+ * ships them later, on a timer or when the queue fills. A span populated
10
+ * with rich content under an `enhanced`/`debug` lease can still be sitting
11
+ * in that buffer, un-exported, when the organization's lease becomes
12
+ * stricter -- because the policy changed, or because a refresh came back
13
+ * lower after the previous lease expired. Without a check at the moment
14
+ * spans actually leave the process, the stale, too-permissive attributes
15
+ * baked onto the span at creation time would still ship, even though the
16
+ * CURRENT lease no longer authorizes them.
17
+ *
18
+ * This module closes that gap at two points, both delegating to the exact
19
+ * same governance the runtime uses at span-creation time
20
+ * (`ArmorIQTelemetryRuntime.currentCeilingSnapshot` /
21
+ * `reapplyContentCeiling`, which in turn shares `contentAuthority` with
22
+ * `safeAttributesWithAuthority`), so there is exactly one place that defines
23
+ * "what counts as content" and "what survives a given capture mode":
24
+ *
25
+ * - `CeilingReapplyingSpanExporter` wraps the real span exporter. Its
26
+ * `export` re-reads the current policy snapshot immediately before
27
+ * handing spans to the wrapped exporter -- this runs whenever the
28
+ * batching processor actually flushes, so it catches a span regardless of
29
+ * how long it sat queued.
30
+ * - `CeilingReapplyingSpanProcessor` wraps the real span processor. Its
31
+ * `onEnd` performs the same re-check at the moment a span is handed off
32
+ * for export, closing the narrower window between when a long-running
33
+ * operation's attributes were set and when `span.end()` is finally
34
+ * called.
35
+ *
36
+ * Both are no-ops when the current snapshot still authorizes everything
37
+ * already on the span, so a session whose lease never changes exports
38
+ * exactly what it would have exported before this module existed.
39
+ *
40
+ * This is a privacy boundary, so re-governance fails CLOSED in two tiers
41
+ * rather than fail-open. A policy-lookup or re-governance failure used to
42
+ * leave a span exactly as it was, after which it would export with content
43
+ * the current lease may have revoked. Instead:
44
+ *
45
+ * 1. Replace the attributes with the same withheld-content markers
46
+ * `projectTelemetryContent` produces for a disallowed capture mode. This
47
+ * keeps the span itself -- name, timing, status, parent/child structure
48
+ * -- which is the point of the telemetry, while guaranteeing no governed
49
+ * content ships.
50
+ * 2. Only if even that replacement cannot be written is the span dropped
51
+ * outright: the exporter filters it out and the processor never queues
52
+ * it. A batch left empty by privacy drops reports `SUCCESS`, not
53
+ * `FAILED` -- the drop is a deliberate, permanent verdict, and `FAILED`
54
+ * would have `BatchSpanProcessor` treat it as a retryable transport
55
+ * problem.
56
+ */
57
+ Object.defineProperty(exports, "__esModule", { value: true });
58
+ exports.CeilingReapplyingSpanProcessor = exports.CeilingReapplyingSpanExporter = void 0;
59
+ const core_1 = require("@opentelemetry/core");
60
+ const content_capture_1 = require("./content-capture");
61
+ /** The reason recorded when even the ceiling itself could not be resolved. */
62
+ const POLICY_UNAVAILABLE_AUTHORITY = {
63
+ mode: 'off',
64
+ external: false,
65
+ contentAllowed: false,
66
+ reasonCode: 'policy_unavailable',
67
+ };
68
+ function hasOwn(value, key) {
69
+ return Object.prototype.hasOwnProperty.call(value, key);
70
+ }
71
+ /**
72
+ * Overwrite an already-ended span's attribute mapping in place.
73
+ *
74
+ * Unlike OpenTelemetry Python's `BoundedAttributes` (which freezes on
75
+ * `span.end()` and requires an unsupported private-field thaw), the Node SDK
76
+ * keeps a completed span's `attributes` as a plain, ordinarily-mutable
77
+ * object -- there is no supported *setter*, but replacing its contents does
78
+ * not require fighting an immutability flag. A test that wants to prove the
79
+ * mutation-failure tier freezes that object (`Object.freeze(span.attributes)`),
80
+ * which makes the `delete`/assign below throw, exactly like a real
81
+ * environment where some other guard made the object non-writable.
82
+ *
83
+ * Returns true when the span now carries exactly `attributes`.
84
+ */
85
+ function writeAttributes(span, attributes) {
86
+ try {
87
+ const target = span;
88
+ if (typeof target.attributes !== 'object' || target.attributes === null)
89
+ return false;
90
+ for (const key of Object.keys(target.attributes))
91
+ delete target.attributes[key];
92
+ Object.assign(target.attributes, attributes);
93
+ return true;
94
+ }
95
+ catch {
96
+ return false;
97
+ }
98
+ }
99
+ function attributesEqual(a, b) {
100
+ const aKeys = Object.keys(a);
101
+ if (aKeys.length !== Object.keys(b).length)
102
+ return false;
103
+ return aKeys.every((key) => hasOwn(b, key) && Object.is(a[key], b[key]));
104
+ }
105
+ /**
106
+ * Replace a span's attributes with the known-safe withheld marker set.
107
+ *
108
+ * Used when the ceiling itself could not be computed or applied: a policy
109
+ * lookup or re-governance failure must never leave the stale, possibly
110
+ * unauthorized attributes in place. Nothing the lease might no longer
111
+ * authorize survives -- every content-scoped key is replaced with the exact
112
+ * placeholders `projectTelemetryContent` writes for a disallowed capture
113
+ * mode, reported under `policy_unavailable` since the real reason could not
114
+ * be determined. Non-content attributes (tool name, durations, ids, ...)
115
+ * are untouched: they are not content, so a failure to re-govern content
116
+ * does not require rewriting them.
117
+ *
118
+ * Returns true when the span is safe to export, false when it must be
119
+ * dropped because even this replacement could not be written.
120
+ */
121
+ function withholdContent(span, currentAttributes) {
122
+ try {
123
+ const hasInput = hasOwn(currentAttributes, 'gen_ai.input.messages') || hasOwn(currentAttributes, 'armoriq.content.input');
124
+ const hasOutput = hasOwn(currentAttributes, 'gen_ai.output.messages') || hasOwn(currentAttributes, 'armoriq.content.output');
125
+ const hasToolArguments = hasOwn(currentAttributes, 'gen_ai.tool.call.arguments');
126
+ const hasToolResult = hasOwn(currentAttributes, 'gen_ai.tool.call.result');
127
+ const hasErrorSummary = hasOwn(currentAttributes, 'armoriq.observability.error.summary');
128
+ const presence = {};
129
+ if (hasInput)
130
+ presence.input = undefined;
131
+ if (hasOutput)
132
+ presence.output = undefined;
133
+ if (hasToolArguments)
134
+ presence.toolArguments = undefined;
135
+ if (hasToolResult)
136
+ presence.toolResult = undefined;
137
+ if (hasErrorSummary)
138
+ presence.errorSummary = undefined;
139
+ const projected = (0, content_capture_1.projectTelemetryContent)(presence, POLICY_UNAVAILABLE_AUTHORITY);
140
+ const next = { ...currentAttributes };
141
+ // Unconditional: every content-scoped key is cleared regardless of which
142
+ // fields were present, so an error-summary-only span (no input/output/
143
+ // tool-call keys at all) is still fully purged.
144
+ for (const key of content_capture_1.CONTENT_ATTRIBUTE_KEYS)
145
+ delete next[key];
146
+ Object.assign(next, projected.attributes);
147
+ return writeAttributes(span, next);
148
+ }
149
+ catch {
150
+ // Even reading which content keys are present failed. Nothing on this
151
+ // span can be inspected, so nothing on it can be cleared selectively;
152
+ // strip it to the empty attribute set.
153
+ return writeAttributes(span, {});
154
+ }
155
+ }
156
+ /**
157
+ * Recompute one span's attributes under the policy current right now.
158
+ *
159
+ * Returns true when the span may be exported, false when it must be
160
+ * dropped.
161
+ */
162
+ function reapplyCeiling(runtime, span) {
163
+ let currentAttributes;
164
+ try {
165
+ currentAttributes = { ...(span.attributes ?? {}) };
166
+ }
167
+ catch {
168
+ // The span's own attribute mapping is unreadable. Nothing on it can be
169
+ // inspected, so nothing on it can be cleared selectively.
170
+ return writeAttributes(span, {});
171
+ }
172
+ let governed;
173
+ try {
174
+ const snapshot = runtime.currentCeilingSnapshot();
175
+ governed = runtime.reapplyContentCeiling(snapshot, currentAttributes);
176
+ // A public span attribute can be forged, so `reapplyContentCeiling` drops
177
+ // the capture-diagnostic flags and re-derives only what re-binding proves.
178
+ // Restore the trusted facts a prior governance pass recorded privately for
179
+ // this span (kept only while content survives), then record the merged
180
+ // result so the next export-time pass carries it forward too.
181
+ Object.assign(governed, (0, content_capture_1.spanContentDiagnostics)(span, governed));
182
+ (0, content_capture_1.rememberSpanContentDiagnostics)(span, governed);
183
+ }
184
+ catch {
185
+ // Policy lookup or re-governance failed. Fail closed rather than ship
186
+ // the span exactly as it was.
187
+ return withholdContent(span, currentAttributes);
188
+ }
189
+ if (attributesEqual(governed, currentAttributes))
190
+ return true;
191
+ if (writeAttributes(span, governed))
192
+ return true;
193
+ // The rewrite that just failed is the only way to purge the stale
194
+ // attributes, so re-governing is impossible; drop rather than ship them.
195
+ return false;
196
+ }
197
+ /**
198
+ * Re-govern span attributes against the CURRENT lease before shipping.
199
+ *
200
+ * This is the true export-time gate: `export` runs whenever the wrapped
201
+ * batching processor actually flushes its buffer, so a span that has been
202
+ * queued for any length of time is re-evaluated against the lease that is
203
+ * authoritative right now, not the one that was authoritative when it was
204
+ * created or ended.
205
+ */
206
+ class CeilingReapplyingSpanExporter {
207
+ exporter;
208
+ runtime;
209
+ constructor(exporter, runtime) {
210
+ this.exporter = exporter;
211
+ this.runtime = runtime;
212
+ }
213
+ export(spans, resultCallback) {
214
+ const exportable = spans.filter((span) => reapplyCeiling(this.runtime, span));
215
+ if (exportable.length === 0) {
216
+ // Every span was dropped for privacy. Report SUCCESS, not FAILED:
217
+ // FAILED tells BatchSpanProcessor the batch did not ship, which it
218
+ // treats as a retryable transport problem. A ceiling drop is a
219
+ // deliberate, permanent decision -- the span must never ship -- so a
220
+ // retry would be pointless work over the same verdict.
221
+ resultCallback({ code: core_1.ExportResultCode.SUCCESS });
222
+ return;
223
+ }
224
+ this.exporter.export(exportable, resultCallback);
225
+ }
226
+ shutdown() {
227
+ return this.exporter.shutdown();
228
+ }
229
+ forceFlush() {
230
+ return this.exporter.forceFlush ? this.exporter.forceFlush() : Promise.resolve();
231
+ }
232
+ }
233
+ exports.CeilingReapplyingSpanExporter = CeilingReapplyingSpanExporter;
234
+ /**
235
+ * Re-govern span attributes at the `onEnd` hand-off boundary.
236
+ *
237
+ * Complements `CeilingReapplyingSpanExporter`: this closes the window
238
+ * between when a span's attributes were last set (which can be well before
239
+ * a long-running operation actually calls `span.end()`) and when it is
240
+ * handed to the wrapped processor for batching. `shutdown`/`forceFlush`
241
+ * delegate to the wrapped processor unchanged -- this class only adds a
242
+ * re-governance step, it does not change close/flush lifecycle semantics.
243
+ */
244
+ class CeilingReapplyingSpanProcessor {
245
+ processor;
246
+ runtime;
247
+ constructor(processor, runtime) {
248
+ this.processor = processor;
249
+ this.runtime = runtime;
250
+ }
251
+ onStart(span, parentContext) {
252
+ this.processor.onStart(span, parentContext);
253
+ }
254
+ onEnd(span) {
255
+ if (!reapplyCeiling(this.runtime, span)) {
256
+ // Undeliverable: the span could not be re-governed and could not be
257
+ // stripped either. Never queue it -- once it is inside the wrapped
258
+ // processor's buffer this module cannot reach it again.
259
+ return;
260
+ }
261
+ this.processor.onEnd(span);
262
+ }
263
+ shutdown() {
264
+ return this.processor.shutdown();
265
+ }
266
+ forceFlush() {
267
+ return this.processor.forceFlush();
268
+ }
269
+ }
270
+ exports.CeilingReapplyingSpanProcessor = CeilingReapplyingSpanProcessor;
271
+ //# sourceMappingURL=otel-export-ceiling.js.map