@openwop/openwop-conformance 1.0.0 → 1.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/CHANGELOG.md +17 -0
- package/README.md +31 -6
- package/api/grpc/openwop.proto +251 -0
- package/api/openapi.yaml +109 -3
- package/coverage.md +48 -9
- package/fixtures/conformance-configurable-schema.json +39 -0
- package/fixtures/conformance-subworkflow-parent.json +1 -1
- package/fixtures/conformance-wasm-pack-memory-cap-breach.json +23 -0
- package/fixtures/openwop-smoke-byok-roundtrip.json +25 -0
- package/fixtures.md +21 -0
- package/package.json +3 -1
- package/schemas/README.md +4 -0
- package/schemas/audit-verify-result.schema.json +90 -0
- package/schemas/capabilities.schema.json +293 -1
- package/schemas/node-pack-manifest.schema.json +4 -4
- package/schemas/pack-lockfile.schema.json +92 -0
- package/schemas/registry-version-manifest.schema.json +145 -0
- package/schemas/run-event-payloads.schema.json +2 -2
- package/schemas/security-advisory.schema.json +109 -0
- package/src/lib/a2a-fake-peer.ts +143 -56
- package/src/lib/behavior-gate.ts +68 -0
- package/src/lib/env.ts +10 -0
- package/src/lib/grpc-framing.test.ts +96 -0
- package/src/lib/grpc-framing.ts +76 -0
- package/src/lib/oidc-issuer.test.ts +328 -0
- package/src/lib/oidc-issuer.ts +241 -0
- package/src/lib/otel-collector-grpc.test.ts +191 -0
- package/src/lib/otel-collector.test.ts +303 -0
- package/src/lib/otel-collector.ts +318 -14
- package/src/lib/otlp-protobuf.test.ts +461 -0
- package/src/lib/otlp-protobuf.ts +529 -0
- package/src/scenarios/a2a-task-roundtrip.test.ts +147 -28
- package/src/scenarios/agentConfidenceEscalation.test.ts +1 -0
- package/src/scenarios/agentMemoryCrossTenantIsolation.test.ts +1 -0
- package/src/scenarios/agentMemoryRedactionContract.test.ts +1 -0
- package/src/scenarios/agentMemoryRoundTrip.test.ts +1 -0
- package/src/scenarios/agentMemoryTtlExpiry.test.ts +1 -0
- package/src/scenarios/agentMessageReducer.test.ts +1 -0
- package/src/scenarios/agentMetadata.test.ts +1 -0
- package/src/scenarios/agentPackExport.test.ts +1 -0
- package/src/scenarios/agentPackInstall.test.ts +1 -0
- package/src/scenarios/agentPackProvenance.test.ts +1 -0
- package/src/scenarios/audit-log-integrity.test.ts +3 -6
- package/src/scenarios/auth-api-key-rotation.test.ts +182 -0
- package/src/scenarios/auth-mtls.test.ts +274 -0
- package/src/scenarios/auth-oauth2-client-credentials.test.ts +259 -0
- package/src/scenarios/auth-oidc-user-bearer.test.ts +361 -0
- package/src/scenarios/bulk-cancel.test.ts +111 -0
- package/src/scenarios/configurable-schema.test.ts +48 -0
- package/src/scenarios/conversationCapabilityNegotiation.test.ts +1 -0
- package/src/scenarios/conversationLifecycle.test.ts +1 -0
- package/src/scenarios/conversationReplayDeterminism.test.ts +1 -0
- package/src/scenarios/conversationVsLegacySuspend.test.ts +1 -0
- package/src/scenarios/debug-bundle-truncation.test.ts +95 -0
- package/src/scenarios/discovery.test.ts +183 -0
- package/src/scenarios/http-client-ssrf.test.ts +71 -0
- package/src/scenarios/idempotency.test.ts +6 -0
- package/src/scenarios/idempotencyRetry.test.ts +3 -0
- package/src/scenarios/mcp-tool-roundtrip.test.ts +198 -34
- package/src/scenarios/mcp-toolcall-redaction.test.ts +66 -0
- package/src/scenarios/metric-emission.test.ts +113 -0
- package/src/scenarios/orchestratorConservativePath.test.ts +1 -0
- package/src/scenarios/orchestratorDispatch.test.ts +1 -0
- package/src/scenarios/orchestratorTermination.test.ts +1 -0
- package/src/scenarios/otel-emission-grpc.test.ts +98 -0
- package/src/scenarios/pause-resume.test.ts +119 -0
- package/src/scenarios/production-backpressure.test.ts +342 -0
- package/src/scenarios/production-retention-expiry.test.ts +164 -0
- package/src/scenarios/registry-public.test.ts +131 -0
- package/src/scenarios/replay-llm-cache-key.test.ts +35 -0
- package/src/scenarios/replay-retention-expiry.test.ts +178 -0
- package/src/scenarios/restart-during-run.test.ts +177 -0
- package/src/scenarios/spec-corpus-validity.test.ts +54 -26
- package/src/scenarios/staleClaim.test.ts +3 -0
- package/src/scenarios/wasm-pack-abi-version-rejection.test.ts +67 -10
- package/src/scenarios/wasm-pack-memory-cap.test.ts +64 -9
- package/src/scenarios/webhook-negative.test.ts +90 -0
- package/src/scenarios/webhook-signed-delivery.test.ts +178 -0
- package/src/setup.ts +25 -1
- package/vitest.config.ts +5 -1
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal OTLP/protobuf decoder for the conformance suite's OTel collector.
|
|
3
|
+
*
|
|
4
|
+
* OTLP supports three transport encodings: HTTP-JSON, HTTP-protobuf, and
|
|
5
|
+
* gRPC. The collector at `otel-collector.ts` previously accepted only
|
|
6
|
+
* HTTP-JSON, returning `415` to any host configured for HTTP-protobuf.
|
|
7
|
+
* Track 11 follow-up (gap-closure plan): unlock HTTP-protobuf by decoding
|
|
8
|
+
* the binary OTLP payload in-process. gRPC remains out of scope (deferred
|
|
9
|
+
* to v1.2+ per `docs/PROTOCOL-GAP-CLOSURE-PLAN.md` Track 11).
|
|
10
|
+
*
|
|
11
|
+
* Scope: this decoder handles the subset of OTLP needed by the conformance
|
|
12
|
+
* scenarios — trace + metric exports, with KeyValue attributes (string,
|
|
13
|
+
* int, double, bool, array, kvlist, bytes variants). It is NOT a general-
|
|
14
|
+
* purpose protobuf library. Zero npm dependencies; uses only node:crypto
|
|
15
|
+
* stdlib types.
|
|
16
|
+
*
|
|
17
|
+
* Output shape: deliberately matches the OTLP/HTTP-JSON shape the
|
|
18
|
+
* existing `_ingestTraces` / `_ingestMetrics` helpers already consume,
|
|
19
|
+
* so the protobuf path requires no changes to the ingest logic — only
|
|
20
|
+
* a content-type-routed call to `decodeExportTraceServiceRequest()` or
|
|
21
|
+
* `decodeExportMetricsServiceRequest()` instead of `JSON.parse()`.
|
|
22
|
+
*
|
|
23
|
+
* Wire format reference:
|
|
24
|
+
* https://protobuf.dev/programming-guides/encoding/
|
|
25
|
+
* OTLP proto definitions:
|
|
26
|
+
* https://github.com/open-telemetry/opentelemetry-proto/tree/main/opentelemetry/proto
|
|
27
|
+
*
|
|
28
|
+
* @see conformance/src/lib/otel-collector.ts
|
|
29
|
+
* @see spec/v1/observability.md
|
|
30
|
+
* @see docs/PROTOCOL-GAP-CLOSURE-PLAN.md Track 11
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const WIRE_VARINT = 0;
|
|
34
|
+
const WIRE_I64 = 1;
|
|
35
|
+
const WIRE_LEN = 2;
|
|
36
|
+
const WIRE_I32 = 5;
|
|
37
|
+
|
|
38
|
+
const textDecoder = new TextDecoder('utf-8', { fatal: false });
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Reader for the OTLP protobuf wire format subset. Supports varint, LEN,
|
|
42
|
+
* and I64 wire types. I32 is supported only for skip (no OTLP field we
|
|
43
|
+
* decode uses fixed32 today).
|
|
44
|
+
*/
|
|
45
|
+
export class PbReader {
|
|
46
|
+
private offset: number;
|
|
47
|
+
public readonly end: number;
|
|
48
|
+
private readonly buf: Uint8Array;
|
|
49
|
+
|
|
50
|
+
constructor(buf: Uint8Array, offset: number = 0, end?: number) {
|
|
51
|
+
this.buf = buf;
|
|
52
|
+
this.offset = offset;
|
|
53
|
+
this.end = end ?? buf.length;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
hasMore(): boolean {
|
|
57
|
+
return this.offset < this.end;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Read a varint as a bigint (full 64-bit range). */
|
|
61
|
+
readVarint(): bigint {
|
|
62
|
+
let result = 0n;
|
|
63
|
+
let shift = 0n;
|
|
64
|
+
for (let i = 0; i < 10; i++) {
|
|
65
|
+
if (this.offset >= this.end) throw new Error('PbReader: unexpected EOF in varint');
|
|
66
|
+
const b = this.buf[this.offset++];
|
|
67
|
+
result |= BigInt(b & 0x7f) << shift;
|
|
68
|
+
if ((b & 0x80) === 0) return result;
|
|
69
|
+
shift += 7n;
|
|
70
|
+
}
|
|
71
|
+
throw new Error('PbReader: varint exceeds 10 bytes');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Convenience: varint as a JS number (truncates above 2^53). */
|
|
75
|
+
readVarintNumber(): number {
|
|
76
|
+
return Number(this.readVarint());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Decode the field tag at the current offset. */
|
|
80
|
+
readTag(): { fieldNumber: number; wireType: number } {
|
|
81
|
+
const t = this.readVarintNumber();
|
|
82
|
+
return { fieldNumber: t >>> 3, wireType: t & 0x7 };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Skip a field's payload based on its wire type. */
|
|
86
|
+
skip(wireType: number): void {
|
|
87
|
+
switch (wireType) {
|
|
88
|
+
case WIRE_VARINT:
|
|
89
|
+
this.readVarint();
|
|
90
|
+
return;
|
|
91
|
+
case WIRE_I64:
|
|
92
|
+
this.offset += 8;
|
|
93
|
+
return;
|
|
94
|
+
case WIRE_LEN: {
|
|
95
|
+
const n = this.readVarintNumber();
|
|
96
|
+
this.offset += n;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
case WIRE_I32:
|
|
100
|
+
this.offset += 4;
|
|
101
|
+
return;
|
|
102
|
+
default:
|
|
103
|
+
throw new Error(`PbReader: cannot skip unknown wire type ${wireType}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Read a LEN-prefixed byte run as raw bytes. */
|
|
108
|
+
readLengthDelimited(): Uint8Array {
|
|
109
|
+
const n = this.readVarintNumber();
|
|
110
|
+
const start = this.offset;
|
|
111
|
+
if (start + n > this.end) throw new Error('PbReader: LEN overruns buffer');
|
|
112
|
+
this.offset = start + n;
|
|
113
|
+
return this.buf.subarray(start, start + n);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Read a LEN-prefixed UTF-8 string. */
|
|
117
|
+
readString(): string {
|
|
118
|
+
return textDecoder.decode(this.readLengthDelimited());
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Read a LEN-prefixed byte run as a lowercase hex string. */
|
|
122
|
+
readBytesAsHex(): string {
|
|
123
|
+
const bytes = this.readLengthDelimited();
|
|
124
|
+
let hex = '';
|
|
125
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
126
|
+
hex += bytes[i].toString(16).padStart(2, '0');
|
|
127
|
+
}
|
|
128
|
+
return hex;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Read 8 bytes as a little-endian IEEE 754 double. */
|
|
132
|
+
readDouble(): number {
|
|
133
|
+
if (this.offset + 8 > this.end) throw new Error('PbReader: double overruns buffer');
|
|
134
|
+
const view = new DataView(this.buf.buffer, this.buf.byteOffset + this.offset, 8);
|
|
135
|
+
const v = view.getFloat64(0, true);
|
|
136
|
+
this.offset += 8;
|
|
137
|
+
return v;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Read 8 bytes as a little-endian unsigned 64-bit integer (bigint). */
|
|
141
|
+
readFixed64Uint(): bigint {
|
|
142
|
+
if (this.offset + 8 > this.end) throw new Error('PbReader: fixed64 overruns buffer');
|
|
143
|
+
const view = new DataView(this.buf.buffer, this.buf.byteOffset + this.offset, 8);
|
|
144
|
+
const lo = BigInt(view.getUint32(0, true));
|
|
145
|
+
const hi = BigInt(view.getUint32(4, true));
|
|
146
|
+
this.offset += 8;
|
|
147
|
+
return (hi << 32n) | lo;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Read 8 bytes as a little-endian signed 64-bit (two's complement). */
|
|
151
|
+
readFixed64Int(): bigint {
|
|
152
|
+
const u = this.readFixed64Uint();
|
|
153
|
+
if (u >= 0x8000000000000000n) return u - 0x10000000000000000n;
|
|
154
|
+
return u;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Open a sub-reader over a LEN-prefixed embedded message. */
|
|
158
|
+
readMessage(): PbReader {
|
|
159
|
+
const n = this.readVarintNumber();
|
|
160
|
+
const start = this.offset;
|
|
161
|
+
const end = start + n;
|
|
162
|
+
if (end > this.end) throw new Error('PbReader: embedded message overruns buffer');
|
|
163
|
+
this.offset = end;
|
|
164
|
+
return new PbReader(this.buf, start, end);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── OTLP type aliases (deliberately match JSON shape) ─────────────────────
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Attribute value variant matching OTLP/HTTP-JSON encoding. The collector's
|
|
172
|
+
* `decodeAttrValue()` already handles these shapes; the protobuf decoder
|
|
173
|
+
* produces the same objects so no downstream change is needed.
|
|
174
|
+
*/
|
|
175
|
+
type JsonAnyValue =
|
|
176
|
+
| { stringValue: string }
|
|
177
|
+
| { intValue: string }
|
|
178
|
+
| { doubleValue: number }
|
|
179
|
+
| { boolValue: boolean }
|
|
180
|
+
| { arrayValue: { values: JsonAnyValue[] } }
|
|
181
|
+
| { kvlistValue: { values: JsonKeyValue[] } }
|
|
182
|
+
| { bytesValue: string }
|
|
183
|
+
| Record<string, never>;
|
|
184
|
+
|
|
185
|
+
interface JsonKeyValue {
|
|
186
|
+
key: string;
|
|
187
|
+
value: JsonAnyValue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
interface JsonResource {
|
|
191
|
+
attributes?: JsonKeyValue[];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
interface JsonSpan {
|
|
195
|
+
traceId?: string;
|
|
196
|
+
spanId?: string;
|
|
197
|
+
parentSpanId?: string;
|
|
198
|
+
name?: string;
|
|
199
|
+
startTimeUnixNano?: string;
|
|
200
|
+
endTimeUnixNano?: string;
|
|
201
|
+
attributes?: JsonKeyValue[];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
interface JsonScopeSpans {
|
|
205
|
+
spans?: JsonSpan[];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
interface JsonResourceSpans {
|
|
209
|
+
resource?: JsonResource;
|
|
210
|
+
scopeSpans?: JsonScopeSpans[];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface JsonExportTraceServiceRequest {
|
|
214
|
+
resourceSpans: JsonResourceSpans[];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
interface JsonNumberDataPoint {
|
|
218
|
+
attributes?: JsonKeyValue[];
|
|
219
|
+
asDouble?: number;
|
|
220
|
+
asInt?: string;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
interface JsonMetric {
|
|
224
|
+
name?: string;
|
|
225
|
+
description?: string;
|
|
226
|
+
unit?: string;
|
|
227
|
+
gauge?: { dataPoints?: JsonNumberDataPoint[] };
|
|
228
|
+
sum?: { dataPoints?: JsonNumberDataPoint[] };
|
|
229
|
+
histogram?: { dataPoints?: JsonNumberDataPoint[] };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
interface JsonScopeMetrics {
|
|
233
|
+
metrics?: JsonMetric[];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
interface JsonResourceMetrics {
|
|
237
|
+
resource?: JsonResource;
|
|
238
|
+
scopeMetrics?: JsonScopeMetrics[];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface JsonExportMetricsServiceRequest {
|
|
242
|
+
resourceMetrics: JsonResourceMetrics[];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ─── Decoders ──────────────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
/** AnyValue oneof — field numbers per opentelemetry-proto/common/v1/common.proto. */
|
|
248
|
+
function decodeAnyValue(r: PbReader): JsonAnyValue {
|
|
249
|
+
while (r.hasMore()) {
|
|
250
|
+
const { fieldNumber, wireType } = r.readTag();
|
|
251
|
+
switch (fieldNumber) {
|
|
252
|
+
case 1: // string_value
|
|
253
|
+
return { stringValue: r.readString() };
|
|
254
|
+
case 2: // bool_value (varint)
|
|
255
|
+
return { boolValue: r.readVarintNumber() !== 0 };
|
|
256
|
+
case 3: // int_value (varint, signed)
|
|
257
|
+
return { intValue: String(r.readVarint()) };
|
|
258
|
+
case 4: // double_value (fixed64 → double)
|
|
259
|
+
return { doubleValue: r.readDouble() };
|
|
260
|
+
case 5: { // array_value (ArrayValue)
|
|
261
|
+
const sub = r.readMessage();
|
|
262
|
+
const values: JsonAnyValue[] = [];
|
|
263
|
+
while (sub.hasMore()) {
|
|
264
|
+
const t = sub.readTag();
|
|
265
|
+
if (t.fieldNumber === 1 && t.wireType === WIRE_LEN) {
|
|
266
|
+
values.push(decodeAnyValue(sub.readMessage()));
|
|
267
|
+
} else {
|
|
268
|
+
sub.skip(t.wireType);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return { arrayValue: { values } };
|
|
272
|
+
}
|
|
273
|
+
case 6: { // kvlist_value (KeyValueList)
|
|
274
|
+
const sub = r.readMessage();
|
|
275
|
+
const values: JsonKeyValue[] = [];
|
|
276
|
+
while (sub.hasMore()) {
|
|
277
|
+
const t = sub.readTag();
|
|
278
|
+
if (t.fieldNumber === 1 && t.wireType === WIRE_LEN) {
|
|
279
|
+
values.push(decodeKeyValue(sub.readMessage()));
|
|
280
|
+
} else {
|
|
281
|
+
sub.skip(t.wireType);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return { kvlistValue: { values } };
|
|
285
|
+
}
|
|
286
|
+
case 7: // bytes_value
|
|
287
|
+
return { bytesValue: r.readBytesAsHex() };
|
|
288
|
+
default:
|
|
289
|
+
r.skip(wireType);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return {};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function decodeKeyValue(r: PbReader): JsonKeyValue {
|
|
296
|
+
let key = '';
|
|
297
|
+
let value: JsonAnyValue = {};
|
|
298
|
+
while (r.hasMore()) {
|
|
299
|
+
const { fieldNumber, wireType } = r.readTag();
|
|
300
|
+
if (fieldNumber === 1 && wireType === WIRE_LEN) {
|
|
301
|
+
key = r.readString();
|
|
302
|
+
} else if (fieldNumber === 2 && wireType === WIRE_LEN) {
|
|
303
|
+
value = decodeAnyValue(r.readMessage());
|
|
304
|
+
} else {
|
|
305
|
+
r.skip(wireType);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return { key, value };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function decodeAttributes(r: PbReader): JsonKeyValue[] {
|
|
312
|
+
// Reader already positioned at the START of a KeyValue message body.
|
|
313
|
+
return [decodeKeyValue(r)];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function decodeResource(r: PbReader): JsonResource {
|
|
317
|
+
const attributes: JsonKeyValue[] = [];
|
|
318
|
+
while (r.hasMore()) {
|
|
319
|
+
const { fieldNumber, wireType } = r.readTag();
|
|
320
|
+
if (fieldNumber === 1 && wireType === WIRE_LEN) {
|
|
321
|
+
attributes.push(...decodeAttributes(r.readMessage()));
|
|
322
|
+
} else {
|
|
323
|
+
r.skip(wireType);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return { attributes };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function decodeSpan(r: PbReader): JsonSpan {
|
|
330
|
+
const span: JsonSpan = {};
|
|
331
|
+
const attributes: JsonKeyValue[] = [];
|
|
332
|
+
while (r.hasMore()) {
|
|
333
|
+
const { fieldNumber, wireType } = r.readTag();
|
|
334
|
+
switch (fieldNumber) {
|
|
335
|
+
case 1: // trace_id (bytes)
|
|
336
|
+
span.traceId = r.readBytesAsHex();
|
|
337
|
+
break;
|
|
338
|
+
case 2: // span_id (bytes)
|
|
339
|
+
span.spanId = r.readBytesAsHex();
|
|
340
|
+
break;
|
|
341
|
+
case 4: { // parent_span_id (bytes)
|
|
342
|
+
const hex = r.readBytesAsHex();
|
|
343
|
+
if (hex.length > 0) span.parentSpanId = hex;
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
case 5: // name (string)
|
|
347
|
+
span.name = r.readString();
|
|
348
|
+
break;
|
|
349
|
+
case 7: // start_time_unix_nano (fixed64)
|
|
350
|
+
span.startTimeUnixNano = String(r.readFixed64Uint());
|
|
351
|
+
break;
|
|
352
|
+
case 8: // end_time_unix_nano (fixed64)
|
|
353
|
+
span.endTimeUnixNano = String(r.readFixed64Uint());
|
|
354
|
+
break;
|
|
355
|
+
case 9: // attributes (repeated KeyValue)
|
|
356
|
+
attributes.push(...decodeAttributes(r.readMessage()));
|
|
357
|
+
break;
|
|
358
|
+
default:
|
|
359
|
+
r.skip(wireType);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (attributes.length > 0) span.attributes = attributes;
|
|
363
|
+
return span;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function decodeScopeSpans(r: PbReader): JsonScopeSpans {
|
|
367
|
+
const spans: JsonSpan[] = [];
|
|
368
|
+
while (r.hasMore()) {
|
|
369
|
+
const { fieldNumber, wireType } = r.readTag();
|
|
370
|
+
if (fieldNumber === 2 && wireType === WIRE_LEN) {
|
|
371
|
+
spans.push(decodeSpan(r.readMessage()));
|
|
372
|
+
} else {
|
|
373
|
+
r.skip(wireType);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return { spans };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function decodeResourceSpans(r: PbReader): JsonResourceSpans {
|
|
380
|
+
let resource: JsonResource | undefined;
|
|
381
|
+
const scopeSpans: JsonScopeSpans[] = [];
|
|
382
|
+
while (r.hasMore()) {
|
|
383
|
+
const { fieldNumber, wireType } = r.readTag();
|
|
384
|
+
if (fieldNumber === 1 && wireType === WIRE_LEN) {
|
|
385
|
+
resource = decodeResource(r.readMessage());
|
|
386
|
+
} else if (fieldNumber === 2 && wireType === WIRE_LEN) {
|
|
387
|
+
scopeSpans.push(decodeScopeSpans(r.readMessage()));
|
|
388
|
+
} else {
|
|
389
|
+
r.skip(wireType);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const out: JsonResourceSpans = { scopeSpans };
|
|
393
|
+
if (resource) out.resource = resource;
|
|
394
|
+
return out;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Decode an OTLP ExportTraceServiceRequest protobuf payload. */
|
|
398
|
+
export function decodeExportTraceServiceRequest(
|
|
399
|
+
buf: Uint8Array,
|
|
400
|
+
): JsonExportTraceServiceRequest {
|
|
401
|
+
const r = new PbReader(buf);
|
|
402
|
+
const resourceSpans: JsonResourceSpans[] = [];
|
|
403
|
+
while (r.hasMore()) {
|
|
404
|
+
const { fieldNumber, wireType } = r.readTag();
|
|
405
|
+
if (fieldNumber === 1 && wireType === WIRE_LEN) {
|
|
406
|
+
resourceSpans.push(decodeResourceSpans(r.readMessage()));
|
|
407
|
+
} else {
|
|
408
|
+
r.skip(wireType);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return { resourceSpans };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function decodeNumberDataPoint(r: PbReader): JsonNumberDataPoint {
|
|
415
|
+
const dp: JsonNumberDataPoint = {};
|
|
416
|
+
const attributes: JsonKeyValue[] = [];
|
|
417
|
+
while (r.hasMore()) {
|
|
418
|
+
const { fieldNumber, wireType } = r.readTag();
|
|
419
|
+
switch (fieldNumber) {
|
|
420
|
+
case 4: // as_double (fixed64 → double)
|
|
421
|
+
dp.asDouble = r.readDouble();
|
|
422
|
+
break;
|
|
423
|
+
case 6: // as_int (sfixed64)
|
|
424
|
+
dp.asInt = String(r.readFixed64Int());
|
|
425
|
+
break;
|
|
426
|
+
case 7: // attributes
|
|
427
|
+
attributes.push(...decodeAttributes(r.readMessage()));
|
|
428
|
+
break;
|
|
429
|
+
default:
|
|
430
|
+
r.skip(wireType);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (attributes.length > 0) dp.attributes = attributes;
|
|
434
|
+
return dp;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function decodeDataPointContainer(r: PbReader): { dataPoints: JsonNumberDataPoint[] } {
|
|
438
|
+
const dataPoints: JsonNumberDataPoint[] = [];
|
|
439
|
+
while (r.hasMore()) {
|
|
440
|
+
const { fieldNumber, wireType } = r.readTag();
|
|
441
|
+
if (fieldNumber === 1 && wireType === WIRE_LEN) {
|
|
442
|
+
dataPoints.push(decodeNumberDataPoint(r.readMessage()));
|
|
443
|
+
} else {
|
|
444
|
+
r.skip(wireType);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return { dataPoints };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function decodeMetric(r: PbReader): JsonMetric {
|
|
451
|
+
const metric: JsonMetric = {};
|
|
452
|
+
while (r.hasMore()) {
|
|
453
|
+
const { fieldNumber, wireType } = r.readTag();
|
|
454
|
+
switch (fieldNumber) {
|
|
455
|
+
case 1: // name
|
|
456
|
+
metric.name = r.readString();
|
|
457
|
+
break;
|
|
458
|
+
case 2: // description
|
|
459
|
+
metric.description = r.readString();
|
|
460
|
+
break;
|
|
461
|
+
case 3: // unit
|
|
462
|
+
metric.unit = r.readString();
|
|
463
|
+
break;
|
|
464
|
+
case 5: // gauge
|
|
465
|
+
metric.gauge = decodeDataPointContainer(r.readMessage());
|
|
466
|
+
break;
|
|
467
|
+
case 7: // sum
|
|
468
|
+
metric.sum = decodeDataPointContainer(r.readMessage());
|
|
469
|
+
break;
|
|
470
|
+
case 9: // histogram (data points have a different shape; we only
|
|
471
|
+
// extract attributes + skip numeric value detail, which
|
|
472
|
+
// the JSON path also handles via the same DataPointContainer
|
|
473
|
+
// shape for the subset the conformance suite asserts on)
|
|
474
|
+
metric.histogram = decodeDataPointContainer(r.readMessage());
|
|
475
|
+
break;
|
|
476
|
+
default:
|
|
477
|
+
r.skip(wireType);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return metric;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function decodeScopeMetrics(r: PbReader): JsonScopeMetrics {
|
|
484
|
+
const metrics: JsonMetric[] = [];
|
|
485
|
+
while (r.hasMore()) {
|
|
486
|
+
const { fieldNumber, wireType } = r.readTag();
|
|
487
|
+
if (fieldNumber === 2 && wireType === WIRE_LEN) {
|
|
488
|
+
metrics.push(decodeMetric(r.readMessage()));
|
|
489
|
+
} else {
|
|
490
|
+
r.skip(wireType);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return { metrics };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function decodeResourceMetrics(r: PbReader): JsonResourceMetrics {
|
|
497
|
+
let resource: JsonResource | undefined;
|
|
498
|
+
const scopeMetrics: JsonScopeMetrics[] = [];
|
|
499
|
+
while (r.hasMore()) {
|
|
500
|
+
const { fieldNumber, wireType } = r.readTag();
|
|
501
|
+
if (fieldNumber === 1 && wireType === WIRE_LEN) {
|
|
502
|
+
resource = decodeResource(r.readMessage());
|
|
503
|
+
} else if (fieldNumber === 2 && wireType === WIRE_LEN) {
|
|
504
|
+
scopeMetrics.push(decodeScopeMetrics(r.readMessage()));
|
|
505
|
+
} else {
|
|
506
|
+
r.skip(wireType);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
const out: JsonResourceMetrics = { scopeMetrics };
|
|
510
|
+
if (resource) out.resource = resource;
|
|
511
|
+
return out;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** Decode an OTLP ExportMetricsServiceRequest protobuf payload. */
|
|
515
|
+
export function decodeExportMetricsServiceRequest(
|
|
516
|
+
buf: Uint8Array,
|
|
517
|
+
): JsonExportMetricsServiceRequest {
|
|
518
|
+
const r = new PbReader(buf);
|
|
519
|
+
const resourceMetrics: JsonResourceMetrics[] = [];
|
|
520
|
+
while (r.hasMore()) {
|
|
521
|
+
const { fieldNumber, wireType } = r.readTag();
|
|
522
|
+
if (fieldNumber === 1 && wireType === WIRE_LEN) {
|
|
523
|
+
resourceMetrics.push(decodeResourceMetrics(r.readMessage()));
|
|
524
|
+
} else {
|
|
525
|
+
r.skip(wireType);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return { resourceMetrics };
|
|
529
|
+
}
|