@sentientui/policy 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,14 +30,16 @@ replayable decisions.
30
30
  - `validateSlotDecl(decl)` — structural validation of a `SlotDecl`, returning `{ ok: true }` or `{ ok: false, reason }`.
31
31
 
32
32
  **Layout selection** (`layout-heuristics.ts`, `choose-layout.ts`, `hash.ts`)
33
- - `candidateLayouts(sections, sectionTypes, persona)` — the candidate section orderings for a persona.
34
- - `applyClusterHeuristic(sections, sectionTypes, persona)` — the persona's heuristic ordering (`CLUSTER_PRIORITY`), used as the fallback.
35
- - `chooseLayout(sections, sectionTypes, persona, learned, rand?)` — Thompson-samples the learned layout posteriors over the candidates, falling back to the heuristic.
33
+ - `candidateLayouts(sections, sectionTypes, sectionRoles?)` — the candidate orderings for a PAGE: the authored order plus each archetype's. Takes no persona — the persona selects posteriors, not which layouts are reachable.
34
+ - `LAYOUT_ARCHETYPES` / `orderByArchetype(sections, sectionTypes, archetype, sectionRoles?)` — a catalogue of orderings (`conversion_led`, `evidence_led`, `price_led`, `discovery_led`). Not a persona taxonomy.
35
+ - `previewOrderForPersona(sections, sectionTypes, persona, sectionRoles?)` — **keyless local mode only.** Maps any persona key onto an archetype so `?sentient_persona=<anything>` visibly rearranges a page with no server. Never use it for serving.
36
+ - `chooseLayout(sections, sectionTypes, persona, learned, rand?)` — Thompson-samples the learned layout posteriors over the candidates. The authored order is always among them, so "leave the page alone" can win.
37
+ - *Deprecated:* `applyClusterHeuristic` (use `orderByArchetype` on the server, `previewOrderForPersona` locally).
36
38
  - `hashLayout(order)` — stable hash of a section order (the `layoutHash` key).
37
39
 
38
40
  **Personas** (`personas.ts`)
39
- - `PERSONAS`, `PersonaKey`, `UNKNOWN_PERSONA`, `PERSONA_DISPLAY` — the canonical persona set and display names.
40
- - `canonicalPersona(label)` — normalize an arbitrary/legacy label to a `PersonaKey`.
41
+ - `UNKNOWN_PERSONA`, `UNKNOWN_PERSONA_DISPLAY` — the only persona key the package defines. There is no built-in persona list: every persona comes from the project's own vocabulary (declared by the app, or promoted by discovery).
42
+ - `canonicalPersona(label)` — normalize any label to a persona key (trimmed, lowercased, key-shaped), or `'unknown'`. Says nothing about vocabulary membership.
41
43
 
42
44
  **Deterministic helpers** (`deterministic.ts`)
43
45
  - `fnv1a(input)` — FNV-1a hash.
@@ -65,7 +67,7 @@ const chosenSeeded = sampleArm(arms, mySeededRng);
65
67
  import { chooseLayout, hashLayout, type LearnedLayout } from '@sentientui/policy';
66
68
 
67
69
  const learned = new Map<string, LearnedLayout>(); // from your layout_weights store
68
- const order = chooseLayout(sections, sectionTypes, 'buyer', learned);
70
+ const order = chooseLayout(sections, sectionTypes, 'evaluator', learned); // a key from the project's own vocabulary
69
71
  const key = hashLayout(order);
