feat(core-shared/conformance): runtime brand markers + isX predicates
This commit is contained in:
71
packages/core-shared/src/conformance/brand-runtime.test.ts
Normal file
71
packages/core-shared/src/conformance/brand-runtime.test.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
attachBrand,
|
||||
isInstrumented,
|
||||
isCaptured,
|
||||
isAudited,
|
||||
} from "@/conformance/brand-runtime";
|
||||
|
||||
describe("brand-runtime", () => {
|
||||
it("attachBrand adds a non-enumerable property and returns the same reference", () => {
|
||||
const fn = () => {};
|
||||
const result = attachBrand(fn, "__instrumented");
|
||||
expect(result).toBe(fn);
|
||||
expect(isInstrumented(fn)).toBe(true);
|
||||
// Non-enumerable: must not show up in Object.keys
|
||||
expect(Object.keys(fn)).not.toContain("__instrumented");
|
||||
});
|
||||
|
||||
it("predicates return false for unwrapped functions", () => {
|
||||
const fn = () => {};
|
||||
expect(isInstrumented(fn)).toBe(false);
|
||||
expect(isCaptured(fn)).toBe(false);
|
||||
expect(isAudited(fn)).toBe(false);
|
||||
});
|
||||
|
||||
it("predicates discriminate between brands", () => {
|
||||
const instrumented = () => {};
|
||||
attachBrand(instrumented, "__instrumented");
|
||||
expect(isInstrumented(instrumented)).toBe(true);
|
||||
expect(isCaptured(instrumented)).toBe(false);
|
||||
expect(isAudited(instrumented)).toBe(false);
|
||||
|
||||
const captured = () => {};
|
||||
attachBrand(captured, "__captured");
|
||||
expect(isInstrumented(captured)).toBe(false);
|
||||
expect(isCaptured(captured)).toBe(true);
|
||||
expect(isAudited(captured)).toBe(false);
|
||||
|
||||
const audited = () => {};
|
||||
attachBrand(audited, "__audited");
|
||||
expect(isAudited(audited)).toBe(true);
|
||||
});
|
||||
|
||||
it("composing brands stacks them on the same function", () => {
|
||||
const fn = () => {};
|
||||
attachBrand(fn, "__instrumented");
|
||||
attachBrand(fn, "__captured");
|
||||
attachBrand(fn, "__audited");
|
||||
expect(isInstrumented(fn)).toBe(true);
|
||||
expect(isCaptured(fn)).toBe(true);
|
||||
expect(isAudited(fn)).toBe(true);
|
||||
});
|
||||
|
||||
it("attached brands are non-writable and non-configurable", () => {
|
||||
const fn = () => {};
|
||||
attachBrand(fn, "__instrumented");
|
||||
const desc = Object.getOwnPropertyDescriptor(fn, "__instrumented");
|
||||
expect(desc?.writable).toBe(false);
|
||||
expect(desc?.configurable).toBe(false);
|
||||
expect(desc?.enumerable).toBe(false);
|
||||
expect(desc?.value).toBe(true);
|
||||
});
|
||||
|
||||
it("predicates return false for non-function inputs", () => {
|
||||
expect(isInstrumented(null)).toBe(false);
|
||||
expect(isInstrumented(undefined)).toBe(false);
|
||||
expect(isInstrumented(42)).toBe(false);
|
||||
expect(isInstrumented("string")).toBe(false);
|
||||
expect(isInstrumented({})).toBe(false);
|
||||
});
|
||||
});
|
||||
51
packages/core-shared/src/conformance/brand-runtime.ts
Normal file
51
packages/core-shared/src/conformance/brand-runtime.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Runtime brand attachment + predicates. The companion to the phantom-type
|
||||
* brands defined in `./brands.ts`: at compile time the brand is a structural
|
||||
* intersection; at runtime it is a non-enumerable, non-writable,
|
||||
* non-configurable property with the same name.
|
||||
*
|
||||
* Why non-enumerable: a wrapped function should not leak the marker through
|
||||
* `Object.keys`, `JSON.stringify`, `for…in`, or spread. The marker only
|
||||
* shows up to explicit lookups via `Reflect.has` or direct property access.
|
||||
*
|
||||
* Why non-writable + non-configurable: the marker is meant to be permanent
|
||||
* once attached. The wrapper is the only caller; the marker is a one-shot
|
||||
* commitment, not a mutable flag.
|
||||
*/
|
||||
|
||||
type Brand = "__instrumented" | "__captured" | "__audited";
|
||||
|
||||
/**
|
||||
* Attaches the brand as a non-enumerable property on the given function.
|
||||
* Returns the same reference (no allocation). Idempotent: calling twice with
|
||||
* the same brand throws because the property is non-configurable — wrappers
|
||||
* never double-wrap, so this is the correct safety net.
|
||||
*/
|
||||
export function attachBrand<F extends object>(fn: F, brand: Brand): F {
|
||||
Object.defineProperty(fn, brand, {
|
||||
value: true,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
configurable: false,
|
||||
});
|
||||
return fn;
|
||||
}
|
||||
|
||||
function hasBrand(fn: unknown, brand: Brand): boolean {
|
||||
if (typeof fn !== "function" && (typeof fn !== "object" || fn === null)) {
|
||||
return false;
|
||||
}
|
||||
return (fn as Record<string, unknown>)[brand] === true;
|
||||
}
|
||||
|
||||
export function isInstrumented(fn: unknown): boolean {
|
||||
return hasBrand(fn, "__instrumented");
|
||||
}
|
||||
|
||||
export function isCaptured(fn: unknown): boolean {
|
||||
return hasBrand(fn, "__captured");
|
||||
}
|
||||
|
||||
export function isAudited(fn: unknown): boolean {
|
||||
return hasBrand(fn, "__audited");
|
||||
}
|
||||
@@ -2,3 +2,9 @@ export type { Instrumented, Captured } from "./brands";
|
||||
export type { FeatureManifest, UseCaseManifest } from "./define-feature";
|
||||
export { defineFeature } from "./define-feature";
|
||||
export type { ProductionUseCase } from "./production-use-case";
|
||||
export {
|
||||
attachBrand,
|
||||
isInstrumented,
|
||||
isCaptured,
|
||||
isAudited,
|
||||
} from "./brand-runtime";
|
||||
|
||||
Reference in New Issue
Block a user