import type { Container } from "inversify"; import type { FeatureManifest } from "./define-feature"; import { isInstrumented, isCaptured, isAudited } from "./brand-runtime"; import { ConformanceError } from "./conformance-error"; import type { BindContext } from "../di/bind-context"; /** * Runtime check that every manifest-declared use case is bound through the * brand-attaching wrappers (`withSpan` → `__instrumented`, * `withCapture` → `__captured`, `withAudit` → `__audited` when required). * * Called at the tail of each feature's `bindProductionX(ctx)` so: * - `pnpm dev` refuses to boot on drift (synchronous throw) * - The check runs once per feature, not per request * - Future apps that wire `bindProductionX` inherit the check for free * * The `symbols` map is declared inline by the feature's binder; the manifest * holds the contract, the binder holds the container symbols. This keeps * the manifest free of DI-coupling while letting each feature declare its * own wiring keys. */ export function assertFeatureConformance( container: Container, manifest: FeatureManifest, symbols: Record, _ctx: BindContext, ): void { void _ctx; // future: also check ctx.bus / ctx.auditLog presence vs requiredCores for (const [name, useCase] of Object.entries(manifest.useCases)) { const sym = symbols[name]; if (!sym) { throw new ConformanceError( `${manifest.name}.${name}: no symbol provided in symbols map`, ); } let bound: unknown; try { bound = container.get(sym); } catch (cause) { throw new ConformanceError( `${manifest.name}.${name}: container could not resolve symbol (${String(cause)})`, ); } if (!isInstrumented(bound)) { throw new ConformanceError( `${manifest.name}.${name}: missing __instrumented brand — was withSpan applied at bind time?`, ); } if (!isCaptured(bound)) { throw new ConformanceError( `${manifest.name}.${name}: missing __captured brand — was withCapture applied at bind time?`, ); } if (useCase.mutates && useCase.audits.length > 0) { if (!isAudited(bound)) { throw new ConformanceError( `${manifest.name}.${name}: declares audits but binding is missing __audited brand — was withAudit applied at bind time?`, ); } } } }