feat(core-shared): extend wireUseCase with analytics arg and Analyzed brand propagation

Add AnalyticsProtocol to bind-protocols, extend WireUseCaseOptions with
optional analytics field, and compose the __analyzed brand inline in
wireUseCase (innermost, before withAudit) when analytics is provided.

Propagate __analyzed through withCapture and withSpan PROPAGATED_BRANDS
so the outermost container binding carries the brand for boot-time
assertion checks (Story 05).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 15:12:57 +00:00
parent ff1e0b052c
commit 32018e8a7b
5 changed files with 149 additions and 9 deletions

View File

@@ -6,6 +6,7 @@ import {
isInstrumented,
isCaptured,
isAudited,
isAnalyzed,
} from "@/conformance/brand-runtime";
import type {
ITracer,
@@ -13,7 +14,7 @@ import type {
SpanOpts,
} from "@/instrumentation/tracer.interface";
import type { ILogger } from "@/instrumentation/logger.interface";
import type { AuditLogProtocol } from "@/di/bind-protocols";
import type { AuditLogProtocol, AnalyticsProtocol } from "@/di/bind-protocols";
function makeTracer() {
const calls: SpanOpts[] = [];
@@ -42,6 +43,10 @@ function makeAuditLog(): AuditLogProtocol {
return { record: vi.fn() };
}
function makeAnalytics(): AnalyticsProtocol {
return { track: vi.fn() };
}
const doubleFactory = () => async (x: number) => x * 2;
describe("wireUseCase — no-audit path", () => {
@@ -241,6 +246,107 @@ describe("wireUseCase — container binding", () => {
});
});
describe("wireUseCase — analytics path", () => {
it("attaches Analyzed brand when analytics is provided", () => {
const { tracer } = makeTracer();
const logger = makeLogger();
const container = new Container();
const sym = Symbol("test.double");
const analytics = makeAnalytics();
const wired = wireUseCase({
container,
symbol: sym,
factory: doubleFactory,
deps: [],
feature: "test",
layer: "use-case",
name: "double",
tracer,
logger,
analytics,
});
expect(isAnalyzed(wired)).toBe(true);
expect(isInstrumented(wired)).toBe(true);
expect(isCaptured(wired)).toBe(true);
});
it("attaches Analyzed + Audited brands when both analytics and auditLog are provided", () => {
const { tracer } = makeTracer();
const logger = makeLogger();
const container = new Container();
const sym = Symbol("test.double");
const analytics = makeAnalytics();
const auditLog = makeAuditLog();
const wired = wireUseCase({
container,
symbol: sym,
factory: doubleFactory,
deps: [],
feature: "test",
layer: "use-case",
name: "double",
tracer,
logger,
analytics,
auditLog,
});
expect(isAnalyzed(wired)).toBe(true);
expect(isAudited(wired)).toBe(true);
expect(isInstrumented(wired)).toBe(true);
expect(isCaptured(wired)).toBe(true);
});
it("executes the factory result on invocation (analytics path)", async () => {
const { tracer } = makeTracer();
const logger = makeLogger();
const container = new Container();
const sym = Symbol("test.double");
const analytics = makeAnalytics();
const wired = wireUseCase({
container,
symbol: sym,
factory: doubleFactory,
deps: [],
feature: "test",
layer: "use-case",
name: "double",
tracer,
logger,
analytics,
});
await expect(wired(7)).resolves.toBe(14);
});
});
describe("wireUseCase — no-analytics path", () => {
it("does not attach Analyzed brand when analytics is absent", () => {
const { tracer } = makeTracer();
const logger = makeLogger();
const container = new Container();
const sym = Symbol("test.double");
const wired = wireUseCase({
container,
symbol: sym,
factory: doubleFactory,
deps: [],
feature: "test",
layer: "use-case",
name: "double",
tracer,
logger,
});
expect(isAnalyzed(wired)).toBe(false);
});
});
describe("wireUseCase — idempotent re-bind", () => {
it("unbinds the existing binding and replaces it when the symbol is already bound", () => {
const { tracer } = makeTracer();