Files
agentic-dev/packages/core-audit/src/with-audit.ts

37 lines
1.8 KiB
TypeScript

import type { IAuditLog } from "./audit-log.interface";
import { attachBrand } from "@repo/core-shared/conformance";
/**
* Phantom-type brand attached at wrap time by `withAudit`. The conformance
* system uses this as the type-level seam for mutating use cases that
* declare `audits: [...]` in their manifest — without `__audited`, the
* binding is not assignable to `ProductionUseCase<I, O, M>` when M demands
* it. At runtime the brand is a non-enumerable property attached by
* `attachBrand` from `@repo/core-shared/conformance`, so the boot-time
* assertion can verify the binding went through the audit-aware path.
*/
export type Audited<F> = F & { readonly __audited: true };
/**
* Use-case wrapper applied at DI bind time. The wrapper is a thin closure
* that forwards to `fn` unchanged and carries the `__audited` brand. The
* forward closure (instead of returning `fn` directly) keeps the brand on
* a fresh function so the caller's original `fn` is not mutated — important
* when the same factory output is used elsewhere unwrapped (dev-seed paths,
* tests).
*/
export function withAudit<Args extends unknown[], R>(
// TODO: wire automated recording from manifest declarations.
// `audits[]` declarations. For now, the wrapper exists to:
// (1) require callers to pass the auditLog at bind time (dep is available)
// (2) attach the `__audited` brand so the boot-time assertion can verify
// mutating use cases were bound through the audit-aware path.
auditLog: IAuditLog,
fn: (...args: Args) => Promise<R>,
): Audited<(...args: Args) => Promise<R>> {
void auditLog;
const wrapped: (...args: Args) => Promise<R> = (...args) => fn(...args);
attachBrand(wrapped, "__audited");
return wrapped as Audited<(...args: Args) => Promise<R>>;
}