@graphorin/observability 0.5.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 (138) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +115 -0
  4. package/dist/cost/cost-tracker.d.ts +28 -0
  5. package/dist/cost/cost-tracker.d.ts.map +1 -0
  6. package/dist/cost/cost-tracker.js +169 -0
  7. package/dist/cost/cost-tracker.js.map +1 -0
  8. package/dist/cost/index.d.ts +3 -0
  9. package/dist/cost/index.js +3 -0
  10. package/dist/cost/types.d.ts +104 -0
  11. package/dist/cost/types.d.ts.map +1 -0
  12. package/dist/eval/index.d.ts +3 -0
  13. package/dist/eval/index.js +3 -0
  14. package/dist/eval/runner.d.ts +11 -0
  15. package/dist/eval/runner.d.ts.map +1 -0
  16. package/dist/eval/runner.js +113 -0
  17. package/dist/eval/runner.js.map +1 -0
  18. package/dist/eval/types.d.ts +108 -0
  19. package/dist/eval/types.d.ts.map +1 -0
  20. package/dist/exporters/console.d.ts +27 -0
  21. package/dist/exporters/console.d.ts.map +1 -0
  22. package/dist/exporters/console.js +47 -0
  23. package/dist/exporters/console.js.map +1 -0
  24. package/dist/exporters/index.d.ts +6 -0
  25. package/dist/exporters/index.js +7 -0
  26. package/dist/exporters/jsonl.d.ts +57 -0
  27. package/dist/exporters/jsonl.d.ts.map +1 -0
  28. package/dist/exporters/jsonl.js +95 -0
  29. package/dist/exporters/jsonl.js.map +1 -0
  30. package/dist/exporters/otlp-http.d.ts +38 -0
  31. package/dist/exporters/otlp-http.d.ts.map +1 -0
  32. package/dist/exporters/otlp-http.js +115 -0
  33. package/dist/exporters/otlp-http.js.map +1 -0
  34. package/dist/exporters/types.d.ts +72 -0
  35. package/dist/exporters/types.d.ts.map +1 -0
  36. package/dist/exporters/types.js +13 -0
  37. package/dist/exporters/types.js.map +1 -0
  38. package/dist/exporters/with-validation.d.ts +45 -0
  39. package/dist/exporters/with-validation.d.ts.map +1 -0
  40. package/dist/exporters/with-validation.js +99 -0
  41. package/dist/exporters/with-validation.js.map +1 -0
  42. package/dist/gen-ai/emit.d.ts +27 -0
  43. package/dist/gen-ai/emit.d.ts.map +1 -0
  44. package/dist/gen-ai/emit.js +88 -0
  45. package/dist/gen-ai/emit.js.map +1 -0
  46. package/dist/gen-ai/index.d.ts +5 -0
  47. package/dist/gen-ai/index.js +5 -0
  48. package/dist/gen-ai/operation-mapping.d.ts +24 -0
  49. package/dist/gen-ai/operation-mapping.d.ts.map +1 -0
  50. package/dist/gen-ai/operation-mapping.js +67 -0
  51. package/dist/gen-ai/operation-mapping.js.map +1 -0
  52. package/dist/gen-ai/system-derivation.d.ts +41 -0
  53. package/dist/gen-ai/system-derivation.d.ts.map +1 -0
  54. package/dist/gen-ai/system-derivation.js +67 -0
  55. package/dist/gen-ai/system-derivation.js.map +1 -0
  56. package/dist/gen-ai/types.d.ts +87 -0
  57. package/dist/gen-ai/types.d.ts.map +1 -0
  58. package/dist/index.d.ts +58 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +60 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/logger/index.d.ts +2 -0
  63. package/dist/logger/index.js +3 -0
  64. package/dist/logger/logger.d.ts +53 -0
  65. package/dist/logger/logger.d.ts.map +1 -0
  66. package/dist/logger/logger.js +145 -0
  67. package/dist/logger/logger.js.map +1 -0
  68. package/dist/openinference/index.d.ts +45 -0
  69. package/dist/openinference/index.d.ts.map +1 -0
  70. package/dist/openinference/index.js +92 -0
  71. package/dist/openinference/index.js.map +1 -0
  72. package/dist/redaction/config.d.ts +39 -0
  73. package/dist/redaction/config.d.ts.map +1 -0
  74. package/dist/redaction/config.js +15 -0
  75. package/dist/redaction/config.js.map +1 -0
  76. package/dist/redaction/errors.d.ts +36 -0
  77. package/dist/redaction/errors.d.ts.map +1 -0
  78. package/dist/redaction/errors.js +42 -0
  79. package/dist/redaction/errors.js.map +1 -0
  80. package/dist/redaction/imperative-patterns.d.ts +108 -0
  81. package/dist/redaction/imperative-patterns.d.ts.map +1 -0
  82. package/dist/redaction/imperative-patterns.js +187 -0
  83. package/dist/redaction/imperative-patterns.js.map +1 -0
  84. package/dist/redaction/index.d.ts +7 -0
  85. package/dist/redaction/index.js +7 -0
  86. package/dist/redaction/patterns.d.ts +85 -0
  87. package/dist/redaction/patterns.d.ts.map +1 -0
  88. package/dist/redaction/patterns.js +172 -0
  89. package/dist/redaction/patterns.js.map +1 -0
  90. package/dist/redaction/types.d.ts +103 -0
  91. package/dist/redaction/types.d.ts.map +1 -0
  92. package/dist/redaction/validator.d.ts +26 -0
  93. package/dist/redaction/validator.d.ts.map +1 -0
  94. package/dist/redaction/validator.js +195 -0
  95. package/dist/redaction/validator.js.map +1 -0
  96. package/dist/replay/config.d.ts +56 -0
  97. package/dist/replay/config.d.ts.map +1 -0
  98. package/dist/replay/config.js +18 -0
  99. package/dist/replay/config.js.map +1 -0
  100. package/dist/replay/index.d.ts +5 -0
  101. package/dist/replay/index.js +5 -0
  102. package/dist/replay/log.d.ts +39 -0
  103. package/dist/replay/log.d.ts.map +1 -0
  104. package/dist/replay/log.js +59 -0
  105. package/dist/replay/log.js.map +1 -0
  106. package/dist/replay/replay.d.ts +25 -0
  107. package/dist/replay/replay.d.ts.map +1 -0
  108. package/dist/replay/replay.js +173 -0
  109. package/dist/replay/replay.js.map +1 -0
  110. package/dist/replay/types.d.ts +110 -0
  111. package/dist/replay/types.d.ts.map +1 -0
  112. package/dist/telemetry/index.d.ts +63 -0
  113. package/dist/telemetry/index.d.ts.map +1 -0
  114. package/dist/telemetry/index.js +49 -0
  115. package/dist/telemetry/index.js.map +1 -0
  116. package/dist/tracer/ids.d.ts +23 -0
  117. package/dist/tracer/ids.d.ts.map +1 -0
  118. package/dist/tracer/ids.js +30 -0
  119. package/dist/tracer/ids.js.map +1 -0
  120. package/dist/tracer/index.d.ts +6 -0
  121. package/dist/tracer/index.js +6 -0
  122. package/dist/tracer/sampling.d.ts +77 -0
  123. package/dist/tracer/sampling.d.ts.map +1 -0
  124. package/dist/tracer/sampling.js +67 -0
  125. package/dist/tracer/sampling.js.map +1 -0
  126. package/dist/tracer/span-names.d.ts +11 -0
  127. package/dist/tracer/span-names.d.ts.map +1 -0
  128. package/dist/tracer/span-names.js +11 -0
  129. package/dist/tracer/span-names.js.map +1 -0
  130. package/dist/tracer/span.d.ts +27 -0
  131. package/dist/tracer/span.d.ts.map +1 -0
  132. package/dist/tracer/span.js +80 -0
  133. package/dist/tracer/span.js.map +1 -0
  134. package/dist/tracer/tracer.d.ts +101 -0
  135. package/dist/tracer/tracer.d.ts.map +1 -0
  136. package/dist/tracer/tracer.js +150 -0
  137. package/dist/tracer/tracer.js.map +1 -0
  138. package/package.json +138 -0
