feat(core-shared): add Analyzed brand and isAnalyzed type guard
Adds the `Analyzed<F>` phantom-type brand to brands.ts and the `isAnalyzed` type-guard to brand-runtime.ts, mirroring the existing Instrumented/Captured/Audited pattern. Exports both from the conformance index so Story 04 (wireUseCase) can key off the brand without a circular dep. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
isInstrumented,
|
||||
isCaptured,
|
||||
isAudited,
|
||||
isAnalyzed,
|
||||
} from "@/conformance/brand-runtime";
|
||||
|
||||
describe("brand-runtime", () => {
|
||||
@@ -21,6 +22,7 @@ describe("brand-runtime", () => {
|
||||
expect(isInstrumented(fn)).toBe(false);
|
||||
expect(isCaptured(fn)).toBe(false);
|
||||
expect(isAudited(fn)).toBe(false);
|
||||
expect(isAnalyzed(fn)).toBe(false);
|
||||
});
|
||||
|
||||
it("predicates discriminate between brands", () => {
|
||||
@@ -39,6 +41,10 @@ describe("brand-runtime", () => {
|
||||
const audited = () => {};
|
||||
attachBrand(audited, "__audited");
|
||||
expect(isAudited(audited)).toBe(true);
|
||||
|
||||
const analyzed = () => {};
|
||||
attachBrand(analyzed, "__analyzed");
|
||||
expect(isAnalyzed(analyzed)).toBe(true);
|
||||
});
|
||||
|
||||
it("composing brands stacks them on the same function", () => {
|
||||
@@ -46,9 +52,11 @@ describe("brand-runtime", () => {
|
||||
attachBrand(fn, "__instrumented");
|
||||
attachBrand(fn, "__captured");
|
||||
attachBrand(fn, "__audited");
|
||||
attachBrand(fn, "__analyzed");
|
||||
expect(isInstrumented(fn)).toBe(true);
|
||||
expect(isCaptured(fn)).toBe(true);
|
||||
expect(isAudited(fn)).toBe(true);
|
||||
expect(isAnalyzed(fn)).toBe(true);
|
||||
});
|
||||
|
||||
it("attached brands are non-writable and non-configurable", () => {
|
||||
@@ -67,5 +75,8 @@ describe("brand-runtime", () => {
|
||||
expect(isInstrumented(42)).toBe(false);
|
||||
expect(isInstrumented("string")).toBe(false);
|
||||
expect(isInstrumented({})).toBe(false);
|
||||
expect(isAnalyzed(null)).toBe(false);
|
||||
expect(isAnalyzed(undefined)).toBe(false);
|
||||
expect(isAnalyzed(42)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
* commitment, not a mutable flag.
|
||||
*/
|
||||
|
||||
type Brand = "__instrumented" | "__captured" | "__audited";
|
||||
import type { Analyzed } from "./brands";
|
||||
|
||||
type Brand = "__instrumented" | "__captured" | "__audited" | "__analyzed";
|
||||
|
||||
/**
|
||||
* Attaches the brand as a non-enumerable property on the given function.
|
||||
@@ -51,3 +53,7 @@ export function isCaptured(fn: unknown): boolean {
|
||||
export function isAudited(fn: unknown): boolean {
|
||||
return hasBrand(fn, "__audited");
|
||||
}
|
||||
|
||||
export function isAnalyzed<F extends object>(fn: unknown): fn is Analyzed<F> {
|
||||
return hasBrand(fn, "__analyzed");
|
||||
}
|
||||
|
||||
@@ -9,3 +9,4 @@
|
||||
*/
|
||||
export type Instrumented<F> = F & { readonly __instrumented: true };
|
||||
export type Captured<F> = F & { readonly __captured: true };
|
||||
export type Analyzed<F> = F & { readonly __analyzed: true };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type { Instrumented, Captured } from "./brands";
|
||||
export type { Instrumented, Captured, Analyzed } from "./brands";
|
||||
export type { FeatureManifest, UseCaseManifest } from "./define-feature";
|
||||
export { defineFeature } from "./define-feature";
|
||||
export type {
|
||||
@@ -22,6 +22,7 @@ export {
|
||||
isInstrumented,
|
||||
isCaptured,
|
||||
isAudited,
|
||||
isAnalyzed,
|
||||
} from "./brand-runtime";
|
||||
export { ConformanceError } from "./conformance-error";
|
||||
export { assertFeatureConformance } from "./assert-bindings";
|
||||
|
||||
Reference in New Issue
Block a user