autotel-schema 0.1.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.
- package/README.md +131 -0
- package/dist/cli.cjs +111 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +14 -0
- package/dist/cli.d.cts.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +82 -0
- package/dist/cli.js.map +1 -0
- package/dist/contract-DGjxR9nb.d.cts +123 -0
- package/dist/contract-DGjxR9nb.d.cts.map +1 -0
- package/dist/contract-DGjxR9nb.d.ts +123 -0
- package/dist/contract-DGjxR9nb.d.ts.map +1 -0
- package/dist/diff-BQPh72vY.d.cts +89 -0
- package/dist/diff-BQPh72vY.d.cts.map +1 -0
- package/dist/diff-D7qkNn0-.d.ts +89 -0
- package/dist/diff-D7qkNn0-.d.ts.map +1 -0
- package/dist/diff.cjs +185 -0
- package/dist/diff.cjs.map +1 -0
- package/dist/diff.d.cts +2 -0
- package/dist/diff.d.ts +2 -0
- package/dist/diff.js +181 -0
- package/dist/diff.js.map +1 -0
- package/dist/index.cjs +63 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +33 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/processor-CK7LAdaa.d.ts +100 -0
- package/dist/processor-CK7LAdaa.d.ts.map +1 -0
- package/dist/processor-CkBkzK6y.d.cts +100 -0
- package/dist/processor-CkBkzK6y.d.cts.map +1 -0
- package/dist/processor-D93TAXvZ.cjs +366 -0
- package/dist/processor-D93TAXvZ.cjs.map +1 -0
- package/dist/processor-FmvKYllX.js +306 -0
- package/dist/processor-FmvKYllX.js.map +1 -0
- package/dist/processor.cjs +5 -0
- package/dist/processor.d.cts +2 -0
- package/dist/processor.d.ts +2 -0
- package/dist/processor.js +3 -0
- package/dist/snapshot-CyWGJaJT.cjs +119 -0
- package/dist/snapshot-CyWGJaJT.cjs.map +1 -0
- package/dist/snapshot-h8pb_Up_.js +89 -0
- package/dist/snapshot-h8pb_Up_.js.map +1 -0
- package/package.json +80 -0
- package/src/attrs.ts +23 -0
- package/src/cli.ts +117 -0
- package/src/contract.test.ts +67 -0
- package/src/contract.ts +231 -0
- package/src/diff.ts +282 -0
- package/src/index.ts +88 -0
- package/src/processor.test.ts +74 -0
- package/src/processor.ts +152 -0
- package/src/redaction.ts +64 -0
- package/src/snapshot.test.ts +88 -0
- package/src/snapshot.ts +119 -0
- package/src/validate.test.ts +100 -0
- package/src/validate.ts +237 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/contract.ts
|
|
3
|
+
const ATTRIBUTE_TYPES = [
|
|
4
|
+
"string",
|
|
5
|
+
"number",
|
|
6
|
+
"boolean",
|
|
7
|
+
"string[]",
|
|
8
|
+
"number[]",
|
|
9
|
+
"boolean[]"
|
|
10
|
+
];
|
|
11
|
+
const STABILITIES = [
|
|
12
|
+
"stable",
|
|
13
|
+
"experimental",
|
|
14
|
+
"deprecated"
|
|
15
|
+
];
|
|
16
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[\w.]+)?$/;
|
|
17
|
+
function assert(condition, message) {
|
|
18
|
+
if (!condition) throw new Error(`autotel-schema: ${message}`);
|
|
19
|
+
}
|
|
20
|
+
function validateAttribute(scope, key, spec) {
|
|
21
|
+
assert(ATTRIBUTE_TYPES.includes(spec.type), `${scope} attribute "${key}" has invalid type "${spec.type}"`);
|
|
22
|
+
if (spec.stability) assert(STABILITIES.includes(spec.stability), `${scope} attribute "${key}" has invalid stability "${spec.stability}"`);
|
|
23
|
+
if (spec.stability === "deprecated") assert(spec.replacedBy !== void 0 || spec.deprecatedReason !== void 0, `${scope} attribute "${key}" is deprecated but has no replacedBy or deprecatedReason`);
|
|
24
|
+
if (spec.enum) assert(spec.enum.length > 0, `${scope} attribute "${key}" declares an empty enum`);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Validate and freeze a telemetry contract. Throws on structural mistakes
|
|
28
|
+
* (bad semver, unknown attribute type, deprecation with no replacement) so the
|
|
29
|
+
* contract fails loudly at module load, not silently at runtime.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* export const contract = defineContract({
|
|
34
|
+
* service: 'checkout',
|
|
35
|
+
* version: '1.2.0',
|
|
36
|
+
* commonAttributes: {
|
|
37
|
+
* 'user.id': { type: 'string', highCardinality: true, description: 'Authenticated user' },
|
|
38
|
+
* },
|
|
39
|
+
* spans: {
|
|
40
|
+
* 'checkout.charge': {
|
|
41
|
+
* description: 'Charge a payment method',
|
|
42
|
+
* attributes: {
|
|
43
|
+
* 'payment.provider': { type: 'string', required: true, enum: ['stripe', 'paypal'] },
|
|
44
|
+
* 'payment.amount_cents': { type: 'number', required: true },
|
|
45
|
+
* },
|
|
46
|
+
* },
|
|
47
|
+
* },
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
function defineContract(contract) {
|
|
52
|
+
assert(typeof contract.service === "string" && contract.service.length > 0, "contract.service must be a non-empty string");
|
|
53
|
+
assert(SEMVER_RE.test(contract.version), `contract.version "${contract.version}" is not valid semver (e.g. "1.2.0")`);
|
|
54
|
+
assert(contract.spans && typeof contract.spans === "object", "contract.spans must be an object");
|
|
55
|
+
for (const [spanName, spanSpec] of Object.entries(contract.spans)) {
|
|
56
|
+
if (spanSpec.stability) assert(STABILITIES.includes(spanSpec.stability), `span "${spanName}" has invalid stability "${spanSpec.stability}"`);
|
|
57
|
+
for (const [key, spec] of Object.entries(spanSpec.attributes ?? {})) validateAttribute(`span "${spanName}"`, key, spec);
|
|
58
|
+
}
|
|
59
|
+
for (const [key, spec] of Object.entries(contract.commonAttributes ?? {})) validateAttribute("common", key, spec);
|
|
60
|
+
return Object.freeze(contract);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolve the effective attribute spec for `key` on `spanName`: span-specific
|
|
64
|
+
* attributes win over common attributes. Returns `undefined` when the key is
|
|
65
|
+
* declared nowhere.
|
|
66
|
+
*/
|
|
67
|
+
function resolveAttributeSpec(contract, spanName, key) {
|
|
68
|
+
return contract.spans[spanName]?.attributes?.[key] ?? contract.commonAttributes?.[key];
|
|
69
|
+
}
|
|
70
|
+
/** Whether attributes outside the declared set are tolerated for a span. */
|
|
71
|
+
function allowsAdditionalAttributes(contract, spanName) {
|
|
72
|
+
return contract.spans[spanName]?.additionalAttributes ?? contract.additionalAttributes ?? false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/validate.ts
|
|
77
|
+
/**
|
|
78
|
+
* Pure span-vs-contract validation. No SDK, no side effects — the same engine
|
|
79
|
+
* the runtime processor ({@link ./processor}) and any test harness can call.
|
|
80
|
+
*/
|
|
81
|
+
/** `'empty[]'` is a distinct marker: an empty array satisfies any array type. */
|
|
82
|
+
function actualType(value) {
|
|
83
|
+
if (typeof value === "string") return "string";
|
|
84
|
+
if (typeof value === "number") return "number";
|
|
85
|
+
if (typeof value === "boolean") return "boolean";
|
|
86
|
+
if (Array.isArray(value)) {
|
|
87
|
+
const first = value.find((v) => v !== null && v !== void 0);
|
|
88
|
+
if (first === void 0) return "empty[]";
|
|
89
|
+
if (typeof first === "string") return "string[]";
|
|
90
|
+
if (typeof first === "number") return "number[]";
|
|
91
|
+
if (typeof first === "boolean") return "boolean[]";
|
|
92
|
+
}
|
|
93
|
+
return "unknown";
|
|
94
|
+
}
|
|
95
|
+
function typeMatches(expected, value) {
|
|
96
|
+
const actual = actualType(value);
|
|
97
|
+
if (actual === "unknown") return false;
|
|
98
|
+
if (actual === "empty[]") return expected.endsWith("[]");
|
|
99
|
+
return actual === expected;
|
|
100
|
+
}
|
|
101
|
+
/** Levenshtein distance — small, allocation-light, good enough for key typos. */
|
|
102
|
+
function editDistance(a, b) {
|
|
103
|
+
const m = a.length;
|
|
104
|
+
const n = b.length;
|
|
105
|
+
if (m === 0) return n;
|
|
106
|
+
if (n === 0) return m;
|
|
107
|
+
let prev = Array.from({ length: n + 1 }, (_, i) => i);
|
|
108
|
+
let curr = Array.from({ length: n + 1 });
|
|
109
|
+
for (let i = 1; i <= m; i++) {
|
|
110
|
+
curr[0] = i;
|
|
111
|
+
for (let j = 1; j <= n; j++) {
|
|
112
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
113
|
+
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
|
114
|
+
}
|
|
115
|
+
[prev, curr] = [curr, prev];
|
|
116
|
+
}
|
|
117
|
+
return prev[n];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Closest declared key to `key`, when one is within a small edit distance.
|
|
121
|
+
* Turns "you emitted an attribute I don't know" into "did you mean `user.id`?".
|
|
122
|
+
*/
|
|
123
|
+
function nearestKey(key, candidates) {
|
|
124
|
+
let best;
|
|
125
|
+
let bestDistance = Infinity;
|
|
126
|
+
const threshold = Math.max(1, Math.floor(key.length / 4) + 1);
|
|
127
|
+
for (const candidate of candidates) {
|
|
128
|
+
const d = editDistance(key, candidate);
|
|
129
|
+
if (d < bestDistance && d <= threshold) {
|
|
130
|
+
best = candidate;
|
|
131
|
+
bestDistance = d;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return best;
|
|
135
|
+
}
|
|
136
|
+
function declaredKeysFor(contract, spanName) {
|
|
137
|
+
return [...Object.keys(contract.spans[spanName]?.attributes ?? {}), ...Object.keys(contract.commonAttributes ?? {})];
|
|
138
|
+
}
|
|
139
|
+
function checkValue(spanName, key, value, spec, out) {
|
|
140
|
+
if (!typeMatches(spec.type, value)) {
|
|
141
|
+
out.push({
|
|
142
|
+
code: "type_mismatch",
|
|
143
|
+
severity: "error",
|
|
144
|
+
spanName,
|
|
145
|
+
attribute: key,
|
|
146
|
+
message: `attribute "${key}" should be ${spec.type} but got ${actualType(value)}`
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (spec.enum && (typeof value === "string" || typeof value === "number") && !spec.enum.includes(value)) out.push({
|
|
151
|
+
code: "enum_violation",
|
|
152
|
+
severity: "error",
|
|
153
|
+
spanName,
|
|
154
|
+
attribute: key,
|
|
155
|
+
message: `attribute "${key}" value ${JSON.stringify(value)} is not one of ${JSON.stringify(spec.enum)}`
|
|
156
|
+
});
|
|
157
|
+
if (spec.stability === "deprecated") {
|
|
158
|
+
const hint = spec.replacedBy ? ` — use "${spec.replacedBy}" instead` : spec.deprecatedReason ? ` — ${spec.deprecatedReason}` : "";
|
|
159
|
+
out.push({
|
|
160
|
+
code: "deprecated_attribute",
|
|
161
|
+
severity: "warning",
|
|
162
|
+
spanName,
|
|
163
|
+
attribute: key,
|
|
164
|
+
message: `attribute "${key}" is deprecated${hint}`,
|
|
165
|
+
suggestion: spec.replacedBy
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Validate one emitted span against the contract, returning every discrepancy.
|
|
171
|
+
* Order is deterministic: required-but-missing first, then per-attribute checks
|
|
172
|
+
* in attribute insertion order.
|
|
173
|
+
*/
|
|
174
|
+
function validateSpan(span, contract, options = {}) {
|
|
175
|
+
const out = [];
|
|
176
|
+
const spanSpec = contract.spans[span.name];
|
|
177
|
+
if (!spanSpec) {
|
|
178
|
+
if (options.strictSpanNames) out.push({
|
|
179
|
+
code: "unknown_span",
|
|
180
|
+
severity: "warning",
|
|
181
|
+
spanName: span.name,
|
|
182
|
+
message: `span "${span.name}" is not declared in the contract`
|
|
183
|
+
});
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
const required = [...Object.entries(spanSpec.attributes ?? {}), ...Object.entries(contract.commonAttributes ?? {})].filter(([, spec]) => spec.required);
|
|
187
|
+
for (const [key] of required) if (!(key in span.attributes)) out.push({
|
|
188
|
+
code: "missing_required",
|
|
189
|
+
severity: "error",
|
|
190
|
+
spanName: span.name,
|
|
191
|
+
attribute: key,
|
|
192
|
+
message: `required attribute "${key}" is missing`
|
|
193
|
+
});
|
|
194
|
+
const allowExtra = allowsAdditionalAttributes(contract, span.name);
|
|
195
|
+
const declared = allowExtra ? [] : declaredKeysFor(contract, span.name);
|
|
196
|
+
for (const [key, value] of Object.entries(span.attributes)) {
|
|
197
|
+
if (value === null || value === void 0) continue;
|
|
198
|
+
const spec = resolveAttributeSpec(contract, span.name, key);
|
|
199
|
+
if (!spec) {
|
|
200
|
+
if (!allowExtra) out.push({
|
|
201
|
+
code: "unknown_attribute",
|
|
202
|
+
severity: "warning",
|
|
203
|
+
spanName: span.name,
|
|
204
|
+
attribute: key,
|
|
205
|
+
message: `attribute "${key}" is not declared on span "${span.name}"`,
|
|
206
|
+
suggestion: nearestKey(key, declared)
|
|
207
|
+
});
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
checkValue(span.name, key, value, spec, out);
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
/** `true` when any violation is `error` severity. */
|
|
215
|
+
function hasErrors(violations) {
|
|
216
|
+
return violations.some((v) => v.severity === "error");
|
|
217
|
+
}
|
|
218
|
+
/** One-line human/agent-readable rendering of a violation. */
|
|
219
|
+
function formatViolation(v) {
|
|
220
|
+
const where = v.attribute ? `${v.spanName}.${v.attribute}` : v.spanName;
|
|
221
|
+
const suffix = v.suggestion ? ` (did you mean "${v.suggestion}"?)` : "";
|
|
222
|
+
return `[${v.severity}] ${v.code} @ ${where}: ${v.message}${suffix}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/processor.ts
|
|
227
|
+
/**
|
|
228
|
+
* Runtime contract enforcement as an OpenTelemetry SpanProcessor.
|
|
229
|
+
*
|
|
230
|
+
* Wire it into `init({ spanProcessors: [...] })` and every span your service
|
|
231
|
+
* emits is validated against the contract as it ends. In development a typo'd
|
|
232
|
+
* or undeclared attribute surfaces immediately instead of silently drifting
|
|
233
|
+
* the public telemetry API out from under the agents reading it.
|
|
234
|
+
*
|
|
235
|
+
* Fail-open by construction: a bug in validation must never break the app or
|
|
236
|
+
* lose a span. Off in production by default (validation belongs in CI and dev),
|
|
237
|
+
* but `enabledInProduction` is there if you want a sampled canary in prod.
|
|
238
|
+
*/
|
|
239
|
+
const DEFAULT_WARN_INTERVAL_MS = 6e4;
|
|
240
|
+
function isProduction() {
|
|
241
|
+
return process.env.NODE_ENV === "production";
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Validates each ending span against a {@link TelemetryContract}. Bounded,
|
|
245
|
+
* deduplicated warnings; fail-open on any internal error.
|
|
246
|
+
*/
|
|
247
|
+
var SchemaValidationSpanProcessor = class {
|
|
248
|
+
opts;
|
|
249
|
+
enabled;
|
|
250
|
+
warnIntervalMs;
|
|
251
|
+
lastWarnAt = /* @__PURE__ */ new Map();
|
|
252
|
+
violationCount = 0;
|
|
253
|
+
constructor(opts) {
|
|
254
|
+
this.opts = opts;
|
|
255
|
+
this.enabled = opts.enabledInProduction === true || !isProduction();
|
|
256
|
+
this.warnIntervalMs = opts.warnIntervalMs ?? DEFAULT_WARN_INTERVAL_MS;
|
|
257
|
+
}
|
|
258
|
+
/** Number of violations seen since startup (across all spans). */
|
|
259
|
+
get totalViolations() {
|
|
260
|
+
return this.violationCount;
|
|
261
|
+
}
|
|
262
|
+
onStart(_span, _parentContext) {}
|
|
263
|
+
onEnd(span) {
|
|
264
|
+
if (!this.enabled) return;
|
|
265
|
+
let violations;
|
|
266
|
+
try {
|
|
267
|
+
violations = validateSpan({
|
|
268
|
+
name: span.name,
|
|
269
|
+
attributes: span.attributes
|
|
270
|
+
}, this.opts.contract, { strictSpanNames: this.opts.strictSpanNames });
|
|
271
|
+
} catch {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
for (const violation of violations) {
|
|
275
|
+
this.violationCount++;
|
|
276
|
+
this.opts.onViolation?.(violation, span);
|
|
277
|
+
this.handle(violation);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
handle(violation) {
|
|
281
|
+
const mode = this.opts.mode ?? "warn";
|
|
282
|
+
if (mode === "silent") return;
|
|
283
|
+
if (mode === "throw" && violation.severity === "error") throw new Error(`autotel-schema: contract violation (${violation.code}) on span "${violation.spanName}": ${violation.message}`);
|
|
284
|
+
this.maybeWarn(violation);
|
|
285
|
+
}
|
|
286
|
+
maybeWarn(violation) {
|
|
287
|
+
const key = `${violation.code}:${violation.spanName}:${violation.attribute ?? ""}`;
|
|
288
|
+
const now = Date.now();
|
|
289
|
+
if (now - (this.lastWarnAt.get(key) ?? 0) < this.warnIntervalMs) return;
|
|
290
|
+
this.lastWarnAt.set(key, now);
|
|
291
|
+
const suffix = violation.suggestion ? ` (did you mean "${violation.suggestion}"?)` : "";
|
|
292
|
+
const message = `autotel-schema [${violation.severity}] ${violation.code} on "${violation.spanName}"${violation.attribute ? `.${violation.attribute}` : ""}: ${violation.message}${suffix}`;
|
|
293
|
+
if (this.opts.onWarn) this.opts.onWarn(message);
|
|
294
|
+
else console.warn(message);
|
|
295
|
+
}
|
|
296
|
+
async forceFlush() {}
|
|
297
|
+
async shutdown() {
|
|
298
|
+
this.lastWarnAt.clear();
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
function createSchemaValidationProcessor(opts) {
|
|
302
|
+
return new SchemaValidationSpanProcessor(opts);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
//#endregion
|
|
306
|
+
Object.defineProperty(exports, 'ATTRIBUTE_TYPES', {
|
|
307
|
+
enumerable: true,
|
|
308
|
+
get: function () {
|
|
309
|
+
return ATTRIBUTE_TYPES;
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
Object.defineProperty(exports, 'STABILITIES', {
|
|
313
|
+
enumerable: true,
|
|
314
|
+
get: function () {
|
|
315
|
+
return STABILITIES;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
Object.defineProperty(exports, 'SchemaValidationSpanProcessor', {
|
|
319
|
+
enumerable: true,
|
|
320
|
+
get: function () {
|
|
321
|
+
return SchemaValidationSpanProcessor;
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
Object.defineProperty(exports, 'allowsAdditionalAttributes', {
|
|
325
|
+
enumerable: true,
|
|
326
|
+
get: function () {
|
|
327
|
+
return allowsAdditionalAttributes;
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
Object.defineProperty(exports, 'createSchemaValidationProcessor', {
|
|
331
|
+
enumerable: true,
|
|
332
|
+
get: function () {
|
|
333
|
+
return createSchemaValidationProcessor;
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
Object.defineProperty(exports, 'defineContract', {
|
|
337
|
+
enumerable: true,
|
|
338
|
+
get: function () {
|
|
339
|
+
return defineContract;
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
Object.defineProperty(exports, 'formatViolation', {
|
|
343
|
+
enumerable: true,
|
|
344
|
+
get: function () {
|
|
345
|
+
return formatViolation;
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
Object.defineProperty(exports, 'hasErrors', {
|
|
349
|
+
enumerable: true,
|
|
350
|
+
get: function () {
|
|
351
|
+
return hasErrors;
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
Object.defineProperty(exports, 'resolveAttributeSpec', {
|
|
355
|
+
enumerable: true,
|
|
356
|
+
get: function () {
|
|
357
|
+
return resolveAttributeSpec;
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
Object.defineProperty(exports, 'validateSpan', {
|
|
361
|
+
enumerable: true,
|
|
362
|
+
get: function () {
|
|
363
|
+
return validateSpan;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
//# sourceMappingURL=processor-D93TAXvZ.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processor-D93TAXvZ.cjs","names":[],"sources":["../src/contract.ts","../src/validate.ts","../src/processor.ts"],"sourcesContent":["/**\n * Telemetry contract model.\n *\n * The premise: when the primary reader of your telemetry is an agent, your\n * span names and attribute keys are a **public API**. Renaming `fast_path_hit`\n * to `fast_path_taken` in a refactor PR silently breaks every prompt that\n * mentions it — there is no compiler to catch it, because to the compiler these\n * are just strings in a JSON blob.\n *\n * `defineContract()` makes that surface explicit, typed, and versionable: you\n * declare which spans your service emits and which attributes live on them,\n * then validate live spans against it ({@link ./validate}) and diff it across\n * commits to catch breaking changes before they ship ({@link ./diff}).\n *\n * This module is dependency-free and side-effect-free by design — safe to\n * import anywhere (browser, edge, CLI) without pulling in the OpenTelemetry SDK.\n */\n\n/** Scalar and array attribute types permitted on a span (OTLP value shapes). */\nexport type AttributeType =\n | 'string'\n | 'number'\n | 'boolean'\n | 'string[]'\n | 'number[]'\n | 'boolean[]';\n\nexport const ATTRIBUTE_TYPES: readonly AttributeType[] = [\n 'string',\n 'number',\n 'boolean',\n 'string[]',\n 'number[]',\n 'boolean[]',\n];\n\n/**\n * Lifecycle of a span or attribute, mirroring how the OpenTelemetry semantic\n * conventions stage their own surface. `stable` is a promise to agent readers\n * that the name will not change without a major contract bump.\n */\nexport type Stability = 'stable' | 'experimental' | 'deprecated';\n\nexport const STABILITIES: readonly Stability[] = [\n 'stable',\n 'experimental',\n 'deprecated',\n];\n\n/** Declaration for a single attribute key on a span. */\nexport interface AttributeSpec {\n /** OTLP value shape. Validated at runtime against the emitted value. */\n type: AttributeType;\n /** Lifecycle stage. Defaults to `stable`. */\n stability?: Stability;\n /** When `true`, the attribute must be present on every matching span. */\n required?: boolean;\n /** Human/agent-facing description of what the attribute means. */\n description?: string;\n /**\n * Marks an attribute as intentionally high-cardinality (user id, sender\n * domain, request id). For an agent reader these are the single most useful\n * fields on a trace, so {@link ./redaction.highCardinalityKeys} surfaces them\n * as a *protect* list — telling redactors/normalizers NOT to strip them.\n */\n highCardinality?: boolean;\n /** Closed set of permitted values. Reported as `enum_violation` if exceeded. */\n enum?: readonly (string | number)[];\n /** Set when `stability: 'deprecated'`; explains what to use instead. */\n replacedBy?: string;\n /** Free-text note shown alongside deprecation warnings. */\n deprecatedReason?: string;\n}\n\n/** Declaration for a single span name your service emits. */\nexport interface SpanSpec {\n /** Human/agent-facing description of when this span is produced. */\n description?: string;\n /** Lifecycle stage. Defaults to `stable`. */\n stability?: Stability;\n /** Attributes specific to this span, keyed by attribute name. */\n attributes?: Record<string, AttributeSpec>;\n /**\n * When `true`, attributes not declared here are allowed without an\n * `unknown_attribute` violation. Defaults to the contract-level setting.\n */\n additionalAttributes?: boolean;\n}\n\n/** The full telemetry contract for one service. */\nexport interface TelemetryContract {\n /** `service.name` this contract describes. */\n service: string;\n /**\n * Semver of the *contract itself* (not the app). Bumped when the trace\n * surface changes; surfaced to readers as the `telemetry.schema.version`\n * resource attribute via {@link ./attrs.SCHEMA_ATTRS}.\n */\n version: string;\n /** Spans this service emits, keyed by span name. */\n spans: Record<string, SpanSpec>;\n /** Attributes permitted on *any* span (e.g. `user.id`, `tenant.id`). */\n commonAttributes?: Record<string, AttributeSpec>;\n /**\n * Default for `SpanSpec.additionalAttributes` when a span does not set it.\n * Defaults to `false` (declared-only — the stricter, agent-friendlier mode).\n */\n additionalAttributes?: boolean;\n}\n\nconst SEMVER_RE = /^\\d+\\.\\d+\\.\\d+(?:-[\\w.]+)?$/;\n\nfunction assert(condition: unknown, message: string): asserts condition {\n if (!condition) {\n throw new Error(`autotel-schema: ${message}`);\n }\n}\n\nfunction validateAttribute(\n scope: string,\n key: string,\n spec: AttributeSpec,\n): void {\n assert(\n ATTRIBUTE_TYPES.includes(spec.type),\n `${scope} attribute \"${key}\" has invalid type \"${spec.type}\"`,\n );\n if (spec.stability) {\n assert(\n STABILITIES.includes(spec.stability),\n `${scope} attribute \"${key}\" has invalid stability \"${spec.stability}\"`,\n );\n }\n if (spec.stability === 'deprecated') {\n assert(\n spec.replacedBy !== undefined || spec.deprecatedReason !== undefined,\n `${scope} attribute \"${key}\" is deprecated but has no replacedBy or deprecatedReason`,\n );\n }\n if (spec.enum) {\n assert(\n spec.enum.length > 0,\n `${scope} attribute \"${key}\" declares an empty enum`,\n );\n }\n}\n\n/**\n * Validate and freeze a telemetry contract. Throws on structural mistakes\n * (bad semver, unknown attribute type, deprecation with no replacement) so the\n * contract fails loudly at module load, not silently at runtime.\n *\n * @example\n * ```ts\n * export const contract = defineContract({\n * service: 'checkout',\n * version: '1.2.0',\n * commonAttributes: {\n * 'user.id': { type: 'string', highCardinality: true, description: 'Authenticated user' },\n * },\n * spans: {\n * 'checkout.charge': {\n * description: 'Charge a payment method',\n * attributes: {\n * 'payment.provider': { type: 'string', required: true, enum: ['stripe', 'paypal'] },\n * 'payment.amount_cents': { type: 'number', required: true },\n * },\n * },\n * },\n * });\n * ```\n */\nexport function defineContract(contract: TelemetryContract): TelemetryContract {\n assert(\n typeof contract.service === 'string' && contract.service.length > 0,\n 'contract.service must be a non-empty string',\n );\n assert(\n SEMVER_RE.test(contract.version),\n `contract.version \"${contract.version}\" is not valid semver (e.g. \"1.2.0\")`,\n );\n assert(\n contract.spans && typeof contract.spans === 'object',\n 'contract.spans must be an object',\n );\n\n for (const [spanName, spanSpec] of Object.entries(contract.spans)) {\n if (spanSpec.stability) {\n assert(\n STABILITIES.includes(spanSpec.stability),\n `span \"${spanName}\" has invalid stability \"${spanSpec.stability}\"`,\n );\n }\n for (const [key, spec] of Object.entries(spanSpec.attributes ?? {})) {\n validateAttribute(`span \"${spanName}\"`, key, spec);\n }\n }\n for (const [key, spec] of Object.entries(contract.commonAttributes ?? {})) {\n validateAttribute('common', key, spec);\n }\n\n return Object.freeze(contract);\n}\n\n/**\n * Resolve the effective attribute spec for `key` on `spanName`: span-specific\n * attributes win over common attributes. Returns `undefined` when the key is\n * declared nowhere.\n */\nexport function resolveAttributeSpec(\n contract: TelemetryContract,\n spanName: string,\n key: string,\n): AttributeSpec | undefined {\n return (\n contract.spans[spanName]?.attributes?.[key] ??\n contract.commonAttributes?.[key]\n );\n}\n\n/** Whether attributes outside the declared set are tolerated for a span. */\nexport function allowsAdditionalAttributes(\n contract: TelemetryContract,\n spanName: string,\n): boolean {\n return (\n contract.spans[spanName]?.additionalAttributes ??\n contract.additionalAttributes ??\n false\n );\n}\n","/**\n * Pure span-vs-contract validation. No SDK, no side effects — the same engine\n * the runtime processor ({@link ./processor}) and any test harness can call.\n */\n\nimport {\n allowsAdditionalAttributes,\n resolveAttributeSpec,\n type AttributeSpec,\n type AttributeType,\n type TelemetryContract,\n} from './contract.js';\n\n/** Severity of a contract violation. `error` = a breaking-shaped problem. */\nexport type ViolationSeverity = 'error' | 'warning';\n\nexport type ViolationCode =\n | 'unknown_span'\n | 'unknown_attribute'\n | 'type_mismatch'\n | 'missing_required'\n | 'deprecated_attribute'\n | 'enum_violation';\n\n/** A single discrepancy between an emitted span and the contract. */\nexport interface SchemaViolation {\n code: ViolationCode;\n severity: ViolationSeverity;\n spanName: string;\n /** Attribute key involved, when the violation is attribute-scoped. */\n attribute?: string;\n message: string;\n /** Nearest declared key, for likely typos (`unknown_attribute` only). */\n suggestion?: string;\n}\n\n/** Minimal emitted-span shape — avoids a hard dependency on the OTel SDK. */\nexport interface SpanShape {\n name: string;\n attributes: Record<string, unknown>;\n}\n\nexport interface ValidateOptions {\n /** Report `unknown_span` for span names not in the contract. Default `false`. */\n strictSpanNames?: boolean;\n}\n\n/** `'empty[]'` is a distinct marker: an empty array satisfies any array type. */\nfunction actualType(value: unknown): AttributeType | 'empty[]' | 'unknown' {\n if (typeof value === 'string') return 'string';\n if (typeof value === 'number') return 'number';\n if (typeof value === 'boolean') return 'boolean';\n if (Array.isArray(value)) {\n const first = value.find((v) => v !== null && v !== undefined);\n if (first === undefined) return 'empty[]';\n if (typeof first === 'string') return 'string[]';\n if (typeof first === 'number') return 'number[]';\n if (typeof first === 'boolean') return 'boolean[]';\n }\n return 'unknown';\n}\n\nfunction typeMatches(expected: AttributeType, value: unknown): boolean {\n const actual = actualType(value);\n if (actual === 'unknown') return false;\n if (actual === 'empty[]') return expected.endsWith('[]');\n return actual === expected;\n}\n\n/** Levenshtein distance — small, allocation-light, good enough for key typos. */\nfunction editDistance(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n if (m === 0) return n;\n if (n === 0) return m;\n let prev = Array.from({ length: n + 1 }, (_, i) => i);\n let curr = Array.from<number>({ length: n + 1 });\n for (let i = 1; i <= m; i++) {\n curr[0] = i;\n for (let j = 1; j <= n; j++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);\n }\n [prev, curr] = [curr, prev];\n }\n return prev[n];\n}\n\n/**\n * Closest declared key to `key`, when one is within a small edit distance.\n * Turns \"you emitted an attribute I don't know\" into \"did you mean `user.id`?\".\n */\nfunction nearestKey(key: string, candidates: string[]): string | undefined {\n let best: string | undefined;\n let bestDistance = Infinity;\n const threshold = Math.max(1, Math.floor(key.length / 4) + 1);\n for (const candidate of candidates) {\n const d = editDistance(key, candidate);\n if (d < bestDistance && d <= threshold) {\n best = candidate;\n bestDistance = d;\n }\n }\n return best;\n}\n\nfunction declaredKeysFor(\n contract: TelemetryContract,\n spanName: string,\n): string[] {\n return [\n ...Object.keys(contract.spans[spanName]?.attributes ?? {}),\n ...Object.keys(contract.commonAttributes ?? {}),\n ];\n}\n\nfunction checkValue(\n spanName: string,\n key: string,\n value: unknown,\n spec: AttributeSpec,\n out: SchemaViolation[],\n): void {\n if (!typeMatches(spec.type, value)) {\n out.push({\n code: 'type_mismatch',\n severity: 'error',\n spanName,\n attribute: key,\n message: `attribute \"${key}\" should be ${spec.type} but got ${actualType(value)}`,\n });\n return; // a wrong type makes enum/deprecation checks noise\n }\n if (spec.enum && (typeof value === 'string' || typeof value === 'number') && !spec.enum.includes(value)) {\n out.push({\n code: 'enum_violation',\n severity: 'error',\n spanName,\n attribute: key,\n message: `attribute \"${key}\" value ${JSON.stringify(value)} is not one of ${JSON.stringify(spec.enum)}`,\n });\n }\n if (spec.stability === 'deprecated') {\n const hint = spec.replacedBy\n ? ` — use \"${spec.replacedBy}\" instead`\n : spec.deprecatedReason\n ? ` — ${spec.deprecatedReason}`\n : '';\n out.push({\n code: 'deprecated_attribute',\n severity: 'warning',\n spanName,\n attribute: key,\n message: `attribute \"${key}\" is deprecated${hint}`,\n suggestion: spec.replacedBy,\n });\n }\n}\n\n/**\n * Validate one emitted span against the contract, returning every discrepancy.\n * Order is deterministic: required-but-missing first, then per-attribute checks\n * in attribute insertion order.\n */\nexport function validateSpan(\n span: SpanShape,\n contract: TelemetryContract,\n options: ValidateOptions = {},\n): SchemaViolation[] {\n const out: SchemaViolation[] = [];\n const spanSpec = contract.spans[span.name];\n\n if (!spanSpec) {\n if (options.strictSpanNames) {\n out.push({\n code: 'unknown_span',\n severity: 'warning',\n spanName: span.name,\n message: `span \"${span.name}\" is not declared in the contract`,\n });\n }\n return out; // unknown span → no attribute contract to check against\n }\n\n // Required attributes that never showed up.\n const required = [\n ...Object.entries(spanSpec.attributes ?? {}),\n ...Object.entries(contract.commonAttributes ?? {}),\n ].filter(([, spec]) => spec.required);\n for (const [key] of required) {\n if (!(key in span.attributes)) {\n out.push({\n code: 'missing_required',\n severity: 'error',\n spanName: span.name,\n attribute: key,\n message: `required attribute \"${key}\" is missing`,\n });\n }\n }\n\n const allowExtra = allowsAdditionalAttributes(contract, span.name);\n const declared = allowExtra ? [] : declaredKeysFor(contract, span.name);\n\n for (const [key, value] of Object.entries(span.attributes)) {\n if (value === null || value === undefined) continue;\n const spec = resolveAttributeSpec(contract, span.name, key);\n if (!spec) {\n if (!allowExtra) {\n out.push({\n code: 'unknown_attribute',\n severity: 'warning',\n spanName: span.name,\n attribute: key,\n message: `attribute \"${key}\" is not declared on span \"${span.name}\"`,\n suggestion: nearestKey(key, declared),\n });\n }\n continue;\n }\n checkValue(span.name, key, value, spec, out);\n }\n\n return out;\n}\n\n/** `true` when any violation is `error` severity. */\nexport function hasErrors(violations: SchemaViolation[]): boolean {\n return violations.some((v) => v.severity === 'error');\n}\n\n/** One-line human/agent-readable rendering of a violation. */\nexport function formatViolation(v: SchemaViolation): string {\n const where = v.attribute ? `${v.spanName}.${v.attribute}` : v.spanName;\n const suffix = v.suggestion ? ` (did you mean \"${v.suggestion}\"?)` : '';\n return `[${v.severity}] ${v.code} @ ${where}: ${v.message}${suffix}`;\n}\n","/**\n * Runtime contract enforcement as an OpenTelemetry SpanProcessor.\n *\n * Wire it into `init({ spanProcessors: [...] })` and every span your service\n * emits is validated against the contract as it ends. In development a typo'd\n * or undeclared attribute surfaces immediately instead of silently drifting\n * the public telemetry API out from under the agents reading it.\n *\n * Fail-open by construction: a bug in validation must never break the app or\n * lose a span. Off in production by default (validation belongs in CI and dev),\n * but `enabledInProduction` is there if you want a sampled canary in prod.\n */\n\nimport { validateSpan, type SchemaViolation, type ValidateOptions } from './validate.js';\nimport type { TelemetryContract } from './contract.js';\n\n/** Minimal ReadableSpan shape — matches OTel without a hard SDK dependency. */\nexport interface ReadableSpanLike {\n name: string;\n attributes: Record<string, unknown>;\n}\n\nexport interface SpanLike {\n spanContext(): { traceId: string; spanId: string };\n}\n\n/** Opaque parent context — matches OTel SpanProcessor without importing it. */\nexport type OtelContext = unknown;\n\nexport interface SpanProcessorLike {\n onStart(span: SpanLike, parentContext: OtelContext): void;\n onEnd(span: ReadableSpanLike): void;\n shutdown(): Promise<void>;\n forceFlush(): Promise<void>;\n}\n\n/** How the processor reacts to a contract violation. */\nexport type SchemaProcessorMode = 'warn' | 'throw' | 'silent';\n\nexport interface SchemaValidationProcessorOptions extends ValidateOptions {\n contract: TelemetryContract;\n /**\n * `warn` (default): log each distinct violation once per interval.\n * `throw`: throw on the first error-severity violation — for tests/CI only.\n * `silent`: collect via `onViolation` without logging.\n */\n mode?: SchemaProcessorMode;\n /** Called for every violation, before mode handling. */\n onViolation?: (violation: SchemaViolation, span: ReadableSpanLike) => void;\n /** Override the warn sink (defaults to `console.warn`). */\n onWarn?: (message: string) => void;\n /** Run even when `NODE_ENV === 'production'`. Default `false`. */\n enabledInProduction?: boolean;\n /** Throttle window for repeated identical warnings (ms). Default 60s. */\n warnIntervalMs?: number;\n}\n\nconst DEFAULT_WARN_INTERVAL_MS = 60_000;\n\nfunction isProduction(): boolean {\n return process.env.NODE_ENV === 'production';\n}\n\n/**\n * Validates each ending span against a {@link TelemetryContract}. Bounded,\n * deduplicated warnings; fail-open on any internal error.\n */\nexport class SchemaValidationSpanProcessor implements SpanProcessorLike {\n private readonly opts: SchemaValidationProcessorOptions;\n private readonly enabled: boolean;\n private readonly warnIntervalMs: number;\n private readonly lastWarnAt = new Map<string, number>();\n private violationCount = 0;\n\n constructor(opts: SchemaValidationProcessorOptions) {\n this.opts = opts;\n this.enabled = opts.enabledInProduction === true || !isProduction();\n this.warnIntervalMs = opts.warnIntervalMs ?? DEFAULT_WARN_INTERVAL_MS;\n }\n\n /** Number of violations seen since startup (across all spans). */\n get totalViolations(): number {\n return this.violationCount;\n }\n\n onStart(_span: SpanLike, _parentContext: OtelContext): void {\n // no-op — validation happens once the span is complete\n }\n\n onEnd(span: ReadableSpanLike): void {\n if (!this.enabled) return;\n let violations: SchemaViolation[];\n try {\n // Validation itself is fail-open: a bug here must never break export.\n violations = validateSpan(\n { name: span.name, attributes: span.attributes },\n this.opts.contract,\n { strictSpanNames: this.opts.strictSpanNames },\n );\n } catch {\n return;\n }\n // Mode handling runs outside the fail-open guard so `throw` mode propagates.\n for (const violation of violations) {\n this.violationCount++;\n this.opts.onViolation?.(violation, span);\n this.handle(violation);\n }\n }\n\n private handle(violation: SchemaViolation): void {\n const mode = this.opts.mode ?? 'warn';\n if (mode === 'silent') return;\n if (mode === 'throw' && violation.severity === 'error') {\n throw new Error(\n `autotel-schema: contract violation (${violation.code}) on span \"${violation.spanName}\": ${violation.message}`,\n );\n }\n this.maybeWarn(violation);\n }\n\n private maybeWarn(violation: SchemaViolation): void {\n const key = `${violation.code}:${violation.spanName}:${violation.attribute ?? ''}`;\n const now = Date.now();\n const last = this.lastWarnAt.get(key) ?? 0;\n if (now - last < this.warnIntervalMs) return;\n this.lastWarnAt.set(key, now);\n const suffix = violation.suggestion\n ? ` (did you mean \"${violation.suggestion}\"?)`\n : '';\n const message = `autotel-schema [${violation.severity}] ${violation.code} on \"${violation.spanName}\"${violation.attribute ? `.${violation.attribute}` : ''}: ${violation.message}${suffix}`;\n if (this.opts.onWarn) {\n this.opts.onWarn(message);\n } else {\n console.warn(message);\n }\n }\n\n async forceFlush(): Promise<void> {\n // nothing buffered — validation is synchronous in onEnd\n }\n\n async shutdown(): Promise<void> {\n this.lastWarnAt.clear();\n }\n}\n\nexport function createSchemaValidationProcessor(\n opts: SchemaValidationProcessorOptions,\n): SchemaValidationSpanProcessor {\n return new SchemaValidationSpanProcessor(opts);\n}\n"],"mappings":"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"}
|