feat(core-shared): add Analyzed brand check to assertFeatureConformance

Mirrors the existing Audited check: when a use case declares
analyticsEvents.length > 0 and the bound function lacks the __analyzed
brand, assertFeatureConformance throws ConformanceError at boot time.

Adds three synthetic conformance tests: passes when brand present +
events declared, throws naming Analyzed when events declared + brand
missing, passes when events empty + brand absent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 15:19:29 +00:00
parent 81f75f756e
commit 92b17f7d64
3 changed files with 132 additions and 7 deletions

View File

@@ -1,6 +1,6 @@
{
"generatedAt": "2026-05-18T11:50:20.112Z",
"commit": "a5ab698",
"generatedAt": "2026-05-18T15:19:07.787Z",
"commit": "81f75f7",
"repo": {
"statements": 95.89,
"branches": 89.07,

View File

@@ -6,6 +6,7 @@ import { ConformanceError } from "@/conformance/conformance-error";
import { assertFeatureConformance } from "@/conformance/assert-bindings";
import { withSpan } from "@/instrumentation/with-span";
import { withCapture } from "@/instrumentation/with-capture";
import { attachBrand } from "@/conformance/brand-runtime";
import type { ITracer, ISpan } from "@/instrumentation/tracer.interface";
import type { ILogger } from "@/instrumentation/logger.interface";
import type { BindContext } from "@/di/bind-context";
@@ -40,7 +41,11 @@ describe("assertFeatureConformance", () => {
const bound = withSpan(
ctx.tracer,
{ name: "test.signIn", op: "use-case" },
withCapture(ctx.logger, { feature: "test", layer: "use-case" }, async (x: number) => x + 1),
withCapture(
ctx.logger,
{ feature: "test", layer: "use-case" },
async (x: number) => x + 1,
),
);
container.bind(sym).toConstantValue(bound);
@@ -185,9 +190,9 @@ describe("assertFeatureConformance", () => {
realtimeChannels: [],
jobs: [],
} as const);
expect(() => assertFeatureConformance(container, manifest, {}, ctx)).toThrow(
/no symbol provided/,
);
expect(() =>
assertFeatureConformance(container, manifest, {}, ctx),
).toThrow(/no symbol provided/);
});
it("throws when the container cannot resolve the symbol", () => {
@@ -207,4 +212,112 @@ describe("assertFeatureConformance", () => {
assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
).toThrow(ConformanceError);
});
it("passes when analyticsEvents declared and binding carries __analyzed brand", () => {
const container = new Container();
const sym = Symbol("test.trackClick");
const ctx = makeCtx();
const base = withSpan(
ctx.tracer,
{ name: "test.trackClick", op: "use-case" },
withCapture(
ctx.logger,
{ feature: "test", layer: "use-case" },
async (x: number) => x,
),
);
attachBrand(base, "__analyzed");
container.bind(sym).toConstantValue(base);
const manifest = defineFeature({
name: "test",
requiredCores: [],
useCases: {
trackClick: {
mutates: false,
audits: [],
publishes: [],
consumes: [],
analyticsEvents: ["button.clicked"],
},
},
realtimeChannels: [],
jobs: [],
} as const);
expect(() =>
assertFeatureConformance(container, manifest, { trackClick: sym }, ctx),
).not.toThrow();
});
it("throws when analyticsEvents declared but binding is missing __analyzed brand", () => {
const container = new Container();
const sym = Symbol("test.trackClick");
const ctx = makeCtx();
const wrappedNoAnalytics = withSpan(
ctx.tracer,
{ name: "test.trackClick", op: "use-case" },
withCapture(ctx.logger, { feature: "test" }, async (x: number) => x),
);
container.bind(sym).toConstantValue(wrappedNoAnalytics);
const manifest = defineFeature({
name: "test",
requiredCores: [],
useCases: {
trackClick: {
mutates: false,
audits: [],
publishes: [],
consumes: [],
analyticsEvents: ["button.clicked"],
},
},
realtimeChannels: [],
jobs: [],
} as const);
expect(() =>
assertFeatureConformance(container, manifest, { trackClick: sym }, ctx),
).toThrow(ConformanceError);
expect(() =>
assertFeatureConformance(container, manifest, { trackClick: sym }, ctx),
).toThrow(/Analyzed|__analyzed/);
});
it("passes when analyticsEvents is empty and __analyzed brand is absent", () => {
const container = new Container();
const sym = Symbol("test.signIn");
const ctx = makeCtx();
const bound = withSpan(
ctx.tracer,
{ name: "test.signIn", op: "use-case" },
withCapture(
ctx.logger,
{ feature: "test", layer: "use-case" },
async (x: number) => x,
),
);
container.bind(sym).toConstantValue(bound);
const manifest = defineFeature({
name: "test",
requiredCores: [],
useCases: {
signIn: {
mutates: false,
audits: [],
publishes: [],
consumes: [],
analyticsEvents: [],
},
},
realtimeChannels: [],
jobs: [],
} as const);
expect(() =>
assertFeatureConformance(container, manifest, { signIn: sym }, ctx),
).not.toThrow();
});
});

View File

@@ -1,6 +1,11 @@
import type { Container } from "inversify";
import type { FeatureManifest } from "./define-feature";
import { isInstrumented, isCaptured, isAudited } from "./brand-runtime";
import {
isInstrumented,
isCaptured,
isAudited,
isAnalyzed,
} from "./brand-runtime";
import { ConformanceError } from "./conformance-error";
import type { BindContext } from "../di/bind-context";
@@ -58,5 +63,12 @@ export function assertFeatureConformance(
);
}
}
if ((useCase.analyticsEvents?.length ?? 0) > 0) {
if (!isAnalyzed(bound)) {
throw new ConformanceError(
`${manifest.name}.${name}: declares analyticsEvents but binding is missing __analyzed brand — was withAnalytics applied at bind time?`,
);
}
}
}
}