@sentientui/policy 0.2.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.
@@ -0,0 +1,166 @@
1
+ declare const PERSONAS: readonly ["buyer", "researcher", "deal_seeker", "browser"];
2
+ type Persona = (typeof PERSONAS)[number];
3
+ declare const UNKNOWN_PERSONA: "unknown";
4
+ type PersonaKey = Persona | typeof UNKNOWN_PERSONA;
5
+ /** Human-facing names — dashboard/devtools copy must use these, never raw keys. */
6
+ declare const PERSONA_DISPLAY: Record<PersonaKey, string>;
7
+ /**
8
+ * Every label ever written for a persona, mapped to its canonical form. The
9
+ * plural/hyphen labels are the pre-069 cluster seed labels
10
+ * ('buyers'/'deal-seekers'/…); identity mappings make canonical input a no-op.
11
+ */
12
+ declare const LEGACY_PERSONA_MAP: Record<string, Persona>;
13
+ /**
14
+ * Canonicalizes any persona/cluster label to the pinned vocabulary.
15
+ * Null, undefined, empty, and unrecognized labels all become 'unknown' —
16
+ * "we don't know" is always a safe answer; a guessed persona is not.
17
+ */
18
+ declare function canonicalPersona(label: string | null | undefined): PersonaKey;
19
+
20
+ declare const CLUSTER_PRIORITY: Record<Persona, string[]>;
21
+ /**
22
+ * Reorders section IDs based on the persona's semantic priority.
23
+ * Sections with no graph entry are treated as 'generic'.
24
+ * Returns the input unchanged for the unknown persona.
25
+ */
26
+ declare function applyClusterHeuristic(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey): string[];
27
+ /**
28
+ * The candidate layout orderings for a page — the distinct section orders
29
+ * produced by every persona's semantic priority (plus the requesting
30
+ * persona's own, which for 'unknown' is the identity order). These are the
31
+ * "arms" the layout bandit explores. Returned as hash → order so it joins
32
+ * directly against layout_weights rows keyed by the same hashLayout.
33
+ */
34
+ declare function candidateLayouts(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey): Map<string, string[]>;
35
+
36
+ /**
37
+ * Stable 16-char SHA-256 prefix for a section order array.
38
+ * MUST stay byte-identical to the legacy implementation in
39
+ * apps/api (node:crypto createHash('sha256')) — every
40
+ * layout_weights.layout_hash row in production was written by it
41
+ * (parity-pinned in hash.test.ts).
42
+ *
43
+ * Implemented as pure-JS FIPS 180-4 SHA-256 (no node:crypto) so this package
44
+ * is fully isomorphic: the keyless local engine and browser bundles import it
45
+ * without a Node builtin. Only the first 16 hex chars (h0+h1) are returned.
46
+ */
47
+ declare function hashLayout(order: string[]): string;
48
+
49
+ /** Learned Beta(alpha,beta) posterior for one candidate layout, keyed by hash. */
50
+ type LearnedLayout = {
51
+ layoutHash: string;
52
+ alpha: number;
53
+ beta: number;
54
+ };
55
+ /**
56
+ * Thompson-samples the layout order to serve a persona over the candidate
57
+ * orderings, using learned posteriors from layout_weights. Candidates with no
58
+ * learned row use the uniform 1/1 prior — identical to variant cold start.
59
+ * Falls back to the persona's heuristic only if sampling yields no candidate.
60
+ */
61
+ declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey, learned: Map<string, LearnedLayout>, rand?: () => number): string[];
62
+
63
+ /** Learned Beta(alpha, beta) posterior for one arm. */
64
+ type ArmPosterior = {
65
+ arm: string;
66
+ alpha: number;
67
+ beta: number;
68
+ };
69
+ /** One draw from Beta(alpha, beta). Moved verbatim from apps/api/src/domain/bandit.ts. */
70
+ declare function sampleBeta(alpha: number, beta: number, rand?: () => number): number;
71
+ /**
72
+ * Thompson Sampling selection: samples Beta(alpha, beta) per arm and returns
73
+ * the argmax arm id, or null when no arms are given. Uncertain arms get
74
+ * explored; confident winners get exploited — same semantics as the legacy
75
+ * chooseVariant, generalized to arbitrary arm strings.
76
+ */
77
+ declare function sampleArm(arms: ArmPosterior[], rand?: () => number): string | null;
78
+
79
+ /** A slot declaration as sent on /v1/decide — exactly one of arms|dims. */
80
+ type SlotDecl = {
81
+ id: string;
82
+ arms?: string[];
83
+ dims?: Record<string, string[]>;
84
+ baseline?: string | Record<string, string>;
85
+ };
86
+ /** What the client receives for a slot: arm id, or per-dim values for dims slots. */
87
+ type SlotResult = string | Record<string, string>;
88
+ /** Canonical arm string for a dims combination: sorted `dim=value` joined with '|'. */
89
+ declare function canonicalArm(values: Record<string, string>): string;
90
+ /**
91
+ * Inverse of canonicalArm. Returns null when the string is not a
92
+ * dims-encoded arm (enumerated arm ids, empty, malformed, duplicate dims) —
93
+ * callers use `parseArm(arm) !== null` to distinguish dims arms from
94
+ * enumerated arms.
95
+ */
96
+ declare function parseArm(arm: string): Record<string, string> | null;
97
+ /** Storage key for one marginal posterior row of a dims slot. */
98
+ declare function marginalArmKey(dim: string, value: string): string;
99
+ /**
100
+ * Canonical arm string of the declared baseline, or the default baseline
101
+ * (first arm / first value per dim) when none is declared. Assumes the decl
102
+ * passed validateSlotDecl.
103
+ */
104
+ declare function slotBaselineArm(decl: SlotDecl): string;
105
+ /**
106
+ * Validates a slot declaration against the pinned rules: exactly one of
107
+ * arms|dims; arms 2..12 (unique); dims 1..4 dims of 2..6 unique values each
108
+ * with product ≤ 64; a declared baseline must live in the declared space.
109
+ */
110
+ declare function validateSlotDecl(decl: SlotDecl): {
111
+ ok: true;
112
+ } | {
113
+ ok: false;
114
+ reason: string;
115
+ };
116
+ /**
117
+ * Decodes a stored/served arm for the client: dims slot → parsed per-dim
118
+ * record (falling back to the baseline record if the arm fails to parse);
119
+ * arms slot → the arm id verbatim.
120
+ */
121
+ declare function slotResultFor(decl: SlotDecl, arm: string): SlotResult;
122
+
123
+ /** Empirical-Bayes pooling strength: w = m / (m + exposures). */
124
+ declare const SHRINKAGE_M = 20;
125
+ /**
126
+ * Empirical-Bayes persona shrinkage at read time. Persona cells are born warm
127
+ * (pooled posterior dominates at 0 exposures) and detach as their own data
128
+ * accumulates. Formula pinned in CONTRACTS.md:
129
+ * w = m / (m + persona.exposures)
130
+ * alpha' = persona.alpha + w * pooled.alpha
131
+ * beta' = persona.beta + w * pooled.beta
132
+ */
133
+ declare function shrunkPosterior(persona: {
134
+ alpha: number;
135
+ beta: number;
136
+ exposures: number;
137
+ }, pooled: {
138
+ alpha: number;
139
+ beta: number;
140
+ }, m?: number): {
141
+ alpha: number;
142
+ beta: number;
143
+ };
144
+
145
+ /**
146
+ * FNV-1a 32-bit hash. Same algorithm (offset basis, prime-by-shifts, >>> 0)
147
+ * as the private hashUnit in apps/api/src/domain/holdout.ts — parity is
148
+ * pinned by fixture in deterministic.test.ts and cross-checked against
149
+ * assignHoldout in apps/api.
150
+ */
151
+ declare function fnv1a(input: string): number;
152
+ /**
153
+ * Deterministic arm pick for the keyless local engine:
154
+ * fnv1a(`${sessionId}:${slotId}`) % arms.length over the SORTED arms, so the
155
+ * same session sees the same decision across reloads and tabs regardless of
156
+ * declaration order.
157
+ */
158
+ declare function pickDeterministicArm(sessionId: string, slotId: string, arms: string[]): string;
159
+ /**
160
+ * Buckets a raw confidence float for CSS-facing use.
161
+ * Pinned: c < 0.3 → low; c < 0.7 → medium; else high.
162
+ * (Written NaN-safe: a non-comparable confidence must read as low.)
163
+ */
164
+ declare function confidenceBand(c: number): 'low' | 'medium' | 'high';
165
+
166
+ export { type ArmPosterior, CLUSTER_PRIORITY, LEGACY_PERSONA_MAP, type LearnedLayout, PERSONAS, PERSONA_DISPLAY, type Persona, type PersonaKey, SHRINKAGE_M, type SlotDecl, type SlotResult, UNKNOWN_PERSONA, applyClusterHeuristic, candidateLayouts, canonicalArm, canonicalPersona, chooseLayout, confidenceBand, fnv1a, hashLayout, marginalArmKey, parseArm, pickDeterministicArm, sampleArm, sampleBeta, shrunkPosterior, slotBaselineArm, slotResultFor, validateSlotDecl };
@@ -0,0 +1,166 @@
1
+ declare const PERSONAS: readonly ["buyer", "researcher", "deal_seeker", "browser"];
2
+ type Persona = (typeof PERSONAS)[number];
3
+ declare const UNKNOWN_PERSONA: "unknown";
4
+ type PersonaKey = Persona | typeof UNKNOWN_PERSONA;
5
+ /** Human-facing names — dashboard/devtools copy must use these, never raw keys. */
6
+ declare const PERSONA_DISPLAY: Record<PersonaKey, string>;
7
+ /**
8
+ * Every label ever written for a persona, mapped to its canonical form. The
9
+ * plural/hyphen labels are the pre-069 cluster seed labels
10
+ * ('buyers'/'deal-seekers'/…); identity mappings make canonical input a no-op.
11
+ */
12
+ declare const LEGACY_PERSONA_MAP: Record<string, Persona>;
13
+ /**
14
+ * Canonicalizes any persona/cluster label to the pinned vocabulary.
15
+ * Null, undefined, empty, and unrecognized labels all become 'unknown' —
16
+ * "we don't know" is always a safe answer; a guessed persona is not.
17
+ */
18
+ declare function canonicalPersona(label: string | null | undefined): PersonaKey;
19
+
20
+ declare const CLUSTER_PRIORITY: Record<Persona, string[]>;
21
+ /**
22
+ * Reorders section IDs based on the persona's semantic priority.
23
+ * Sections with no graph entry are treated as 'generic'.
24
+ * Returns the input unchanged for the unknown persona.
25
+ */
26
+ declare function applyClusterHeuristic(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey): string[];
27
+ /**
28
+ * The candidate layout orderings for a page — the distinct section orders
29
+ * produced by every persona's semantic priority (plus the requesting
30
+ * persona's own, which for 'unknown' is the identity order). These are the
31
+ * "arms" the layout bandit explores. Returned as hash → order so it joins
32
+ * directly against layout_weights rows keyed by the same hashLayout.
33
+ */
34
+ declare function candidateLayouts(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey): Map<string, string[]>;
35
+
36
+ /**
37
+ * Stable 16-char SHA-256 prefix for a section order array.
38
+ * MUST stay byte-identical to the legacy implementation in
39
+ * apps/api (node:crypto createHash('sha256')) — every
40
+ * layout_weights.layout_hash row in production was written by it
41
+ * (parity-pinned in hash.test.ts).
42
+ *
43
+ * Implemented as pure-JS FIPS 180-4 SHA-256 (no node:crypto) so this package
44
+ * is fully isomorphic: the keyless local engine and browser bundles import it
45
+ * without a Node builtin. Only the first 16 hex chars (h0+h1) are returned.
46
+ */
47
+ declare function hashLayout(order: string[]): string;
48
+
49
+ /** Learned Beta(alpha,beta) posterior for one candidate layout, keyed by hash. */
50
+ type LearnedLayout = {
51
+ layoutHash: string;
52
+ alpha: number;
53
+ beta: number;
54
+ };
55
+ /**
56
+ * Thompson-samples the layout order to serve a persona over the candidate
57
+ * orderings, using learned posteriors from layout_weights. Candidates with no
58
+ * learned row use the uniform 1/1 prior — identical to variant cold start.
59
+ * Falls back to the persona's heuristic only if sampling yields no candidate.
60
+ */
61
+ declare function chooseLayout(sections: string[], sectionTypes: Map<string, string>, persona: PersonaKey, learned: Map<string, LearnedLayout>, rand?: () => number): string[];
62
+
63
+ /** Learned Beta(alpha, beta) posterior for one arm. */
64
+ type ArmPosterior = {
65
+ arm: string;
66
+ alpha: number;
67
+ beta: number;
68
+ };
69
+ /** One draw from Beta(alpha, beta). Moved verbatim from apps/api/src/domain/bandit.ts. */
70
+ declare function sampleBeta(alpha: number, beta: number, rand?: () => number): number;
71
+ /**
72
+ * Thompson Sampling selection: samples Beta(alpha, beta) per arm and returns
73
+ * the argmax arm id, or null when no arms are given. Uncertain arms get
74
+ * explored; confident winners get exploited — same semantics as the legacy
75
+ * chooseVariant, generalized to arbitrary arm strings.
76
+ */
77
+ declare function sampleArm(arms: ArmPosterior[], rand?: () => number): string | null;
78
+
79
+ /** A slot declaration as sent on /v1/decide — exactly one of arms|dims. */
80
+ type SlotDecl = {
81
+ id: string;
82
+ arms?: string[];
83
+ dims?: Record<string, string[]>;
84
+ baseline?: string | Record<string, string>;
85
+ };
86
+ /** What the client receives for a slot: arm id, or per-dim values for dims slots. */
87
+ type SlotResult = string | Record<string, string>;
88
+ /** Canonical arm string for a dims combination: sorted `dim=value` joined with '|'. */
89
+ declare function canonicalArm(values: Record<string, string>): string;
90
+ /**
91
+ * Inverse of canonicalArm. Returns null when the string is not a
92
+ * dims-encoded arm (enumerated arm ids, empty, malformed, duplicate dims) —
93
+ * callers use `parseArm(arm) !== null` to distinguish dims arms from
94
+ * enumerated arms.
95
+ */
96
+ declare function parseArm(arm: string): Record<string, string> | null;
97
+ /** Storage key for one marginal posterior row of a dims slot. */
98
+ declare function marginalArmKey(dim: string, value: string): string;
99
+ /**
100
+ * Canonical arm string of the declared baseline, or the default baseline
101
+ * (first arm / first value per dim) when none is declared. Assumes the decl
102
+ * passed validateSlotDecl.
103
+ */
104
+ declare function slotBaselineArm(decl: SlotDecl): string;
105
+ /**
106
+ * Validates a slot declaration against the pinned rules: exactly one of
107
+ * arms|dims; arms 2..12 (unique); dims 1..4 dims of 2..6 unique values each
108
+ * with product ≤ 64; a declared baseline must live in the declared space.
109
+ */
110
+ declare function validateSlotDecl(decl: SlotDecl): {
111
+ ok: true;
112
+ } | {
113
+ ok: false;
114
+ reason: string;
115
+ };
116
+ /**
117
+ * Decodes a stored/served arm for the client: dims slot → parsed per-dim
118
+ * record (falling back to the baseline record if the arm fails to parse);
119
+ * arms slot → the arm id verbatim.
120
+ */
121
+ declare function slotResultFor(decl: SlotDecl, arm: string): SlotResult;
122
+
123
+ /** Empirical-Bayes pooling strength: w = m / (m + exposures). */
124
+ declare const SHRINKAGE_M = 20;
125
+ /**
126
+ * Empirical-Bayes persona shrinkage at read time. Persona cells are born warm
127
+ * (pooled posterior dominates at 0 exposures) and detach as their own data
128
+ * accumulates. Formula pinned in CONTRACTS.md:
129
+ * w = m / (m + persona.exposures)
130
+ * alpha' = persona.alpha + w * pooled.alpha
131
+ * beta' = persona.beta + w * pooled.beta
132
+ */
133
+ declare function shrunkPosterior(persona: {
134
+ alpha: number;
135
+ beta: number;
136
+ exposures: number;
137
+ }, pooled: {
138
+ alpha: number;
139
+ beta: number;
140
+ }, m?: number): {
141
+ alpha: number;
142
+ beta: number;
143
+ };
144
+
145
+ /**
146
+ * FNV-1a 32-bit hash. Same algorithm (offset basis, prime-by-shifts, >>> 0)
147
+ * as the private hashUnit in apps/api/src/domain/holdout.ts — parity is
148
+ * pinned by fixture in deterministic.test.ts and cross-checked against
149
+ * assignHoldout in apps/api.
150
+ */
151
+ declare function fnv1a(input: string): number;
152
+ /**
153
+ * Deterministic arm pick for the keyless local engine:
154
+ * fnv1a(`${sessionId}:${slotId}`) % arms.length over the SORTED arms, so the
155
+ * same session sees the same decision across reloads and tabs regardless of
156
+ * declaration order.
157
+ */
158
+ declare function pickDeterministicArm(sessionId: string, slotId: string, arms: string[]): string;
159
+ /**
160
+ * Buckets a raw confidence float for CSS-facing use.
161
+ * Pinned: c < 0.3 → low; c < 0.7 → medium; else high.
162
+ * (Written NaN-safe: a non-comparable confidence must read as low.)
163
+ */
164
+ declare function confidenceBand(c: number): 'low' | 'medium' | 'high';
165
+
166
+ export { type ArmPosterior, CLUSTER_PRIORITY, LEGACY_PERSONA_MAP, type LearnedLayout, PERSONAS, PERSONA_DISPLAY, type Persona, type PersonaKey, SHRINKAGE_M, type SlotDecl, type SlotResult, UNKNOWN_PERSONA, applyClusterHeuristic, candidateLayouts, canonicalArm, canonicalPersona, chooseLayout, confidenceBand, fnv1a, hashLayout, marginalArmKey, parseArm, pickDeterministicArm, sampleArm, sampleBeta, shrunkPosterior, slotBaselineArm, slotResultFor, validateSlotDecl };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";var v=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var X=(e,r)=>{for(var t in r)v(e,t,{get:r[t],enumerable:!0})},Z=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of J(r))!Q.call(e,o)&&o!==t&&v(e,o,{get:()=>r[o],enumerable:!(n=V(r,o))||n.enumerable});return e};var ee=e=>Z(v({},"__esModule",{value:!0}),e);var me={};X(me,{CLUSTER_PRIORITY:()=>G,LEGACY_PERSONA_MAP:()=>B,PERSONAS:()=>L,PERSONA_DISPLAY:()=>re,SHRINKAGE_M:()=>we,UNKNOWN_PERSONA:()=>k,applyClusterHeuristic:()=>S,candidateLayouts:()=>q,canonicalArm:()=>U,canonicalPersona:()=>te,chooseLayout:()=>oe,confidenceBand:()=>le,fnv1a:()=>W,hashLayout:()=>E,marginalArmKey:()=>ae,parseArm:()=>H,pickDeterministicArm:()=>fe,sampleArm:()=>j,sampleBeta:()=>$,shrunkPosterior:()=>ue,slotBaselineArm:()=>z,slotResultFor:()=>ce,validateSlotDecl:()=>ie});module.exports=ee(me);var L=["buyer","researcher","deal_seeker","browser"],k="unknown",re={buyer:"Buyer",researcher:"Researcher",deal_seeker:"Deal seeker",browser:"Browser",unknown:"Unknown"},B={buyers:"buyer",researchers:"researcher","deal-seekers":"deal_seeker",browsers:"browser",buyer:"buyer",researcher:"researcher",deal_seeker:"deal_seeker",browser:"browser"};function te(e){var t;if(e==null)return k;let r=e.trim().toLowerCase();return(t=B[r])!=null?t:k}var ne=[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 b(e,r){return e>>>r|e<<32-r}function C(e){return(e>>>0).toString(16).padStart(8,"0")}function se(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 u=1779033703,c=3144134277,f=1013904242,p=2773480762,d=1359893119,m=2600822924,w=528734635,R=1541459225,l=new Uint32Array(64);for(let M=0;M<o;M+=64){for(let i=0;i<16;i++)l[i]=a.getUint32(M+i*4,!1);for(let i=16;i<64;i++){let _=b(l[i-15],7)^b(l[i-15],18)^l[i-15]>>>3,K=b(l[i-2],17)^b(l[i-2],19)^l[i-2]>>>10;l[i]=l[i-16]+_+l[i-7]+K>>>0}let x=u,h=c,y=f,O=p,g=d,A=m,P=w,N=R;for(let i=0;i<64;i++){let _=b(g,6)^b(g,11)^b(g,25),K=g&A^~g&P,I=N+_+K+ne[i]+l[i]>>>0,Y=b(x,2)^b(x,13)^b(x,22),F=x&h^x&y^h&y,T=Y+F>>>0;N=P,P=A,A=g,g=O+I>>>0,O=y,y=h,h=x,x=I+T>>>0}u=u+x>>>0,c=c+h>>>0,f=f+y>>>0,p=p+O>>>0,d=d+g>>>0,m=m+A>>>0,w=w+P>>>0,R=R+N>>>0}return C(u)+C(c)}function E(e){return se(e.join("|"))}var G={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 S(e,r,t){let n=t===k?void 0:G[t];return n?[...e].sort((o,s)=>{var c,f;let a=(c=r.get(o))!=null?c:"generic",u=(f=r.get(s))!=null?f:"generic";return n.indexOf(a)-n.indexOf(u)}):e}function q(e,r,t){let n=new Map;for(let o of[...L,t]){let s=S(e,r,o);n.set(E(s),s)}return n}function D(e,r){if(e<1)return D(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 u=Math.max(1e-15,r()),c=r();o=Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*c),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 $(e,r,t=Math.random){let n=D(e,t),o=D(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=$(t.alpha,t.beta,r);for(let o=1;o<e.length;o++){let s=e[o],a=$(s.alpha,s.beta,r);a>n&&(t=s,n=a)}return t.arm}function oe(e,r,t,n,o=Math.random){var f,p;let s=q(e,r,t),a=[];for(let d of s.keys()){let m=n.get(d);a.push({arm:d,alpha:(f=m==null?void 0:m.alpha)!=null?f:1,beta:(p=m==null?void 0:m.beta)!=null?p:1})}let u=j(a,o),c=u?s.get(u):void 0;return c!=null?c:S(e,r,t)}function U(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("|")}function H(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 ae(e,r){return`${e}=${r}`}function z(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 U(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 U(r)}function ie(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(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(([c])=>c).sort(),u=Object.keys(s).sort();if(a.join(" ")!==u.join(" "))return{ok:!1,reason:"baseline must set every declared dim exactly once"};for(let[c,f]of n)if(!f.includes(s[c]))return{ok:!1,reason:`baseline value for dim "${c}" is not declared`}}return{ok:!0}}function ce(e,r){var t,n;return e.dims!=null?(n=(t=H(r))!=null?t:H(z(e)))!=null?n:{}:r}var we=20;function ue(e,r,t=20){let n=t/(t+e.exposures);return{alpha:e.alpha+n*r.alpha,beta:e.beta+n*r.beta}}function W(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 fe(e,r,t){if(t.length===0)throw new Error("pickDeterministicArm requires at least one arm");let n=[...t].sort();return n[W(`${e}:${r}`)%n.length]}function le(e){return e>=.3?e<.7?"medium":"high":"low"}0&&(module.exports={CLUSTER_PRIORITY,LEGACY_PERSONA_MAP,PERSONAS,PERSONA_DISPLAY,SHRINKAGE_M,UNKNOWN_PERSONA,applyClusterHeuristic,candidateLayouts,canonicalArm,canonicalPersona,chooseLayout,confidenceBand,fnv1a,hashLayout,marginalArmKey,parseArm,pickDeterministicArm,sampleArm,sampleBeta,shrunkPosterior,slotBaselineArm,slotResultFor,validateSlotDecl});
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ var E=["buyer","researcher","deal_seeker","browser"],P="unknown",J={buyer:"Buyer",researcher:"Researcher",deal_seeker:"Deal seeker",browser:"Browser",unknown:"Unknown"},z={buyers:"buyer",researchers:"researcher","deal-seekers":"deal_seeker",browsers:"browser",buyer:"buyer",researcher:"researcher",deal_seeker:"deal_seeker",browser:"browser"};function Q(e){var t;if(e==null)return P;let r=e.trim().toLowerCase();return(t=z[r])!=null?t:P}var W=[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 b(e,r){return e>>>r|e<<32-r}function q(e){return(e>>>0).toString(16).padStart(8,"0")}function Y(e){let r=new TextEncoder().encode(e),t=r.length,s=t*8,o=(t+8>>6)+1<<6,n=new Uint8Array(o);n.set(r),n[t]=128;let a=new DataView(n.buffer);a.setUint32(o-8,Math.floor(s/4294967296),!1),a.setUint32(o-4,s>>>0,!1);let u=1779033703,c=3144134277,f=1013904242,p=2773480762,d=1359893119,m=2600822924,S=528734635,w=1541459225,l=new Uint32Array(64);for(let R=0;R<o;R+=64){for(let i=0;i<16;i++)l[i]=a.getUint32(R+i*4,!1);for(let i=16;i<64;i++){let N=b(l[i-15],7)^b(l[i-15],18)^l[i-15]>>>3,_=b(l[i-2],17)^b(l[i-2],19)^l[i-2]>>>10;l[i]=l[i-16]+N+l[i-7]+_>>>0}let x=u,h=c,y=f,M=p,g=d,k=m,A=S,O=w;for(let i=0;i<64;i++){let N=b(g,6)^b(g,11)^b(g,25),_=g&k^~g&A,L=O+N+_+W[i]+l[i]>>>0,B=b(x,2)^b(x,13)^b(x,22),C=x&h^x&y^h&y,G=B+C>>>0;O=A,A=k,k=g,g=M+L>>>0,M=y,y=h,h=x,x=L+G>>>0}u=u+x>>>0,c=c+h>>>0,f=f+y>>>0,p=p+M>>>0,d=d+g>>>0,m=m+k>>>0,S=S+A>>>0,w=w+O>>>0}return q(u)+q(c)}function D(e){return Y(e.join("|"))}var F={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 K(e,r,t){let s=t===P?void 0:F[t];return s?[...e].sort((o,n)=>{var c,f;let a=(c=r.get(o))!=null?c:"generic",u=(f=r.get(n))!=null?f:"generic";return s.indexOf(a)-s.indexOf(u)}):e}function $(e,r,t){let s=new Map;for(let o of[...E,t]){let n=K(e,r,o);s.set(D(n),n)}return s}function v(e,r){if(e<1)return v(1+e,r)*Math.pow(Math.max(1e-15,r()),1/e);let t=e-1/3,s=1/Math.sqrt(9*t);for(;;){let o,n;do{let u=Math.max(1e-15,r()),c=r();o=Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*c),n=1+s*o}while(n<=0);n=n*n*n;let a=r();if(a<1-.0331*o*o*o*o||Math.log(a)<.5*o*o+t*(1-n+Math.log(n)))return t*n}}function j(e,r,t=Math.random){let s=v(e,t),o=v(r,t),n=s+o;return n<=0?e/(e+r):s/n}function U(e,r=Math.random){if(e.length===0)return null;if(e.length===1)return e[0].arm;let t=e[0],s=j(t.alpha,t.beta,r);for(let o=1;o<e.length;o++){let n=e[o],a=j(n.alpha,n.beta,r);a>s&&(t=n,s=a)}return t.arm}function ae(e,r,t,s,o=Math.random){var f,p;let n=$(e,r,t),a=[];for(let d of n.keys()){let m=s.get(d);a.push({arm:d,alpha:(f=m==null?void 0:m.alpha)!=null?f:1,beta:(p=m==null?void 0:m.beta)!=null?p:1})}let u=U(a,o),c=u?n.get(u):void 0;return c!=null?c:K(e,r,t)}function H(e){return Object.keys(e).sort().map(r=>`${r}=${e[r]}`).join("|")}function I(e){if(e.length===0)return null;let r={};for(let t of e.split("|")){let s=t.indexOf("=");if(s<=0||s!==t.lastIndexOf("=")||s===t.length-1)return null;let o=t.slice(0,s);if(o in r)return null;r[o]=t.slice(s+1)}return r}function ce(e,r){return`${e}=${r}`}function T(e){var t,s,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 H(e.baseline);if(typeof e.baseline=="string")return e.baseline;let r={};for(let[n,a]of Object.entries((s=e.dims)!=null?s:{}))r[n]=(o=a[0])!=null?o:"";return H(r)}function ue(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 n=e.arms;if(n.length<2)return{ok:!1,reason:"arms requires at least 2 entries"};if(n.length>12)return{ok:!1,reason:"arms allows at most 12 entries"};if(new Set(n).size!==n.length)return{ok:!1,reason:"arms must be unique"};if(e.baseline!==void 0){if(typeof e.baseline!="string")return{ok:!1,reason:"baseline for an arms slot must be a string"};if(!n.includes(e.baseline))return{ok:!1,reason:"baseline must be one of the declared arms"}}return{ok:!0}}let s=Object.entries(e.dims);if(s.length<1)return{ok:!1,reason:"dims requires at least 1 dimension"};if(s.length>4)return{ok:!1,reason:"dims allows at most 4 dimensions"};let o=1;for(let[n,a]of s){if(a.length<2)return{ok:!1,reason:`dim "${n}" requires at least 2 values`};if(a.length>6)return{ok:!1,reason:`dim "${n}" allows at most 6 values`};if(new Set(a).size!==a.length)return{ok:!1,reason:`dim "${n}" 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 n=e.baseline,a=s.map(([c])=>c).sort(),u=Object.keys(n).sort();if(a.join(" ")!==u.join(" "))return{ok:!1,reason:"baseline must set every declared dim exactly once"};for(let[c,f]of s)if(!f.includes(n[c]))return{ok:!1,reason:`baseline value for dim "${c}" is not declared`}}return{ok:!0}}function fe(e,r){var t,s;return e.dims!=null?(s=(t=I(r))!=null?t:I(T(e)))!=null?s:{}:r}var me=20;function be(e,r,t=20){let s=t/(t+e.exposures);return{alpha:e.alpha+s*r.alpha,beta:e.beta+s*r.beta}}function V(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 ge(e,r,t){if(t.length===0)throw new Error("pickDeterministicArm requires at least one arm");let s=[...t].sort();return s[V(`${e}:${r}`)%s.length]}function pe(e){return e>=.3?e<.7?"medium":"high":"low"}export{F as CLUSTER_PRIORITY,z as LEGACY_PERSONA_MAP,E as PERSONAS,J as PERSONA_DISPLAY,me as SHRINKAGE_M,P as UNKNOWN_PERSONA,K as applyClusterHeuristic,$ as candidateLayouts,H as canonicalArm,Q as canonicalPersona,ae as chooseLayout,pe as confidenceBand,V as fnv1a,D as hashLayout,ce as marginalArmKey,I as parseArm,ge as pickDeterministicArm,U as sampleArm,j as sampleBeta,be as shrunkPosterior,T as slotBaselineArm,fe as slotResultFor,ue as validateSlotDecl};
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@sentientui/policy",
3
+ "version": "0.2.0",
4
+ "private": false,
5
+ "description": "Pure decision-policy functions shared by the SentientUI API and the keyless local engine",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "sideEffects": false,
9
+ "main": "./dist/index.js",
10
+ "module": "./dist/index.mjs",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.mjs",
16
+ "require": "./dist/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "devDependencies": {
23
+ "@types/node": "^22.10.2",
24
+ "tsup": "^8.3.5",
25
+ "typescript": "^5.7.2",
26
+ "vitest": "^2.1.8"
27
+ },
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "test": "vitest run",
31
+ "typecheck": "tsc --noEmit",
32
+ "lint": "eslint src"
33
+ }
34
+ }