70
72
  ```
71
73
 
package/dist/index.d.cts CHANGED
@@ -1,50 +1,111 @@
1
1
  import { SectionRole } from './taxonomy.cjs';
2
2
 
3
- declare const PERSONAS: readonly ["buyer", "researcher", "deal_seeker", "browser"];
4
- type Persona = (typeof PERSONAS)[number];
5
- declare const UNKNOWN_PERSONA: "unknown";
6
- type PersonaKey = Persona | typeof UNKNOWN_PERSONA;
7
- /** Human-facing names — dashboard/devtools copy must use these, never raw keys. */
8
- declare const PERSONA_DISPLAY: Record<PersonaKey, string>;
9
3
  /**
10
- * Every label ever written for a persona, mapped to its canonical form. The
11
- * plural/hyphen labels are the pre-069 cluster seed labels
12
- * ('buyers'/'deal-seekers'/…); identity mappings make canonical input a no-op.
4
+ * The one persona key the product itself defines: "we don't know".
5
+ *
6
+ * There is deliberately no persona LIST here. Until 2026-09-13 this module
7
+ * exported a hardcoded four-persona vocabulary (with display names and a
8
+ * plural-label alias map) that every project inherited, and the product
9
+ * presented those names as if it knew the customer's audience. Every persona
10
+ * now comes from the project's own vocabulary — declared by the app or
11
+ * promoted by discovery (see `vocabulary.ts`) — and anything else is
12
+ * `unknown`, which is where the pooled bandit does the actual work.
13
13
  */
14
- declare const LEGACY_PERSONA_MAP: Record<string, Persona>;
14
+ declare const UNKNOWN_PERSONA: "unknown";
15
+ /** Human-facing name of `UNKNOWN_PERSONA`. Every other persona's display name
16
+ * comes from its vocabulary member, never from a table in this package. */
17
+ declare const UNKNOWN_PERSONA_DISPLAY = "Unknown";
18
+
15
19
  /**
16
- * Canonicalizes any persona/cluster label to the pinned vocabulary.
17
- * Null, undefined, empty, and unrecognized labels all become 'unknown' —
18
- * "we don't know" is always a safe answer; a guessed persona is not.
20
+ * A catalogue of plausible page orderings NOT a persona taxonomy.
21
+ *
22
+ * These four orderings were keyed by the four seeded persona names until
23
+ * 2026-09-13 (the table was exported as `CLUSTER_PRIORITY`, now removed).
24
+ * Those personas were removed, but the deeper problem was that keying orderings by persona NAME was
25
+ * wrong even while they existed, in two ways that were invisible until a
26
+ * customer declared a persona of their own:
27
+ *
28
+ * 1. THE ARM SPACE DEPENDED ON WHAT THE CUSTOMER NAMED THEIR PERSONA. A
29
+ * project that declared `admin` got five candidate orders including the
30
+ * page exactly as authored; a project that declared one of the seeded names got four, and
31
+ * the authored order was NOT among them — so its own page order could never
32
+ * be served to that persona, and there was no control arm to lose to.
33
+ * Renaming a persona silently changed which layouts were reachable.
34
+ *
35
+ * 2. THE FEASIBILITY GATE SERVED THE COLLIDING ARCHETYPE DETERMINISTICALLY.
36
+ * `routes/decide.ts` serves a heuristic order (never explored, never
37
+ * learned) when layout feasibility is `infeasible` — the state every new
38
+ * project is in. A customer who declared the seeded conversion persona had pricing hoisted above
39
+ * everything on every visit, permanently, because of a table written in
40
+ * May; a customer who declared `admin` correctly kept their own page.
41
+ *
42
+ * So the orderings are now named for what they DO. The names are internal and
43
+ * carry no claim about any visitor: they are four opinions about what a page
44
+ * should lead with, and the bandit decides between them and the authored order
45
+ * using evidence. The arrays are unchanged, so every `layout_weights` row in
46
+ * production still joins — `hashLayout` hashes the resulting ORDER, never the
47
+ * key that produced it.
19
48
  */
20
- declare function canonicalPersona(label: string | null | undefined): PersonaKey;
21
-
22
- declare const CLUSTER_PRIORITY: Record<Persona, string[]>;
49
+ declare const LAYOUT_ARCHETYPES: Record<string, readonly string[]>;
50
+ /** The archetype names, in a pinned order — iteration order decides candidate
51
+ * insertion order, so it must not depend on object-key enumeration luck. */
52
+ declare const LAYOUT_ARCHETYPE_NAMES: readonly ["conversion_led", "evidence_led", "price_led", "discovery_led"];
53
+ type LayoutArchetype = (typeof LAYOUT_ARCHETYPE_NAMES)[number];
23
54
  /**
24
- * Reorders section IDs based on the persona's semantic priority.
55
+ * Reorders section IDs by one archetype's semantic priority.
25
56
  * Sections with no graph entry are treated as 'generic'.
26
- * Returns the input unchanged for 'unknown' — and for any custom vocabulary
27
- * persona (declared/discovered): those have no semantic prior, so they serve
28
- * the natural order until the layout bandit has learned rows, the same
29
- * cold-start posture 'unknown' gets.
30
57
  *
31
58
  * With `sectionRoles` (spec 2026-09-04 §1, phase 2d) the ordering projection is
32
59
  * `(role, parent)`: structural sections are PINNED at their original index and
33
60
  * only converters/persuaders re-rank around them. The pin is not cosmetic —
34
- * 'navigation' ranks near last in every persona priority, so an unpinned navbar
35
- * or footer would sort to the bottom of the page, exactly the visible damage a
61
+ * 'navigation' ranks near last in every archetype, so an unpinned navbar or
62
+ * footer would sort to the bottom of the page, exactly the visible damage a
36
63
  * reorder must never do. Callers without role data (the client-local fallback)
37
64
  * omit the map and get the pre-2d behaviour unchanged.
38
65
  */
39
- declare function applyClusterHeuristic(sections: string[], sectionTypes: Map<string, string>, persona: string, sectionRoles?: Map<string, SectionRole>): string[];
66
+ declare function orderByArchetype(sections: string[], sectionTypes: Map<string, string>, archetype: LayoutArchetype, sectionRoles?: Map<string, SectionRole>): string[];
40
67
  /**
41
- * The candidate layout orderings for a page the distinct section orders
42
- * produced by every persona's semantic priority (plus the requesting
43
- * persona's own, which for 'unknown' is the identity order). These are the
44
- * "arms" the layout bandit explores. Returned as hash order so it joins
45
- * directly against layout_weights rows keyed by the same hashLayout.
68
+ * PREVIEW ONLY the keyless local engine (`@sentientui/core` index-local).
69
+ *
70
+ * Maps an arbitrary persona string to one archetype so that
71
+ * `?sentient_persona=<anything>` visibly rearranges a page with no API key and
72
+ * no server. It used to look the key up in the four-persona table, so only
73
+ * the four seeded names did anything and every other key silently no-oped;
74
+ * now any key previews an arrangement, which is what the docs promise.
75
+ *
76
+ * DO NOT USE THIS ON THE SERVER. The mapping is a hash, not a belief: it
77
+ * carries no claim that this persona wants this ordering. Server serving picks
78
+ * between the archetypes and the authored order with `chooseLayout` /
79
+ * `chooseLayoutFactored`, on evidence.
80
+ *
81
+ * 'unknown' and the empty string return the sections untouched — the natural
82
+ * order is what an unidentified visitor gets, here as everywhere.
83
+ */
84
+ declare function previewOrderForPersona(sections: string[], sectionTypes: Map<string, string>, persona: string, sectionRoles?: Map<string, SectionRole>): string[];
85
+ /**
86
+ * @deprecated Renamed. Use `orderByArchetype` on the server (by archetype) or
87
+ * `previewOrderForPersona` in the keyless local engine (by arbitrary key).
88
+ * Kept as an alias of the preview mapping so the published signature survives.
46
89
  */
47
- declare function candidateLayouts(sections: string[], sectionTypes: Map<string, string>, persona: string, sectionRoles?: Map<string, SectionRole>): Map<string, string[]>;
90
+ declare const applyClusterHeuristic: typeof previewOrderForPersona;
91
+ /**
92
+ * The candidate layout orderings for a page — the arms the layout bandit
93
+ * explores. Returned as hash → order so it joins directly against
94
+ * `layout_weights` rows keyed by the same `hashLayout`.
95
+ *
96
+ * THE AUTHORED ORDER IS ALWAYS AN ARM. It is the control: the customer built
97
+ * this page in this order, and a bandit whose arm space excludes the baseline
98
+ * can never conclude "leave it alone" — it is structurally obliged to reorder
99
+ * something, and there is nothing for a holdout comparison to mean. Before
100
+ * 2026-09-13 the authored order was included only by accident, when the
101
+ * requesting persona's name happened to miss the archetype table; a persona
102
+ * whose name matched the table had it excluded entirely.
103
+ *
104
+ * It no longer takes a persona. The set of orderings a page COULD be shown in
105
+ * is a property of the page, not of who is looking at it — what the persona
106
+ * changes is which arm wins, and that is the posterior's job.
107
+ */
108
+ declare function candidateLayouts(sections: string[], sectionTypes: Map<string, string>, sectionRoles?: Map<string, SectionRole>): Map<string, string[]>;
48
109
 
49
110
  /**
50
111
  * Stable 16-char SHA-256 prefix for a section order array.
@@ -69,15 +130,25 @@ type LearnedLayout = {
69
130
  * Thompson-samples the layout order to serve a persona over the candidate
70
131
  * orderings, using learned posteriors from layout_weights. Candidates with no
71
132
  * learned row use the uniform 1/1 prior — identical to variant cold start.
72
- * Falls back to the persona's heuristic only if sampling yields no candidate.
133
+ *
134
+ * The candidate set always contains the AUTHORED order, so "leave this page
135
+ * alone" is a real arm that can win, and at cold start it is exactly as likely
136
+ * as any reorder. This used to fall back to the persona's heuristic when
137
+ * sampling yielded nothing — which could not happen (candidateLayouts is never
138
+ * empty) and would have been the wrong answer anyway: with nothing to choose
139
+ * on, the page the customer built is the only defensible thing to serve.
140
+ *
141
+ * `persona` no longer selects candidates — the orderings a page COULD be shown
142
+ * in are a property of the page. It stays in the signature because it is what
143
+ * the CALLER keyed `learned` by, which is where the persona belongs.
73
144
  *
74
145
  * @param rand Uniform [0,1) source. Defaults to `Math.random`, which is
75
146
  * NON-DETERMINISTIC. Pass a seeded PRNG when you need a reproducible layout
76
147
  * (tests, replayable decisions) — otherwise the sampled order varies per call.
77
148
  * @param sectionRoles Optional role map (phase 2d): structural sections are
78
- * pinned in place across every candidate; see `applyClusterHeuristic`.
149
+ * pinned in place across every candidate; see `orderByArchetype`.
79
150
  */
80
- declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>, persona: string, learned: Map<string, LearnedLayout>, rand?: () => number, sectionRoles?: Map<string, SectionRole>): string[];
151
+ declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>, _persona: string, learned: Map<string, LearnedLayout>, rand?: () => number, sectionRoles?: Map<string, SectionRole>): string[];
81
152
 
82
153
  /**
83
154
  * Factored layout value model (spec 2026-09-04 §3a).
@@ -133,10 +204,130 @@ declare function factorCellsForOrder(order: string[], sectionTypes: Map<string,
133
204
  * start degrades gracefully: empty cells draw from Beta(1,1), which still
134
205
  * randomises across candidates, so exploration survives the switch.
135
206
  *
136
- * Falls back to the persona's heuristic prior when there are no candidates.
207
+ * The candidate set always contains the AUTHORED order, so leaving the page as
208
+ * built is a real arm rather than something only reachable by accident.
137
209
  */
138
210
  declare function chooseLayoutFactored(sections: string[], sectionTypes: Map<string, string>, persona: string, cells: LayoutFactorCell[], rand?: () => number, sectionRoles?: Map<string, SectionRole>): string[];
139
211
 
212
+ /**
213
+ * Factored slot value model — the slot analogue of `layout-factored.ts`.
214
+ *
215
+ * `slot_weights` keys one independent Beta posterior per
216
+ * (arm, segment, persona), where `segment` is the joined `device:source`. That
217
+ * is why the decision context stopped at two fields: every field ADDED
218
+ * multiplies the cell count. Three devices x six sources x five personas is
219
+ * already 90 cells per arm; adding new-vs-returning makes it 180, adding
220
+ * country multiplies again — and each cell estimates from its own slice of the
221
+ * same thin traffic, so a richer context converges WORSE, not better.
222
+ *
223
+ * This model replaces one-parameter-per-context-combination with one per
224
+ * context FACTOR:
225
+ *
226
+ * V(arm | context) = Σ_f draw[arm, factor f, level of f in this context]
227
+ *
228
+ * Four factors at 3 + 6 + 5 + 2 levels is 16 cells per arm instead of 180, and
229
+ * every trial teaches every context that shares ANY factor level: a conversion
230
+ * on mobile/paid/returning updates "this arm on mobile", which transfers to
231
+ * mobile/organic/new. Additive over factors is the same shape the layout model
232
+ * uses additively over positions, and it is fair across arms for the same
233
+ * reason — every arm in one request is scored under the identical factor set,
234
+ * so the sum has the same number of terms for each.
235
+ *
236
+ * It improves sample efficiency; it does not manufacture signal. With a handful
237
+ * of conversions no model learns a ranking, which is what the feasibility gate
238
+ * exists to say out loud.
239
+ */
240
+ /** Persona key of the pooled global cells. Reserved — never a real persona. */
241
+ declare const GLOBAL_FACTOR_LEVEL = "__global__";
242
+ /**
243
+ * Context factors the model conditions on, in a pinned order.
244
+ *
245
+ * Widening this list is the whole point of the design — it costs O(levels), not
246
+ * O(product) — but it is NOT free: each factor adds a term to the sum, and a
247
+ * factor with no signal adds variance to every score. Add one when there is a
248
+ * reason to believe it changes which arm wins, not because the column exists.
249
+ */
250
+ declare const SLOT_FACTORS: readonly ["device", "source", "persona", "visit"];
251
+ type SlotFactor = (typeof SLOT_FACTORS)[number];
252
+ type SlotFactorContext = {
253
+ device: string | null;
254
+ source: string | null;
255
+ persona: string;
256
+ /** 'new' | 'returning' | null when the visit count is unknown. */
257
+ visit: string | null;
258
+ };
259
+ type SlotFactorCell = {
260
+ arm: string;
261
+ factor: string;
262
+ /** Factor level, or GLOBAL_FACTOR_LEVEL for the arm's context-free row. */
263
+ level: string;
264
+ exposures: number;
265
+ conversions: number;
266
+ };
267
+ /**
268
+ * The (factor, level) pairs one served decision contributes to — the write-side
269
+ * projection close-out uses, and the read-side lookup serving uses. Both call
270
+ * this so the two can never disagree about what a context decomposes into.
271
+ *
272
+ * An UNKNOWN persona contributes no persona term. That is the Pareto safety
273
+ * invariant of CONTRACTS §4 restated for this model: unknown-persona traffic
274
+ * must run on exactly the persona-agnostic policy, so the persona factor is not
275
+ * merely empty for them, it is absent — an `unknown` LEVEL would otherwise
276
+ * become a real segment that accumulates its own rate and steers serving.
277
+ *
278
+ * A null device/source/visit is likewise absent rather than levelled as
279
+ * 'unknown', for the same reason: "not measured" is not a level.
280
+ */
281
+ declare function factorLevelsFor(ctx: SlotFactorContext): Array<{
282
+ factor: SlotFactor;
283
+ level: string;
284
+ }>;
285
+ type FactoredSlotChoice = {
286
+ arm: string;
287
+ factorsUsed: number;
288
+ };
289
+ /**
290
+ * Thompson-style selection over the factored model.
291
+ *
292
+ * ONE draw per (arm, factor, level) cell, cached for the call — an arm's score
293
+ * is the sum of its factor draws, and every arm is scored under the same factor
294
+ * set, so the comparison is made in a single sampled world. A fresh draw per
295
+ * comparison would add pure noise to the ranking rather than exploration.
296
+ *
297
+ * Shrinkage follows CONTRACTS §4 exactly: a factor-level cell shrinks toward
298
+ * the arm's GLOBAL cell, and the global cell shrinks toward the slot's pooled
299
+ * rate across all arms. The MEAN crosses each boundary, never the sample size —
300
+ * so a thin factor level keeps a posterior as wide as its own evidence warrants
301
+ * and Thompson sampling still explores it.
302
+ *
303
+ * Cold start degrades to the unfactored behaviour: with no cells at all every
304
+ * arm draws from the same shrunken pool, which still randomises, so exploration
305
+ * survives enabling this model on a project with history.
306
+ */
307
+ declare function chooseSlotArmFactored(arms: readonly string[], ctx: SlotFactorContext, cells: readonly SlotFactorCell[], rand?: () => number): FactoredSlotChoice | null;
308
+ /**
309
+ * The cells one closed trial writes, given its context — the write-side
310
+ * projection. Always includes the arm's GLOBAL row, which is what every factor
311
+ * level shrinks toward and what the slot-wide pool is summed from.
312
+ *
313
+ * `personaWeight` is the SOFT assignment the layout model established: the
314
+ * persona term trains at the portrait's `reliability_score` (declared personas
315
+ * at 1.0) while every other factor trains at 1. A mismeasured persona otherwise
316
+ * induces attenuation bias — it drags a cell toward the population mean in
317
+ * proportion to how often it is wrong — and soft weighting lets the large
318
+ * unknown mass inform the global term instead of forming a dead bucket.
319
+ */
320
+ declare function factorCellsForTrial(arm: string, ctx: SlotFactorContext, personaWeight: number): Array<{
321
+ arm: string;
322
+ factor: string;
323
+ level: string;
324
+ weight: number;
325
+ }>;
326
+ /** Visit-count bucket. Two levels on purpose: new-vs-returning is one of the
327
+ * largest conversion differences on any site, and finer buckets would spend
328
+ * parameters on a tail that prod does not currently have. */
329
+ declare function visitLevel(visitCount: number | null | undefined): string | null;
330
+
140
331
  /** Learned Beta(alpha, beta) posterior for one arm. */
141
332
  type ArmPosterior = {
142
333
  arm: string;
@@ -412,8 +603,7 @@ declare function sampleArmEv(arms: EvArm[], reference: number, rand?: () => numb
412
603
  /**
413
604
  * Per-project persona vocabularies (spec: 2026-08-27-declared-personas-design.md).
414
605
  *
415
- * The persona axis stops being the closed global union in `personas.ts` and
416
- * becomes a per-project member list (persona_sets / persona_set_members,
606
+ * The persona axis is a per-project member list (persona_sets / persona_set_members,
417
607
  * migration 113). This module owns resolution: which persona a decision is
418
608
  * keyed on, given what the customer's app declared and what clustering
419
609
  * inferred. The weight tables and pooling are already string-generic —
@@ -444,12 +634,30 @@ declare const PERSONA_KEY_RE: RegExp;
444
634
  */
445
635
  declare function normalizeDeclaredPersona(raw: string | null | undefined): string | null;
446
636
  /**
447
- * Keys no vocabulary member may claim. 'unknown' and '__all__' are structural
448
- * in weightCellsFor / CONTRACTS §4; the plural forms are pre-069 legacy labels
449
- * that canonicalPersona still remaps, so a member claiming one would be
450
- * silently rewritten at resolve time. MUST stay in sync with migration 113.
637
+ * Keys no vocabulary member may claim: 'unknown' and '__all__' are structural
638
+ * in weightCellsFor / CONTRACTS §4. MUST stay in sync with the CHECK constraint
639
+ * on persona_set_members.key (migration 113, relaxed by 153).
640
+ *
641
+ * This also reserved four plural labels until 2026-09-13, because the
642
+ * name-specific alias map that remapped them at resolve time would have
643
+ * silently rewritten a member claiming one. That map went with the seeded
644
+ * personas, so those strings are ordinary keys now.
451
645
  */
452
646
  declare const RESERVED_PERSONA_KEYS: readonly string[];
647
+ /**
648
+ * Canonicalizes any persona/cluster label to a persona KEY: trimmed,
649
+ * lowercased, and shaped like one (`PERSONA_KEY_RE`). Null, empty, and
650
+ * anything that could never be a key become 'unknown' — "we don't know" is
651
+ * always a safe answer.
652
+ *
653
+ * Generic on purpose. This used to be a lookup in a table of four seeded
654
+ * persona names, so every OTHER label — including every key a customer
655
+ * declared — folded to 'unknown': a registry pin scoped to `admin` was stored
656
+ * under 'unknown' and then applied to every unidentified visitor. It says
657
+ * nothing about membership; callers that serve must still check the project's
658
+ * vocabulary (resolvePersona does).
659
+ */
660
+ declare function canonicalPersona(label: string | null | undefined): string;
453
661
  type PersonaVocabularyMember = {
454
662
  key: string;
455
663
  displayName: string;
@@ -459,9 +667,34 @@ type PersonaVocabularyMember = {
459
667
  status?: 'active' | 'retired';
460
668
  };
461
669
  /**
462
- * The pinned four as a vocabulary what every project's version-1 default set
463
- * contains, and the fallback when a project has no active set (a missed
464
- * app-code insert degrades to today's behaviour, never an error).
670
+ * The vocabulary a project has when it has declared nothing: EMPTY.
671
+ *
672
+ * This shipped as a hardcoded four seeded personas, which the product then
673
+ * presented as if it knew the customer's audience.
674
+ * Earned rows (2026-09-12) demoted them to a `starter` state; this removes them.
675
+ *
676
+ * The measurement that settled it, across all of production history:
677
+ * `unknown` served 10,882 decisions, the four seeded personas served **18
678
+ * between them**. They were not a taxonomy, they were decoration on an axis
679
+ * that was 99.8% empty — and every one of them was an assertion about visitors
680
+ * nobody had met.
681
+ *
682
+ * A persona now has exactly two honest origins:
683
+ *
684
+ * - **declared** — the customer's own code tells us (a role, a plan tier).
685
+ * Ground truth; no evidence gate, because eligibility is their decision.
686
+ * - **discovered** — `persona-discovery.ts` finds it in real behaviour and it
687
+ * clears the interaction gate (the RANKING of arms must differ inside vs
688
+ * outside the segment, not merely the conversion rate).
689
+ *
690
+ * Everything else resolves to `unknown`, which is where day-0 value accrues and
691
+ * where the pooled bandit has always done the actual work.
692
+ *
693
+ * THIS IS ONLY SAFE BECAUSE SERVING NO LONGER NEEDS A PERSONA. The factored
694
+ * model (migration 149) conditions on device, source and visit count as
695
+ * first-class factors — measured, not guessed — so a project with no personas
696
+ * still adapts per visitor. Before that landed, emptying this would have meant
697
+ * no personalization at all.
465
698
  */
466
699
  declare const DEFAULT_PERSONA_VOCABULARY: readonly PersonaVocabularyMember[];
467
700
  type PersonaResolution = {
@@ -481,8 +714,7 @@ type PersonaResolution = {
481
714
  * Resolves the persona a decision is keyed on.
482
715
  *
483
716
  * Precedence, in order:
484
- * 1. Declared value matching an active member (directly, via alias, or via a
485
- * legacy plural label whose canonical form is a member) → that key,
717
+ * 1. Declared value matching an active member (directly or via alias) that key,
486
718
  * confidence 1. Declared skips reliability gating: it is ground truth from
487
719
  * the customer's app, the same trust level as everything else the pk_ key
488
720
  * sends.
@@ -502,11 +734,14 @@ declare function resolvePersona(input: {
502
734
  * Normalizes a DECISION-TIME persona (slot_decisions / layout_decisions rows)
503
735
  * for training. Unlike `canonicalPersona`, this trusts the stored value
504
736
  * verbatim: it was validated against the project vocabulary when the decision
505
- * was written, and re-squashing it through the closed global union at close-out
737
+ * was written, and re-squashing it through a closed global union at close-out
506
738
  * silently rerouted every declared-persona trial onto the 'unknown' marginals
507
- * (the double-squash bug, spec §4.3). Only the legacy plural labels are still
508
- * remapped — pre-069 rows carry them.
739
+ * (the double-squash bug, spec §4.3).
740
+ *
741
+ * It also remapped four pre-069 plural labels until 2026-09-13; migration 069
742
+ * had already rewritten those rows, and the remap was keyed on the retired
743
+ * seeded names, so it went with them.
509
744
  */
510
745
  declare function decisionPersona(label: string | null | undefined): string;
511
746
 
512
- export { type ArmPosterior, CLUSTER_PRIORITY, DEFAULT_PERSONA_VOCABULARY, EV_SHRINK_K, type EvArm, GLOBAL_FACTOR_PERSONA, LAYOUT_FACTOR_BUCKETS, LEGACY_PERSONA_MAP, type LayoutFactorCell, type LearnedLayout, PERSONAS, PERSONA_DISPLAY, PERSONA_KEY_RE, POOL_ALL, type Persona, type PersonaKey, type PersonaResolution, type PersonaVocabularyMember, type PoolCells, type PoolCounts, RESERVED_PERSONA_KEYS, SHRINKAGE_M, type SlotDecl, type SlotResult, UNKNOWN_PERSONA, type ValueCell, type ValueCellRow, WEIGHTS_FALLBACK_PRIOR_PULLS, type WeightsFallbackArm, applyClusterHeuristic, broadestValueCell, candidateLayouts, canonicalArm, canonicalPersona, chooseLayout, chooseLayoutFactored, confidenceBand, decisionPersona, factorCellsForOrder, fnv1a, hashLayout, layoutBucketOf, marginalArmKey, normalizeDeclaredPersona, parseArm, pickDeterministicArm, pickFromWeights, pooledPosterior, posteriorOfCounts, resolvePersona, sampleArm, sampleArmEv, sampleBeta, shrunkAvgValue, shrunkPosterior, slotBaselineArm, slotResultFor, validateSlotDecl, weightCellsFor };
747
+ export { type ArmPosterior, DEFAULT_PERSONA_VOCABULARY, EV_SHRINK_K, type EvArm, type FactoredSlotChoice, GLOBAL_FACTOR_LEVEL, GLOBAL_FACTOR_PERSONA, LAYOUT_ARCHETYPES, LAYOUT_ARCHETYPE_NAMES, LAYOUT_FACTOR_BUCKETS, type LayoutArchetype, type LayoutFactorCell, type LearnedLayout, PERSONA_KEY_RE, POOL_ALL, type PersonaResolution, type PersonaVocabularyMember, type PoolCells, type PoolCounts, RESERVED_PERSONA_KEYS, SHRINKAGE_M, SLOT_FACTORS, type SlotDecl, type SlotFactor, type SlotFactorCell, type SlotFactorContext, type SlotResult, UNKNOWN_PERSONA, UNKNOWN_PERSONA_DISPLAY, type ValueCell, type ValueCellRow, WEIGHTS_FALLBACK_PRIOR_PULLS, type WeightsFallbackArm, applyClusterHeuristic, broadestValueCell, candidateLayouts, canonicalArm, canonicalPersona, chooseLayout, chooseLayoutFactored, chooseSlotArmFactored, confidenceBand, decisionPersona, factorCellsForOrder, factorCellsForTrial, factorLevelsFor, fnv1a, hashLayout, layoutBucketOf, marginalArmKey, normalizeDeclaredPersona, orderByArchetype, parseArm, pickDeterministicArm, pickFromWeights, pooledPosterior, posteriorOfCounts, previewOrderForPersona, resolvePersona, sampleArm, sampleArmEv, sampleBeta, shrunkAvgValue, shrunkPosterior, slotBaselineArm, slotResultFor, validateSlotDecl, visitLevel, weightCellsFor };
package/dist/index.d.ts CHANGED
@@ -1,50 +1,111 @@
1
1
  import { SectionRole } from './taxonomy.js';
2
2
 
3
- declare const PERSONAS: readonly ["buyer", "researcher", "deal_seeker", "browser"];
4
- type Persona = (typeof PERSONAS)[number];
5
- declare const UNKNOWN_PERSONA: "unknown";
6
- type PersonaKey = Persona | typeof UNKNOWN_PERSONA;
7
- /** Human-facing names — dashboard/devtools copy must use these, never raw keys. */
8
- declare const PERSONA_DISPLAY: Record<PersonaKey, string>;
9
3
  /**
10
- * Every label ever written for a persona, mapped to its canonical form. The
11
- * plural/hyphen labels are the pre-069 cluster seed labels
12
- * ('buyers'/'deal-seekers'/…); identity mappings make canonical input a no-op.
4
+ * The one persona key the product itself defines: "we don't know".
5
+ *
6
+ * There is deliberately no persona LIST here. Until 2026-09-13 this module
7
+ * exported a hardcoded four-persona vocabulary (with display names and a
8
+ * plural-label alias map) that every project inherited, and the product
9
+ * presented those names as if it knew the customer's audience. Every persona
10
+ * now comes from the project's own vocabulary — declared by the app or
11
+ * promoted by discovery (see `vocabulary.ts`) — and anything else is
12
+ * `unknown`, which is where the pooled bandit does the actual work.
13
13
  */
14
- declare const LEGACY_PERSONA_MAP: Record<string, Persona>;
14
+ declare const UNKNOWN_PERSONA: "unknown";
15
+ /** Human-facing name of `UNKNOWN_PERSONA`. Every other persona's display name
16
+ * comes from its vocabulary member, never from a table in this package. */
17
+ declare const UNKNOWN_PERSONA_DISPLAY = "Unknown";
18
+
15
19
  /**
16
- * Canonicalizes any persona/cluster label to the pinned vocabulary.
17
- * Null, undefined, empty, and unrecognized labels all become 'unknown' —
18
- * "we don't know" is always a safe answer; a guessed persona is not.
20
+ * A catalogue of plausible page orderings NOT a persona taxonomy.
21
+ *
22
+ * These four orderings were keyed by the four seeded persona names until
23
+ * 2026-09-13 (the table was exported as `CLUSTER_PRIORITY`, now removed).
24
+ * Those personas were removed, but the deeper problem was that keying orderings by persona NAME was
25
+ * wrong even while they existed, in two ways that were invisible until a
26
+ * customer declared a persona of their own:
27
+ *
28
+ * 1. THE ARM SPACE DEPENDED ON WHAT THE CUSTOMER NAMED THEIR PERSONA. A
29
+ * project that declared `admin` got five candidate orders including the
30
+ * page exactly as authored; a project that declared one of the seeded names got four, and
31
+ * the authored order was NOT among them — so its own page order could never
32
+ * be served to that persona, and there was no control arm to lose to.
33
+ * Renaming a persona silently changed which layouts were reachable.
34
+ *
35
+ * 2. THE FEASIBILITY GATE SERVED THE COLLIDING ARCHETYPE DETERMINISTICALLY.
36
+ * `routes/decide.ts` serves a heuristic order (never explored, never
37
+ * learned) when layout feasibility is `infeasible` — the state every new
38
+ * project is in. A customer who declared the seeded conversion persona had pricing hoisted above
39
+ * everything on every visit, permanently, because of a table written in
40
+ * May; a customer who declared `admin` correctly kept their own page.
41
+ *
42
+ * So the orderings are now named for what they DO. The names are internal and
43
+ * carry no claim about any visitor: they are four opinions about what a page
44
+ * should lead with, and the bandit decides between them and the authored order
45
+ * using evidence. The arrays are unchanged, so every `layout_weights` row in
46
+ * production still joins — `hashLayout` hashes the resulting ORDER, never the
47
+ * key that produced it.
19
48
  */
20
- declare function canonicalPersona(label: string | null | undefined): PersonaKey;
21
-
22
- declare const CLUSTER_PRIORITY: Record<Persona, string[]>;
49
+ declare const LAYOUT_ARCHETYPES: Record<string, readonly string[]>;
50
+ /** The archetype names, in a pinned order — iteration order decides candidate
51
+ * insertion order, so it must not depend on object-key enumeration luck. */
52
+ declare const LAYOUT_ARCHETYPE_NAMES: readonly ["conversion_led", "evidence_led", "price_led", "discovery_led"];
53
+ type LayoutArchetype = (typeof LAYOUT_ARCHETYPE_NAMES)[number];
23
54
  /**
24
- * Reorders section IDs based on the persona's semantic priority.
55
+ * Reorders section IDs by one archetype's semantic priority.
25
56
  * Sections with no graph entry are treated as 'generic'.
26
- * Returns the input unchanged for 'unknown' — and for any custom vocabulary
27
- * persona (declared/discovered): those have no semantic prior, so they serve
28
- * the natural order until the layout bandit has learned rows, the same
29
- * cold-start posture 'unknown' gets.
30
57
  *
31
58
  * With `sectionRoles` (spec 2026-09-04 §1, phase 2d) the ordering projection is
32
59
  * `(role, parent)`: structural sections are PINNED at their original index and
33
60
  * only converters/persuaders re-rank around them. The pin is not cosmetic —
34
- * 'navigation' ranks near last in every persona priority, so an unpinned navbar
35
- * or footer would sort to the bottom of the page, exactly the visible damage a
61
+ * 'navigation' ranks near last in every archetype, so an unpinned navbar or
62
+ * footer would sort to the bottom of the page, exactly the visible damage a
36
63
  * reorder must never do. Callers without role data (the client-local fallback)
37
64
  * omit the map and get the pre-2d behaviour unchanged.
38
65
  */
39
- declare function applyClusterHeuristic(sections: string[], sectionTypes: Map<string, string>, persona: string, sectionRoles?: Map<string, SectionRole>): string[];
66
+ declare function orderByArchetype(sections: string[], sectionTypes: Map<string, string>, archetype: LayoutArchetype, sectionRoles?: Map<string, SectionRole>): string[];
40
67
  /**
41
- * The candidate layout orderings for a page the distinct section orders
42
- * produced by every persona's semantic priority (plus the requesting
43
- * persona's own, which for 'unknown' is the identity order). These are the
44
- * "arms" the layout bandit explores. Returned as hash order so it joins
45
- * directly against layout_weights rows keyed by the same hashLayout.
68
+ * PREVIEW ONLY the keyless local engine (`@sentientui/core` index-local).
69
+ *
70
+ * Maps an arbitrary persona string to one archetype so that
71
+ * `?sentient_persona=<anything>` visibly rearranges a page with no API key and
72
+ * no server. It used to look the key up in the four-persona table, so only
73
+ * the four seeded names did anything and every other key silently no-oped;
74
+ * now any key previews an arrangement, which is what the docs promise.
75
+ *
76
+ * DO NOT USE THIS ON THE SERVER. The mapping is a hash, not a belief: it
77
+ * carries no claim that this persona wants this ordering. Server serving picks
78
+ * between the archetypes and the authored order with `chooseLayout` /
79
+ * `chooseLayoutFactored`, on evidence.
80
+ *
81
+ * 'unknown' and the empty string return the sections untouched — the natural
82
+ * order is what an unidentified visitor gets, here as everywhere.
83
+ */
84
+ declare function previewOrderForPersona(sections: string[], sectionTypes: Map<string, string>, persona: string, sectionRoles?: Map<string, SectionRole>): string[];
85
+ /**
86
+ * @deprecated Renamed. Use `orderByArchetype` on the server (by archetype) or
87
+ * `previewOrderForPersona` in the keyless local engine (by arbitrary key).
88
+ * Kept as an alias of the preview mapping so the published signature survives.
46
89
  */
47
- declare function candidateLayouts(sections: string[], sectionTypes: Map<string, string>, persona: string, sectionRoles?: Map<string, SectionRole>): Map<string, string[]>;
90
+ declare const applyClusterHeuristic: typeof previewOrderForPersona;
91
+ /**
92
+ * The candidate layout orderings for a page — the arms the layout bandit
93
+ * explores. Returned as hash → order so it joins directly against
94
+ * `layout_weights` rows keyed by the same `hashLayout`.
95
+ *
96
+ * THE AUTHORED ORDER IS ALWAYS AN ARM. It is the control: the customer built
97
+ * this page in this order, and a bandit whose arm space excludes the baseline
98
+ * can never conclude "leave it alone" — it is structurally obliged to reorder
99
+ * something, and there is nothing for a holdout comparison to mean. Before
100
+ * 2026-09-13 the authored order was included only by accident, when the
101
+ * requesting persona's name happened to miss the archetype table; a persona
102
+ * whose name matched the table had it excluded entirely.
103
+ *
104
+ * It no longer takes a persona. The set of orderings a page COULD be shown in
105
+ * is a property of the page, not of who is looking at it — what the persona
106
+ * changes is which arm wins, and that is the posterior's job.
107
+ */
108
+ declare function candidateLayouts(sections: string[], sectionTypes: Map<string, string>, sectionRoles?: Map<string, SectionRole>): Map<string, string[]>;
48
109
 
49
110
  /**
50
111
  * Stable 16-char SHA-256 prefix for a section order array.
@@ -69,15 +130,25 @@ type LearnedLayout = {
69
130
  * Thompson-samples the layout order to serve a persona over the candidate
70
131
  * orderings, using learned posteriors from layout_weights. Candidates with no
71
132
  * learned row use the uniform 1/1 prior — identical to variant cold start.
72
- * Falls back to the persona's heuristic only if sampling yields no candidate.
133
+ *
134
+ * The candidate set always contains the AUTHORED order, so "leave this page
135
+ * alone" is a real arm that can win, and at cold start it is exactly as likely
136
+ * as any reorder. This used to fall back to the persona's heuristic when
137
+ * sampling yielded nothing — which could not happen (candidateLayouts is never
138
+ * empty) and would have been the wrong answer anyway: with nothing to choose
139
+ * on, the page the customer built is the only defensible thing to serve.
140
+ *
141
+ * `persona` no longer selects candidates — the orderings a page COULD be shown
142
+ * in are a property of the page. It stays in the signature because it is what
143
+ * the CALLER keyed `learned` by, which is where the persona belongs.
73
144
  *
74
145
  * @param rand Uniform [0,1) source. Defaults to `Math.random`, which is
75
146
  * NON-DETERMINISTIC. Pass a seeded PRNG when you need a reproducible layout
76
147
  * (tests, replayable decisions) — otherwise the sampled order varies per call.
77
148
  * @param sectionRoles Optional role map (phase 2d): structural sections are
78
- * pinned in place across every candidate; see `applyClusterHeuristic`.
149
+ * pinned in place across every candidate; see `orderByArchetype`.
79
150
  */
80
- declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>, persona: string, learned: Map<string, LearnedLayout>, rand?: () => number, sectionRoles?: Map<string, SectionRole>): string[];
151
+ declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>, _persona: string, learned: Map<string, LearnedLayout>, rand?: () => number, sectionRoles?: Map<string, SectionRole>): string[];
81
152
 
82
153
  /**
83
154
  * Factored layout value model (spec 2026-09-04 §3a).
@@ -133,10 +204,130 @@ declare function factorCellsForOrder(order: string[], sectionTypes: Map<string,
133
204
  * start degrades gracefully: empty cells draw from Beta(1,1), which still
134
205
  * randomises across candidates, so exploration survives the switch.
135
206
  *
136
- * Falls back to the persona's heuristic prior when there are no candidates.
207
+ * The candidate set always contains the AUTHORED order, so leaving the page as
208
+ * built is a real arm rather than something only reachable by accident.
137
209
  */
138
210
  declare function chooseLayoutFactored(sections: string[], sectionTypes: Map<string, string>, persona: string, cells: LayoutFactorCell[], rand?: () => number, sectionRoles?: Map<string, SectionRole>): string[];
139
211
 
212
+ /**
213
+ * Factored slot value model — the slot analogue of `layout-factored.ts`.
214
+ *
215
+ * `slot_weights` keys one independent Beta posterior per
216
+ * (arm, segment, persona), where `segment` is the joined `device:source`. That
217
+ * is why the decision context stopped at two fields: every field ADDED
218
+ * multiplies the cell count. Three devices x six sources x five personas is
219
+ * already 90 cells per arm; adding new-vs-returning makes it 180, adding
220
+ * country multiplies again — and each cell estimates from its own slice of the
221
+ * same thin traffic, so a richer context converges WORSE, not better.
222
+ *
223
+ * This model replaces one-parameter-per-context-combination with one per
224
+ * context FACTOR:
225
+ *
226
+ * V(arm | context) = Σ_f draw[arm, factor f, level of f in this context]
227
+ *
228
+ * Four factors at 3 + 6 + 5 + 2 levels is 16 cells per arm instead of 180, and
229
+ * every trial teaches every context that shares ANY factor level: a conversion
230
+ * on mobile/paid/returning updates "this arm on mobile", which transfers to
231
+ * mobile/organic/new. Additive over factors is the same shape the layout model
232
+ * uses additively over positions, and it is fair across arms for the same
233
+ * reason — every arm in one request is scored under the identical factor set,
234
+ * so the sum has the same number of terms for each.
235
+ *
236
+ * It improves sample efficiency; it does not manufacture signal. With a handful
237
+ * of conversions no model learns a ranking, which is what the feasibility gate
238
+ * exists to say out loud.
239
+ */
240
+ /** Persona key of the pooled global cells. Reserved — never a real persona. */
241
+ declare const GLOBAL_FACTOR_LEVEL = "__global__";
242
+ /**
243
+ * Context factors the model conditions on, in a pinned order.
244
+ *
245
+ * Widening this list is the whole point of the design — it costs O(levels), not
246
+ * O(product) — but it is NOT free: each factor adds a term to the sum, and a
247
+ * factor with no signal adds variance to every score. Add one when there is a
248
+ * reason to believe it changes which arm wins, not because the column exists.
249
+ */
250
+ declare const SLOT_FACTORS: readonly ["device", "source", "persona", "visit"];
251
+ type SlotFactor = (typeof SLOT_FACTORS)[number];
252
+ type SlotFactorContext = {
253
+ device: string | null;
254
+ source: string | null;
255
+ persona: string;
256
+ /** 'new' | 'returning' | null when the visit count is unknown. */
257
+ visit: string | null;
258
+ };
259
+ type SlotFactorCell = {
260
+ arm: string;
261
+ factor: string;
262
+ /** Factor level, or GLOBAL_FACTOR_LEVEL for the arm's context-free row. */
263
+ level: string;
264
+ exposures: number;
265
+ conversions: number;
266
+ };
267
+ /**
268
+ * The (factor, level) pairs one served decision contributes to — the write-side
269
+ * projection close-out uses, and the read-side lookup serving uses. Both call
270
+ * this so the two can never disagree about what a context decomposes into.
271
+ *
272
+ * An UNKNOWN persona contributes no persona term. That is the Pareto safety
273
+ * invariant of CONTRACTS §4 restated for this model: unknown-persona traffic
274
+ * must run on exactly the persona-agnostic policy, so the persona factor is not
275
+ * merely empty for them, it is absent — an `unknown` LEVEL would otherwise
276
+ * become a real segment that accumulates its own rate and steers serving.
277
+ *
278
+ * A null device/source/visit is likewise absent rather than levelled as
279
+ * 'unknown', for the same reason: "not measured" is not a level.
280
+ */
281
+ declare function factorLevelsFor(ctx: SlotFactorContext): Array<{
282
+ factor: SlotFactor;
283
+ level: string;
284
+ }>;
285
+ type FactoredSlotChoice = {
286
+ arm: string;
287
+ factorsUsed: number;
288
+ };
289
+ /**
290
+ * Thompson-style selection over the factored model.
291
+ *
292
+ * ONE draw per (arm, factor, level) cell, cached for the call — an arm's score
293
+ * is the sum of its factor draws, and every arm is scored under the same factor
294
+ * set, so the comparison is made in a single sampled world. A fresh draw per
295
+ * comparison would add pure noise to the ranking rather than exploration.
296
+ *
297
+ * Shrinkage follows CONTRACTS §4 exactly: a factor-level cell shrinks toward
298
+ * the arm's GLOBAL cell, and the global cell shrinks toward the slot's pooled
299
+ * rate across all arms. The MEAN crosses each boundary, never the sample size —
300
+ * so a thin factor level keeps a posterior as wide as its own evidence warrants
301
+ * and Thompson sampling still explores it.
302
+ *
303
+ * Cold start degrades to the unfactored behaviour: with no cells at all every
304
+ * arm draws from the same shrunken pool, which still randomises, so exploration
305
+ * survives enabling this model on a project with history.
306
+ */
307
+ declare function chooseSlotArmFactored(arms: readonly string[], ctx: SlotFactorContext, cells: readonly SlotFactorCell[], rand?: () => number): FactoredSlotChoice | null;
308
+ /**
309
+ * The cells one closed trial writes, given its context — the write-side
310
+ * projection. Always includes the arm's GLOBAL row, which is what every factor
311
+ * level shrinks toward and what the slot-wide pool is summed from.
312
+ *
313
+ * `personaWeight` is the SOFT assignment the layout model established: the
314
+ * persona term trains at the portrait's `reliability_score` (declared personas
315
+ * at 1.0) while every other factor trains at 1. A mismeasured persona otherwise
316
+ * induces attenuation bias — it drags a cell toward the population mean in
317
+ * proportion to how often it is wrong — and soft weighting lets the large
318
+ * unknown mass inform the global term instead of forming a dead bucket.
319
+ */
320
+ declare function factorCellsForTrial(arm: string, ctx: SlotFactorContext, personaWeight: number): Array<{
321
+ arm: string;
322
+ factor: string;
323
+ level: string;
324
+ weight: number;
325
+ }>;
326
+ /** Visit-count bucket. Two levels on purpose: new-vs-returning is one of the
327
+ * largest conversion differences on any site, and finer buckets would spend
328
+ * parameters on a tail that prod does not currently have. */
329
+ declare function visitLevel(visitCount: number | null | undefined): string | null;
330
+
140
331
  /** Learned Beta(alpha, beta) posterior for one arm. */
141
332
  type ArmPosterior = {
142
333
  arm: string;
@@ -412,8 +603,7 @@ declare function sampleArmEv(arms: EvArm[], reference: number, rand?: () => numb
412
603
  /**
413
604
  * Per-project persona vocabularies (spec: 2026-08-27-declared-personas-design.md).
414
605
  *
415
- * The persona axis stops being the closed global union in `personas.ts` and
416
- * becomes a per-project member list (persona_sets / persona_set_members,
606
+ * The persona axis is a per-project member list (persona_sets / persona_set_members,
417
607
  * migration 113). This module owns resolution: which persona a decision is
418
608
  * keyed on, given what the customer's app declared and what clustering
419
609
  * inferred. The weight tables and pooling are already string-generic —
@@ -444,12 +634,30 @@ declare const PERSONA_KEY_RE: RegExp;
444
634
  */
445
635
  declare function normalizeDeclaredPersona(raw: string | null | undefined): string | null;
446
636
  /**
447
- * Keys no vocabulary member may claim. 'unknown' and '__all__' are structural
448
- * in weightCellsFor / CONTRACTS §4; the plural forms are pre-069 legacy labels
449
- * that canonicalPersona still remaps, so a member claiming one would be
450
- * silently rewritten at resolve time. MUST stay in sync with migration 113.
637
+ * Keys no vocabulary member may claim: 'unknown' and '__all__' are structural
638
+ * in weightCellsFor / CONTRACTS §4. MUST stay in sync with the CHECK constraint
639
+ * on persona_set_members.key (migration 113, relaxed by 153).
640
+ *
641
+ * This also reserved four plural labels until 2026-09-13, because the
642
+ * name-specific alias map that remapped them at resolve time would have
643
+ * silently rewritten a member claiming one. That map went with the seeded
644
+ * personas, so those strings are ordinary keys now.
451
645
  */
452
646
  declare const RESERVED_PERSONA_KEYS: readonly string[];
647
+ /**
648
+ * Canonicalizes any persona/cluster label to a persona KEY: trimmed,
649
+ * lowercased, and shaped like one (`PERSONA_KEY_RE`). Null, empty, and
650
+ * anything that could never be a key become 'unknown' — "we don't know" is
651
+ * always a safe answer.
652
+ *
653
+ * Generic on purpose. This used to be a lookup in a table of four seeded
654
+ * persona names, so every OTHER label — including every key a customer
655
+ * declared — folded to 'unknown': a registry pin scoped to `admin` was stored
656
+ * under 'unknown' and then applied to every unidentified visitor. It says
657
+ * nothing about membership; callers that serve must still check the project's
658
+ * vocabulary (resolvePersona does).
659
+ */
660
+ declare function canonicalPersona(label: string | null | undefined): string;
453
661
  type PersonaVocabularyMember = {
454
662
  key: string;
455
663
  displayName: string;
@@ -459,9 +667,34 @@ type PersonaVocabularyMember = {
459
667
  status?: 'active' | 'retired';
460
668
  };
461
669
  /**
462
- * The pinned four as a vocabulary what every project's version-1 default set
463
- * contains, and the fallback when a project has no active set (a missed
464
- * app-code insert degrades to today's behaviour, never an error).
670
+ * The vocabulary a project has when it has declared nothing: EMPTY.
671
+ *
672
+ * This shipped as a hardcoded four seeded personas, which the product then
673
+ * presented as if it knew the customer's audience.
674
+ * Earned rows (2026-09-12) demoted them to a `starter` state; this removes them.
675
+ *
676
+ * The measurement that settled it, across all of production history:
677
+ * `unknown` served 10,882 decisions, the four seeded personas served **18
678
+ * between them**. They were not a taxonomy, they were decoration on an axis
679
+ * that was 99.8% empty — and every one of them was an assertion about visitors
680
+ * nobody had met.
681
+ *
682
+ * A persona now has exactly two honest origins:
683
+ *
684
+ * - **declared** — the customer's own code tells us (a role, a plan tier).
685
+ * Ground truth; no evidence gate, because eligibility is their decision.
686
+ * - **discovered** — `persona-discovery.ts` finds it in real behaviour and it
687
+ * clears the interaction gate (the RANKING of arms must differ inside vs
688
+ * outside the segment, not merely the conversion rate).
689
+ *
690
+ * Everything else resolves to `unknown`, which is where day-0 value accrues and
691
+ * where the pooled bandit has always done the actual work.
692
+ *
693
+ * THIS IS ONLY SAFE BECAUSE SERVING NO LONGER NEEDS A PERSONA. The factored
694
+ * model (migration 149) conditions on device, source and visit count as
695
+ * first-class factors — measured, not guessed — so a project with no personas
696
+ * still adapts per visitor. Before that landed, emptying this would have meant
697
+ * no personalization at all.
465
698
  */
466
699
  declare const DEFAULT_PERSONA_VOCABULARY: readonly PersonaVocabularyMember[];
467
700
  type PersonaResolution = {
@@ -481,8 +714,7 @@ type PersonaResolution = {
481
714
  * Resolves the persona a decision is keyed on.
482
715
  *
483
716
  * Precedence, in order:
484
- * 1. Declared value matching an active member (directly, via alias, or via a
485
- * legacy plural label whose canonical form is a member) → that key,
717
+ * 1. Declared value matching an active member (directly or via alias) that key,
486
718
  * confidence 1. Declared skips reliability gating: it is ground truth from
487
719
  * the customer's app, the same trust level as everything else the pk_ key
488
720
  * sends.
@@ -502,11 +734,14 @@ declare function resolvePersona(input: {
502
734
  * Normalizes a DECISION-TIME persona (slot_decisions / layout_decisions rows)
503
735
  * for training. Unlike `canonicalPersona`, this trusts the stored value
504
736
  * verbatim: it was validated against the project vocabulary when the decision
505
- * was written, and re-squashing it through the closed global union at close-out
737
+ * was written, and re-squashing it through a closed global union at close-out
506
738
  * silently rerouted every declared-persona trial onto the 'unknown' marginals
507
- * (the double-squash bug, spec §4.3). Only the legacy plural labels are still
508
- * remapped — pre-069 rows carry them.
739
+ * (the double-squash bug, spec §4.3).
740
+ *
741
+ * It also remapped four pre-069 plural labels until 2026-09-13; migration 069
742
+ * had already rewritten those rows, and the remap was keyed on the retired
743
+ * seeded names, so it went with them.
509
744
  */
510
745
  declare function decisionPersona(label: string | null | undefined): string;
511
746
 
512
- export { type ArmPosterior, CLUSTER_PRIORITY, DEFAULT_PERSONA_VOCABULARY, EV_SHRINK_K, type EvArm, GLOBAL_FACTOR_PERSONA, LAYOUT_FACTOR_BUCKETS, LEGACY_PERSONA_MAP, type LayoutFactorCell, type LearnedLayout, PERSONAS, PERSONA_DISPLAY, PERSONA_KEY_RE, POOL_ALL, type Persona, type PersonaKey, type PersonaResolution, type PersonaVocabularyMember, type PoolCells, type PoolCounts, RESERVED_PERSONA_KEYS, SHRINKAGE_M, type SlotDecl, type SlotResult, UNKNOWN_PERSONA, type ValueCell, type ValueCellRow, WEIGHTS_FALLBACK_PRIOR_PULLS, type WeightsFallbackArm, applyClusterHeuristic, broadestValueCell, candidateLayouts, canonicalArm, canonicalPersona, chooseLayout, chooseLayoutFactored, confidenceBand, decisionPersona, factorCellsForOrder, fnv1a, hashLayout, layoutBucketOf, marginalArmKey, normalizeDeclaredPersona, parseArm, pickDeterministicArm, pickFromWeights, pooledPosterior, posteriorOfCounts, resolvePersona, sampleArm, sampleArmEv, sampleBeta, shrunkAvgValue, shrunkPosterior, slotBaselineArm, slotResultFor, validateSlotDecl, weightCellsFor };
747
+ export { type ArmPosterior, DEFAULT_PERSONA_VOCABULARY, EV_SHRINK_K, type EvArm, type FactoredSlotChoice, GLOBAL_FACTOR_LEVEL, GLOBAL_FACTOR_PERSONA, LAYOUT_ARCHETYPES, LAYOUT_ARCHETYPE_NAMES, LAYOUT_FACTOR_BUCKETS, type LayoutArchetype, type LayoutFactorCell, type LearnedLayout, PERSONA_KEY_RE, POOL_ALL, type PersonaResolution, type PersonaVocabularyMember, type PoolCells, type PoolCounts, RESERVED_PERSONA_KEYS, SHRINKAGE_M, SLOT_FACTORS, type SlotDecl, type SlotFactor, type SlotFactorCell, type SlotFactorContext, type SlotResult, UNKNOWN_PERSONA, UNKNOWN_PERSONA_DISPLAY, type ValueCell, type ValueCellRow, WEIGHTS_FALLBACK_PRIOR_PULLS, type WeightsFallbackArm, applyClusterHeuristic, broadestValueCell, candidateLayouts, canonicalArm, canonicalPersona, chooseLayout, chooseLayoutFactored, chooseSlotArmFactored, confidenceBand, decisionPersona, factorCellsForOrder, factorCellsForTrial, factorLevelsFor, fnv1a, hashLayout, layoutBucketOf, marginalArmKey, normalizeDeclaredPersona, orderByArchetype, parseArm, pickDeterministicArm, pickFromWeights, pooledPosterior, posteriorOfCounts, previewOrderForPersona, resolvePersona, sampleArm, sampleArmEv, sampleBeta, shrunkAvgValue, shrunkPosterior, slotBaselineArm, slotResultFor, validateSlotDecl, visitLevel, weightCellsFor };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var F=Object.defineProperty;var de=Object.getOwnPropertyDescriptor;var xe=Object.getOwnPropertyNames,J=Object.getOwnPropertySymbols;var ee=Object.prototype.hasOwnProperty,he=Object.prototype.propertyIsEnumerable;var Q=(e,r,t)=>r in e?F(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t,B=(e,r)=>{for(var t in r||(r={}))ee.call(r,t)&&Q(e,t,r[t]);if(J)for(var t of J(r))he.call(r,t)&&Q(e,t,r[t]);return e};var ye=(e,r)=>{for(var t in r)F(e,t,{get:r[t],enumerable:!0})},ve=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of xe(r))!ee.call(e,o)&&o!==t&&F(e,o,{get:()=>r[o],enumerable:!(n=de(r,o))||n.enumerable});return e};var Pe=e=>ve(F({},"__esModule",{value:!0}),e);var qe={};ye(qe,{CLUSTER_PRIORITY:()=>te,DEFAULT_PERSONA_VOCABULARY:()=>me,EV_SHRINK_K:()=>ue,GLOBAL_FACTOR_PERSONA:()=>se,LAYOUT_FACTOR_BUCKETS:()=>Y,LEGACY_PERSONA_MAP:()=>U,PERSONAS:()=>K,PERSONA_DISPLAY:()=>q,PERSONA_KEY_RE:()=>ce,POOL_ALL:()=>P,RESERVED_PERSONA_KEYS:()=>fe,SHRINKAGE_M:()=>ne,UNKNOWN_PERSONA:()=>R,WEIGHTS_FALLBACK_PRIOR_PULLS:()=>oe,applyClusterHeuristic:()=>L,broadestValueCell:()=>_e,candidateLayouts:()=>D,canonicalArm:()=>T,canonicalPersona:()=>V,chooseLayout:()=>Se,chooseLayoutFactored:()=>Me,confidenceBand:()=>Ke,decisionPersona:()=>Be,factorCellsForOrder:()=>we,fnv1a:()=>ie,hashLayout:()=>H,layoutBucketOf:()=>W,marginalArmKey:()=>Ee,normalizeDeclaredPersona:()=>Fe,parseArm:()=>Z,pickDeterministicArm:()=>Ie,pickFromWeights:()=>Re,pooledPosterior:()=>Ce,posteriorOfCounts:()=>A,resolvePersona:()=>$e,sampleArm:()=>z,sampleArmEv:()=>De,sampleBeta:()=>w,shrunkAvgValue:()=>le,shrunkPosterior:()=>C,slotBaselineArm:()=>ae,slotResultFor:()=>Ne,validateSlotDecl:()=>Oe,weightCellsFor:()=>Le});module.exports=Pe(qe);var K=["buyer","researcher","deal_seeker","browser"],R="unknown",q={buyer:"Buyer",researcher:"Researcher",deal_seeker:"Deal seeker",browser:"Browser",unknown:"Unknown"},U={buyers:"buyer",researchers:"researcher","deal-seekers":"deal_seeker",browsers:"browser",buyer:"buyer",researcher:"researcher",deal_seeker:"deal_seeker",browser:"browser"};function V(e){var t;if(e==null)return R;let r=e.trim().toLowerCase();return(t=U[r])!=null?t:R}var ke=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];function k(e,r){return e>>>r|e<<32-r}function re(e){return(e>>>0).toString(16).padStart(8,"0")}function Ae(e){let r=new TextEncoder().encode(e),t=r.length,n=t*8,o=(t+8>>6)+1<<6,s=new Uint8Array(o);s.set(r),s[t]=128;let a=new DataView(s.buffer);a.setUint32(o-8,Math.floor(n/4294967296),!1),a.setUint32(o-4,n>>>0,!1);let i=1779033703,u=3144134277,l=1013904242,f=2773480762,h=1359893119,g=2600822924,p=528734635,y=1541459225,b=new Uint32Array(64);for(let M=0;M<o;M+=64){for(let m=0;m<16;m++)b[m]=a.getUint32(M+m*4,!1);for(let m=16;m<64;m++){let N=k(b[m-15],7)^k(b[m-15],18)^b[m-15]>>>3,I=k(b[m-2],17)^k(b[m-2],19)^b[m-2]>>>10;b[m]=b[m-16]+N+b[m-7]+I>>>0}let c=i,v=u,d=l,E=f,x=h,_=g,S=p,O=y;for(let m=0;m<64;m++){let N=k(x,6)^k(x,11)^k(x,25),I=x&_^~x&S,X=O+N+I+ke[m]+b[m]>>>0,pe=k(c,2)^k(c,13)^k(c,22),ge=c&v^c&d^v&d,be=pe+ge>>>0;O=S,S=_,_=x,x=E+X>>>0,E=d,d=v,v=c,c=X+be>>>0}i=i+c>>>0,u=u+v>>>0,l=l+d>>>0,f=f+E>>>0,h=h+x>>>0,g=g+_>>>0,p=p+S>>>0,y=y+O>>>0}return re(i)+re(u)}function H(e){return Ae(e.join("|"))}var te={buyer:["pricing","cta","hero","comparison","social_proof","trust","features","faq","navigation","generic"],researcher:["features","comparison","faq","hero","trust","social_proof","pricing","cta","navigation","generic"],deal_seeker:["pricing","comparison","social_proof","trust","cta","hero","features","faq","navigation","generic"],browser:["hero","features","social_proof","pricing","cta","trust","faq","comparison","navigation","generic"]};function L(e,r,t,n){let o=te[t];if(!o)return e;let s=o.indexOf("generic"),a=l=>{let f=o.indexOf(l);return f===-1?s:f},i=n?e.filter(l=>n.get(l)!=="structural"):[...e];if(i.sort((l,f)=>{var p,y;let h=(p=r.get(l))!=null?p:"generic",g=(y=r.get(f))!=null?y:"generic";return a(h)-a(g)}),!n)return i;let u=0;return e.map(l=>n.get(l)==="structural"?l:i[u++])}function D(e,r,t,n){let o=new Map;for(let s of[...K,t]){let a=L(e,r,s,n);o.set(H(a),a)}return o}function j(e,r){if(e<1)return j(1+e,r)*Math.pow(Math.max(1e-15,r()),1/e);let t=e-1/3,n=1/Math.sqrt(9*t);for(;;){let o,s;do{let i=Math.max(1e-15,r()),u=r();o=Math.sqrt(-2*Math.log(i))*Math.cos(2*Math.PI*u),s=1+n*o}while(s<=0);s=s*s*s;let a=r();if(a<1-.0331*o*o*o*o||Math.log(a)<.5*o*o+t*(1-s+Math.log(s)))return t*s}}function w(e,r,t=Math.random){let n=j(e,t),o=j(r,t),s=n+o;return s<=0?e/(e+r):n/s}function z(e,r=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let t=e[0],n=w(t.alpha,t.beta,r);for(let o=1;o<e.length;o++){let s=e[o],a=w(s.alpha,s.beta,r);a>n&&(t=s,n=a)}return t.arm}function Se(e,r,t,n,o=Math.random,s){var f,h;let a=D(e,r,t,s),i=[];for(let g of a.keys()){let p=n.get(g);i.push({arm:g,alpha:(f=p==null?void 0:p.alpha)!=null?f:1,beta:(h=p==null?void 0:p.beta)!=null?h:1})}let u=z(i,o),l=u?a.get(u):void 0;return l!=null?l:L(e,r,t,s)}var ne=20;function C(e,r,t=20){let n=r.alpha+r.beta;if(n<=0||t<=0)return{alpha:e.alpha,beta:e.beta};let o=r.alpha/n,s=t*n/(n+t);return{alpha:e.alpha+s*o,beta:e.beta+s*(1-o)}}var oe=5;function Re(e,r){var n,o;let t=null;for(let s of e){if(!r.includes(s.variantId))continue;let a=(n=s.pulls)!=null?n:0,i=a>0?a*s.avgReward/(a+oe):0;(!t||i>t.score)&&(t={variantId:s.variantId,score:i})}return(o=t==null?void 0:t.variantId)!=null?o:null}var P="__all__",$={exposures:0,conversions:0};function A(e){return{alpha:e.conversions+1,beta:Math.max(0,e.exposures-e.conversions)+1}}function Ce(e,r,t=20){var g,p,y,b;let n=(g=e.segment)!=null?g:$,o=(p=e.global)!=null?p:$,s=A(o),a=C(A(n),s,t);if(!r)return a;let i=(y=e.persona)!=null?y:$,u=(b=e.child)!=null?b:$,l=C(A(i),s,t),f=(n.exposures+1)/(n.exposures+i.exposures+2),h={alpha:f*a.alpha+(1-f)*l.alpha,beta:f*a.beta+(1-f)*l.beta};return C(A(u),h,t)}function _e(e){var n,o;let r=null,t=-1;for(let s of e){let a=s.segment===P,i=s.persona===P,u=a&&i?3:a||i?2:1;u>t&&(t=u,r=s)}return{valueSum:(n=r==null?void 0:r.valueSum)!=null?n:0,valueCount:(o=r==null?void 0:r.valueCount)!=null?o:0}}function Le(e,r){return r==="unknown"||r===P||r===""?[{segment:e,persona:P},{segment:P,persona:P}]:[{segment:e,persona:r},{segment:e,persona:P},{segment:P,persona:r},{segment:P,persona:P}]}var Y=4,se="__global__";function W(e,r){return r<=0?0:Math.min(Y-1,Math.floor(e*Y/r))}function we(e,r){return e.map((t,n)=>{var o;return{parent:(o=r.get(t))!=null?o:"generic",bucket:W(n,e.length)}})}var G=(e,r)=>`${e}#${r}`;function Me(e,r,t,n,o=Math.random,s){var M;let a=D(e,r,t,s);if(a.size===0)return L(e,r,t,s);let i=new Map,u=new Map,l=0,f=0;for(let c of n)c.persona===se?(i.set(G(c.parent,c.bucket),c),l+=c.exposures,f+=c.conversions):c.persona===t&&u.set(G(c.parent,c.bucket),c);let h=A({exposures:l,conversions:f}),g=new Map,p=(c,v)=>{var N,I;let d=G(c,v),E=g.get(d);if(E!==void 0)return E;let x=i.get(d),_=C(A({exposures:(N=x==null?void 0:x.exposures)!=null?N:0,conversions:(I=x==null?void 0:x.conversions)!=null?I:0}),h),S=u.get(d),O=S?C(A({exposures:S.exposures,conversions:S.conversions}),_):_,m=w(O.alpha,O.beta,o);return g.set(d,m),m},y=null,b=-1/0;for(let c of a.values()){let v=0;for(let d=0;d<c.length;d++)v+=p((M=r.get(c[d]))!=null?M:"generic",W(d,c.length));v>b&&(b=v,y=c)}return y!=null?y:L(e,r,t,s)}function T(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("|")}function Z(e){if(e.length===0)return null;let r={};for(let t of e.split("|")){let n=t.indexOf("=");if(n<=0||n!==t.lastIndexOf("=")||n===t.length-1)return null;let o=t.slice(0,n);if(o in r)return null;r[o]=t.slice(n+1)}return r}function Ee(e,r){return`${e}=${r}`}function ae(e){var t,n,o;if(e.arms)return typeof e.baseline=="string"?e.baseline:(t=e.arms[0])!=null?t:"";if(e.baseline!==void 0&&typeof e.baseline=="object")return T(e.baseline);if(typeof e.baseline=="string")return e.baseline;let r={};for(let[s,a]of Object.entries((n=e.dims)!=null?n:{}))r[s]=(o=a[0])!=null?o:"";return T(r)}function Oe(e){let r=Array.isArray(e.arms),t=e.dims!=null;if(r&&t)return{ok:!1,reason:"declare exactly one of arms or dims (got both)"};if(!r&&!t)return{ok:!1,reason:"declare exactly one of arms or dims (got neither)"};if(r){let s=e.arms;if(s.length<2)return{ok:!1,reason:"arms requires at least 2 entries"};if(s.length>12)return{ok:!1,reason:"arms allows at most 12 entries"};if(new Set(s).size!==s.length)return{ok:!1,reason:"arms must be unique"};if(s.some(a=>a.includes("=")))return{ok:!1,reason:"enumerated arm ids may not contain '=' (reserved for dims encoding)"};if(e.baseline!==void 0){if(typeof e.baseline!="string")return{ok:!1,reason:"baseline for an arms slot must be a string"};if(!s.includes(e.baseline))return{ok:!1,reason:"baseline must be one of the declared arms"}}return{ok:!0}}let n=Object.entries(e.dims);if(n.length<1)return{ok:!1,reason:"dims requires at least 1 dimension"};if(n.length>4)return{ok:!1,reason:"dims allows at most 4 dimensions"};let o=1;for(let[s,a]of n){if(a.length<2)return{ok:!1,reason:`dim "${s}" requires at least 2 values`};if(a.length>6)return{ok:!1,reason:`dim "${s}" allows at most 6 values`};if(new Set(a).size!==a.length)return{ok:!1,reason:`dim "${s}" has duplicate values`};o*=a.length}if(o>64)return{ok:!1,reason:`declared space of ${o} combinations exceeds the 64 maximum`};if(e.baseline!==void 0){if(typeof e.baseline=="string")return{ok:!1,reason:"baseline for a dims slot must be a per-dim record"};let s=e.baseline,a=n.map(([u])=>u).sort(),i=Object.keys(s).sort();if(a.join(" ")!==i.join(" "))return{ok:!1,reason:"baseline must set every declared dim exactly once"};for(let[u,l]of n)if(!l.includes(s[u]))return{ok:!1,reason:`baseline value for dim "${u}" is not declared`}}return{ok:!0}}function Ne(e,r){var t,n;return e.dims!=null?(n=(t=Z(r))!=null?t:Z(ae(e)))!=null?n:{}:r}function ie(e){let r=2166136261;for(let t=0;t<e.length;t++)r^=e.charCodeAt(t),r=r+((r<<1)+(r<<4)+(r<<7)+(r<<8)+(r<<24))>>>0;return r}function Ie(e,r,t){if(t.length===0)throw new Error("pickDeterministicArm requires at least one arm");let n=[...t].sort();return n[ie(`${e}:${r}`)%n.length]}function Ke(e){return e>=.3?e<.7?"medium":"high":"low"}var ue=20;function le(e,r,t=ue){let n=e.valueCount>0?e.valueSum/e.valueCount:0;if(r<=0)return n;if(e.valueCount<=0)return r;let o=e.valueCount/(e.valueCount+t);return o*n+(1-o)*r}function De(e,r,t=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let n=null,o=-1/0;for(let s of e){let a=w(s.alpha,s.beta,t)*le(s,r);a>o&&(n=s,o=a)}return n.arm}var ce=/^[a-z0-9][a-z0-9_-]{0,31}$/;function Fe(e){if(typeof e!="string")return null;let r=e.trim().toLowerCase();return r&&ce.test(r)?r:null}var fe=["unknown","__all__","buyers","researchers","deal-seekers","browsers"],me=K.map(e=>({key:e,displayName:q[e]})),Ue=64;function Ve(e){var t;let r=new Map;for(let n of e)if(n.status!=="retired"){r.set(n.key,n.key);for(let o of(t=n.aliases)!=null?t:[])fe.includes(o)||r.set(o,n.key)}return r}function $e(e,r=me){var i,u,l;let t=Ve(r),n=(i=e.inferredConfidence)!=null?i:0,o,s=(l=(u=e.declared)==null?void 0:u.trim().toLowerCase())!=null?l:"";if(s!==""){let f=t.get(s),h=f===void 0?t.get(V(s)):void 0,g=f!=null?f:h;if(g!==void 0)return{persona:g,source:"declared",confidence:1};o=s.slice(0,Ue)}let a=V(e.clusterLabel);return a!==R&&t.has(a)?B({persona:t.get(a),source:"inferred",confidence:n},o!==void 0&&{unrecognizedDeclared:o}):B({persona:R,source:"none",confidence:n},o!==void 0&&{unrecognizedDeclared:o})}function Be(e){var t;if(e==null)return R;let r=e.trim().toLowerCase();return r===""?R:(t=U[r])!=null?t:r}0&&(module.exports={CLUSTER_PRIORITY,DEFAULT_PERSONA_VOCABULARY,EV_SHRINK_K,GLOBAL_FACTOR_PERSONA,LAYOUT_FACTOR_BUCKETS,LEGACY_PERSONA_MAP,PERSONAS,PERSONA_DISPLAY,PERSONA_KEY_RE,POOL_ALL,RESERVED_PERSONA_KEYS,SHRINKAGE_M,UNKNOWN_PERSONA,WEIGHTS_FALLBACK_PRIOR_PULLS,applyClusterHeuristic,broadestValueCell,candidateLayouts,canonicalArm,canonicalPersona,chooseLayout,chooseLayoutFactored,confidenceBand,decisionPersona,factorCellsForOrder,fnv1a,hashLayout,layoutBucketOf,marginalArmKey,normalizeDeclaredPersona,parseArm,pickDeterministicArm,pickFromWeights,pooledPosterior,posteriorOfCounts,resolvePersona,sampleArm,sampleArmEv,sampleBeta,shrunkAvgValue,shrunkPosterior,slotBaselineArm,slotResultFor,validateSlotDecl,weightCellsFor});
1
+ "use strict";var K=Object.defineProperty;var ye=Object.getOwnPropertyDescriptor;var ve=Object.getOwnPropertyNames,J=Object.getOwnPropertySymbols;var ee=Object.prototype.hasOwnProperty,Ae=Object.prototype.propertyIsEnumerable;var Q=(e,r,t)=>r in e?K(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t,$=(e,r)=>{for(var t in r||(r={}))ee.call(r,t)&&Q(e,t,r[t]);if(J)for(var t of J(r))Ae.call(r,t)&&Q(e,t,r[t]);return e};var Se=(e,r)=>{for(var t in r)K(e,t,{get:r[t],enumerable:!0})},Ce=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of ve(r))!ee.call(e,o)&&o!==t&&K(e,o,{get:()=>r[o],enumerable:!(n=ye(r,o))||n.enumerable});return e};var _e=e=>Ce(K({},"__esModule",{value:!0}),e);var Xe={};Se(Xe,{DEFAULT_PERSONA_VOCABULARY:()=>be,EV_SHRINK_K:()=>ue,GLOBAL_FACTOR_LEVEL:()=>U,GLOBAL_FACTOR_PERSONA:()=>ae,LAYOUT_ARCHETYPES:()=>te,LAYOUT_ARCHETYPE_NAMES:()=>T,LAYOUT_FACTOR_BUCKETS:()=>Y,PERSONA_KEY_RE:()=>fe,POOL_ALL:()=>_,RESERVED_PERSONA_KEYS:()=>me,SHRINKAGE_M:()=>oe,SLOT_FACTORS:()=>Ie,UNKNOWN_PERSONA:()=>M,UNKNOWN_PERSONA_DISPLAY:()=>ke,WEIGHTS_FALLBACK_PRIOR_PULLS:()=>se,applyClusterHeuristic:()=>Re,broadestValueCell:()=>Fe,candidateLayouts:()=>N,canonicalArm:()=>W,canonicalPersona:()=>ge,chooseLayout:()=>we,chooseLayoutFactored:()=>Ke,chooseSlotArmFactored:()=>Te,confidenceBand:()=>Ye,decisionPersona:()=>Ze,factorCellsForOrder:()=>Ue,factorCellsForTrial:()=>Ve,factorLevelsFor:()=>G,fnv1a:()=>le,hashLayout:()=>I,layoutBucketOf:()=>j,marginalArmKey:()=>De,normalizeDeclaredPersona:()=>pe,orderByArchetype:()=>D,parseArm:()=>Z,pickDeterministicArm:()=>qe,pickFromWeights:()=>Oe,pooledPosterior:()=>Ee,posteriorOfCounts:()=>S,previewOrderForPersona:()=>ne,resolvePersona:()=>We,sampleArm:()=>H,sampleArmEv:()=>je,sampleBeta:()=>R,shrunkAvgValue:()=>ce,shrunkPosterior:()=>C,slotBaselineArm:()=>ie,slotResultFor:()=>He,validateSlotDecl:()=>Be,visitLevel:()=>$e,weightCellsFor:()=>Ne});module.exports=_e(Xe);var M="unknown",ke="Unknown";var Le=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];function P(e,r){return e>>>r|e<<32-r}function re(e){return(e>>>0).toString(16).padStart(8,"0")}function Pe(e){let r=new TextEncoder().encode(e),t=r.length,n=t*8,o=(t+8>>6)+1<<6,s=new Uint8Array(o);s.set(r),s[t]=128;let a=new DataView(s.buffer);a.setUint32(o-8,Math.floor(n/4294967296),!1),a.setUint32(o-4,n>>>0,!1);let l=1779033703,u=3144134277,f=1013904242,m=2773480762,y=1359893119,d=2600822924,b=528734635,c=1541459225,p=new Uint32Array(64);for(let A=0;A<o;A+=64){for(let g=0;g<16;g++)p[g]=a.getUint32(A+g*4,!1);for(let g=16;g<64;g++){let E=P(p[g-15],7)^P(p[g-15],18)^p[g-15]>>>3,F=P(p[g-2],17)^P(p[g-2],19)^p[g-2]>>>10;p[g]=p[g-16]+E+p[g-7]+F>>>0}let i=l,v=u,x=f,k=m,h=y,L=d,w=b,O=c;for(let g=0;g<64;g++){let E=P(h,6)^P(h,11)^P(h,25),F=h&L^~h&w,X=O+E+F+Le[g]+p[g]>>>0,de=P(i,2)^P(i,13)^P(i,22),xe=i&v^i&x^v&x,he=de+xe>>>0;O=w,w=L,L=h,h=k+X>>>0,k=x,x=v,v=i,i=X+he>>>0}l=l+i>>>0,u=u+v>>>0,f=f+x>>>0,m=m+k>>>0,y=y+h>>>0,d=d+L>>>0,b=b+w>>>0,c=c+O>>>0}return re(l)+re(u)}function I(e){return Pe(e.join("|"))}var te={conversion_led:["pricing","cta","hero","comparison","social_proof","trust","features","faq","navigation","generic"],evidence_led:["features","comparison","faq","hero","trust","social_proof","pricing","cta","navigation","generic"],price_led:["pricing","comparison","social_proof","trust","cta","hero","features","faq","navigation","generic"],discovery_led:["hero","features","social_proof","pricing","cta","trust","faq","comparison","navigation","generic"]},T=["conversion_led","evidence_led","price_led","discovery_led"];function D(e,r,t,n){let o=te[t];if(!o)return e;let s=o.indexOf("generic"),a=f=>{let m=o.indexOf(f);return m===-1?s:m},l=n?e.filter(f=>n.get(f)!=="structural"):[...e];if(l.sort((f,m)=>{var b,c;let y=(b=r.get(f))!=null?b:"generic",d=(c=r.get(m))!=null?c:"generic";return a(y)-a(d)}),!n)return l;let u=0;return e.map(f=>n.get(f)==="structural"?f:l[u++])}function Me(e){let r=2166136261;for(let t=0;t<e.length;t++)r^=e.charCodeAt(t),r=Math.imul(r,16777619)>>>0;return r>>>0}function ne(e,r,t,n){if(!t||t==="unknown")return e;let o=T[Me(t)%T.length];return D(e,r,o,n)}var Re=ne;function N(e,r,t){let n=new Map;n.set(I(e),[...e]);for(let o of T){let s=D(e,r,o,t),a=I(s);n.has(a)||n.set(a,s)}return n}function B(e,r){if(e<1)return B(1+e,r)*Math.pow(Math.max(1e-15,r()),1/e);let t=e-1/3,n=1/Math.sqrt(9*t);for(;;){let o,s;do{let l=Math.max(1e-15,r()),u=r();o=Math.sqrt(-2*Math.log(l))*Math.cos(2*Math.PI*u),s=1+n*o}while(s<=0);s=s*s*s;let a=r();if(a<1-.0331*o*o*o*o||Math.log(a)<.5*o*o+t*(1-s+Math.log(s)))return t*s}}function R(e,r,t=Math.random){let n=B(e,t),o=B(r,t),s=n+o;return s<=0?e/(e+r):n/s}function H(e,r=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let t=e[0],n=R(t.alpha,t.beta,r);for(let o=1;o<e.length;o++){let s=e[o],a=R(s.alpha,s.beta,r);a>n&&(t=s,n=a)}return t.arm}function we(e,r,t,n,o=Math.random,s){var m,y;let a=N(e,r,s),l=[];for(let d of a.keys()){let b=n.get(d);l.push({arm:d,alpha:(m=b==null?void 0:b.alpha)!=null?m:1,beta:(y=b==null?void 0:b.beta)!=null?y:1})}let u=H(l,o),f=u?a.get(u):void 0;return f!=null?f:e}var oe=20;function C(e,r,t=20){let n=r.alpha+r.beta;if(n<=0||t<=0)return{alpha:e.alpha,beta:e.beta};let o=r.alpha/n,s=t*n/(n+t);return{alpha:e.alpha+s*o,beta:e.beta+s*(1-o)}}var se=5;function Oe(e,r){var n,o;let t=null;for(let s of e){if(!r.includes(s.variantId))continue;let a=(n=s.pulls)!=null?n:0,l=a>0?a*s.avgReward/(a+se):0;(!t||l>t.score)&&(t={variantId:s.variantId,score:l})}return(o=t==null?void 0:t.variantId)!=null?o:null}var _="__all__",V={exposures:0,conversions:0};function S(e){return{alpha:e.conversions+1,beta:Math.max(0,e.exposures-e.conversions)+1}}function Ee(e,r,t=20){var d,b,c,p;let n=(d=e.segment)!=null?d:V,o=(b=e.global)!=null?b:V,s=S(o),a=C(S(n),s,t);if(!r)return a;let l=(c=e.persona)!=null?c:V,u=(p=e.child)!=null?p:V,f=C(S(l),s,t),m=(n.exposures+1)/(n.exposures+l.exposures+2),y={alpha:m*a.alpha+(1-m)*f.alpha,beta:m*a.beta+(1-m)*f.beta};return C(S(u),y,t)}function Fe(e){var n,o;let r=null,t=-1;for(let s of e){let a=s.segment===_,l=s.persona===_,u=a&&l?3:a||l?2:1;u>t&&(t=u,r=s)}return{valueSum:(n=r==null?void 0:r.valueSum)!=null?n:0,valueCount:(o=r==null?void 0:r.valueCount)!=null?o:0}}function Ne(e,r){return r==="unknown"||r===_||r===""?[{segment:e,persona:_},{segment:_,persona:_}]:[{segment:e,persona:r},{segment:e,persona:_},{segment:_,persona:r},{segment:_,persona:_}]}var Y=4,ae="__global__";function j(e,r){return r<=0?0:Math.min(Y-1,Math.floor(e*Y/r))}function Ue(e,r){return e.map((t,n)=>{var o;return{parent:(o=r.get(t))!=null?o:"generic",bucket:j(n,e.length)}})}var q=(e,r)=>`${e}#${r}`;function Ke(e,r,t,n,o=Math.random,s){var A;let a=N(e,r,s),l=new Map,u=new Map,f=0,m=0;for(let i of n)i.persona===ae?(l.set(q(i.parent,i.bucket),i),f+=i.exposures,m+=i.conversions):i.persona===t&&u.set(q(i.parent,i.bucket),i);let y=S({exposures:f,conversions:m}),d=new Map,b=(i,v)=>{var E,F;let x=q(i,v),k=d.get(x);if(k!==void 0)return k;let h=l.get(x),L=C(S({exposures:(E=h==null?void 0:h.exposures)!=null?E:0,conversions:(F=h==null?void 0:h.conversions)!=null?F:0}),y),w=u.get(x),O=w?C(S({exposures:w.exposures,conversions:w.conversions}),L):L,g=R(O.alpha,O.beta,o);return d.set(x,g),g},c=null,p=-1/0;for(let i of a.values()){let v=0;for(let x=0;x<i.length;x++)v+=b((A=r.get(i[x]))!=null?A:"generic",j(x,i.length));v>p&&(p=v,c=i)}return c!=null?c:e}var U="__global__",Ie=["device","source","persona","visit"];function G(e){let r=[];return e.device&&r.push({factor:"device",level:e.device}),e.source&&r.push({factor:"source",level:e.source}),e.persona&&e.persona!==M&&r.push({factor:"persona",level:e.persona}),e.visit&&r.push({factor:"visit",level:e.visit}),r}var z=(e,r,t)=>`${e}\0${r}\0${t}`;function Te(e,r,t,n=Math.random){if(e.length===0)return null;if(e.length===1)return{arm:e[0],factorsUsed:0};let o=new Map,s=0,a=0;for(let c of t)o.set(z(c.arm,c.factor,c.level),c),c.level===U&&(s+=c.exposures,a+=c.conversions);let l=S({exposures:s,conversions:a}),u=G(r),f=new Map,m=c=>{var A,i;let p=o.get(z(c,"global",U));return C(S({exposures:(A=p==null?void 0:p.exposures)!=null?A:0,conversions:(i=p==null?void 0:p.conversions)!=null?i:0}),l)},y=(c,p,A)=>{let i=z(c,p,A),v=f.get(i);if(v!==void 0)return v;let x=m(c),k=o.get(i),h=k?C(S({exposures:k.exposures,conversions:k.conversions}),x):x,L=R(h.alpha,h.beta,n);return f.set(i,L),L},d=null,b=-1/0;for(let c of e){let p=u.length===0?y(c,"global",U):0;for(let{factor:A,level:i}of u)p+=y(c,A,i);p>b&&(b=p,d=c)}return d===null?null:{arm:d,factorsUsed:u.length}}function Ve(e,r,t){let n=Number.isFinite(t)?Math.max(0,Math.min(1,t)):0,o=[{arm:e,factor:"global",level:U,weight:1}];for(let{factor:s,level:a}of G(r))o.push({arm:e,factor:s,level:a,weight:s==="persona"?n:1});return o}function $e(e){return e==null||!Number.isFinite(e)||e<1?null:e>1?"returning":"new"}function W(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("|")}function Z(e){if(e.length===0)return null;let r={};for(let t of e.split("|")){let n=t.indexOf("=");if(n<=0||n!==t.lastIndexOf("=")||n===t.length-1)return null;let o=t.slice(0,n);if(o in r)return null;r[o]=t.slice(n+1)}return r}function De(e,r){return`${e}=${r}`}function ie(e){var t,n,o;if(e.arms)return typeof e.baseline=="string"?e.baseline:(t=e.arms[0])!=null?t:"";if(e.baseline!==void 0&&typeof e.baseline=="object")return W(e.baseline);if(typeof e.baseline=="string")return e.baseline;let r={};for(let[s,a]of Object.entries((n=e.dims)!=null?n:{}))r[s]=(o=a[0])!=null?o:"";return W(r)}function Be(e){let r=Array.isArray(e.arms),t=e.dims!=null;if(r&&t)return{ok:!1,reason:"declare exactly one of arms or dims (got both)"};if(!r&&!t)return{ok:!1,reason:"declare exactly one of arms or dims (got neither)"};if(r){let s=e.arms;if(s.length<2)return{ok:!1,reason:"arms requires at least 2 entries"};if(s.length>12)return{ok:!1,reason:"arms allows at most 12 entries"};if(new Set(s).size!==s.length)return{ok:!1,reason:"arms must be unique"};if(s.some(a=>a.includes("=")))return{ok:!1,reason:"enumerated arm ids may not contain '=' (reserved for dims encoding)"};if(e.baseline!==void 0){if(typeof e.baseline!="string")return{ok:!1,reason:"baseline for an arms slot must be a string"};if(!s.includes(e.baseline))return{ok:!1,reason:"baseline must be one of the declared arms"}}return{ok:!0}}let n=Object.entries(e.dims);if(n.length<1)return{ok:!1,reason:"dims requires at least 1 dimension"};if(n.length>4)return{ok:!1,reason:"dims allows at most 4 dimensions"};let o=1;for(let[s,a]of n){if(a.length<2)return{ok:!1,reason:`dim "${s}" requires at least 2 values`};if(a.length>6)return{ok:!1,reason:`dim "${s}" allows at most 6 values`};if(new Set(a).size!==a.length)return{ok:!1,reason:`dim "${s}" has duplicate values`};o*=a.length}if(o>64)return{ok:!1,reason:`declared space of ${o} combinations exceeds the 64 maximum`};if(e.baseline!==void 0){if(typeof e.baseline=="string")return{ok:!1,reason:"baseline for a dims slot must be a per-dim record"};let s=e.baseline,a=n.map(([u])=>u).sort(),l=Object.keys(s).sort();if(a.join(" ")!==l.join(" "))return{ok:!1,reason:"baseline must set every declared dim exactly once"};for(let[u,f]of n)if(!f.includes(s[u]))return{ok:!1,reason:`baseline value for dim "${u}" is not declared`}}return{ok:!0}}function He(e,r){var t,n;return e.dims!=null?(n=(t=Z(r))!=null?t:Z(ie(e)))!=null?n:{}:r}function le(e){let r=2166136261;for(let t=0;t<e.length;t++)r^=e.charCodeAt(t),r=r+((r<<1)+(r<<4)+(r<<7)+(r<<8)+(r<<24))>>>0;return r}function qe(e,r,t){if(t.length===0)throw new Error("pickDeterministicArm requires at least one arm");let n=[...t].sort();return n[le(`${e}:${r}`)%n.length]}function Ye(e){return e>=.3?e<.7?"medium":"high":"low"}var ue=20;function ce(e,r,t=ue){let n=e.valueCount>0?e.valueSum/e.valueCount:0;if(r<=0)return n;if(e.valueCount<=0)return r;let o=e.valueCount/(e.valueCount+t);return o*n+(1-o)*r}function je(e,r,t=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let n=null,o=-1/0;for(let s of e){let a=R(s.alpha,s.beta,t)*ce(s,r);a>o&&(n=s,o=a)}return n.arm}var fe=/^[a-z0-9][a-z0-9_-]{0,31}$/;function pe(e){if(typeof e!="string")return null;let r=e.trim().toLowerCase();return r&&fe.test(r)?r:null}var me=["unknown","__all__"];function ge(e){var r;return(r=pe(e))!=null?r:M}var be=[],ze=64;function Ge(e){var t;let r=new Map;for(let n of e)if(n.status!=="retired"){r.set(n.key,n.key);for(let o of(t=n.aliases)!=null?t:[])me.includes(o)||r.set(o,n.key)}return r}function We(e,r=be){var l,u,f;let t=Ge(r),n=(l=e.inferredConfidence)!=null?l:0,o,s=(f=(u=e.declared)==null?void 0:u.trim().toLowerCase())!=null?f:"";if(s!==""){let m=t.get(s);if(m!==void 0)return{persona:m,source:"declared",confidence:1};o=s.slice(0,ze)}let a=ge(e.clusterLabel);return a!==M&&t.has(a)?$({persona:t.get(a),source:"inferred",confidence:n},o!==void 0&&{unrecognizedDeclared:o}):$({persona:M,source:"none",confidence:n},o!==void 0&&{unrecognizedDeclared:o})}function Ze(e){if(e==null)return M;let r=e.trim().toLowerCase();return r===""?M:r}0&&(module.exports={DEFAULT_PERSONA_VOCABULARY,EV_SHRINK_K,GLOBAL_FACTOR_LEVEL,GLOBAL_FACTOR_PERSONA,LAYOUT_ARCHETYPES,LAYOUT_ARCHETYPE_NAMES,LAYOUT_FACTOR_BUCKETS,PERSONA_KEY_RE,POOL_ALL,RESERVED_PERSONA_KEYS,SHRINKAGE_M,SLOT_FACTORS,UNKNOWN_PERSONA,UNKNOWN_PERSONA_DISPLAY,WEIGHTS_FALLBACK_PRIOR_PULLS,applyClusterHeuristic,broadestValueCell,candidateLayouts,canonicalArm,canonicalPersona,chooseLayout,chooseLayoutFactored,chooseSlotArmFactored,confidenceBand,decisionPersona,factorCellsForOrder,factorCellsForTrial,factorLevelsFor,fnv1a,hashLayout,layoutBucketOf,marginalArmKey,normalizeDeclaredPersona,orderByArchetype,parseArm,pickDeterministicArm,pickFromWeights,pooledPosterior,posteriorOfCounts,previewOrderForPersona,resolvePersona,sampleArm,sampleArmEv,sampleBeta,shrunkAvgValue,shrunkPosterior,slotBaselineArm,slotResultFor,validateSlotDecl,visitLevel,weightCellsFor});
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{a as U}from"./chunk-HBG7RQ56.mjs";var K=["buyer","researcher","deal_seeker","browser"],C="unknown",j={buyer:"Buyer",researcher:"Researcher",deal_seeker:"Deal seeker",browser:"Browser",unknown:"Unknown"},V={buyers:"buyer",researchers:"researcher","deal-seekers":"deal_seeker",browsers:"browser",buyer:"buyer",researcher:"researcher",deal_seeker:"deal_seeker",browser:"browser"};function $(e){var t;if(e==null)return C;let r=e.trim().toLowerCase();return(t=V[r])!=null?t:C}var te=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];function P(e,r){return e>>>r|e<<32-r}function z(e){return(e>>>0).toString(16).padStart(8,"0")}function ne(e){let r=new TextEncoder().encode(e),t=r.length,n=t*8,s=(t+8>>6)+1<<6,o=new Uint8Array(s);o.set(r),o[t]=128;let a=new DataView(o.buffer);a.setUint32(s-8,Math.floor(n/4294967296),!1),a.setUint32(s-4,n>>>0,!1);let i=1779033703,u=3144134277,l=1013904242,f=2773480762,h=1359893119,g=2600822924,p=528734635,y=1541459225,b=new Uint32Array(64);for(let L=0;L<s;L+=64){for(let m=0;m<16;m++)b[m]=a.getUint32(L+m*4,!1);for(let m=16;m<64;m++){let E=P(b[m-15],7)^P(b[m-15],18)^b[m-15]>>>3,O=P(b[m-2],17)^P(b[m-2],19)^b[m-2]>>>10;b[m]=b[m-16]+E+b[m-7]+O>>>0}let c=i,v=u,d=l,w=f,x=h,R=g,A=p,M=y;for(let m=0;m<64;m++){let E=P(x,6)^P(x,11)^P(x,25),O=x&R^~x&A,H=M+E+O+te[m]+b[m]>>>0,Q=P(c,2)^P(c,13)^P(c,22),ee=c&v^c&d^v&d,re=Q+ee>>>0;M=A,A=R,R=x,x=w+H>>>0,w=d,d=v,v=c,c=H+re>>>0}i=i+c>>>0,u=u+v>>>0,l=l+d>>>0,f=f+w>>>0,h=h+x>>>0,g=g+R>>>0,p=p+A>>>0,y=y+M>>>0}return z(i)+z(u)}function G(e){return ne(e.join("|"))}var oe={buyer:["pricing","cta","hero","comparison","social_proof","trust","features","faq","navigation","generic"],researcher:["features","comparison","faq","hero","trust","social_proof","pricing","cta","navigation","generic"],deal_seeker:["pricing","comparison","social_proof","trust","cta","hero","features","faq","navigation","generic"],browser:["hero","features","social_proof","pricing","cta","trust","faq","comparison","navigation","generic"]};function N(e,r,t,n){let s=oe[t];if(!s)return e;let o=s.indexOf("generic"),a=l=>{let f=s.indexOf(l);return f===-1?o:f},i=n?e.filter(l=>n.get(l)!=="structural"):[...e];if(i.sort((l,f)=>{var p,y;let h=(p=r.get(l))!=null?p:"generic",g=(y=r.get(f))!=null?y:"generic";return a(h)-a(g)}),!n)return i;let u=0;return e.map(l=>n.get(l)==="structural"?l:i[u++])}function D(e,r,t,n){let s=new Map;for(let o of[...K,t]){let a=N(e,r,o,n);s.set(G(a),a)}return s}function B(e,r){if(e<1)return B(1+e,r)*Math.pow(Math.max(1e-15,r()),1/e);let t=e-1/3,n=1/Math.sqrt(9*t);for(;;){let s,o;do{let i=Math.max(1e-15,r()),u=r();s=Math.sqrt(-2*Math.log(i))*Math.cos(2*Math.PI*u),o=1+n*s}while(o<=0);o=o*o*o;let a=r();if(a<1-.0331*s*s*s*s||Math.log(a)<.5*s*s+t*(1-o+Math.log(o)))return t*o}}function I(e,r,t=Math.random){let n=B(e,t),s=B(r,t),o=n+s;return o<=0?e/(e+r):n/o}function Y(e,r=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let t=e[0],n=I(t.alpha,t.beta,r);for(let s=1;s<e.length;s++){let o=e[s],a=I(o.alpha,o.beta,r);a>n&&(t=o,n=a)}return t.arm}function Se(e,r,t,n,s=Math.random,o){var f,h;let a=D(e,r,t,o),i=[];for(let g of a.keys()){let p=n.get(g);i.push({arm:g,alpha:(f=p==null?void 0:p.alpha)!=null?f:1,beta:(h=p==null?void 0:p.beta)!=null?h:1})}let u=Y(i,s),l=u?a.get(u):void 0;return l!=null?l:N(e,r,t,o)}var W=20;function _(e,r,t=20){let n=r.alpha+r.beta;if(n<=0||t<=0)return{alpha:e.alpha,beta:e.beta};let s=r.alpha/n,o=t*n/(n+t);return{alpha:e.alpha+o*s,beta:e.beta+o*(1-s)}}var se=5;function Ce(e,r){var n,s;let t=null;for(let o of e){if(!r.includes(o.variantId))continue;let a=(n=o.pulls)!=null?n:0,i=a>0?a*o.avgReward/(a+se):0;(!t||i>t.score)&&(t={variantId:o.variantId,score:i})}return(s=t==null?void 0:t.variantId)!=null?s:null}var k="__all__",F={exposures:0,conversions:0};function S(e){return{alpha:e.conversions+1,beta:Math.max(0,e.exposures-e.conversions)+1}}function we(e,r,t=20){var g,p,y,b;let n=(g=e.segment)!=null?g:F,s=(p=e.global)!=null?p:F,o=S(s),a=_(S(n),o,t);if(!r)return a;let i=(y=e.persona)!=null?y:F,u=(b=e.child)!=null?b:F,l=_(S(i),o,t),f=(n.exposures+1)/(n.exposures+i.exposures+2),h={alpha:f*a.alpha+(1-f)*l.alpha,beta:f*a.beta+(1-f)*l.beta};return _(S(u),h,t)}function Me(e){var n,s;let r=null,t=-1;for(let o of e){let a=o.segment===k,i=o.persona===k,u=a&&i?3:a||i?2:1;u>t&&(t=u,r=o)}return{valueSum:(n=r==null?void 0:r.valueSum)!=null?n:0,valueCount:(s=r==null?void 0:r.valueCount)!=null?s:0}}function Ee(e,r){return r==="unknown"||r===k||r===""?[{segment:e,persona:k},{segment:k,persona:k}]:[{segment:e,persona:r},{segment:e,persona:k},{segment:k,persona:r},{segment:k,persona:k}]}var T=4,ae="__global__";function Z(e,r){return r<=0?0:Math.min(T-1,Math.floor(e*T/r))}function Fe(e,r){return e.map((t,n)=>{var s;return{parent:(s=r.get(t))!=null?s:"generic",bucket:Z(n,e.length)}})}var q=(e,r)=>`${e}#${r}`;function Ue(e,r,t,n,s=Math.random,o){var L;let a=D(e,r,t,o);if(a.size===0)return N(e,r,t,o);let i=new Map,u=new Map,l=0,f=0;for(let c of n)c.persona===ae?(i.set(q(c.parent,c.bucket),c),l+=c.exposures,f+=c.conversions):c.persona===t&&u.set(q(c.parent,c.bucket),c);let h=S({exposures:l,conversions:f}),g=new Map,p=(c,v)=>{var E,O;let d=q(c,v),w=g.get(d);if(w!==void 0)return w;let x=i.get(d),R=_(S({exposures:(E=x==null?void 0:x.exposures)!=null?E:0,conversions:(O=x==null?void 0:x.conversions)!=null?O:0}),h),A=u.get(d),M=A?_(S({exposures:A.exposures,conversions:A.conversions}),R):R,m=I(M.alpha,M.beta,s);return g.set(d,m),m},y=null,b=-1/0;for(let c of a.values()){let v=0;for(let d=0;d<c.length;d++)v+=p((L=r.get(c[d]))!=null?L:"generic",Z(d,c.length));v>b&&(b=v,y=c)}return y!=null?y:N(e,r,t,o)}function X(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("|")}function J(e){if(e.length===0)return null;let r={};for(let t of e.split("|")){let n=t.indexOf("=");if(n<=0||n!==t.lastIndexOf("=")||n===t.length-1)return null;let s=t.slice(0,n);if(s in r)return null;r[s]=t.slice(n+1)}return r}function $e(e,r){return`${e}=${r}`}function ie(e){var t,n,s;if(e.arms)return typeof e.baseline=="string"?e.baseline:(t=e.arms[0])!=null?t:"";if(e.baseline!==void 0&&typeof e.baseline=="object")return X(e.baseline);if(typeof e.baseline=="string")return e.baseline;let r={};for(let[o,a]of Object.entries((n=e.dims)!=null?n:{}))r[o]=(s=a[0])!=null?s:"";return X(r)}function Be(e){let r=Array.isArray(e.arms),t=e.dims!=null;if(r&&t)return{ok:!1,reason:"declare exactly one of arms or dims (got both)"};if(!r&&!t)return{ok:!1,reason:"declare exactly one of arms or dims (got neither)"};if(r){let o=e.arms;if(o.length<2)return{ok:!1,reason:"arms requires at least 2 entries"};if(o.length>12)return{ok:!1,reason:"arms allows at most 12 entries"};if(new Set(o).size!==o.length)return{ok:!1,reason:"arms must be unique"};if(o.some(a=>a.includes("=")))return{ok:!1,reason:"enumerated arm ids may not contain '=' (reserved for dims encoding)"};if(e.baseline!==void 0){if(typeof e.baseline!="string")return{ok:!1,reason:"baseline for an arms slot must be a string"};if(!o.includes(e.baseline))return{ok:!1,reason:"baseline must be one of the declared arms"}}return{ok:!0}}let n=Object.entries(e.dims);if(n.length<1)return{ok:!1,reason:"dims requires at least 1 dimension"};if(n.length>4)return{ok:!1,reason:"dims allows at most 4 dimensions"};let s=1;for(let[o,a]of n){if(a.length<2)return{ok:!1,reason:`dim "${o}" requires at least 2 values`};if(a.length>6)return{ok:!1,reason:`dim "${o}" allows at most 6 values`};if(new Set(a).size!==a.length)return{ok:!1,reason:`dim "${o}" has duplicate values`};s*=a.length}if(s>64)return{ok:!1,reason:`declared space of ${s} combinations exceeds the 64 maximum`};if(e.baseline!==void 0){if(typeof e.baseline=="string")return{ok:!1,reason:"baseline for a dims slot must be a per-dim record"};let o=e.baseline,a=n.map(([u])=>u).sort(),i=Object.keys(o).sort();if(a.join(" ")!==i.join(" "))return{ok:!1,reason:"baseline must set every declared dim exactly once"};for(let[u,l]of n)if(!l.includes(o[u]))return{ok:!1,reason:`baseline value for dim "${u}" is not declared`}}return{ok:!0}}function qe(e,r){var t,n;return e.dims!=null?(n=(t=J(r))!=null?t:J(ie(e)))!=null?n:{}:r}function ue(e){let r=2166136261;for(let t=0;t<e.length;t++)r^=e.charCodeAt(t),r=r+((r<<1)+(r<<4)+(r<<7)+(r<<8)+(r<<24))>>>0;return r}function je(e,r,t){if(t.length===0)throw new Error("pickDeterministicArm requires at least one arm");let n=[...t].sort();return n[ue(`${e}:${r}`)%n.length]}function ze(e){return e>=.3?e<.7?"medium":"high":"low"}var le=20;function ce(e,r,t=le){let n=e.valueCount>0?e.valueSum/e.valueCount:0;if(r<=0)return n;if(e.valueCount<=0)return r;let s=e.valueCount/(e.valueCount+t);return s*n+(1-s)*r}function We(e,r,t=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let n=null,s=-1/0;for(let o of e){let a=I(o.alpha,o.beta,t)*ce(o,r);a>s&&(n=o,s=a)}return n.arm}var fe=/^[a-z0-9][a-z0-9_-]{0,31}$/;function Xe(e){if(typeof e!="string")return null;let r=e.trim().toLowerCase();return r&&fe.test(r)?r:null}var me=["unknown","__all__","buyers","researchers","deal-seekers","browsers"],pe=K.map(e=>({key:e,displayName:j[e]})),ge=64;function be(e){var t;let r=new Map;for(let n of e)if(n.status!=="retired"){r.set(n.key,n.key);for(let s of(t=n.aliases)!=null?t:[])me.includes(s)||r.set(s,n.key)}return r}function Je(e,r=pe){var i,u,l;let t=be(r),n=(i=e.inferredConfidence)!=null?i:0,s,o=(l=(u=e.declared)==null?void 0:u.trim().toLowerCase())!=null?l:"";if(o!==""){let f=t.get(o),h=f===void 0?t.get($(o)):void 0,g=f!=null?f:h;if(g!==void 0)return{persona:g,source:"declared",confidence:1};s=o.slice(0,ge)}let a=$(e.clusterLabel);return a!==C&&t.has(a)?U({persona:t.get(a),source:"inferred",confidence:n},s!==void 0&&{unrecognizedDeclared:s}):U({persona:C,source:"none",confidence:n},s!==void 0&&{unrecognizedDeclared:s})}function Qe(e){var t;if(e==null)return C;let r=e.trim().toLowerCase();return r===""?C:(t=V[r])!=null?t:r}export{oe as CLUSTER_PRIORITY,pe as DEFAULT_PERSONA_VOCABULARY,le as EV_SHRINK_K,ae as GLOBAL_FACTOR_PERSONA,T as LAYOUT_FACTOR_BUCKETS,V as LEGACY_PERSONA_MAP,K as PERSONAS,j as PERSONA_DISPLAY,fe as PERSONA_KEY_RE,k as POOL_ALL,me as RESERVED_PERSONA_KEYS,W as SHRINKAGE_M,C as UNKNOWN_PERSONA,se as WEIGHTS_FALLBACK_PRIOR_PULLS,N as applyClusterHeuristic,Me as broadestValueCell,D as candidateLayouts,X as canonicalArm,$ as canonicalPersona,Se as chooseLayout,Ue as chooseLayoutFactored,ze as confidenceBand,Qe as decisionPersona,Fe as factorCellsForOrder,ue as fnv1a,G as hashLayout,Z as layoutBucketOf,$e as marginalArmKey,Xe as normalizeDeclaredPersona,J as parseArm,je as pickDeterministicArm,Ce as pickFromWeights,we as pooledPosterior,S as posteriorOfCounts,Je as resolvePersona,Y as sampleArm,We as sampleArmEv,I as sampleBeta,ce as shrunkAvgValue,_ as shrunkPosterior,ie as slotBaselineArm,qe as slotResultFor,Be as validateSlotDecl,Ee as weightCellsFor};
1
+ import{a as I}from"./chunk-HBG7RQ56.mjs";var R="unknown",ve="Unknown";var te=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];function k(e,r){return e>>>r|e<<32-r}function q(e){return(e>>>0).toString(16).padStart(8,"0")}function ne(e){let r=new TextEncoder().encode(e),t=r.length,n=t*8,o=(t+8>>6)+1<<6,s=new Uint8Array(o);s.set(r),s[t]=128;let a=new DataView(s.buffer);a.setUint32(o-8,Math.floor(n/4294967296),!1),a.setUint32(o-4,n>>>0,!1);let l=1779033703,u=3144134277,f=1013904242,m=2773480762,y=1359893119,d=2600822924,b=528734635,c=1541459225,p=new Uint32Array(64);for(let A=0;A<o;A+=64){for(let g=0;g<16;g++)p[g]=a.getUint32(A+g*4,!1);for(let g=16;g<64;g++){let E=k(p[g-15],7)^k(p[g-15],18)^p[g-15]>>>3,F=k(p[g-2],17)^k(p[g-2],19)^p[g-2]>>>10;p[g]=p[g-16]+E+p[g-7]+F>>>0}let i=l,v=u,x=f,C=m,h=y,_=d,M=b,O=c;for(let g=0;g<64;g++){let E=k(h,6)^k(h,11)^k(h,25),F=h&_^~h&M,H=O+E+F+te[g]+p[g]>>>0,Q=k(i,2)^k(i,13)^k(i,22),ee=i&v^i&x^v&x,re=Q+ee>>>0;O=M,M=_,_=h,h=C+H>>>0,C=x,x=v,v=i,i=H+re>>>0}l=l+i>>>0,u=u+v>>>0,f=f+x>>>0,m=m+C>>>0,y=y+h>>>0,d=d+_>>>0,b=b+M>>>0,c=c+O>>>0}return q(l)+q(u)}function T(e){return ne(e.join("|"))}var oe={conversion_led:["pricing","cta","hero","comparison","social_proof","trust","features","faq","navigation","generic"],evidence_led:["features","comparison","faq","hero","trust","social_proof","pricing","cta","navigation","generic"],price_led:["pricing","comparison","social_proof","trust","cta","hero","features","faq","navigation","generic"],discovery_led:["hero","features","social_proof","pricing","cta","trust","faq","comparison","navigation","generic"]},V=["conversion_led","evidence_led","price_led","discovery_led"];function Y(e,r,t,n){let o=oe[t];if(!o)return e;let s=o.indexOf("generic"),a=f=>{let m=o.indexOf(f);return m===-1?s:m},l=n?e.filter(f=>n.get(f)!=="structural"):[...e];if(l.sort((f,m)=>{var b,c;let y=(b=r.get(f))!=null?b:"generic",d=(c=r.get(m))!=null?c:"generic";return a(y)-a(d)}),!n)return l;let u=0;return e.map(f=>n.get(f)==="structural"?f:l[u++])}function se(e){let r=2166136261;for(let t=0;t<e.length;t++)r^=e.charCodeAt(t),r=Math.imul(r,16777619)>>>0;return r>>>0}function ae(e,r,t,n){if(!t||t==="unknown")return e;let o=V[se(t)%V.length];return Y(e,r,o,n)}var _e=ae;function N(e,r,t){let n=new Map;n.set(T(e),[...e]);for(let o of V){let s=Y(e,r,o,t),a=T(s);n.has(a)||n.set(a,s)}return n}function $(e,r){if(e<1)return $(1+e,r)*Math.pow(Math.max(1e-15,r()),1/e);let t=e-1/3,n=1/Math.sqrt(9*t);for(;;){let o,s;do{let l=Math.max(1e-15,r()),u=r();o=Math.sqrt(-2*Math.log(l))*Math.cos(2*Math.PI*u),s=1+n*o}while(s<=0);s=s*s*s;let a=r();if(a<1-.0331*o*o*o*o||Math.log(a)<.5*o*o+t*(1-s+Math.log(s)))return t*s}}function w(e,r,t=Math.random){let n=$(e,t),o=$(r,t),s=n+o;return s<=0?e/(e+r):n/s}function j(e,r=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let t=e[0],n=w(t.alpha,t.beta,r);for(let o=1;o<e.length;o++){let s=e[o],a=w(s.alpha,s.beta,r);a>n&&(t=s,n=a)}return t.arm}function Re(e,r,t,n,o=Math.random,s){var m,y;let a=N(e,r,s),l=[];for(let d of a.keys()){let b=n.get(d);l.push({arm:d,alpha:(m=b==null?void 0:b.alpha)!=null?m:1,beta:(y=b==null?void 0:b.beta)!=null?y:1})}let u=j(l,o),f=u?a.get(u):void 0;return f!=null?f:e}var z=20;function L(e,r,t=20){let n=r.alpha+r.beta;if(n<=0||t<=0)return{alpha:e.alpha,beta:e.beta};let o=r.alpha/n,s=t*n/(n+t);return{alpha:e.alpha+s*o,beta:e.beta+s*(1-o)}}var ie=5;function Oe(e,r){var n,o;let t=null;for(let s of e){if(!r.includes(s.variantId))continue;let a=(n=s.pulls)!=null?n:0,l=a>0?a*s.avgReward/(a+ie):0;(!t||l>t.score)&&(t={variantId:s.variantId,score:l})}return(o=t==null?void 0:t.variantId)!=null?o:null}var P="__all__",U={exposures:0,conversions:0};function S(e){return{alpha:e.conversions+1,beta:Math.max(0,e.exposures-e.conversions)+1}}function Ne(e,r,t=20){var d,b,c,p;let n=(d=e.segment)!=null?d:U,o=(b=e.global)!=null?b:U,s=S(o),a=L(S(n),s,t);if(!r)return a;let l=(c=e.persona)!=null?c:U,u=(p=e.child)!=null?p:U,f=L(S(l),s,t),m=(n.exposures+1)/(n.exposures+l.exposures+2),y={alpha:m*a.alpha+(1-m)*f.alpha,beta:m*a.beta+(1-m)*f.beta};return L(S(u),y,t)}function Ue(e){var n,o;let r=null,t=-1;for(let s of e){let a=s.segment===P,l=s.persona===P,u=a&&l?3:a||l?2:1;u>t&&(t=u,r=s)}return{valueSum:(n=r==null?void 0:r.valueSum)!=null?n:0,valueCount:(o=r==null?void 0:r.valueCount)!=null?o:0}}function Ke(e,r){return r==="unknown"||r===P||r===""?[{segment:e,persona:P},{segment:P,persona:P}]:[{segment:e,persona:r},{segment:e,persona:P},{segment:P,persona:r},{segment:P,persona:P}]}var G=4,le="__global__";function W(e,r){return r<=0?0:Math.min(G-1,Math.floor(e*G/r))}function Be(e,r){return e.map((t,n)=>{var o;return{parent:(o=r.get(t))!=null?o:"generic",bucket:W(n,e.length)}})}var D=(e,r)=>`${e}#${r}`;function He(e,r,t,n,o=Math.random,s){var A;let a=N(e,r,s),l=new Map,u=new Map,f=0,m=0;for(let i of n)i.persona===le?(l.set(D(i.parent,i.bucket),i),f+=i.exposures,m+=i.conversions):i.persona===t&&u.set(D(i.parent,i.bucket),i);let y=S({exposures:f,conversions:m}),d=new Map,b=(i,v)=>{var E,F;let x=D(i,v),C=d.get(x);if(C!==void 0)return C;let h=l.get(x),_=L(S({exposures:(E=h==null?void 0:h.exposures)!=null?E:0,conversions:(F=h==null?void 0:h.conversions)!=null?F:0}),y),M=u.get(x),O=M?L(S({exposures:M.exposures,conversions:M.conversions}),_):_,g=w(O.alpha,O.beta,o);return d.set(x,g),g},c=null,p=-1/0;for(let i of a.values()){let v=0;for(let x=0;x<i.length;x++)v+=b((A=r.get(i[x]))!=null?A:"generic",W(x,i.length));v>p&&(p=v,c=i)}return c!=null?c:e}var K="__global__",We=["device","source","persona","visit"];function Z(e){let r=[];return e.device&&r.push({factor:"device",level:e.device}),e.source&&r.push({factor:"source",level:e.source}),e.persona&&e.persona!==R&&r.push({factor:"persona",level:e.persona}),e.visit&&r.push({factor:"visit",level:e.visit}),r}var B=(e,r,t)=>`${e}\0${r}\0${t}`;function Ze(e,r,t,n=Math.random){if(e.length===0)return null;if(e.length===1)return{arm:e[0],factorsUsed:0};let o=new Map,s=0,a=0;for(let c of t)o.set(B(c.arm,c.factor,c.level),c),c.level===K&&(s+=c.exposures,a+=c.conversions);let l=S({exposures:s,conversions:a}),u=Z(r),f=new Map,m=c=>{var A,i;let p=o.get(B(c,"global",K));return L(S({exposures:(A=p==null?void 0:p.exposures)!=null?A:0,conversions:(i=p==null?void 0:p.conversions)!=null?i:0}),l)},y=(c,p,A)=>{let i=B(c,p,A),v=f.get(i);if(v!==void 0)return v;let x=m(c),C=o.get(i),h=C?L(S({exposures:C.exposures,conversions:C.conversions}),x):x,_=w(h.alpha,h.beta,n);return f.set(i,_),_},d=null,b=-1/0;for(let c of e){let p=u.length===0?y(c,"global",K):0;for(let{factor:A,level:i}of u)p+=y(c,A,i);p>b&&(b=p,d=c)}return d===null?null:{arm:d,factorsUsed:u.length}}function Xe(e,r,t){let n=Number.isFinite(t)?Math.max(0,Math.min(1,t)):0,o=[{arm:e,factor:"global",level:K,weight:1}];for(let{factor:s,level:a}of Z(r))o.push({arm:e,factor:s,level:a,weight:s==="persona"?n:1});return o}function Je(e){return e==null||!Number.isFinite(e)||e<1?null:e>1?"returning":"new"}function X(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("|")}function J(e){if(e.length===0)return null;let r={};for(let t of e.split("|")){let n=t.indexOf("=");if(n<=0||n!==t.lastIndexOf("=")||n===t.length-1)return null;let o=t.slice(0,n);if(o in r)return null;r[o]=t.slice(n+1)}return r}function er(e,r){return`${e}=${r}`}function ue(e){var t,n,o;if(e.arms)return typeof e.baseline=="string"?e.baseline:(t=e.arms[0])!=null?t:"";if(e.baseline!==void 0&&typeof e.baseline=="object")return X(e.baseline);if(typeof e.baseline=="string")return e.baseline;let r={};for(let[s,a]of Object.entries((n=e.dims)!=null?n:{}))r[s]=(o=a[0])!=null?o:"";return X(r)}function rr(e){let r=Array.isArray(e.arms),t=e.dims!=null;if(r&&t)return{ok:!1,reason:"declare exactly one of arms or dims (got both)"};if(!r&&!t)return{ok:!1,reason:"declare exactly one of arms or dims (got neither)"};if(r){let s=e.arms;if(s.length<2)return{ok:!1,reason:"arms requires at least 2 entries"};if(s.length>12)return{ok:!1,reason:"arms allows at most 12 entries"};if(new Set(s).size!==s.length)return{ok:!1,reason:"arms must be unique"};if(s.some(a=>a.includes("=")))return{ok:!1,reason:"enumerated arm ids may not contain '=' (reserved for dims encoding)"};if(e.baseline!==void 0){if(typeof e.baseline!="string")return{ok:!1,reason:"baseline for an arms slot must be a string"};if(!s.includes(e.baseline))return{ok:!1,reason:"baseline must be one of the declared arms"}}return{ok:!0}}let n=Object.entries(e.dims);if(n.length<1)return{ok:!1,reason:"dims requires at least 1 dimension"};if(n.length>4)return{ok:!1,reason:"dims allows at most 4 dimensions"};let o=1;for(let[s,a]of n){if(a.length<2)return{ok:!1,reason:`dim "${s}" requires at least 2 values`};if(a.length>6)return{ok:!1,reason:`dim "${s}" allows at most 6 values`};if(new Set(a).size!==a.length)return{ok:!1,reason:`dim "${s}" has duplicate values`};o*=a.length}if(o>64)return{ok:!1,reason:`declared space of ${o} combinations exceeds the 64 maximum`};if(e.baseline!==void 0){if(typeof e.baseline=="string")return{ok:!1,reason:"baseline for a dims slot must be a per-dim record"};let s=e.baseline,a=n.map(([u])=>u).sort(),l=Object.keys(s).sort();if(a.join(" ")!==l.join(" "))return{ok:!1,reason:"baseline must set every declared dim exactly once"};for(let[u,f]of n)if(!f.includes(s[u]))return{ok:!1,reason:`baseline value for dim "${u}" is not declared`}}return{ok:!0}}function tr(e,r){var t,n;return e.dims!=null?(n=(t=J(r))!=null?t:J(ue(e)))!=null?n:{}:r}function ce(e){let r=2166136261;for(let t=0;t<e.length;t++)r^=e.charCodeAt(t),r=r+((r<<1)+(r<<4)+(r<<7)+(r<<8)+(r<<24))>>>0;return r}function or(e,r,t){if(t.length===0)throw new Error("pickDeterministicArm requires at least one arm");let n=[...t].sort();return n[ce(`${e}:${r}`)%n.length]}function sr(e){return e>=.3?e<.7?"medium":"high":"low"}var fe=20;function pe(e,r,t=fe){let n=e.valueCount>0?e.valueSum/e.valueCount:0;if(r<=0)return n;if(e.valueCount<=0)return r;let o=e.valueCount/(e.valueCount+t);return o*n+(1-o)*r}function lr(e,r,t=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let n=null,o=-1/0;for(let s of e){let a=w(s.alpha,s.beta,t)*pe(s,r);a>o&&(n=s,o=a)}return n.arm}var me=/^[a-z0-9][a-z0-9_-]{0,31}$/;function ge(e){if(typeof e!="string")return null;let r=e.trim().toLowerCase();return r&&me.test(r)?r:null}var be=["unknown","__all__"];function de(e){var r;return(r=ge(e))!=null?r:R}var xe=[],he=64;function ye(e){var t;let r=new Map;for(let n of e)if(n.status!=="retired"){r.set(n.key,n.key);for(let o of(t=n.aliases)!=null?t:[])be.includes(o)||r.set(o,n.key)}return r}function fr(e,r=xe){var l,u,f;let t=ye(r),n=(l=e.inferredConfidence)!=null?l:0,o,s=(f=(u=e.declared)==null?void 0:u.trim().toLowerCase())!=null?f:"";if(s!==""){let m=t.get(s);if(m!==void 0)return{persona:m,source:"declared",confidence:1};o=s.slice(0,he)}let a=de(e.clusterLabel);return a!==R&&t.has(a)?I({persona:t.get(a),source:"inferred",confidence:n},o!==void 0&&{unrecognizedDeclared:o}):I({persona:R,source:"none",confidence:n},o!==void 0&&{unrecognizedDeclared:o})}function pr(e){if(e==null)return R;let r=e.trim().toLowerCase();return r===""?R:r}export{xe as DEFAULT_PERSONA_VOCABULARY,fe as EV_SHRINK_K,K as GLOBAL_FACTOR_LEVEL,le as GLOBAL_FACTOR_PERSONA,oe as LAYOUT_ARCHETYPES,V as LAYOUT_ARCHETYPE_NAMES,G as LAYOUT_FACTOR_BUCKETS,me as PERSONA_KEY_RE,P as POOL_ALL,be as RESERVED_PERSONA_KEYS,z as SHRINKAGE_M,We as SLOT_FACTORS,R as UNKNOWN_PERSONA,ve as UNKNOWN_PERSONA_DISPLAY,ie as WEIGHTS_FALLBACK_PRIOR_PULLS,_e as applyClusterHeuristic,Ue as broadestValueCell,N as candidateLayouts,X as canonicalArm,de as canonicalPersona,Re as chooseLayout,He as chooseLayoutFactored,Ze as chooseSlotArmFactored,sr as confidenceBand,pr as decisionPersona,Be as factorCellsForOrder,Xe as factorCellsForTrial,Z as factorLevelsFor,ce as fnv1a,T as hashLayout,W as layoutBucketOf,er as marginalArmKey,ge as normalizeDeclaredPersona,Y as orderByArchetype,J as parseArm,or as pickDeterministicArm,Oe as pickFromWeights,Ne as pooledPosterior,S as posteriorOfCounts,ae as previewOrderForPersona,fr as resolvePersona,j as sampleArm,lr as sampleArmEv,w as sampleBeta,pe as shrunkAvgValue,L as shrunkPosterior,ue as slotBaselineArm,tr as slotResultFor,rr as validateSlotDecl,Je as visitLevel,Ke as weightCellsFor};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentientui/policy",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "private": false,
5
5
  "description": "Pure decision-policy functions shared by the SentientUI API and the keyless local engine",
6
6
  "license": "MIT",