feat(core-consent): scaffold package with types, IConsent, withConsent brand wrapper

- Add packages/core-consent with ConsentCategory, ConsentState,
  UserConsentState types and IConsent interface
- Add withConsent wrapper attaching __consentChecked brand at bind time;
  unit tests assert brand attachment and factory passthrough
- Add ConsentChecked<F> type to core-shared/conformance/brands.ts and
  isConsentChecked helper to brand-runtime.ts
- Extend FeatureManifest with requiresConsent?: readonly string[] field
- Extend assertFeatureConformance to require __consentChecked brand when
  requiresConsent.length > 0; synthetic fixture tests cover pass/fail cases
- Propagate __consentChecked in withSpan PROPAGATED_BRANDS so the outermost
  binding carries the brand

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 10:40:40 +00:00
parent f5d08dc84a
commit 9cb2fa321c
20 changed files with 410 additions and 27 deletions

View File

@@ -0,0 +1,30 @@
import type { IConsent } from "./consent.interface";
import type { ConsentChecked } from "@repo/core-shared/conformance";
import { attachBrand } from "@repo/core-shared/conformance";
export type { ConsentChecked };
/**
* Use-case wrapper applied at DI bind time. The wrapper is a thin closure
* that forwards to `fn` unchanged and carries the `__consentChecked` brand.
* The forward closure keeps the brand on a fresh function so the original
* `fn` reference is not mutated — important when the same factory output is
* used elsewhere unwrapped (dev-seed paths, tests).
*
* Composition order (innermost to outermost):
* withSpan → withCapture → withAudit → withAnalytics → withConsent → factory(deps)
*
* The wrapper exists to:
* (1) require callers to pass the consent instance at bind time (dep is available)
* (2) attach the `__consentChecked` brand so the boot-time assertion can verify
* consent-gated use cases were bound through the consent-aware path.
*/
export function withConsent<Args extends unknown[], R>(
consent: IConsent,
fn: (...args: Args) => Promise<R>,
): ConsentChecked<(...args: Args) => Promise<R>> {
void consent;
const wrapped: (...args: Args) => Promise<R> = (...args) => fn(...args);
attachBrand(wrapped, "__consentChecked");
return wrapped as ConsentChecked<(...args: Args) => Promise<R>>;
}