@openwop/openwop-conformance 1.153.0 → 1.154.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 +8 -0
- package/README.md +9 -0
- package/dist/cli.js +7 -2
- package/package.json +31 -2
- package/schemas/CORPUS-STAMP.json +99 -3
- package/src/cli.ts +7 -5
- package/src/global-setup.ts +13 -0
- package/src/lib/corpus-stamp.ts +125 -0
- package/src/lib/capabilities-auth-subject-link.test.ts +0 -103
- package/src/lib/fork-availability.test.ts +0 -69
- package/src/lib/global-setup.test.ts +0 -76
- package/src/lib/grpc-framing.test.ts +0 -96
- package/src/lib/oidc-issuer.test.ts +0 -328
- package/src/lib/otel-collector-grpc.test.ts +0 -191
- package/src/lib/otel-collector.test.ts +0 -303
- package/src/lib/otlp-protobuf.test.ts +0 -461
- package/src/lib/polling.test.ts +0 -80
- package/src/lib/requirement-ids.test.ts +0 -83
- package/src/lib/requirement-ledger.test.ts +0 -75
- package/src/lib/risk-disposition.test.ts +0 -91
- package/src/lib/saml-idp.test.ts +0 -127
- package/src/lib/spec-coherence-registry.test.ts +0 -155
- package/src/lib/webhook-receiver.test.ts +0 -144
- package/src/scenarios/artifact-schema-compile-bounded.test.ts +0 -126
- package/src/scenarios/artifact-type-legacy-ids.test.ts +0 -124
- package/src/scenarios/capability-example-root-layout.test.ts +0 -272
- package/src/scenarios/certification-floor-enforcement.test.ts +0 -204
- package/src/scenarios/chain-subchain-unsupported-refused.test.ts +0 -70
- package/src/scenarios/compensation-profile.test.ts +0 -340
- package/src/scenarios/core-manifest-and-extension-registry.test.ts +0 -250
- package/src/scenarios/discovery-canonical-family-no-shadow.test.ts +0 -219
- package/src/scenarios/edge-condition-truthy-falsy.test.ts +0 -108
- package/src/scenarios/effect-identity-composition.test.ts +0 -129
- package/src/scenarios/effect-identity-cross-scope.test.ts +0 -82
- package/src/scenarios/error-envelope-canonical-shape.test.ts +0 -64
- package/src/scenarios/form-content-packs.test.ts +0 -415
- package/src/scenarios/multi-region-effect-vocabulary.test.ts +0 -175
- package/src/scenarios/normative-example-extraction.test.ts +0 -242
- package/src/scenarios/openapi-asyncapi-sdk-parity.test.ts +0 -309
- package/src/scenarios/pack-manifest-extensions.test.ts +0 -203
- package/src/scenarios/protocol-version-grammar.test.ts +0 -119
- package/src/scenarios/registry-declarative-kinds.test.ts +0 -121
- package/src/scenarios/rfc-0147-self-audit.test.ts +0 -104
- package/src/scenarios/rfc-lifecycle-coherence.test.ts +0 -215
- package/src/scenarios/semantic-digest-v2.test.ts +0 -128
- package/src/scenarios/spec-corpus-validity.test.ts +0 -1727
- package/src/scenarios/spec-section-citations.test.ts +0 -132
- package/src/scenarios/tool-result-trust-monotone.test.ts +0 -168
- package/src/scenarios/versioned-composition-profiles.test.ts +0 -201
- package/src/scenarios/workflow-chain-internal-flag.test.ts +0 -84
- package/src/scenarios/workload-identity-profile.test.ts +0 -184
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* End-to-end unit tests for the OTel collector's HTTP receiver.
|
|
3
|
-
*
|
|
4
|
-
* Boots the collector on an ephemeral port, posts synthesized OTLP
|
|
5
|
-
* payloads (both JSON and protobuf), and asserts the collector
|
|
6
|
-
* correctly captures them. Closes the gap the senior code-review pass
|
|
7
|
-
* flagged as MEDIUM-3 — the protobuf decoder has 18 unit tests, but
|
|
8
|
-
* those don't exercise the collector's HTTP receive wiring
|
|
9
|
-
* (content-type routing, body-size guard, error responses).
|
|
10
|
-
*
|
|
11
|
-
* Server-free (binds to 127.0.0.1 on an ephemeral port; no host required).
|
|
12
|
-
*
|
|
13
|
-
* @see conformance/src/lib/otel-collector.ts
|
|
14
|
-
* @see conformance/src/lib/otlp-protobuf.ts
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { afterAll, beforeAll, describe, it, expect } from 'vitest';
|
|
18
|
-
import { OtelCollector } from './otel-collector.js';
|
|
19
|
-
|
|
20
|
-
// ─── Minimal in-test OTLP/protobuf encoder ─────────────────────────────────
|
|
21
|
-
// Hand-rolled so the e2e test doesn't depend on the decoder's own test file
|
|
22
|
-
// re-exporting its writer. ~50 LOC; only encodes the wire-format subset this
|
|
23
|
-
// file actually emits.
|
|
24
|
-
|
|
25
|
-
const WIRE_I64 = 1;
|
|
26
|
-
const WIRE_LEN = 2;
|
|
27
|
-
|
|
28
|
-
function encVarint(out: number[], v: number | bigint): void {
|
|
29
|
-
let x = typeof v === 'bigint' ? v : BigInt(v);
|
|
30
|
-
while (x >= 0x80n) {
|
|
31
|
-
out.push(Number(x & 0x7fn) | 0x80);
|
|
32
|
-
x >>= 7n;
|
|
33
|
-
}
|
|
34
|
-
out.push(Number(x & 0x7fn));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function encTag(out: number[], field: number, wire: number): void {
|
|
38
|
-
encVarint(out, (field << 3) | wire);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function encString(out: number[], field: number, s: string): void {
|
|
42
|
-
const bytes = new TextEncoder().encode(s);
|
|
43
|
-
encTag(out, field, WIRE_LEN);
|
|
44
|
-
encVarint(out, bytes.length);
|
|
45
|
-
for (const b of bytes) out.push(b);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function encBytesHex(out: number[], field: number, hex: string): void {
|
|
49
|
-
const len = hex.length / 2;
|
|
50
|
-
encTag(out, field, WIRE_LEN);
|
|
51
|
-
encVarint(out, len);
|
|
52
|
-
for (let i = 0; i < len; i++) {
|
|
53
|
-
out.push(Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16));
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function encFixed64(out: number[], field: number, v: bigint): void {
|
|
58
|
-
encTag(out, field, WIRE_I64);
|
|
59
|
-
const buf = new ArrayBuffer(8);
|
|
60
|
-
new DataView(buf).setBigUint64(0, v, true);
|
|
61
|
-
for (let i = 0; i < 8; i++) out.push(new Uint8Array(buf)[i]);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function encMessage(out: number[], field: number, body: number[]): void {
|
|
65
|
-
encTag(out, field, WIRE_LEN);
|
|
66
|
-
encVarint(out, body.length);
|
|
67
|
-
for (const b of body) out.push(b);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function buildMinimalProtobufExportTrace(spanName: string, traceIdHex: string, runIdAttr: string): Uint8Array {
|
|
71
|
-
// KeyValue { key: "openwop.run_id", value: { stringValue: runIdAttr } }
|
|
72
|
-
const anyValue: number[] = [];
|
|
73
|
-
encString(anyValue, 1, runIdAttr);
|
|
74
|
-
const kv: number[] = [];
|
|
75
|
-
encString(kv, 1, 'openwop.run_id');
|
|
76
|
-
encMessage(kv, 2, anyValue);
|
|
77
|
-
|
|
78
|
-
// Span { trace_id, span_id, name, start, end, attributes }
|
|
79
|
-
const span: number[] = [];
|
|
80
|
-
encBytesHex(span, 1, traceIdHex);
|
|
81
|
-
encBytesHex(span, 2, '0123456789abcdef');
|
|
82
|
-
encString(span, 5, spanName);
|
|
83
|
-
encFixed64(span, 7, 1700000000000000000n);
|
|
84
|
-
encFixed64(span, 8, 1700000000100000000n);
|
|
85
|
-
encMessage(span, 9, kv);
|
|
86
|
-
|
|
87
|
-
// ScopeSpans { spans: [span] }
|
|
88
|
-
const scopeSpans: number[] = [];
|
|
89
|
-
encMessage(scopeSpans, 2, span);
|
|
90
|
-
|
|
91
|
-
// ResourceSpans { scope_spans: [scopeSpans] }
|
|
92
|
-
const resourceSpans: number[] = [];
|
|
93
|
-
encMessage(resourceSpans, 2, scopeSpans);
|
|
94
|
-
|
|
95
|
-
// ExportTraceServiceRequest { resource_spans: [resourceSpans] }
|
|
96
|
-
const req: number[] = [];
|
|
97
|
-
encMessage(req, 1, resourceSpans);
|
|
98
|
-
|
|
99
|
-
return new Uint8Array(req);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ─── Test fixture ──────────────────────────────────────────────────────────
|
|
103
|
-
|
|
104
|
-
let collector: OtelCollector;
|
|
105
|
-
let endpoint: string;
|
|
106
|
-
|
|
107
|
-
beforeAll(async () => {
|
|
108
|
-
collector = new OtelCollector();
|
|
109
|
-
await collector.start(0);
|
|
110
|
-
endpoint = collector.endpoint();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
afterAll(async () => {
|
|
114
|
-
await collector.stop();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// ─── Tests ─────────────────────────────────────────────────────────────────
|
|
118
|
-
|
|
119
|
-
describe('OtelCollector: HTTP receiver wiring', () => {
|
|
120
|
-
it('accepts OTLP/HTTP-protobuf POST on /v1/traces and captures spans', async () => {
|
|
121
|
-
collector.reset();
|
|
122
|
-
const body = buildMinimalProtobufExportTrace(
|
|
123
|
-
'openwop.run',
|
|
124
|
-
'0123456789abcdef0123456789abcdef',
|
|
125
|
-
'run-pb-e2e',
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
const res = await fetch(`${endpoint}/v1/traces`, {
|
|
129
|
-
method: 'POST',
|
|
130
|
-
headers: { 'Content-Type': 'application/x-protobuf' },
|
|
131
|
-
body,
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
expect(res.status).toBe(200);
|
|
135
|
-
|
|
136
|
-
const captured = collector.spansWithAttribute('openwop.run_id', 'run-pb-e2e');
|
|
137
|
-
expect(captured.length).toBe(1);
|
|
138
|
-
expect(captured[0].name).toBe('openwop.run');
|
|
139
|
-
expect(captured[0].traceId).toBe('0123456789abcdef0123456789abcdef');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('accepts OTLP/HTTP-JSON POST on /v1/traces and captures spans', async () => {
|
|
143
|
-
collector.reset();
|
|
144
|
-
const payload = {
|
|
145
|
-
resourceSpans: [
|
|
146
|
-
{
|
|
147
|
-
resource: { attributes: [] },
|
|
148
|
-
scopeSpans: [
|
|
149
|
-
{
|
|
150
|
-
spans: [
|
|
151
|
-
{
|
|
152
|
-
traceId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
153
|
-
spanId: 'bbbbbbbbbbbbbbbb',
|
|
154
|
-
name: 'openwop.run',
|
|
155
|
-
startTimeUnixNano: '1700000000000000000',
|
|
156
|
-
endTimeUnixNano: '1700000000050000000',
|
|
157
|
-
attributes: [
|
|
158
|
-
{ key: 'openwop.run_id', value: { stringValue: 'run-json-e2e' } },
|
|
159
|
-
],
|
|
160
|
-
},
|
|
161
|
-
],
|
|
162
|
-
},
|
|
163
|
-
],
|
|
164
|
-
},
|
|
165
|
-
],
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const res = await fetch(`${endpoint}/v1/traces`, {
|
|
169
|
-
method: 'POST',
|
|
170
|
-
headers: { 'Content-Type': 'application/json' },
|
|
171
|
-
body: JSON.stringify(payload),
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
expect(res.status).toBe(200);
|
|
175
|
-
|
|
176
|
-
const captured = collector.spansWithAttribute('openwop.run_id', 'run-json-e2e');
|
|
177
|
-
expect(captured.length).toBe(1);
|
|
178
|
-
expect(captured[0].name).toBe('openwop.run');
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
// Note: the collector also accepts "no Content-Type" as JSON for
|
|
182
|
-
// back-compat with non-spec OTLP clients. fetch() can't reproduce
|
|
183
|
-
// that case — Node automatically sets Content-Type when given a body
|
|
184
|
-
// — so the empty-content-type path is exercised only via direct
|
|
185
|
-
// node:http use (out of scope for this e2e suite).
|
|
186
|
-
|
|
187
|
-
it('returns 415 for an unsupported Content-Type', async () => {
|
|
188
|
-
collector.reset();
|
|
189
|
-
const res = await fetch(`${endpoint}/v1/traces`, {
|
|
190
|
-
method: 'POST',
|
|
191
|
-
headers: { 'Content-Type': 'text/csv' },
|
|
192
|
-
body: 'a,b,c\n1,2,3',
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
expect(res.status).toBe(415);
|
|
196
|
-
const body = (await res.json()) as { error?: string; message?: string };
|
|
197
|
-
expect(body.error).toBe('unsupported_media_type');
|
|
198
|
-
expect(body.message).toContain('text/csv');
|
|
199
|
-
expect(collector.spans().length).toBe(0);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('returns 400 for malformed JSON', async () => {
|
|
203
|
-
collector.reset();
|
|
204
|
-
const res = await fetch(`${endpoint}/v1/traces`, {
|
|
205
|
-
method: 'POST',
|
|
206
|
-
headers: { 'Content-Type': 'application/json' },
|
|
207
|
-
body: '{ this is not valid json',
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
expect(res.status).toBe(400);
|
|
211
|
-
const body = (await res.json()) as { error?: string };
|
|
212
|
-
expect(body.error).toBe('invalid_json');
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('returns 400 for malformed protobuf', async () => {
|
|
216
|
-
collector.reset();
|
|
217
|
-
// Garbage bytes — first byte is a tag for field 0 (invalid) which the
|
|
218
|
-
// decoder skips, but the second byte is mid-varint with continuation
|
|
219
|
-
// bit set and no follow-up → readVarint throws on unexpected EOF.
|
|
220
|
-
const res = await fetch(`${endpoint}/v1/traces`, {
|
|
221
|
-
method: 'POST',
|
|
222
|
-
headers: { 'Content-Type': 'application/x-protobuf' },
|
|
223
|
-
body: new Uint8Array([0x0a, 0xff]),
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
expect(res.status).toBe(400);
|
|
227
|
-
const body = (await res.json()) as { error?: string };
|
|
228
|
-
expect(body.error).toBe('invalid_protobuf');
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('returns 405 for non-POST methods', async () => {
|
|
232
|
-
collector.reset();
|
|
233
|
-
const res = await fetch(`${endpoint}/v1/traces`, { method: 'GET' });
|
|
234
|
-
expect(res.status).toBe(405);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it('returns 413 when body exceeds 16 MiB cap', async () => {
|
|
238
|
-
collector.reset();
|
|
239
|
-
// 16 MiB + 1 byte. Use a fresh ArrayBuffer to avoid TypedArray-cap issues.
|
|
240
|
-
const oversize = new Uint8Array(16 * 1024 * 1024 + 1);
|
|
241
|
-
oversize.fill(0x00);
|
|
242
|
-
|
|
243
|
-
const res = await fetch(`${endpoint}/v1/traces`, {
|
|
244
|
-
method: 'POST',
|
|
245
|
-
headers: { 'Content-Type': 'application/x-protobuf' },
|
|
246
|
-
body: oversize,
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
expect(res.status).toBe(413);
|
|
250
|
-
const body = (await res.json()) as { error?: string };
|
|
251
|
-
expect(body.error).toBe('payload_too_large');
|
|
252
|
-
}, 30_000); // larger timeout — uploading 16 MiB to localhost still costs a few hundred ms
|
|
253
|
-
|
|
254
|
-
it('routes /v1/metrics to the metrics ingest path (JSON)', async () => {
|
|
255
|
-
collector.reset();
|
|
256
|
-
const payload = {
|
|
257
|
-
resourceMetrics: [
|
|
258
|
-
{
|
|
259
|
-
scopeMetrics: [
|
|
260
|
-
{
|
|
261
|
-
metrics: [
|
|
262
|
-
{
|
|
263
|
-
name: 'openwop.queue.depth',
|
|
264
|
-
unit: 'count',
|
|
265
|
-
gauge: {
|
|
266
|
-
dataPoints: [
|
|
267
|
-
{
|
|
268
|
-
asDouble: 3,
|
|
269
|
-
attributes: [],
|
|
270
|
-
},
|
|
271
|
-
],
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
],
|
|
275
|
-
},
|
|
276
|
-
],
|
|
277
|
-
},
|
|
278
|
-
],
|
|
279
|
-
};
|
|
280
|
-
const res = await fetch(`${endpoint}/v1/metrics`, {
|
|
281
|
-
method: 'POST',
|
|
282
|
-
headers: { 'Content-Type': 'application/json' },
|
|
283
|
-
body: JSON.stringify(payload),
|
|
284
|
-
});
|
|
285
|
-
expect(res.status).toBe(200);
|
|
286
|
-
const m = collector.metricByName('openwop.queue.depth');
|
|
287
|
-
expect(m).toBeDefined();
|
|
288
|
-
expect(m?.kind).toBe('gauge');
|
|
289
|
-
expect(m?.dataPoint.value).toBe(3);
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it('200-OKs unknown paths without ingesting (forward-compat for /v1/logs)', async () => {
|
|
293
|
-
collector.reset();
|
|
294
|
-
const res = await fetch(`${endpoint}/v1/logs`, {
|
|
295
|
-
method: 'POST',
|
|
296
|
-
headers: { 'Content-Type': 'application/json' },
|
|
297
|
-
body: '{}',
|
|
298
|
-
});
|
|
299
|
-
expect(res.status).toBe(200);
|
|
300
|
-
expect(collector.spans().length).toBe(0);
|
|
301
|
-
expect(collector.metrics().length).toBe(0);
|
|
302
|
-
});
|
|
303
|
-
});
|