diff --git a/packages/core-shared/src/instrumentation/with-span.test.ts b/packages/core-shared/src/instrumentation/with-span.test.ts index 91ccac4..1667cbd 100644 --- a/packages/core-shared/src/instrumentation/with-span.test.ts +++ b/packages/core-shared/src/instrumentation/with-span.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect, expectTypeOf, vi } from "vitest"; import { withSpan } from "@/instrumentation/with-span"; import type { ITracer, ISpan, SpanOpts } from "@/instrumentation/tracer.interface"; import type { Instrumented } from "@/conformance/brands"; +import { isInstrumented } from "@/conformance/brand-runtime"; function makeRecordingTracer() { const calls: SpanOpts[] = []; @@ -70,3 +71,13 @@ describe("withSpan — brand", () => { expectTypeOf(wrapped).toMatchTypeOf>(); }); }); + +describe("withSpan — runtime brand", () => { + it("attaches __instrumented as a non-enumerable property on the wrapped function", async () => { + const { tracer } = makeRecordingTracer(); + const fn = async (a: number) => a + 1; + const wrapped = withSpan(tracer, { name: "test.brand", op: "use-case" }, fn); + expect(isInstrumented(wrapped)).toBe(true); + expect(Object.keys(wrapped)).not.toContain("__instrumented"); + }); +}); diff --git a/packages/core-shared/src/instrumentation/with-span.ts b/packages/core-shared/src/instrumentation/with-span.ts index 0685b0c..839a4ea 100644 --- a/packages/core-shared/src/instrumentation/with-span.ts +++ b/packages/core-shared/src/instrumentation/with-span.ts @@ -1,5 +1,6 @@ import type { ITracer, SpanOpts } from "./tracer.interface"; import type { Instrumented } from "../conformance/brands"; +import { attachBrand } from "../conformance/brand-runtime"; export function withSpan( tracer: ITracer, @@ -20,7 +21,8 @@ export function withSpan( const resolved = typeof opts === "function" ? opts(args) : opts; return tracer.startSpan(resolved, () => fn(...args)); }; - // Cast is the only runtime concession — the brand is a phantom type; - // there is no real `__instrumented` property at runtime. + attachBrand(wrapped, "__instrumented"); + // Cast is the type-level concession — the brand is now also a non-enumerable + // runtime property attached above by `attachBrand`. return wrapped as Instrumented<(...args: Args) => Promise>; }