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:
2026-05-18 11:41:48 +00:00
parent 92294bc7ff
commit d128106fd2
4 changed files with 21 additions and 2 deletions

View File

@@ -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);
});
});

View File

@@ -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");
}

View File

@@ -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 };

View File

@@ -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";