@@ -0,0 +1,173 @@
1
+ import { compareSensitivityTiers, createRedactionValidator } from "../redaction/validator.js";
2
+ import { sanitizeRecord } from "../exporters/with-validation.js";
3
+
4
+ //#region src/replay/replay.ts
5
+ const DEFAULT_VALIDATOR = createRedactionValidator({ minTier: "public" });
6
+ const DEFAULT_ACTOR = {
7
+ kind: "system",
8
+ id: "in-process"
9
+ };
10
+ /**
11
+ * Build a replay primitive. The returned object exposes a single
12
+ * `run(...)` async iterator that yields {@link ReplayEvent} records.
13
+ *
14
+ * Sanitized mode is the default and applies the configured
15
+ * {@link RedactionValidatorInstance} to every record. Raw mode
16
+ * requires the `canReadRaw` callback to return `true` AND emits an
17
+ * audit log entry on every invocation.
18
+ *
19
+ * @stable
20
+ */
21
+ function createReplay(opts = {}) {
22
+ const validator = opts.validator ?? DEFAULT_VALIDATOR;
23
+ const audit = opts.audit;
24
+ const defaultActor = opts.defaultActor ?? DEFAULT_ACTOR;
25
+ const canReadRaw = opts.canReadRaw ?? (() => true);
26
+ return { async *run(input) {
27
+ const mode = input.mode ?? "sanitized";
28
+ const minSensitivity = input.minSensitivity ?? "public";
29
+ const actor = input.actor ?? defaultActor;
30
+ const start = Date.now();
31
+ if (mode === "raw" && !canReadRaw({ target: input.target })) {
32
+ emitAudit(audit, {
33
+ action: "trace.replay.accessed",
34
+ actor,
35
+ target: input.target,
36
+ decision: "denied",
37
+ metadata: {
38
+ mode,
39
+ minSensitivity,
40
+ eventCount: 0,
41
+ durationMs: Date.now() - start
42
+ }
43
+ });
44
+ yield {
45
+ type: "replay.start",
46
+ target: input.target,
47
+ mode
48
+ };
49
+ yield {
50
+ type: "replay.skipped",
51
+ reason: "access-denied",
52
+ spanId: input.target
53
+ };
54
+ yield {
55
+ type: "replay.end",
56
+ durationMs: Date.now() - start,
57
+ eventsEmitted: 0,
58
+ eventsSkipped: 1
59
+ };
60
+ return;
61
+ }
62
+ let emitted = 0;
63
+ let skipped = 0;
64
+ let started = false;
65
+ let reachedFrom = input.fromSpanId === void 0;
66
+ const iterator = isAsyncIterable(input.source) ? input.source[Symbol.asyncIterator]() : asAsync(input.source[Symbol.iterator]());
67
+ try {
68
+ while (true) {
69
+ const next = await iterator.next();
70
+ if (next.done === true) break;
71
+ const record = next.value;
72
+ if (!started) {
73
+ started = true;
74
+ yield {
75
+ type: "replay.start",
76
+ target: input.target,
77
+ mode
78
+ };
79
+ }
80
+ if (!reachedFrom) if (record.id === input.fromSpanId) reachedFrom = true;
81
+ else continue;
82
+ if (mode === "raw") {
83
+ yield {
84
+ type: "replay.event",
85
+ span: record,
86
+ sanitized: false
87
+ };
88
+ emitted += 1;
89
+ continue;
90
+ }
91
+ if (skipBySensitivity(record, minSensitivity)) {
92
+ skipped += 1;
93
+ yield {
94
+ type: "replay.skipped",
95
+ reason: "sensitivity",
96
+ spanId: record.id
97
+ };
98
+ continue;
99
+ }
100
+ const sanitized = sanitizeRecord(record, validator);
101
+ if (sanitized.droppedReason !== void 0) {
102
+ skipped += 1;
103
+ yield {
104
+ type: "replay.skipped",
105
+ reason: "redaction-violation",
106
+ spanId: record.id
107
+ };
108
+ continue;
109
+ }
110
+ yield {
111
+ type: "replay.event",
112
+ span: sanitized,
113
+ sanitized: true
114
+ };
115
+ emitted += 1;
116
+ }
117
+ } finally {
118
+ const durationMs = Date.now() - start;
119
+ if (!started) yield {
120
+ type: "replay.start",
121
+ target: input.target,
122
+ mode
123
+ };
124
+ emitAudit(audit, {
125
+ action: "trace.replay.accessed",
126
+ actor,
127
+ target: input.target,
128
+ decision: "success",
129
+ metadata: {
130
+ mode,
131
+ minSensitivity,
132
+ ...input.fromSpanId === void 0 ? {} : { fromSpanId: input.fromSpanId },
133
+ eventCount: emitted,
134
+ durationMs
135
+ }
136
+ });
137
+ yield {
138
+ type: "replay.end",
139
+ durationMs,
140
+ eventsEmitted: emitted,
141
+ eventsSkipped: skipped
142
+ };
143
+ }
144
+ } };
145
+ }
146
+ function emitAudit(bridge, event) {
147
+ if (bridge === void 0) return;
148
+ try {
149
+ bridge.emit(event);
150
+ } catch {}
151
+ }
152
+ function skipBySensitivity(record, floor) {
153
+ if (record.sensitivityByAttribute === void 0) return false;
154
+ for (const value of Object.values(record.sensitivityByAttribute)) if (typeof value === "string") {
155
+ const tier = value;
156
+ if (tier === "public" || tier === "internal" || tier === "secret") {
157
+ if (compareSensitivityTiers(tier, floor) > 0) return true;
158
+ }
159
+ }
160
+ return false;
161
+ }
162
+ function isAsyncIterable(value) {
163
+ return value !== null && typeof value === "object" && Symbol.asyncIterator in value;
164
+ }
165
+ function asAsync(it) {
166
+ return { next() {
167
+ return Promise.resolve(it.next());
168
+ } };
169
+ }
170
+
171
+ //#endregion
172
+ export { createReplay };
173
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","names":["DEFAULT_ACTOR: ReplayAuditEvent['actor']","validator: RedactionValidatorInstance","audit: ReplayAuditBridge | undefined","minSensitivity: Sensitivity"],"sources":["../../src/replay/replay.ts"],"sourcesContent":["/**\n * `createReplay(...)` — sanitized-by-default replay primitives.\n *\n * Replay never opens the underlying file directly; callers feed it an\n * iterable of `SpanRecord`s. The {@link createTraceLogReader} helper\n * (file-backed JSONL) and `getTraceLog(...)` aggregate utility live\n * alongside this module.\n *\n * @packageDocumentation\n */\n\nimport type { Sensitivity } from '@graphorin/core';\n\nimport type { SpanRecord } from '../exporters/types.js';\nimport { sanitizeRecord } from '../exporters/with-validation.js';\nimport type { RedactionValidatorInstance } from '../redaction/types.js';\nimport { compareSensitivityTiers, createRedactionValidator } from '../redaction/validator.js';\n\nimport type {\n ReplayAuditBridge,\n ReplayAuditEvent,\n ReplayEvent,\n ReplayOptions,\n ReplayRunInput,\n} from './types.js';\n\nconst DEFAULT_VALIDATOR = createRedactionValidator({ minTier: 'public' });\nconst DEFAULT_ACTOR: ReplayAuditEvent['actor'] = { kind: 'system', id: 'in-process' };\n\n/**\n * @stable\n */\nexport interface Replay {\n run(input: ReplayRunInput): AsyncIterable<ReplayEvent>;\n}\n\n/**\n * Build a replay primitive. The returned object exposes a single\n * `run(...)` async iterator that yields {@link ReplayEvent} records.\n *\n * Sanitized mode is the default and applies the configured\n * {@link RedactionValidatorInstance} to every record. Raw mode\n * requires the `canReadRaw` callback to return `true` AND emits an\n * audit log entry on every invocation.\n *\n * @stable\n */\nexport function createReplay(opts: ReplayOptions = {}): Replay {\n const validator: RedactionValidatorInstance = opts.validator ?? DEFAULT_VALIDATOR;\n const audit: ReplayAuditBridge | undefined = opts.audit;\n const defaultActor = opts.defaultActor ?? DEFAULT_ACTOR;\n const canReadRaw = opts.canReadRaw ?? (() => true);\n\n return {\n async *run(input: ReplayRunInput): AsyncIterable<ReplayEvent> {\n const mode = input.mode ?? 'sanitized';\n const minSensitivity: Sensitivity = input.minSensitivity ?? 'public';\n const actor = input.actor ?? defaultActor;\n const start = Date.now();\n\n if (mode === 'raw' && !canReadRaw({ target: input.target })) {\n emitAudit(audit, {\n action: 'trace.replay.accessed',\n actor,\n target: input.target,\n decision: 'denied',\n metadata: { mode, minSensitivity, eventCount: 0, durationMs: Date.now() - start },\n });\n yield { type: 'replay.start', target: input.target, mode };\n yield { type: 'replay.skipped', reason: 'access-denied', spanId: input.target };\n yield {\n type: 'replay.end',\n durationMs: Date.now() - start,\n eventsEmitted: 0,\n eventsSkipped: 1,\n };\n return;\n }\n\n let emitted = 0;\n let skipped = 0;\n let started = false;\n let reachedFrom = input.fromSpanId === undefined;\n\n const iterator = isAsyncIterable(input.source)\n ? input.source[Symbol.asyncIterator]()\n : asAsync(input.source[Symbol.iterator]());\n\n try {\n while (true) {\n const next = await iterator.next();\n if (next.done === true) break;\n const record = next.value;\n\n if (!started) {\n started = true;\n yield { type: 'replay.start', target: input.target, mode };\n }\n\n if (!reachedFrom) {\n if (record.id === input.fromSpanId) reachedFrom = true;\n else continue;\n }\n\n if (mode === 'raw') {\n yield { type: 'replay.event', span: record, sanitized: false };\n emitted += 1;\n continue;\n }\n\n if (skipBySensitivity(record, minSensitivity)) {\n skipped += 1;\n yield { type: 'replay.skipped', reason: 'sensitivity', spanId: record.id };\n continue;\n }\n\n const sanitized = sanitizeRecord(record, validator);\n // RP-18: sanitization is attribute-granular and never drops the whole\n // span. A record arrives pre-flagged with `droppedReason` only if an\n // upstream stage marked it; honour that, otherwise replay the\n // stripped span.\n if (sanitized.droppedReason !== undefined) {\n skipped += 1;\n yield {\n type: 'replay.skipped',\n reason: 'redaction-violation',\n spanId: record.id,\n };\n continue;\n }\n\n yield { type: 'replay.event', span: sanitized, sanitized: true };\n emitted += 1;\n }\n } finally {\n const durationMs = Date.now() - start;\n if (!started) {\n yield { type: 'replay.start', target: input.target, mode };\n }\n emitAudit(audit, {\n action: 'trace.replay.accessed',\n actor,\n target: input.target,\n decision: 'success',\n metadata: {\n mode,\n minSensitivity,\n ...(input.fromSpanId === undefined ? {} : { fromSpanId: input.fromSpanId }),\n eventCount: emitted,\n durationMs,\n },\n });\n yield {\n type: 'replay.end',\n durationMs,\n eventsEmitted: emitted,\n eventsSkipped: skipped,\n };\n }\n },\n };\n}\n\nfunction emitAudit(bridge: ReplayAuditBridge | undefined, event: ReplayAuditEvent): void {\n if (bridge === undefined) return;\n try {\n bridge.emit(event);\n } catch {\n // Audit listener errors must never break the replay pipeline.\n }\n}\n\nfunction skipBySensitivity(record: SpanRecord, floor: Sensitivity): boolean {\n if (record.sensitivityByAttribute === undefined) return false;\n for (const value of Object.values(record.sensitivityByAttribute)) {\n if (typeof value === 'string') {\n const tier = value as Sensitivity;\n if (tier === 'public' || tier === 'internal' || tier === 'secret') {\n if (compareSensitivityTiers(tier, floor) > 0) return true;\n }\n }\n }\n return false;\n}\n\nfunction isAsyncIterable<T>(value: unknown): value is AsyncIterable<T> {\n return value !== null && typeof value === 'object' && Symbol.asyncIterator in (value as object);\n}\n\nfunction asAsync<T>(it: Iterator<T>): AsyncIterator<T> {\n return {\n next(): Promise<IteratorResult<T>> {\n return Promise.resolve(it.next());\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ import { SpanRecord } from "../exporters/types.js";
2
+ import { RedactionValidatorInstance } from "../redaction/types.js";
3
+ import { Sensitivity } from "@graphorin/core";
4
+
5
+ //#region src/replay/types.d.ts
6
+
7
+ /**
8
+ * Replay scope hint. Server mode requires `'raw'` to be backed by a
9
+ * token carrying the `traces:read:raw` scope. The library mode uses
10
+ * the same flag but without scope enforcement (the server is the only
11
+ * boundary that can grant `'raw'`).
12
+ *
13
+ * @stable
14
+ */
15
+ type ReplayMode = 'sanitized' | 'raw';
16
+ /**
17
+ * Audit-bridge contract used by the replay layer. Every replay
18
+ * invocation emits one entry through the bridge — sanitized + raw
19
+ * alike. The actual audit storage lives in `@graphorin/security`; the
20
+ * replay layer keeps the bridge generic so the package stays free of
21
+ * a hard dependency on the security package.
22
+ *
23
+ * @stable
24
+ */
25
+ interface ReplayAuditBridge {
26
+ emit(event: ReplayAuditEvent): void;
27
+ }
28
+ /**
29
+ * Sanitized event passed to the audit bridge.
30
+ *
31
+ * @stable
32
+ */
33
+ interface ReplayAuditEvent {
34
+ readonly action: 'trace.replay.accessed';
35
+ readonly actor: {
36
+ readonly kind: 'token' | 'cli' | 'agent' | 'system';
37
+ readonly id: string;
38
+ };
39
+ readonly target: string;
40
+ readonly decision: 'success' | 'denied';
41
+ readonly metadata: {
42
+ readonly mode: ReplayMode;
43
+ readonly minSensitivity: Sensitivity;
44
+ readonly fromSpanId?: string;
45
+ readonly eventCount: number;
46
+ readonly durationMs: number;
47
+ };
48
+ }
49
+ /**
50
+ * Single record yielded by the replay iterator.
51
+ *
52
+ * @stable
53
+ */
54
+ type ReplayEvent = {
55
+ readonly type: 'replay.start';
56
+ readonly target: string;
57
+ readonly mode: ReplayMode;
58
+ readonly eventCount?: number;
59
+ } | {
60
+ readonly type: 'replay.event';
61
+ readonly span: SpanRecord;
62
+ readonly sanitized: boolean;
63
+ } | {
64
+ readonly type: 'replay.skipped';
65
+ readonly reason: 'sensitivity' | 'redaction-violation' | 'access-denied';
66
+ readonly spanId: string;
67
+ } | {
68
+ readonly type: 'replay.end';
69
+ readonly durationMs: number;
70
+ readonly eventsEmitted: number;
71
+ readonly eventsSkipped: number;
72
+ };
73
+ /**
74
+ * Configuration shape for {@link createReplay}.
75
+ *
76
+ * @stable
77
+ */
78
+ interface ReplayOptions {
79
+ /** Validator used to sanitize records when `mode === 'sanitized'`. */
80
+ readonly validator?: RedactionValidatorInstance;
81
+ /** Optional audit bridge — called once per replay invocation. */
82
+ readonly audit?: ReplayAuditBridge;
83
+ /** Default actor reported via `audit.actor` when none is supplied. */
84
+ readonly defaultActor?: ReplayAuditEvent['actor'];
85
+ /**
86
+ * Scope check invoked when the caller asks for `mode: 'raw'`. Returns
87
+ * `true` to allow, `false` to deny. The server (Phase 14) wires this
88
+ * to the `traces:read:raw` token scope; in library mode it defaults
89
+ * to `() => true` (operators trust their own process).
90
+ */
91
+ readonly canReadRaw?: (context: {
92
+ readonly target: string;
93
+ }) => boolean;
94
+ }
95
+ /**
96
+ * Per-call options consumed by `Replay.run(...)`.
97
+ *
98
+ * @stable
99
+ */
100
+ interface ReplayRunInput {
101
+ readonly source: AsyncIterable<SpanRecord> | Iterable<SpanRecord>;
102
+ readonly target: string;
103
+ readonly mode?: ReplayMode;
104
+ readonly minSensitivity?: Sensitivity;
105
+ readonly fromSpanId?: string;
106
+ readonly actor?: ReplayAuditEvent['actor'];
107
+ }
108
+ //#endregion
109
+ export { ReplayAuditBridge, ReplayAuditEvent, ReplayEvent, ReplayMode, ReplayOptions, ReplayRunInput };
110
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../src/replay/types.ts"],"sourcesContent":[],"mappings":";;;;;;AA0DA;AAyBA;;;;;AAqBA;;AACmB,KAtFP,UAAA,GAsFO,WAAA,GAAA,KAAA;;;;;;;;;;UA3EF,iBAAA;cACH;;;;;;;UAQG,gBAAA;;;;;;;;;mBAME;6BACU;;;;;;;;;;;KAYjB,WAAA;;;iBAIS;;;;iBAG+B;;;;;;;;;;;;;;;;;UAkBnC,aAAA;;uBAEM;;mBAEJ;;0BAEO;;;;;;;;;;;;;;;;UAeT,cAAA;mBACE,cAAc,cAAc,SAAS;;kBAEtC;4BACU;;mBAET"}
@@ -0,0 +1,63 @@
1
+ //#region src/telemetry/index.d.ts
2
+ /**
3
+ * Zero-default telemetry stub.
4
+ *
5
+ * The framework promises to make zero outbound network calls without
6
+ * an explicit user action. This module exists so consumers can:
7
+ *
8
+ * - inspect the telemetry posture from CLI / health endpoints,
9
+ * - register the reserved `GRAPHORIN_TELEMETRY` / `GRAPHORIN_NO_PHONE_HOME`
10
+ * environment variables for forward compatibility,
11
+ * - assert in tests that no implicit telemetry surface exists.
12
+ *
13
+ * Attempting to enable telemetry pre-v0.2 returns a sentinel result; it
14
+ * never opens a socket.
15
+ *
16
+ * @packageDocumentation
17
+ */
18
+ /**
19
+ * Result returned by {@link getTelemetryStatus}.
20
+ *
21
+ * @stable
22
+ */
23
+ interface TelemetryStatus {
24
+ /** Always `false` in v0.1. Reserved field. */
25
+ readonly enabled: false;
26
+ /** Plain-English explanation of the current state. */
27
+ readonly reason: string;
28
+ /** Resolved value of `GRAPHORIN_TELEMETRY` (if any). */
29
+ readonly env?: string;
30
+ /** Resolved value of `GRAPHORIN_NO_PHONE_HOME` (if any). */
31
+ readonly noPhoneHome?: string;
32
+ }
33
+ /**
34
+ * Snapshot of the telemetry posture. Reads from `process.env` once
35
+ * unless `env` is provided.
36
+ *
37
+ * @stable
38
+ */
39
+ declare function getTelemetryStatus(env?: NodeJS.ProcessEnv): TelemetryStatus;
40
+ /**
41
+ * Best-effort enable hook. Always returns the sentinel
42
+ * `{ status: 'disabled', reason: ... }` payload. Reserved for v0.2+.
43
+ *
44
+ * @stable
45
+ */
46
+ declare function enableTelemetry(): {
47
+ readonly status: 'disabled';
48
+ readonly reason: string;
49
+ };
50
+ /**
51
+ * Detect the reserved env vars and emit one informational line per
52
+ * process. Returns the lines as an array so callers can route them to
53
+ * any sink they like (defaults to `console.info`).
54
+ *
55
+ * @stable
56
+ */
57
+ declare function announceTelemetryPosture(opts?: {
58
+ readonly env?: NodeJS.ProcessEnv;
59
+ readonly sink?: (line: string) => void;
60
+ }): ReadonlyArray<string>;
61
+ //#endregion
62
+ export { TelemetryStatus, announceTelemetryPosture, enableTelemetry, getTelemetryStatus };
63
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/telemetry/index.ts"],"sourcesContent":[],"mappings":";;AAsBA;AAiBA;AAiBA;AAiBA;;;;;;;;;;;;;;;;;UAnDiB,eAAA;;;;;;;;;;;;;;;;iBAiBD,kBAAA,OAAwB,MAAA,CAAO,aAA2B;;;;;;;iBAiB1D,eAAA,CAAA;;;;;;;;;;;iBAiBA,wBAAA;iBACS,MAAA,CAAO;;IAC7B"}
@@ -0,0 +1,49 @@
1
+ //#region src/telemetry/index.ts
2
+ /**
3
+ * Snapshot of the telemetry posture. Reads from `process.env` once
4
+ * unless `env` is provided.
5
+ *
6
+ * @stable
7
+ */
8
+ function getTelemetryStatus(env = process.env) {
9
+ const telemetry = env.GRAPHORIN_TELEMETRY;
10
+ const noPhoneHome = env.GRAPHORIN_NO_PHONE_HOME;
11
+ return {
12
+ enabled: false,
13
+ reason: "telemetry not yet implemented (v0.1 ships zero default telemetry)",
14
+ ...telemetry === void 0 ? {} : { env: telemetry },
15
+ ...noPhoneHome === void 0 ? {} : { noPhoneHome }
16
+ };
17
+ }
18
+ /**
19
+ * Best-effort enable hook. Always returns the sentinel
20
+ * `{ status: 'disabled', reason: ... }` payload. Reserved for v0.2+.
21
+ *
22
+ * @stable
23
+ */
24
+ function enableTelemetry() {
25
+ return {
26
+ status: "disabled",
27
+ reason: "telemetry not yet implemented (v0.1 ships zero default telemetry)"
28
+ };
29
+ }
30
+ /**
31
+ * Detect the reserved env vars and emit one informational line per
32
+ * process. Returns the lines as an array so callers can route them to
33
+ * any sink they like (defaults to `console.info`).
34
+ *
35
+ * @stable
36
+ */
37
+ function announceTelemetryPosture(opts = {}) {
38
+ const env = opts.env ?? process.env;
39
+ const sink = opts.sink ?? ((line) => console.info(line));
40
+ const lines = [];
41
+ if (env.GRAPHORIN_TELEMETRY !== void 0) lines.push(`[graphorin/observability] info: GRAPHORIN_TELEMETRY=${env.GRAPHORIN_TELEMETRY} — acknowledged. Telemetry is hardcoded \`disabled\` in v0.1.`);
42
+ if (env.GRAPHORIN_NO_PHONE_HOME !== void 0) lines.push(`[graphorin/observability] info: GRAPHORIN_NO_PHONE_HOME=${env.GRAPHORIN_NO_PHONE_HOME} — acknowledged. Graphorin already makes zero outbound calls without explicit user action.`);
43
+ for (const line of lines) sink(line);
44
+ return lines;
45
+ }
46
+
47
+ //#endregion
48
+ export { announceTelemetryPosture, enableTelemetry, getTelemetryStatus };
49
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["lines: string[]"],"sources":["../../src/telemetry/index.ts"],"sourcesContent":["/**\n * Zero-default telemetry stub.\n *\n * The framework promises to make zero outbound network calls without\n * an explicit user action. This module exists so consumers can:\n *\n * - inspect the telemetry posture from CLI / health endpoints,\n * - register the reserved `GRAPHORIN_TELEMETRY` / `GRAPHORIN_NO_PHONE_HOME`\n * environment variables for forward compatibility,\n * - assert in tests that no implicit telemetry surface exists.\n *\n * Attempting to enable telemetry pre-v0.2 returns a sentinel result; it\n * never opens a socket.\n *\n * @packageDocumentation\n */\n\n/**\n * Result returned by {@link getTelemetryStatus}.\n *\n * @stable\n */\nexport interface TelemetryStatus {\n /** Always `false` in v0.1. Reserved field. */\n readonly enabled: false;\n /** Plain-English explanation of the current state. */\n readonly reason: string;\n /** Resolved value of `GRAPHORIN_TELEMETRY` (if any). */\n readonly env?: string;\n /** Resolved value of `GRAPHORIN_NO_PHONE_HOME` (if any). */\n readonly noPhoneHome?: string;\n}\n\n/**\n * Snapshot of the telemetry posture. Reads from `process.env` once\n * unless `env` is provided.\n *\n * @stable\n */\nexport function getTelemetryStatus(env: NodeJS.ProcessEnv = process.env): TelemetryStatus {\n const telemetry = env.GRAPHORIN_TELEMETRY;\n const noPhoneHome = env.GRAPHORIN_NO_PHONE_HOME;\n return {\n enabled: false,\n reason: 'telemetry not yet implemented (v0.1 ships zero default telemetry)',\n ...(telemetry === undefined ? {} : { env: telemetry }),\n ...(noPhoneHome === undefined ? {} : { noPhoneHome }),\n };\n}\n\n/**\n * Best-effort enable hook. Always returns the sentinel\n * `{ status: 'disabled', reason: ... }` payload. Reserved for v0.2+.\n *\n * @stable\n */\nexport function enableTelemetry(): {\n readonly status: 'disabled';\n readonly reason: string;\n} {\n return {\n status: 'disabled',\n reason: 'telemetry not yet implemented (v0.1 ships zero default telemetry)',\n };\n}\n\n/**\n * Detect the reserved env vars and emit one informational line per\n * process. Returns the lines as an array so callers can route them to\n * any sink they like (defaults to `console.info`).\n *\n * @stable\n */\nexport function announceTelemetryPosture(\n opts: { readonly env?: NodeJS.ProcessEnv; readonly sink?: (line: string) => void } = {},\n): ReadonlyArray<string> {\n const env = opts.env ?? process.env;\n const sink = opts.sink ?? ((line: string) => console.info(line));\n const lines: string[] = [];\n if (env.GRAPHORIN_TELEMETRY !== undefined) {\n lines.push(\n `[graphorin/observability] info: GRAPHORIN_TELEMETRY=${env.GRAPHORIN_TELEMETRY} ` +\n '— acknowledged. Telemetry is hardcoded `disabled` in v0.1.',\n );\n }\n if (env.GRAPHORIN_NO_PHONE_HOME !== undefined) {\n lines.push(\n `[graphorin/observability] info: GRAPHORIN_NO_PHONE_HOME=${env.GRAPHORIN_NO_PHONE_HOME} ` +\n '— acknowledged. Graphorin already makes zero outbound calls without explicit user action.',\n );\n }\n for (const line of lines) sink(line);\n return lines;\n}\n"],"mappings":";;;;;;;AAuCA,SAAgB,mBAAmB,MAAyB,QAAQ,KAAsB;CACxF,MAAM,YAAY,IAAI;CACtB,MAAM,cAAc,IAAI;AACxB,QAAO;EACL,SAAS;EACT,QAAQ;EACR,GAAI,cAAc,SAAY,EAAE,GAAG,EAAE,KAAK,WAAW;EACrD,GAAI,gBAAgB,SAAY,EAAE,GAAG,EAAE,aAAa;EACrD;;;;;;;;AASH,SAAgB,kBAGd;AACA,QAAO;EACL,QAAQ;EACR,QAAQ;EACT;;;;;;;;;AAUH,SAAgB,yBACd,OAAqF,EAAE,EAChE;CACvB,MAAM,MAAM,KAAK,OAAO,QAAQ;CAChC,MAAM,OAAO,KAAK,UAAU,SAAiB,QAAQ,KAAK,KAAK;CAC/D,MAAMA,QAAkB,EAAE;AAC1B,KAAI,IAAI,wBAAwB,OAC9B,OAAM,KACJ,uDAAuD,IAAI,oBAAoB,+DAEhF;AAEH,KAAI,IAAI,4BAA4B,OAClC,OAAM,KACJ,2DAA2D,IAAI,wBAAwB,4FAExF;AAEH,MAAK,MAAM,QAAQ,MAAO,MAAK,KAAK;AACpC,QAAO"}
@@ -0,0 +1,23 @@
1
+ //#region src/tracer/ids.d.ts
2
+ /**
3
+ * Trace and span ID generators. The tracer prefers `crypto.randomUUID()`
4
+ * over a custom RNG for parity with the OpenTelemetry SDK behaviour and
5
+ * to avoid pulling another runtime dependency.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ /**
10
+ * Generate a 32-hex-character (16-byte) trace id.
11
+ *
12
+ * @stable
13
+ */
14
+ declare function newTraceId(): string;
15
+ /**
16
+ * Generate a 16-hex-character (8-byte) span id.
17
+ *
18
+ * @stable
19
+ */
20
+ declare function newSpanId(): string;
21
+ //#endregion
22
+ export { newSpanId, newTraceId };
23
+ //# sourceMappingURL=ids.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ids.d.ts","names":[],"sources":["../../src/tracer/ids.ts"],"sourcesContent":[],"mappings":";;AAeA;AASA;;;;;;;;;;iBATgB,UAAA,CAAA;;;;;;iBASA,SAAA,CAAA"}
@@ -0,0 +1,30 @@
1
+ import { randomBytes } from "node:crypto";
2
+
3
+ //#region src/tracer/ids.ts
4
+ /**
5
+ * Trace and span ID generators. The tracer prefers `crypto.randomUUID()`
6
+ * over a custom RNG for parity with the OpenTelemetry SDK behaviour and
7
+ * to avoid pulling another runtime dependency.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ /**
12
+ * Generate a 32-hex-character (16-byte) trace id.
13
+ *
14
+ * @stable
15
+ */
16
+ function newTraceId() {
17
+ return randomBytes(16).toString("hex");
18
+ }
19
+ /**
20
+ * Generate a 16-hex-character (8-byte) span id.
21
+ *
22
+ * @stable
23
+ */
24
+ function newSpanId() {
25
+ return randomBytes(8).toString("hex");
26
+ }
27
+
28
+ //#endregion
29
+ export { newSpanId, newTraceId };
30
+ //# sourceMappingURL=ids.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ids.js","names":[],"sources":["../../src/tracer/ids.ts"],"sourcesContent":["/**\n * Trace and span ID generators. The tracer prefers `crypto.randomUUID()`\n * over a custom RNG for parity with the OpenTelemetry SDK behaviour and\n * to avoid pulling another runtime dependency.\n *\n * @packageDocumentation\n */\n\nimport { randomBytes } from 'node:crypto';\n\n/**\n * Generate a 32-hex-character (16-byte) trace id.\n *\n * @stable\n */\nexport function newTraceId(): string {\n return randomBytes(16).toString('hex');\n}\n\n/**\n * Generate a 16-hex-character (8-byte) span id.\n *\n * @stable\n */\nexport function newSpanId(): string {\n return randomBytes(8).toString('hex');\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,SAAgB,aAAqB;AACnC,QAAO,YAAY,GAAG,CAAC,SAAS,MAAM;;;;;;;AAQxC,SAAgB,YAAoB;AAClC,QAAO,YAAY,EAAE,CAAC,SAAS,MAAM"}
@@ -0,0 +1,6 @@
1
+ import { newSpanId, newTraceId } from "./ids.js";
2
+ import { Sampler, SamplingDecisionMaker, SamplingOptions, SamplingRule, createSampler } from "./sampling.js";
3
+ import { GraphorinSpan, SetAttributeOptions } from "./span.js";
4
+ import { spanNameFor } from "./span-names.js";
5
+ import { GraphorinTracer, TracerOptions, asGraphorinSpan, createTracer } from "./tracer.js";
6
+ export { type GraphorinSpan, type GraphorinTracer, type Sampler, type SamplingDecisionMaker, type SamplingOptions, type SamplingRule, type SetAttributeOptions, type TracerOptions, asGraphorinSpan, createSampler, createTracer, newSpanId, newTraceId, spanNameFor };
@@ -0,0 +1,6 @@
1
+ import { newSpanId, newTraceId } from "./ids.js";
2
+ import { createSampler } from "./sampling.js";
3
+ import { spanNameFor } from "./span-names.js";
4
+ import { asGraphorinSpan, createTracer } from "./tracer.js";
5
+
6
+ export { asGraphorinSpan, createSampler, createTracer, newSpanId, newTraceId, spanNameFor };
@@ -0,0 +1,77 @@
1
+ import { SpanType } from "@graphorin/core";
2
+
3
+ //#region src/tracer/sampling.d.ts
4
+
5
+ /** @stable */
6
+ type SamplingDecisionMaker = 'parent-based' | 'always-on' | 'rate-limit';
7
+ /**
8
+ * Per-span-type rate override.
9
+ *
10
+ * @stable
11
+ */
12
+ interface SamplingRule {
13
+ readonly type: SpanType | string;
14
+ readonly rate: number;
15
+ }
16
+ /**
17
+ * Configuration shape consumed by {@link createSampler}.
18
+ *
19
+ * @stable
20
+ */
21
+ interface SamplingOptions {
22
+ /** Default head-sampling rate. Must be in `[0, 1]`. Defaults to `1.0`. */
23
+ readonly rate?: number;
24
+ /** Per-type overrides. Last write wins on duplicate `type`. */
25
+ readonly rules?: ReadonlyArray<SamplingRule>;
26
+ /** Decision maker. Defaults to `'parent-based'`. */
27
+ readonly decisionMaker?: SamplingDecisionMaker;
28
+ /**
29
+ * Cap for the `'rate-limit'` decision maker: at most this many root spans
30
+ * are sampled per rolling 1-second window (RP-19). `undefined` ⇒ no cap
31
+ * (samples everything); `0` ⇒ sample nothing. Ignored by the other
32
+ * decision makers.
33
+ */
34
+ readonly maxPerSecond?: number;
35
+ /**
36
+ * Clock for the `'rate-limit'` window. Defaults to `Date.now`.
37
+ *
38
+ * @internal
39
+ */
40
+ readonly now?: () => number;
41
+ /**
42
+ * Optional override for streaming-event sampling.
43
+ * @see RB-52 — streaming event family `tool.execute.{progress,partial}`.
44
+ */
45
+ readonly streaming?: {
46
+ readonly eventSamplingRate?: number;
47
+ readonly includeChunkContent?: 'none' | 'text-only' | 'all';
48
+ };
49
+ /**
50
+ * Override for the random source. Useful for deterministic tests.
51
+ *
52
+ * @internal
53
+ */
54
+ readonly random?: () => number;
55
+ }
56
+ /**
57
+ * @stable
58
+ */
59
+ interface Sampler {
60
+ /** Decide whether a span of the given type should be recorded. */
61
+ shouldSample(type: SpanType | string, parentSampled?: boolean): boolean;
62
+ /** Decide whether a span event of the given name should be recorded. */
63
+ shouldRecordEvent(name: string): boolean;
64
+ /** Returns whether chunk *content* should travel through the exporter. */
65
+ shouldIncludeChunkContent(): boolean;
66
+ }
67
+ /**
68
+ * Build a {@link Sampler} from the supplied options. The sampler is
69
+ * intentionally inexpensive — every decision boils down to a single
70
+ * `random < threshold` comparison.
71
+ *
72
+ * @stable
73
+ */
74
+ declare function createSampler(opts?: SamplingOptions): Sampler;
75
+ //#endregion
76
+ export { Sampler, SamplingDecisionMaker, SamplingOptions, SamplingRule, createSampler };
77
+ //# sourceMappingURL=sampling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sampling.d.ts","names":[],"sources":["../../src/tracer/sampling.ts"],"sourcesContent":[],"mappings":";;;;;AAoEiB,KAxDL,qBAAA,GA0DiB,cAAA,GAAA,WAAA,GAAA,YAAA;AAc7B;;;;;UAjEiB,YAAA;iBACA;;;;;;;;UASA,eAAA;;;;mBAIE,cAAc;;2BAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiCV,OAAA;;qBAEI;;;;;;;;;;;;;iBAcL,aAAA,QAAoB,kBAAuB"}
@@ -0,0 +1,67 @@
1
+ //#region src/tracer/sampling.ts
2
+ /**
3
+ * Build a {@link Sampler} from the supplied options. The sampler is
4
+ * intentionally inexpensive — every decision boils down to a single
5
+ * `random < threshold` comparison.
6
+ *
7
+ * @stable
8
+ */
9
+ function createSampler(opts = {}) {
10
+ const baseRate = clampRate(opts.rate ?? 1);
11
+ const decisionMaker = opts.decisionMaker ?? "parent-based";
12
+ const ruleByType = /* @__PURE__ */ new Map();
13
+ for (const rule of opts.rules ?? []) ruleByType.set(rule.type, clampRate(rule.rate));
14
+ const streamingRate = clampRate(opts.streaming?.eventSamplingRate ?? 1);
15
+ const includeChunks = opts.streaming?.includeChunkContent ?? "none";
16
+ const random = opts.random ?? Math.random;
17
+ const maxPerSecond = opts.maxPerSecond;
18
+ const now = opts.now ?? Date.now;
19
+ let windowStart;
20
+ let windowCount = 0;
21
+ return {
22
+ shouldSample(type, parentSampled) {
23
+ if (decisionMaker === "always-on") return true;
24
+ if (decisionMaker === "parent-based") {
25
+ if (parentSampled === true) return true;
26
+ if (parentSampled === false) return false;
27
+ }
28
+ if (decisionMaker === "rate-limit") {
29
+ if (maxPerSecond === void 0) return true;
30
+ if (maxPerSecond <= 0) return false;
31
+ const t = now();
32
+ if (windowStart === void 0 || t - windowStart >= 1e3) {
33
+ windowStart = t;
34
+ windowCount = 0;
35
+ }
36
+ if (windowCount < maxPerSecond) {
37
+ windowCount += 1;
38
+ return true;
39
+ }
40
+ return false;
41
+ }
42
+ const rate = ruleByType.get(type) ?? baseRate;
43
+ if (rate >= 1) return true;
44
+ if (rate <= 0) return false;
45
+ return random() < rate;
46
+ },
47
+ shouldRecordEvent(name) {
48
+ const rate = ruleByType.get(name) ?? streamingRate;
49
+ if (rate >= 1) return true;
50
+ if (rate <= 0) return false;
51
+ return random() < rate;
52
+ },
53
+ shouldIncludeChunkContent() {
54
+ return includeChunks !== "none";
55
+ }
56
+ };
57
+ }
58
+ function clampRate(rate) {
59
+ if (Number.isNaN(rate)) return 1;
60
+ if (rate < 0) return 0;
61
+ if (rate > 1) return 1;
62
+ return rate;
63
+ }
64
+
65
+ //#endregion
66
+ export { createSampler };
67
+ //# sourceMappingURL=sampling.js.map