Files
agentic-dev/packages/core-shared/src/conformance/assert-bindings.ts
Danijel Martinek 5e32074c2e feat(core-shared/conformance): assertFeatureConformance helper
Adds boot-time check that every manifest-declared use case is bound
through withSpan (__instrumented) + withCapture (__captured), with
withAudit (__audited) enforced when audits[] is non-empty. Propagates
inner brands through withSpan so the outermost container-resolved
binding carries all brand markers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:49:18 +02:00

63 lines
2.3 KiB
TypeScript

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<string, symbol>,
_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?`,
);
}
}
}
}