/** A declared property type. Scalars only — an object or array has no bound. */ export type AppPropType = "enum" | "number" | "boolean"; export interface AppPropDecl { type: AppPropType; /** * For `enum`: the permitted values. **A string property MUST be a bounded * enumeration, never a free string** — that is the difference between a bound that * is computed and one that is hoped for, and §12 predicts it will be the first thing * a publisher asks to relax. */ values?: readonly string[]; /** For `number`: the inclusive range. Out-of-range REJECTS the event. */ min?: number; max?: number; } export interface AppEventDecl { /** Per-event declared property set. An undeclared key rejects the event. */ props?: Readonly>; } export interface AppAnalyticsVocabulary { /** Declared event names, namespaced to the app. */ events: Readonly>; /** * Declared ROUTE PATTERNS, e.g. `/patients/:id`. * * §3.1: a raw path is content — `/patients/12345` is not a page name — and * collecting raw paths from unaccountable publishers is not defensible. The app * registers patterns and the platform records the pattern; **the variable segment is * discarded at the boundary and never transmitted.** */ routes?: readonly string[]; } /** Total declared vocabulary cross-product: at most 2^12 distinct emit-shapes. */ export declare const APP_VOCAB_MAX_SHAPES = 4096; /** At most 8 declared properties per event — the §5 discipline, unchanged. */ export declare const APP_MAX_PROPS_PER_EVENT = 8; /** Enum values are strings; each is capped like every other string on the wire. */ export declare const APP_MAX_STR = 256; /** Events per user per day, per app. Published in the consent line's detail. */ export declare const APP_MAX_EVENTS_PER_USER_PER_DAY = 500; /** Declared event names per app. */ export declare const APP_MAX_EVENT_NAMES = 64; /** Declared route patterns per app. */ export declare const APP_MAX_ROUTES = 64; /** * How many distinct emit-shapes a vocabulary can express: `Σ_events Π_props |values|`. * * This is the number §5.1's cap binds, and it is computed rather than eyeballed * because the cross-product grows multiplicatively — eight properties of sixteen values * is 4.3 billion shapes for ONE event name, which looks entirely reasonable written out * as a manifest. */ export declare const vocabularyShapeCount: (v: AppAnalyticsVocabulary) => number; /** Bits per event the vocabulary can carry — §5.1's stated capacity, computed. */ export declare const vocabularyBits: (v: AppAnalyticsVocabulary) => number; /** * Validate a manifest-declared vocabulary. Returns `null` when valid, else the reason. * * FAIL-CLOSED throughout: a vocabulary that does not validate yields NO capability, not * a narrowed one. A narrowed one would mean the consent line described something other * than what the app can emit. */ export declare const validateVocabulary: (raw: unknown) => string | null; /** * A canonical, order-independent serialisation of the vocabulary. * * Order-independent deliberately: a publisher reordering their manifest keys has not * changed the bargain, and forcing re-consent on a formatting change trains users to * click through the prompt that matters. */ export declare const canonicalVocabulary: (v: AppAnalyticsVocabulary) => string; /** * The vocabulary hash bound into the grant record (§2.1). * * FNV-1a over the canonical form, widened by chaining — this is a CHANGE DETECTOR, not * a security primitive: both sides of the comparison are host-held, so there is no * adversary who benefits from a collision they cannot also just declare directly. It is * a plain function rather than `crypto.subtle` so the same value is computable * synchronously on both sides, including inside a consent render. */ export declare const vocabularyHash: (v: AppAnalyticsVocabulary) => string; /** * Match a concrete path against the declared patterns, returning the PATTERN. * * Returns `null` when nothing matches — and a non-matching path yields no row at all, * rather than a row carrying the path. **The variable segment is discarded here**, at * the boundary, so `/patients/12345` never exists downstream in any form. * * §3.1 is also the honest answer to the publisher's question: they asked which screens * are used, and the screen IS the pattern. An app needing per-item counts declares a * bounded enumeration of items; there is no unbounded per-item cardinality, because * adding one reopens §5. */ export declare const matchRoutePattern: (path: string, patterns: readonly string[]) => string | null; export interface AppAnalyticsEvent { name: string; at: string; props?: Record; /** A DECLARED route pattern, already reduced from the concrete path. */ route?: string; } /** * Validate one app event against the app's declared vocabulary. * * **Host-side and PER EVENT, before batching** (§3/§5). App events never share a batch * with host events, because whole-batch rejection on a shared batch would let one * malicious app drop up to 49 co-batched host events per flush — signal burial achieved * through the validator rather than through volume (G-AN-13). * * REJECTS, never strips. An undeclared key that were silently dropped would turn a leak * into a green test. */ export declare const validateAppEvent: (e: unknown, v: AppAnalyticsVocabulary) => string | null;