From 6593861b42757ad6d67eb2fe5df12796ede0d639 Mon Sep 17 00:00:00 2001 From: Cid-oe Date: Wed, 9 Sep 2026 23:13:46 +0530 Subject: [PATCH] fix(adapter-opentelemetry): convert HTTP SERVER spans to access serve --- packages/adapter-opentelemetry/src/index.ts | 22 +++++++-- tests/official-plugins.test.mjs | 50 +++++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/packages/adapter-opentelemetry/src/index.ts b/packages/adapter-opentelemetry/src/index.ts index 40aded7..da5f50b 100644 --- a/packages/adapter-opentelemetry/src/index.ts +++ b/packages/adapter-opentelemetry/src/index.ts @@ -16,6 +16,7 @@ export type OpenTelemetryEvidenceOptions = { type SpanLike = { name?: unknown; + kind?: unknown; attributes?: unknown; }; @@ -38,11 +39,19 @@ function normalizeKind(value: string | undefined): ResourceContractKind | undefi return undefined; } -function normalizeOperation(kind: ResourceContractKind, value: string | undefined): ResourceAccessMode { +function isServerSpanKind(kind: unknown): boolean { + return kind === 2 || kind === "2" || kind === "SPAN_KIND_SERVER" || kind === "SERVER"; +} + +function normalizeOperation(kind: ResourceContractKind, value: string | undefined, spanKind?: unknown): ResourceAccessMode { if (value === "read" || value === "write" || value === "publish" || value === "subscribe" || value === "call" || value === "serve") return value; if (kind === "database") return value && /insert|update|delete|write|upsert|merge|truncate|drop|create|alter|replace|grant|revoke/i.test(value) ? "write" : "read"; if (kind === "queue") return value && /receive|consume|subscribe/i.test(value) ? "subscribe" : "publish"; - if (kind === "http") return value && /server|serve/i.test(value) ? "serve" : "call"; + if (kind === "http") { + if (value && /server|serve/i.test(value)) return "serve"; + if (isServerSpanKind(spanKind)) return "serve"; + return "call"; + } return value && /write/i.test(value) ? "write" : "read"; } @@ -99,7 +108,14 @@ function accessFromSpan(span: SpanLike, options: OpenTelemetryEvidenceOptions): if (!selector) return undefined; return { kind: inferredKind, - access: normalizeOperation(inferredKind, stringAttribute(attributes, "cellfence.resource.operation") || stringAttribute(attributes, "db.operation") || stringAttribute(attributes, "messaging.operation") || stringAttribute(attributes, "http.request.method")), + access: normalizeOperation( + inferredKind, + stringAttribute(attributes, "cellfence.resource.operation") + || stringAttribute(attributes, "db.operation") + || stringAttribute(attributes, "messaging.operation") + || stringAttribute(attributes, "http.request.method"), + span.kind, + ), selector, cellId: stringAttribute(attributes, "cellfence.cell") || stringAttribute(attributes, "cell.id") || stringAttribute(attributes, "service.name") || options.defaultCellId, observedAt: stringAttribute(attributes, "time") || options.generatedAt, diff --git a/tests/official-plugins.test.mjs b/tests/official-plugins.test.mjs index c82fe26..2dd3b95 100644 --- a/tests/official-plugins.test.mjs +++ b/tests/official-plugins.test.mjs @@ -2465,6 +2465,56 @@ test("opentelemetry adapter ignores non-string wrapped values without throwing", }]); }); +test("opentelemetry adapter converts HTTP SERVER spans to access: serve", () => { + const evidence = openTelemetryToResourceEvidence({ + resourceSpans: [{ + scopeSpans: [{ + spans: [ + { + name: "GET /users", + kind: 2, + attributes: [ + { key: "http.route", value: { stringValue: "/users" } }, + { key: "http.request.method", value: { stringValue: "GET" } }, + ], + }, + { + name: "POST /orders", + kind: "SPAN_KIND_SERVER", + attributes: [ + { key: "http.route", value: { stringValue: "/orders" } }, + { key: "http.request.method", value: { stringValue: "POST" } }, + ], + }, + { + name: "GET /client-users", + kind: 3, + attributes: [ + { key: "http.route", value: { stringValue: "/client-users" } }, + { key: "http.request.method", value: { stringValue: "GET" } }, + ], + }, + { + name: "GET /explicit-override", + kind: 2, + attributes: [ + { key: "http.route", value: { stringValue: "/explicit-override" } }, + { key: "cellfence.resource.operation", value: { stringValue: "call" } }, + ], + }, + ], + }], + }], + }, { generatedAt: "2026-01-01T00:00:00.000Z" }); + + assert.deepEqual(evidence.accesses.map((access) => `${access.kind}:${access.access}:${access.selector}`), [ + "http:serve:/users", + "http:serve:/orders", + "http:call:/client-users", + "http:call:/explicit-override", + ]); +}); + test("declarative call-pattern adapter records dynamic resource arguments as unresolved", () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "cellfence-call-pattern-dynamic-")); try {