@contractspec/lib.personalization 6.0.26 → 6.1.1

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
@@ -39,6 +39,15 @@ const tracker = createBehaviorTracker({
39
39
  tenantId: "acme",
40
40
  userId: "user-123",
41
41
  role: "manager",
42
+ roles: ["manager"],
43
+ permissions: ["billing.order.read"],
44
+ policyDecisions: {
45
+ internalNotes: {
46
+ effect: "deny",
47
+ fields: ["internalNotes"],
48
+ missing: { permissions: ["billing.notes.read"] },
49
+ },
50
+ },
42
51
  },
43
52
  autoFlushIntervalMs: 5000,
44
53
  });
@@ -88,6 +97,68 @@ Typical flow:
88
97
  3. Analyze the summary with `BehaviorAnalyzer`.
89
98
  4. Convert insights into an `OverlaySpec` suggestion or workflow adaptation hints.
90
99
 
100
+ ## DataView preferences
101
+
102
+ Use the data-view preference helpers when a collection `DataViewSpec` should
103
+ honor preferred list/grid/table mode, density, or data depth without coupling
104
+ the design-system renderer to personalization:
105
+
106
+ ```tsx
107
+ import { DataViewRenderer } from "@contractspec/lib.design-system";
108
+ import { resolveDataViewPreferences } from "@contractspec/lib.personalization/data-view-preferences";
109
+
110
+ const resolved = resolveDataViewPreferences({
111
+ spec: AccountsDataView,
112
+ preferences: profile.canonical,
113
+ insights,
114
+ record: savedDataViewPreference,
115
+ });
116
+
117
+ <DataViewRenderer
118
+ spec={AccountsDataView}
119
+ items={rows}
120
+ defaultViewMode={resolved.viewMode}
121
+ defaultDensity={resolved.density}
122
+ defaultDataDepth={resolved.dataDepth}
123
+ />;
124
+ ```
125
+
126
+ The helper returns plain data and never imports React or the design-system
127
+ package. Stored data-view preference records win over behavior insights,
128
+ preference dimensions, and authored contract defaults. Disallowed inferred view
129
+ modes are ignored so the renderer only receives modes allowed by the spec.
130
+
131
+ Record renderer interactions with the behavior tracker so later analysis can
132
+ derive scoped preferred modes:
133
+
134
+ ```ts
135
+ tracker.trackDataViewInteraction({
136
+ dataViewKey: AccountsDataView.meta.key,
137
+ dataViewVersion: AccountsDataView.meta.version,
138
+ action: "view_mode_changed",
139
+ viewMode: "grid",
140
+ });
141
+
142
+ tracker.trackDataViewInteraction({
143
+ dataViewKey: AccountsDataView.meta.key,
144
+ action: "data_depth_changed",
145
+ dataDepth: "detailed",
146
+ });
147
+ ```
148
+
149
+ Agent prompt for a DataView preference integration:
150
+
151
+ ```md
152
+ Add DataView personalization for <screen>.
153
+
154
+ - Resolve viewMode, density, dataDepth, and pageSize with resolveDataViewPreferences.
155
+ - Apply resolved values to DataViewRenderer as default or controlled props.
156
+ - Track opened, view_mode_changed, density_changed, data_depth_changed, search_changed, filter_changed, sort_changed, and page_changed actions with trackDataViewInteraction.
157
+ - Persist only the dimensions enabled by view.collection.personalization.persist.
158
+ - Ignore behavior-derived modes not allowed by view.collection.viewModes.allowedModes.
159
+ - Keep @contractspec/lib.personalization free of React and design-system imports.
160
+ ```
161
+
91
162
  ## API map
92
163
 
93
164
  ### Main runtime APIs
@@ -96,18 +167,21 @@ Typical flow:
96
167
  - `InMemoryBehaviorStore`: simple in-memory implementation for tests, demos, and local composition.
97
168
  - `BehaviorTracker` and `createBehaviorTracker`: buffered event capture with tenant/user context and OpenTelemetry instrumentation.
98
169
  - `BehaviorAnalyzer`: converts `BehaviorSummary` data into `BehaviorInsights`.
170
+ - Authorization context: events may carry `roles`, `permissions`, and policy decision summaries so analyzers can suppress denied fields/actions without granting access.
99
171
  - `insightsToOverlaySuggestion`: turns analysis output into an overlay-engine `OverlaySpec`.
100
172
  - `insightsToWorkflowAdaptations`: turns workflow bottlenecks into lightweight adaptation notes.
101
173
 
102
174
  ### Core data contracts
103
175
 
104
- - `BehaviorEvent`: discriminated union with three event kinds: `field_access`, `feature_usage`, and `workflow_step`.
176
+ - `BehaviorEvent`: discriminated union with four event kinds: `field_access`, `feature_usage`, `workflow_step`, and `data_view_interaction`.
105
177
  - `BehaviorQuery`: filter shape used by `BehaviorStore.query()` and `BehaviorStore.summarize()`.
106
178
  - `BehaviorSummary`: aggregated counts returned by store summarization.
107
179
  - `BehaviorInsights`: analyzer output including hidden-field candidates, bottlenecks, and layout preference hints.
180
+ When authorization decisions are supplied, denied fields/actions are surfaced for suppression and are not promoted in overlay reorder suggestions.
108
181
  - `BehaviorAnalyzerOptions`: tuning knobs for inactivity threshold and minimum workflow sample size.
109
182
  - `OverlaySuggestionOptions`: metadata required to build an overlay suggestion.
110
183
  - `WorkflowAdaptation`: workflow, step, and note triple derived from bottlenecks.
184
+ - DataView preference helpers: `resolveDataViewPreferences`, `DataViewPreferenceRecord`, `DataViewPreferencePatch`, and mapping helpers for density and view-mode patches.
111
185
 
112
186
  ### Preference model contracts
113
187
 
@@ -123,6 +197,7 @@ The root barrel at `src/index.ts` re-exports public symbols from:
123
197
 
124
198
  - `adapter`
125
199
  - `analyzer`
200
+ - `data-view-preferences`
126
201
  - `preference-dimensions`
127
202
  - `store`
128
203
  - `tracker`
@@ -133,6 +208,7 @@ Published subpaths from `package.json`:
133
208
  - `.`
134
209
  - `./adapter`
135
210
  - `./analyzer`
211
+ - `./data-view-preferences`
136
212
  - `./docs`
137
213
  - `./docs/behavior-tracking.docblock`
138
214
  - `./docs/overlay-engine.docblock`
@@ -152,7 +228,7 @@ For application code, prefer `.` or the focused subpaths above. The `./docs*` su
152
228
  - Each enqueue also emits OpenTelemetry metrics and tracing through `@opentelemetry/api`.
153
229
  - `BehaviorAnalyzer` uses deterministic threshold heuristics. It does not do ranking, learning, or probabilistic inference.
154
230
  - `insightsToOverlaySuggestion()` currently emits only `hideField` and `reorderFields` modifications.
155
- - `layoutPreference` is inferred from field-count thresholds, not from UI render telemetry.
231
+ - `layoutPreference` is still inferred from field-count thresholds. Data-view-specific preferred modes are derived from `data_view_interaction` events when those events include a valid collection `viewMode`.
156
232
  - `PreferenceDimensions` and related types are contracts. Durable persistence and runtime resolution live elsewhere.
157
233
 
158
234
  ## When not to use this package
package/dist/adapter.d.ts CHANGED
@@ -7,6 +7,7 @@ export interface OverlaySuggestionOptions {
7
7
  userId?: string;
8
8
  role?: string;
9
9
  version?: string;
10
+ deniedFields?: string[];
10
11
  }
11
12
  export interface WorkflowAdaptation {
12
13
  workflow: string;
package/dist/adapter.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- function o(t,e){let r=[];if(t.suggestedHiddenFields.forEach((i)=>{r.push({type:"hideField",field:i,reason:"Automatically hidden because usage is near zero"})}),t.frequentlyUsedFields.length)r.push({type:"reorderFields",fields:t.frequentlyUsedFields});if(!r.length)return null;return{overlayId:e.overlayId,version:e.version??"1.0.0",appliesTo:{tenantId:e.tenantId,capability:e.capability,userId:e.userId,role:e.role},modifications:r,metadata:{generatedAt:new Date().toISOString(),automated:!0}}}function n(t){return t.workflowBottlenecks.map((e)=>({workflow:e.workflow,step:e.step,note:`High drop rate (${Math.round(e.dropRate*100)}%) detected`}))}export{n as insightsToWorkflowAdaptations,o as insightsToOverlaySuggestion};
2
+ function a(t,e){let r=[];t.suggestedHiddenFields.forEach((i)=>{r.push({type:"hideField",field:i,reason:"Automatically hidden because usage is near zero"})});let o=new Set([...t.deniedFields??[],...e.deniedFields??[]]),n=t.frequentlyUsedFields.filter((i)=>!o.has(i));if(n.length)r.push({type:"reorderFields",fields:n});if(!r.length)return null;return{overlayId:e.overlayId,version:e.version??"1.0.0",appliesTo:{tenantId:e.tenantId,capability:e.capability,userId:e.userId,role:e.role},modifications:r,metadata:{generatedAt:new Date().toISOString(),automated:!0}}}function s(t){return t.workflowBottlenecks.map((e)=>({workflow:e.workflow,step:e.step,note:`High drop rate (${Math.round(e.dropRate*100)}%) detected`}))}export{s as insightsToWorkflowAdaptations,a as insightsToOverlaySuggestion};
package/dist/analyzer.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- class h{store;options;constructor(e,t={}){this.store=e;this.options=t}async analyze(e){let t={tenantId:e.tenantId,userId:e.userId,role:e.role};if(e.windowMs)t.since=Date.now()-e.windowMs;let i=await this.store.summarize(t);return y(i,this.options)}}function y(e,t){let i=t.fieldInactivityThreshold??3,d=t.minSamples??10,a=[],u=[];for(let[o,r]of Object.entries(e.fieldCounts)){if(r<=i)a.push(o);if(r>=i*4)u.push(o)}let c=Object.entries(e.workflowStepCounts).flatMap(([o,r])=>{let s=Object.values(r).reduce((n,l)=>n+l,0);if(!s||s<d)return[];return Object.entries(r).filter(([,n])=>n/s<0.4).map(([n,l])=>({workflow:o,step:n,dropRate:1-l/s}))}),f=m(e);return{unusedFields:a,suggestedHiddenFields:a.slice(0,5),frequentlyUsedFields:u.slice(0,10),workflowBottlenecks:c,layoutPreference:f}}function m(e){let t=Object.keys(e.fieldCounts).length;if(!t)return;if(t>=15)return"table";if(t>=8)return"grid";return"form"}export{h as BehaviorAnalyzer};
2
+ class p{store;options;constructor(e,t={}){this.store=e;this.options=t}async analyze(e){let t={tenantId:e.tenantId,userId:e.userId,role:e.role};if(e.windowMs)t.since=Date.now()-e.windowMs;let n=await this.store.summarize(t);return v(n,this.options)}}function v(e,t){let n=t.fieldInactivityThreshold??3,d=t.minSamples??10,r=[],a=[];for(let[i,o]of Object.entries(e.fieldCounts)){if(o<=n)r.push(i);if(o>=n*4)a.push(i)}let l=Object.entries(e.workflowStepCounts).flatMap(([i,o])=>{let c=Object.values(o).reduce((s,u)=>s+u,0);if(!c||c<d)return[];return Object.entries(o).filter(([,s])=>s/c<0.4).map(([s,u])=>({workflow:i,step:s,dropRate:1-u/c}))}),f=Object.keys(e.deniedFieldCounts??{}),h=Object.keys(e.deniedActionCounts??{}),y=g(e);return{unusedFields:r,suggestedHiddenFields:r.slice(0,5),frequentlyUsedFields:a.filter((i)=>!f.includes(i)).slice(0,10),deniedFields:f,deniedActions:h,workflowBottlenecks:l,layoutPreference:y,dataViewPreferences:w(e)}}function w(e){let t=Object.entries(e.dataViewViewModeCounts??{}).flatMap(([n,d])=>{let r=Object.entries(d).sort(([,a],[,l])=>(l??0)-(a??0))[0]?.[0];if(!m(r))return[];return[[n,{preferredViewMode:r}]]});return t.length?Object.fromEntries(t):void 0}function m(e){return e==="list"||e==="grid"||e==="table"}function g(e){let t=Object.keys(e.fieldCounts).length;if(!t)return;if(t>=15)return"table";if(t>=8)return"grid";return"form"}export{p as BehaviorAnalyzer};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import type { DataViewCollectionMode, DataViewDataDepth, DataViewDensity, DataViewSpec } from '@contractspec/lib.contracts-spec/data-views';
2
+ import type { PreferenceDimensions } from './preference-dimensions';
3
+ import type { BehaviorInsights } from './types';
4
+ export interface DataViewPreferenceRecord {
5
+ dataViewKey: string;
6
+ dataViewVersion?: string;
7
+ viewMode?: DataViewCollectionMode;
8
+ density?: DataViewDensity;
9
+ dataDepth?: DataViewDataDepth;
10
+ pageSize?: number;
11
+ updatedAt?: string;
12
+ }
13
+ export interface DataViewPreferencePatch {
14
+ dataViewKey: string;
15
+ dataViewVersion?: string;
16
+ viewMode?: DataViewCollectionMode;
17
+ density?: DataViewDensity;
18
+ dataDepth?: DataViewDataDepth;
19
+ pageSize?: number;
20
+ }
21
+ export interface ResolvedDataViewPreferences {
22
+ viewMode: DataViewCollectionMode;
23
+ density: DataViewDensity;
24
+ dataDepth: DataViewDataDepth;
25
+ pageSize?: number;
26
+ source: {
27
+ viewMode: DataViewPreferenceSource;
28
+ density: DataViewPreferenceSource;
29
+ dataDepth: DataViewPreferenceSource;
30
+ pageSize?: DataViewPreferenceSource;
31
+ };
32
+ }
33
+ export type DataViewPreferenceSource = 'record' | 'insights' | 'preferences' | 'contract' | 'fallback';
34
+ export interface ResolveDataViewPreferencesInput {
35
+ spec: DataViewSpec;
36
+ preferences?: PreferenceDimensions;
37
+ insights?: BehaviorInsights;
38
+ record?: DataViewPreferenceRecord;
39
+ }
40
+ export declare function resolveDataViewPreferences({ spec, preferences, insights, record, }: ResolveDataViewPreferencesInput): ResolvedDataViewPreferences;
41
+ export declare function preferenceDimensionsToDataViewDensity(preferences?: PreferenceDimensions): DataViewDensity | undefined;
42
+ export declare function dataViewDensityToPreferencePatch(density: DataViewDensity): Partial<PreferenceDimensions>;
43
+ export declare function dataViewModeToPreferencePatch(spec: DataViewSpec, viewMode: DataViewCollectionMode): DataViewPreferencePatch;
@@ -0,0 +1,2 @@
1
+ // @bun
2
+ var G=["list","grid","table"];function N({spec:b,preferences:j,insights:k,record:x}){let B=V(b),J=Z(b,B),P=Y(b,k,B),Q=F(x?.viewMode,B),R=$(b),U=q(b);return{viewMode:Q??P??J??"list",density:x?.density??X(j)??R??"comfortable",dataDepth:x?.dataDepth??j?.dataDepth??U??"standard",pageSize:x?.pageSize??z(b)?.pagination?.pageSize,source:{viewMode:Q?"record":P?"insights":J?"contract":"fallback",density:x?.density?"record":j?"preferences":R?"contract":"fallback",dataDepth:x?.dataDepth?"record":j?.dataDepth?"preferences":U?"contract":"fallback",pageSize:x?.pageSize?"record":z(b)?.pagination?.pageSize?"contract":void 0}}}function X(b){if(!b)return;if(b.density==="minimal"||b.density==="compact"||b.density==="dense")return"compact";return"comfortable"}function S(b){return{density:b==="compact"?"compact":"standard"}}function A(b,j){return{dataViewKey:b.meta.key,dataViewVersion:b.meta.version,viewMode:j}}function Y(b,j,k){let x=j?.dataViewPreferences?.[b.meta.key]?.preferredViewMode,B=j?.layoutPreference;return F(x,k)??F(B,k)}function Z(b,j){if(!H(b.view.kind))return;let k=z(b)?.viewModes?.defaultMode;return F(k,j)??F(b.view.kind,j)}function $(b){if(b.view.kind==="table"&&b.view.density)return b.view.density;return z(b)?.density}function q(b){return z(b)?.dataDepth}function V(b){if(!H(b.view.kind))return[];let j=z(b),k=j?.viewModes?.allowedModes?.filter(W);if(k?.length)return K(k);if(j?.toolbar?.viewMode===!0)return G;return j?.viewModes?G:[b.view.kind]}function z(b){if(b.view.kind==="detail")return;return b.view.collection}function F(b,j){return W(b)&&j.includes(b)?b:void 0}function K(b){return G.filter((j)=>b.includes(j))}function H(b){return b==="list"||b==="grid"||b==="table"}function W(b){return H(b)}export{N as resolveDataViewPreferences,X as preferenceDimensionsToDataViewDensity,A as dataViewModeToPreferencePatch,S as dataViewDensityToPreferencePatch};
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './adapter';
2
2
  export * from './analyzer';
3
+ export * from './data-view-preferences';
3
4
  export * from './preference-dimensions';
4
5
  export * from './store';
5
6
  export * from './tracker';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- function O(e,t){let r=[];if(e.suggestedHiddenFields.forEach((o)=>{r.push({type:"hideField",field:o,reason:"Automatically hidden because usage is near zero"})}),e.frequentlyUsedFields.length)r.push({type:"reorderFields",fields:e.frequentlyUsedFields});if(!r.length)return null;return{overlayId:t.overlayId,version:t.version??"1.0.0",appliesTo:{tenantId:t.tenantId,capability:t.capability,userId:t.userId,role:t.role},modifications:r,metadata:{generatedAt:new Date().toISOString(),automated:!0}}}function A(e){return e.workflowBottlenecks.map((t)=>({workflow:t.workflow,step:t.step,note:`High drop rate (${Math.round(t.dropRate*100)}%) detected`}))}class h{store;options;constructor(e,t={}){this.store=e;this.options=t}async analyze(e){let t={tenantId:e.tenantId,userId:e.userId,role:e.role};if(e.windowMs)t.since=Date.now()-e.windowMs;let r=await this.store.summarize(t);return v(r,this.options)}}function v(e,t){let r=t.fieldInactivityThreshold??3,o=t.minSamples??10,i=[],l=[];for(let[n,s]of Object.entries(e.fieldCounts)){if(s<=r)i.push(n);if(s>=r*4)l.push(n)}let c=Object.entries(e.workflowStepCounts).flatMap(([n,s])=>{let u=Object.values(s).reduce((a,d)=>a+d,0);if(!u||u<o)return[];return Object.entries(s).filter(([,a])=>a/u<0.4).map(([a,d])=>({workflow:n,step:a,dropRate:1-d/u}))}),p=m(e);return{unusedFields:i,suggestedHiddenFields:i.slice(0,5),frequentlyUsedFields:l.slice(0,10),workflowBottlenecks:c,layoutPreference:p}}function m(e){let t=Object.keys(e.fieldCounts).length;if(!t)return;if(t>=15)return"table";if(t>=8)return"grid";return"form"}import{registerDocBlocks as g}from"@contractspec/lib.contracts-spec/docs";var y=[{id:"docs.personalization.behavior-tracking",title:"Behavior Tracking",summary:"`@contractspec/lib.personalization` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.",kind:"reference",visibility:"public",route:"/docs/personalization/behavior-tracking",tags:["personalization","behavior-tracking"],body:`# Behavior Tracking
2
+ function G(e,t){let i=[];e.suggestedHiddenFields.forEach((n)=>{i.push({type:"hideField",field:n,reason:"Automatically hidden because usage is near zero"})});let r=new Set([...e.deniedFields??[],...t.deniedFields??[]]),a=e.frequentlyUsedFields.filter((n)=>!r.has(n));if(a.length)i.push({type:"reorderFields",fields:a});if(!i.length)return null;return{overlayId:t.overlayId,version:t.version??"1.0.0",appliesTo:{tenantId:t.tenantId,capability:t.capability,userId:t.userId,role:t.role},modifications:i,metadata:{generatedAt:new Date().toISOString(),automated:!0}}}function J(e){return e.workflowBottlenecks.map((t)=>({workflow:t.workflow,step:t.step,note:`High drop rate (${Math.round(t.dropRate*100)}%) detected`}))}class I{store;options;constructor(e,t={}){this.store=e;this.options=t}async analyze(e){let t={tenantId:e.tenantId,userId:e.userId,role:e.role};if(e.windowMs)t.since=Date.now()-e.windowMs;let i=await this.store.summarize(t);return k(i,this.options)}}function k(e,t){let i=t.fieldInactivityThreshold??3,r=t.minSamples??10,a=[],n=[];for(let[d,c]of Object.entries(e.fieldCounts)){if(c<=i)a.push(d);if(c>=i*4)n.push(d)}let o=Object.entries(e.workflowStepCounts).flatMap(([d,c])=>{let w=Object.values(c).reduce((f,p)=>f+p,0);if(!w||w<r)return[];return Object.entries(c).filter(([,f])=>f/w<0.4).map(([f,p])=>({workflow:d,step:f,dropRate:1-p/w}))}),s=Object.keys(e.deniedFieldCounts??{}),h=Object.keys(e.deniedActionCounts??{}),m=x(e);return{unusedFields:a,suggestedHiddenFields:a.slice(0,5),frequentlyUsedFields:n.filter((d)=>!s.includes(d)).slice(0,10),deniedFields:s,deniedActions:h,workflowBottlenecks:o,layoutPreference:m,dataViewPreferences:V(e)}}function V(e){let t=Object.entries(e.dataViewViewModeCounts??{}).flatMap(([i,r])=>{let a=Object.entries(r).sort(([,n],[,o])=>(o??0)-(n??0))[0]?.[0];if(!b(a))return[];return[[i,{preferredViewMode:a}]]});return t.length?Object.fromEntries(t):void 0}function b(e){return e==="list"||e==="grid"||e==="table"}function x(e){let t=Object.keys(e.fieldCounts).length;if(!t)return;if(t>=15)return"table";if(t>=8)return"grid";return"form"}var g=["list","grid","table"];function Y({spec:e,preferences:t,insights:i,record:r}){let a=P(e),n=F(e,a),o=D(e,i,a),s=l(r?.viewMode,a),h=E(e),m=A(e);return{viewMode:s??o??n??"list",density:r?.density??S(t)??h??"comfortable",dataDepth:r?.dataDepth??t?.dataDepth??m??"standard",pageSize:r?.pageSize??u(e)?.pagination?.pageSize,source:{viewMode:s?"record":o?"insights":n?"contract":"fallback",density:r?.density?"record":t?"preferences":h?"contract":"fallback",dataDepth:r?.dataDepth?"record":t?.dataDepth?"preferences":m?"contract":"fallback",pageSize:r?.pageSize?"record":u(e)?.pagination?.pageSize?"contract":void 0}}}function S(e){if(!e)return;if(e.density==="minimal"||e.density==="compact"||e.density==="dense")return"compact";return"comfortable"}function N(e){return{density:e==="compact"?"compact":"standard"}}function L(e,t){return{dataViewKey:e.meta.key,dataViewVersion:e.meta.version,viewMode:t}}function D(e,t,i){let r=t?.dataViewPreferences?.[e.meta.key]?.preferredViewMode,a=t?.layoutPreference;return l(r,i)??l(a,i)}function F(e,t){if(!v(e.view.kind))return;let i=u(e)?.viewModes?.defaultMode;return l(i,t)??l(e.view.kind,t)}function E(e){if(e.view.kind==="table"&&e.view.density)return e.view.density;return u(e)?.density}function A(e){return u(e)?.dataDepth}function P(e){if(!v(e.view.kind))return[];let t=u(e),i=t?.viewModes?.allowedModes?.filter(y);if(i?.length)return C(i);if(t?.toolbar?.viewMode===!0)return g;return t?.viewModes?g:[e.view.kind]}function u(e){if(e.view.kind==="detail")return;return e.view.collection}function l(e,t){return y(e)&&t.includes(e)?e:void 0}function C(e){return g.filter((t)=>e.includes(t))}function v(e){return e==="list"||e==="grid"||e==="table"}function y(e){return v(e)}import{registerDocBlocks as R}from"@contractspec/lib.contracts-spec/docs";var T=[{id:"docs.personalization.behavior-tracking",title:"Behavior Tracking",summary:"`@contractspec/lib.personalization` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.",kind:"reference",visibility:"public",route:"/docs/personalization/behavior-tracking",tags:["personalization","behavior-tracking"],body:`# Behavior Tracking
3
3
 
4
4
  \`@contractspec/lib.personalization\` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.
5
5
 
@@ -78,7 +78,7 @@ When the adapter returns an overlay spec, pass it to the overlay engine to regis
78
78
 
79
79
 
80
80
 
81
- `}];g(y);import{registerDocBlocks as w}from"@contractspec/lib.contracts-spec/docs";var B=[{id:"docs.personalization.overlay-engine",title:"Overlay Engine",summary:"`@contractspec/lib.overlay-engine` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.",kind:"reference",visibility:"public",route:"/docs/personalization/overlay-engine",tags:["personalization","overlay-engine"],body:`# Overlay Engine
81
+ `}];R(T);import{registerDocBlocks as z}from"@contractspec/lib.contracts-spec/docs";var _=[{id:"docs.personalization.overlay-engine",title:"Overlay Engine",summary:"`@contractspec/lib.overlay-engine` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.",kind:"reference",visibility:"public",route:"/docs/personalization/overlay-engine",tags:["personalization","overlay-engine"],body:`# Overlay Engine
82
82
 
83
83
  \`@contractspec/lib.overlay-engine\` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.
84
84
 
@@ -158,7 +158,7 @@ const result = engine.apply({
158
158
 
159
159
 
160
160
 
161
- `}];w(B);import{registerDocBlocks as k}from"@contractspec/lib.contracts-spec/docs";var I=[{id:"docs.personalization.workflow-composition",title:"Workflow Composition",summary:"`@contractspec/lib.workflow-composer` composes base WorkflowSpecs with tenant/role/device-specific extensions, strict validation, deterministic merge ordering, metadata/annotation overlays, and orphan-graph protection for hidden-step rewrites.",kind:"reference",visibility:"public",route:"/docs/personalization/workflow-composition",tags:["personalization","workflow-composition"],body:`# Workflow Composition
161
+ `}];z(_);import{registerDocBlocks as O}from"@contractspec/lib.contracts-spec/docs";var U=[{id:"docs.personalization.workflow-composition",title:"Workflow Composition",summary:"`@contractspec/lib.workflow-composer` composes base WorkflowSpecs with tenant/role/device-specific extensions, strict validation, deterministic merge ordering, metadata/annotation overlays, and orphan-graph protection for hidden-step rewrites.",kind:"reference",visibility:"public",route:"/docs/personalization/workflow-composition",tags:["personalization","workflow-composition"],body:`# Workflow Composition
162
162
 
163
163
  \`@contractspec/lib.workflow-composer\` composes base WorkflowSpecs with tenant/role/device-specific extensions.
164
164
 
@@ -210,4 +210,4 @@ workflowRunner.execute(runtimeSpec, ctx);
210
210
  - \`metadata\` and \`annotations\` overlays are merged into the composed runtime workflow for downstream observability and rollout tracing.
211
211
 
212
212
  This keeps tenant overlays additive, auditable, and replay-safe.
213
- `}];k(I);class x{events=[];async record(e){this.events.push(e)}async bulkRecord(e){this.events.push(...e)}async query(e){return b(this.events,e)}async summarize(e){let t=await this.query(e),r={fieldCounts:{},featureCounts:{},workflowStepCounts:{},totalEvents:t.length};return t.forEach((o)=>{switch(o.type){case"field_access":r.fieldCounts[o.field]=(r.fieldCounts[o.field]??0)+1;break;case"feature_usage":r.featureCounts[o.feature]=(r.featureCounts[o.feature]??0)+1;break;case"workflow_step":{let i=r.workflowStepCounts[o.workflow]??={};i[o.step]=(i[o.step]??0)+1;break}default:break}}),r}async clear(){this.events=[]}}function b(e,t){return e.filter((r)=>{if(t.tenantId&&r.tenantId!==t.tenantId)return!1;if(t.userId&&r.userId!==t.userId)return!1;if(t.role&&r.role!==t.role)return!1;if(t.since&&r.timestamp<t.since)return!1;if(t.until&&r.timestamp>t.until)return!1;if(t.operation&&r.type==="field_access"&&r.operation!==t.operation)return!1;if(t.feature&&r.type==="feature_usage"&&r.feature!==t.feature)return!1;if(t.workflow&&r.type==="workflow_step"&&r.workflow!==t.workflow)return!1;return!0})}import{metrics as S,trace as E}from"@opentelemetry/api";var F=25;class f{store;context;tracer=E.getTracer("lssm.personalization","1.0.0");counter=S.getMeter("lssm.personalization","1.0.0").createCounter("lssm.personalization.events",{description:"Behavior events tracked for personalization"});buffer=[];bufferSize;flushTimer;constructor(e){if(this.store=e.store,this.context=e.context,this.bufferSize=e.bufferSize??F,e.autoFlushIntervalMs)this.flushTimer=setInterval(()=>{this.flush()},e.autoFlushIntervalMs)}trackFieldAccess(e){let t={type:"field_access",operation:e.operation,field:e.field,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackFeatureUsage(e){let t={type:"feature_usage",feature:e.feature,action:e.action,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackWorkflowStep(e){let t={type:"workflow_step",workflow:e.workflow,step:e.step,status:e.status,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}async flush(){if(!this.buffer.length)return;let e=this.buffer;this.buffer=[],await this.store.bulkRecord(e)}async dispose(){if(this.flushTimer)clearInterval(this.flushTimer);await this.flush()}enqueue(e){if(this.buffer.push(e),this.counter.add(1,{tenantId:this.context.tenantId,type:e.type}),this.tracer.startActiveSpan(`personalization.${e.type}`,(t)=>{if(t.setAttribute("tenant.id",this.context.tenantId),this.context.userId)t.setAttribute("user.id",this.context.userId);t.setAttribute("personalization.event_type",e.type),t.end()}),this.buffer.length>=this.bufferSize)this.flush()}}var $=(e)=>new f(e);import{defineFeature as T}from"@contractspec/lib.contracts-spec/features";var q=T({meta:{key:"libs.personalization",version:"1.0.0",title:"Personalization",description:"Behavior tracking, analysis, and adaptation helpers for ContractSpec personalization.",domain:"personalization",owners:["@contractspec-core"],tags:["package","libs","personalization"],stability:"experimental"}});export{A as insightsToWorkflowAdaptations,O as insightsToOverlaySuggestion,$ as createBehaviorTracker,q as PersonalizationFeature,x as InMemoryBehaviorStore,f as BehaviorTracker,h as BehaviorAnalyzer};
213
+ `}];O(U);class j{events=[];async record(e){this.events.push(e)}async bulkRecord(e){this.events.push(...e)}async query(e){return K(this.events,e)}async summarize(e){let t=await this.query(e),i={fieldCounts:{},featureCounts:{},workflowStepCounts:{},dataViewInteractionCounts:{},dataViewViewModeCounts:{},totalEvents:t.length,deniedFieldCounts:{},deniedActionCounts:{}};return t.forEach((r)=>{switch(Q(i,r),r.type){case"field_access":i.fieldCounts[r.field]=(i.fieldCounts[r.field]??0)+1;break;case"feature_usage":i.featureCounts[r.feature]=(i.featureCounts[r.feature]??0)+1;break;case"workflow_step":{let a=i.workflowStepCounts[r.workflow]??={};a[r.step]=(a[r.step]??0)+1;break}case"data_view_interaction":{let a=i.dataViewInteractionCounts??={},n=a[r.dataViewKey]??={};if(n[r.action]=(n[r.action]??0)+1,r.viewMode){let o=i.dataViewViewModeCounts??={},s=o[r.dataViewKey]??={};s[r.viewMode]=(s[r.viewMode]??0)+1}break}default:break}}),i}async clear(){this.events=[]}}function K(e,t){return e.filter((i)=>{if(t.tenantId&&i.tenantId!==t.tenantId)return!1;if(t.userId&&i.userId!==t.userId)return!1;if(t.role&&i.role!==t.role&&!i.roles?.includes(t.role))return!1;if(t.roles?.length&&!t.roles.some((r)=>i.roles?.includes(r)||i.role===r))return!1;if(t.permission&&!i.permissions?.includes(t.permission))return!1;if(t.permissions?.length&&!t.permissions.every((r)=>i.permissions?.includes(r)))return!1;if(t.since&&i.timestamp<t.since)return!1;if(t.until&&i.timestamp>t.until)return!1;if(t.operation&&i.type==="field_access"&&i.operation!==t.operation)return!1;if(t.feature&&i.type==="feature_usage"&&i.feature!==t.feature)return!1;if(t.workflow&&i.type==="workflow_step"&&i.workflow!==t.workflow)return!1;if(t.dataViewKey&&i.type==="data_view_interaction"&&i.dataViewKey!==t.dataViewKey)return!1;if(t.dataViewAction&&i.type==="data_view_interaction"&&i.action!==t.dataViewAction)return!1;return!0})}function Q(e,t){Object.values(t.policyDecisions??{}).forEach((i)=>{if(i.effect!=="deny")return;i.fields?.forEach((r)=>{let a=e.deniedFieldCounts??={};a[r]=(a[r]??0)+1}),i.actions?.forEach((r)=>{let a=e.deniedActionCounts??={};a[r]=(a[r]??0)+1})})}import{metrics as H,trace as W}from"@opentelemetry/api";var $=25;class B{store;context;tracer=W.getTracer("lssm.personalization","1.0.0");counter=H.getMeter("lssm.personalization","1.0.0").createCounter("lssm.personalization.events",{description:"Behavior events tracked for personalization"});buffer=[];bufferSize;flushTimer;constructor(e){if(this.store=e.store,this.context=e.context,this.bufferSize=e.bufferSize??$,e.autoFlushIntervalMs)this.flushTimer=setInterval(()=>{this.flush()},e.autoFlushIntervalMs)}trackFieldAccess(e){let t={type:"field_access",operation:e.operation,field:e.field,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackFeatureUsage(e){let t={type:"feature_usage",feature:e.feature,action:e.action,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackWorkflowStep(e){let t={type:"workflow_step",workflow:e.workflow,step:e.step,status:e.status,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackDataViewInteraction(e){let t={type:"data_view_interaction",dataViewKey:e.dataViewKey,dataViewVersion:e.dataViewVersion,action:e.action,viewMode:e.viewMode,density:e.density,dataDepth:e.dataDepth,filterKey:e.filterKey,sortField:e.sortField,page:e.page,pageSize:e.pageSize,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}async flush(){if(!this.buffer.length)return;let e=this.buffer;this.buffer=[],await this.store.bulkRecord(e)}async dispose(){if(this.flushTimer)clearInterval(this.flushTimer);await this.flush()}enqueue(e){if(this.buffer.push(e),this.counter.add(1,{tenantId:this.context.tenantId,type:e.type}),this.tracer.startActiveSpan(`personalization.${e.type}`,(t)=>{if(t.setAttribute("tenant.id",this.context.tenantId),this.context.userId)t.setAttribute("user.id",this.context.userId);t.setAttribute("personalization.event_type",e.type),t.end()}),this.buffer.length>=this.bufferSize)this.flush()}}var fe=(e)=>new B(e);import{defineFeature as Z}from"@contractspec/lib.contracts-spec/features";var me=Z({meta:{key:"libs.personalization",version:"1.0.0",title:"Personalization",description:"Behavior tracking, analysis, and adaptation helpers for ContractSpec personalization.",domain:"personalization",owners:["@contractspec-core"],tags:["package","libs","personalization"],stability:"experimental"}});export{Y as resolveDataViewPreferences,S as preferenceDimensionsToDataViewDensity,J as insightsToWorkflowAdaptations,G as insightsToOverlaySuggestion,L as dataViewModeToPreferencePatch,N as dataViewDensityToPreferencePatch,fe as createBehaviorTracker,me as PersonalizationFeature,j as InMemoryBehaviorStore,B as BehaviorTracker,I as BehaviorAnalyzer};
@@ -1 +1 @@
1
- function o(t,e){let r=[];if(t.suggestedHiddenFields.forEach((i)=>{r.push({type:"hideField",field:i,reason:"Automatically hidden because usage is near zero"})}),t.frequentlyUsedFields.length)r.push({type:"reorderFields",fields:t.frequentlyUsedFields});if(!r.length)return null;return{overlayId:e.overlayId,version:e.version??"1.0.0",appliesTo:{tenantId:e.tenantId,capability:e.capability,userId:e.userId,role:e.role},modifications:r,metadata:{generatedAt:new Date().toISOString(),automated:!0}}}function n(t){return t.workflowBottlenecks.map((e)=>({workflow:e.workflow,step:e.step,note:`High drop rate (${Math.round(e.dropRate*100)}%) detected`}))}export{n as insightsToWorkflowAdaptations,o as insightsToOverlaySuggestion};
1
+ function a(t,e){let r=[];t.suggestedHiddenFields.forEach((i)=>{r.push({type:"hideField",field:i,reason:"Automatically hidden because usage is near zero"})});let o=new Set([...t.deniedFields??[],...e.deniedFields??[]]),n=t.frequentlyUsedFields.filter((i)=>!o.has(i));if(n.length)r.push({type:"reorderFields",fields:n});if(!r.length)return null;return{overlayId:e.overlayId,version:e.version??"1.0.0",appliesTo:{tenantId:e.tenantId,capability:e.capability,userId:e.userId,role:e.role},modifications:r,metadata:{generatedAt:new Date().toISOString(),automated:!0}}}function s(t){return t.workflowBottlenecks.map((e)=>({workflow:e.workflow,step:e.step,note:`High drop rate (${Math.round(e.dropRate*100)}%) detected`}))}export{s as insightsToWorkflowAdaptations,a as insightsToOverlaySuggestion};
@@ -1 +1 @@
1
- class h{store;options;constructor(e,t={}){this.store=e;this.options=t}async analyze(e){let t={tenantId:e.tenantId,userId:e.userId,role:e.role};if(e.windowMs)t.since=Date.now()-e.windowMs;let i=await this.store.summarize(t);return y(i,this.options)}}function y(e,t){let i=t.fieldInactivityThreshold??3,d=t.minSamples??10,a=[],u=[];for(let[o,r]of Object.entries(e.fieldCounts)){if(r<=i)a.push(o);if(r>=i*4)u.push(o)}let c=Object.entries(e.workflowStepCounts).flatMap(([o,r])=>{let s=Object.values(r).reduce((n,l)=>n+l,0);if(!s||s<d)return[];return Object.entries(r).filter(([,n])=>n/s<0.4).map(([n,l])=>({workflow:o,step:n,dropRate:1-l/s}))}),f=m(e);return{unusedFields:a,suggestedHiddenFields:a.slice(0,5),frequentlyUsedFields:u.slice(0,10),workflowBottlenecks:c,layoutPreference:f}}function m(e){let t=Object.keys(e.fieldCounts).length;if(!t)return;if(t>=15)return"table";if(t>=8)return"grid";return"form"}export{h as BehaviorAnalyzer};
1
+ class p{store;options;constructor(e,t={}){this.store=e;this.options=t}async analyze(e){let t={tenantId:e.tenantId,userId:e.userId,role:e.role};if(e.windowMs)t.since=Date.now()-e.windowMs;let n=await this.store.summarize(t);return v(n,this.options)}}function v(e,t){let n=t.fieldInactivityThreshold??3,d=t.minSamples??10,r=[],a=[];for(let[i,o]of Object.entries(e.fieldCounts)){if(o<=n)r.push(i);if(o>=n*4)a.push(i)}let l=Object.entries(e.workflowStepCounts).flatMap(([i,o])=>{let c=Object.values(o).reduce((s,u)=>s+u,0);if(!c||c<d)return[];return Object.entries(o).filter(([,s])=>s/c<0.4).map(([s,u])=>({workflow:i,step:s,dropRate:1-u/c}))}),f=Object.keys(e.deniedFieldCounts??{}),h=Object.keys(e.deniedActionCounts??{}),y=g(e);return{unusedFields:r,suggestedHiddenFields:r.slice(0,5),frequentlyUsedFields:a.filter((i)=>!f.includes(i)).slice(0,10),deniedFields:f,deniedActions:h,workflowBottlenecks:l,layoutPreference:y,dataViewPreferences:w(e)}}function w(e){let t=Object.entries(e.dataViewViewModeCounts??{}).flatMap(([n,d])=>{let r=Object.entries(d).sort(([,a],[,l])=>(l??0)-(a??0))[0]?.[0];if(!m(r))return[];return[[n,{preferredViewMode:r}]]});return t.length?Object.fromEntries(t):void 0}function m(e){return e==="list"||e==="grid"||e==="table"}function g(e){let t=Object.keys(e.fieldCounts).length;if(!t)return;if(t>=15)return"table";if(t>=8)return"grid";return"form"}export{p as BehaviorAnalyzer};
@@ -0,0 +1 @@
1
+ var G=["list","grid","table"];function N({spec:b,preferences:j,insights:k,record:x}){let B=V(b),J=Z(b,B),P=Y(b,k,B),Q=F(x?.viewMode,B),R=$(b),U=q(b);return{viewMode:Q??P??J??"list",density:x?.density??X(j)??R??"comfortable",dataDepth:x?.dataDepth??j?.dataDepth??U??"standard",pageSize:x?.pageSize??z(b)?.pagination?.pageSize,source:{viewMode:Q?"record":P?"insights":J?"contract":"fallback",density:x?.density?"record":j?"preferences":R?"contract":"fallback",dataDepth:x?.dataDepth?"record":j?.dataDepth?"preferences":U?"contract":"fallback",pageSize:x?.pageSize?"record":z(b)?.pagination?.pageSize?"contract":void 0}}}function X(b){if(!b)return;if(b.density==="minimal"||b.density==="compact"||b.density==="dense")return"compact";return"comfortable"}function S(b){return{density:b==="compact"?"compact":"standard"}}function A(b,j){return{dataViewKey:b.meta.key,dataViewVersion:b.meta.version,viewMode:j}}function Y(b,j,k){let x=j?.dataViewPreferences?.[b.meta.key]?.preferredViewMode,B=j?.layoutPreference;return F(x,k)??F(B,k)}function Z(b,j){if(!H(b.view.kind))return;let k=z(b)?.viewModes?.defaultMode;return F(k,j)??F(b.view.kind,j)}function $(b){if(b.view.kind==="table"&&b.view.density)return b.view.density;return z(b)?.density}function q(b){return z(b)?.dataDepth}function V(b){if(!H(b.view.kind))return[];let j=z(b),k=j?.viewModes?.allowedModes?.filter(W);if(k?.length)return K(k);if(j?.toolbar?.viewMode===!0)return G;return j?.viewModes?G:[b.view.kind]}function z(b){if(b.view.kind==="detail")return;return b.view.collection}function F(b,j){return W(b)&&j.includes(b)?b:void 0}function K(b){return G.filter((j)=>b.includes(j))}function H(b){return b==="list"||b==="grid"||b==="table"}function W(b){return H(b)}export{N as resolveDataViewPreferences,X as preferenceDimensionsToDataViewDensity,A as dataViewModeToPreferencePatch,S as dataViewDensityToPreferencePatch};
@@ -1,4 +1,4 @@
1
- function O(e,t){let r=[];if(e.suggestedHiddenFields.forEach((o)=>{r.push({type:"hideField",field:o,reason:"Automatically hidden because usage is near zero"})}),e.frequentlyUsedFields.length)r.push({type:"reorderFields",fields:e.frequentlyUsedFields});if(!r.length)return null;return{overlayId:t.overlayId,version:t.version??"1.0.0",appliesTo:{tenantId:t.tenantId,capability:t.capability,userId:t.userId,role:t.role},modifications:r,metadata:{generatedAt:new Date().toISOString(),automated:!0}}}function A(e){return e.workflowBottlenecks.map((t)=>({workflow:t.workflow,step:t.step,note:`High drop rate (${Math.round(t.dropRate*100)}%) detected`}))}class h{store;options;constructor(e,t={}){this.store=e;this.options=t}async analyze(e){let t={tenantId:e.tenantId,userId:e.userId,role:e.role};if(e.windowMs)t.since=Date.now()-e.windowMs;let r=await this.store.summarize(t);return v(r,this.options)}}function v(e,t){let r=t.fieldInactivityThreshold??3,o=t.minSamples??10,i=[],l=[];for(let[n,s]of Object.entries(e.fieldCounts)){if(s<=r)i.push(n);if(s>=r*4)l.push(n)}let c=Object.entries(e.workflowStepCounts).flatMap(([n,s])=>{let u=Object.values(s).reduce((a,d)=>a+d,0);if(!u||u<o)return[];return Object.entries(s).filter(([,a])=>a/u<0.4).map(([a,d])=>({workflow:n,step:a,dropRate:1-d/u}))}),p=m(e);return{unusedFields:i,suggestedHiddenFields:i.slice(0,5),frequentlyUsedFields:l.slice(0,10),workflowBottlenecks:c,layoutPreference:p}}function m(e){let t=Object.keys(e.fieldCounts).length;if(!t)return;if(t>=15)return"table";if(t>=8)return"grid";return"form"}import{registerDocBlocks as g}from"@contractspec/lib.contracts-spec/docs";var y=[{id:"docs.personalization.behavior-tracking",title:"Behavior Tracking",summary:"`@contractspec/lib.personalization` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.",kind:"reference",visibility:"public",route:"/docs/personalization/behavior-tracking",tags:["personalization","behavior-tracking"],body:`# Behavior Tracking
1
+ function G(e,t){let i=[];e.suggestedHiddenFields.forEach((n)=>{i.push({type:"hideField",field:n,reason:"Automatically hidden because usage is near zero"})});let r=new Set([...e.deniedFields??[],...t.deniedFields??[]]),a=e.frequentlyUsedFields.filter((n)=>!r.has(n));if(a.length)i.push({type:"reorderFields",fields:a});if(!i.length)return null;return{overlayId:t.overlayId,version:t.version??"1.0.0",appliesTo:{tenantId:t.tenantId,capability:t.capability,userId:t.userId,role:t.role},modifications:i,metadata:{generatedAt:new Date().toISOString(),automated:!0}}}function J(e){return e.workflowBottlenecks.map((t)=>({workflow:t.workflow,step:t.step,note:`High drop rate (${Math.round(t.dropRate*100)}%) detected`}))}class I{store;options;constructor(e,t={}){this.store=e;this.options=t}async analyze(e){let t={tenantId:e.tenantId,userId:e.userId,role:e.role};if(e.windowMs)t.since=Date.now()-e.windowMs;let i=await this.store.summarize(t);return k(i,this.options)}}function k(e,t){let i=t.fieldInactivityThreshold??3,r=t.minSamples??10,a=[],n=[];for(let[d,c]of Object.entries(e.fieldCounts)){if(c<=i)a.push(d);if(c>=i*4)n.push(d)}let o=Object.entries(e.workflowStepCounts).flatMap(([d,c])=>{let w=Object.values(c).reduce((f,p)=>f+p,0);if(!w||w<r)return[];return Object.entries(c).filter(([,f])=>f/w<0.4).map(([f,p])=>({workflow:d,step:f,dropRate:1-p/w}))}),s=Object.keys(e.deniedFieldCounts??{}),h=Object.keys(e.deniedActionCounts??{}),m=x(e);return{unusedFields:a,suggestedHiddenFields:a.slice(0,5),frequentlyUsedFields:n.filter((d)=>!s.includes(d)).slice(0,10),deniedFields:s,deniedActions:h,workflowBottlenecks:o,layoutPreference:m,dataViewPreferences:V(e)}}function V(e){let t=Object.entries(e.dataViewViewModeCounts??{}).flatMap(([i,r])=>{let a=Object.entries(r).sort(([,n],[,o])=>(o??0)-(n??0))[0]?.[0];if(!b(a))return[];return[[i,{preferredViewMode:a}]]});return t.length?Object.fromEntries(t):void 0}function b(e){return e==="list"||e==="grid"||e==="table"}function x(e){let t=Object.keys(e.fieldCounts).length;if(!t)return;if(t>=15)return"table";if(t>=8)return"grid";return"form"}var g=["list","grid","table"];function Y({spec:e,preferences:t,insights:i,record:r}){let a=P(e),n=F(e,a),o=D(e,i,a),s=l(r?.viewMode,a),h=E(e),m=A(e);return{viewMode:s??o??n??"list",density:r?.density??S(t)??h??"comfortable",dataDepth:r?.dataDepth??t?.dataDepth??m??"standard",pageSize:r?.pageSize??u(e)?.pagination?.pageSize,source:{viewMode:s?"record":o?"insights":n?"contract":"fallback",density:r?.density?"record":t?"preferences":h?"contract":"fallback",dataDepth:r?.dataDepth?"record":t?.dataDepth?"preferences":m?"contract":"fallback",pageSize:r?.pageSize?"record":u(e)?.pagination?.pageSize?"contract":void 0}}}function S(e){if(!e)return;if(e.density==="minimal"||e.density==="compact"||e.density==="dense")return"compact";return"comfortable"}function N(e){return{density:e==="compact"?"compact":"standard"}}function L(e,t){return{dataViewKey:e.meta.key,dataViewVersion:e.meta.version,viewMode:t}}function D(e,t,i){let r=t?.dataViewPreferences?.[e.meta.key]?.preferredViewMode,a=t?.layoutPreference;return l(r,i)??l(a,i)}function F(e,t){if(!v(e.view.kind))return;let i=u(e)?.viewModes?.defaultMode;return l(i,t)??l(e.view.kind,t)}function E(e){if(e.view.kind==="table"&&e.view.density)return e.view.density;return u(e)?.density}function A(e){return u(e)?.dataDepth}function P(e){if(!v(e.view.kind))return[];let t=u(e),i=t?.viewModes?.allowedModes?.filter(y);if(i?.length)return C(i);if(t?.toolbar?.viewMode===!0)return g;return t?.viewModes?g:[e.view.kind]}function u(e){if(e.view.kind==="detail")return;return e.view.collection}function l(e,t){return y(e)&&t.includes(e)?e:void 0}function C(e){return g.filter((t)=>e.includes(t))}function v(e){return e==="list"||e==="grid"||e==="table"}function y(e){return v(e)}import{registerDocBlocks as R}from"@contractspec/lib.contracts-spec/docs";var T=[{id:"docs.personalization.behavior-tracking",title:"Behavior Tracking",summary:"`@contractspec/lib.personalization` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.",kind:"reference",visibility:"public",route:"/docs/personalization/behavior-tracking",tags:["personalization","behavior-tracking"],body:`# Behavior Tracking
2
2
 
3
3
  \`@contractspec/lib.personalization\` provides primitives to observe how tenants/users interact with specs and turn that telemetry into personalization insights.
4
4
 
@@ -77,7 +77,7 @@ When the adapter returns an overlay spec, pass it to the overlay engine to regis
77
77
 
78
78
 
79
79
 
80
- `}];g(y);import{registerDocBlocks as w}from"@contractspec/lib.contracts-spec/docs";var B=[{id:"docs.personalization.overlay-engine",title:"Overlay Engine",summary:"`@contractspec/lib.overlay-engine` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.",kind:"reference",visibility:"public",route:"/docs/personalization/overlay-engine",tags:["personalization","overlay-engine"],body:`# Overlay Engine
80
+ `}];R(T);import{registerDocBlocks as z}from"@contractspec/lib.contracts-spec/docs";var _=[{id:"docs.personalization.overlay-engine",title:"Overlay Engine",summary:"`@contractspec/lib.overlay-engine` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.",kind:"reference",visibility:"public",route:"/docs/personalization/overlay-engine",tags:["personalization","overlay-engine"],body:`# Overlay Engine
81
81
 
82
82
  \`@contractspec/lib.overlay-engine\` is the canonical runtime for OverlaySpecs. It validates specs, tracks scope precedence, and exposes hooks for React renderers.
83
83
 
@@ -157,7 +157,7 @@ const result = engine.apply({
157
157
 
158
158
 
159
159
 
160
- `}];w(B);import{registerDocBlocks as k}from"@contractspec/lib.contracts-spec/docs";var I=[{id:"docs.personalization.workflow-composition",title:"Workflow Composition",summary:"`@contractspec/lib.workflow-composer` composes base WorkflowSpecs with tenant/role/device-specific extensions, strict validation, deterministic merge ordering, metadata/annotation overlays, and orphan-graph protection for hidden-step rewrites.",kind:"reference",visibility:"public",route:"/docs/personalization/workflow-composition",tags:["personalization","workflow-composition"],body:`# Workflow Composition
160
+ `}];z(_);import{registerDocBlocks as O}from"@contractspec/lib.contracts-spec/docs";var U=[{id:"docs.personalization.workflow-composition",title:"Workflow Composition",summary:"`@contractspec/lib.workflow-composer` composes base WorkflowSpecs with tenant/role/device-specific extensions, strict validation, deterministic merge ordering, metadata/annotation overlays, and orphan-graph protection for hidden-step rewrites.",kind:"reference",visibility:"public",route:"/docs/personalization/workflow-composition",tags:["personalization","workflow-composition"],body:`# Workflow Composition
161
161
 
162
162
  \`@contractspec/lib.workflow-composer\` composes base WorkflowSpecs with tenant/role/device-specific extensions.
163
163
 
@@ -209,4 +209,4 @@ workflowRunner.execute(runtimeSpec, ctx);
209
209
  - \`metadata\` and \`annotations\` overlays are merged into the composed runtime workflow for downstream observability and rollout tracing.
210
210
 
211
211
  This keeps tenant overlays additive, auditable, and replay-safe.
212
- `}];k(I);class x{events=[];async record(e){this.events.push(e)}async bulkRecord(e){this.events.push(...e)}async query(e){return b(this.events,e)}async summarize(e){let t=await this.query(e),r={fieldCounts:{},featureCounts:{},workflowStepCounts:{},totalEvents:t.length};return t.forEach((o)=>{switch(o.type){case"field_access":r.fieldCounts[o.field]=(r.fieldCounts[o.field]??0)+1;break;case"feature_usage":r.featureCounts[o.feature]=(r.featureCounts[o.feature]??0)+1;break;case"workflow_step":{let i=r.workflowStepCounts[o.workflow]??={};i[o.step]=(i[o.step]??0)+1;break}default:break}}),r}async clear(){this.events=[]}}function b(e,t){return e.filter((r)=>{if(t.tenantId&&r.tenantId!==t.tenantId)return!1;if(t.userId&&r.userId!==t.userId)return!1;if(t.role&&r.role!==t.role)return!1;if(t.since&&r.timestamp<t.since)return!1;if(t.until&&r.timestamp>t.until)return!1;if(t.operation&&r.type==="field_access"&&r.operation!==t.operation)return!1;if(t.feature&&r.type==="feature_usage"&&r.feature!==t.feature)return!1;if(t.workflow&&r.type==="workflow_step"&&r.workflow!==t.workflow)return!1;return!0})}import{metrics as S,trace as E}from"@opentelemetry/api";var F=25;class f{store;context;tracer=E.getTracer("lssm.personalization","1.0.0");counter=S.getMeter("lssm.personalization","1.0.0").createCounter("lssm.personalization.events",{description:"Behavior events tracked for personalization"});buffer=[];bufferSize;flushTimer;constructor(e){if(this.store=e.store,this.context=e.context,this.bufferSize=e.bufferSize??F,e.autoFlushIntervalMs)this.flushTimer=setInterval(()=>{this.flush()},e.autoFlushIntervalMs)}trackFieldAccess(e){let t={type:"field_access",operation:e.operation,field:e.field,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackFeatureUsage(e){let t={type:"feature_usage",feature:e.feature,action:e.action,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackWorkflowStep(e){let t={type:"workflow_step",workflow:e.workflow,step:e.step,status:e.status,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}async flush(){if(!this.buffer.length)return;let e=this.buffer;this.buffer=[],await this.store.bulkRecord(e)}async dispose(){if(this.flushTimer)clearInterval(this.flushTimer);await this.flush()}enqueue(e){if(this.buffer.push(e),this.counter.add(1,{tenantId:this.context.tenantId,type:e.type}),this.tracer.startActiveSpan(`personalization.${e.type}`,(t)=>{if(t.setAttribute("tenant.id",this.context.tenantId),this.context.userId)t.setAttribute("user.id",this.context.userId);t.setAttribute("personalization.event_type",e.type),t.end()}),this.buffer.length>=this.bufferSize)this.flush()}}var $=(e)=>new f(e);import{defineFeature as T}from"@contractspec/lib.contracts-spec/features";var q=T({meta:{key:"libs.personalization",version:"1.0.0",title:"Personalization",description:"Behavior tracking, analysis, and adaptation helpers for ContractSpec personalization.",domain:"personalization",owners:["@contractspec-core"],tags:["package","libs","personalization"],stability:"experimental"}});export{A as insightsToWorkflowAdaptations,O as insightsToOverlaySuggestion,$ as createBehaviorTracker,q as PersonalizationFeature,x as InMemoryBehaviorStore,f as BehaviorTracker,h as BehaviorAnalyzer};
212
+ `}];O(U);class j{events=[];async record(e){this.events.push(e)}async bulkRecord(e){this.events.push(...e)}async query(e){return K(this.events,e)}async summarize(e){let t=await this.query(e),i={fieldCounts:{},featureCounts:{},workflowStepCounts:{},dataViewInteractionCounts:{},dataViewViewModeCounts:{},totalEvents:t.length,deniedFieldCounts:{},deniedActionCounts:{}};return t.forEach((r)=>{switch(Q(i,r),r.type){case"field_access":i.fieldCounts[r.field]=(i.fieldCounts[r.field]??0)+1;break;case"feature_usage":i.featureCounts[r.feature]=(i.featureCounts[r.feature]??0)+1;break;case"workflow_step":{let a=i.workflowStepCounts[r.workflow]??={};a[r.step]=(a[r.step]??0)+1;break}case"data_view_interaction":{let a=i.dataViewInteractionCounts??={},n=a[r.dataViewKey]??={};if(n[r.action]=(n[r.action]??0)+1,r.viewMode){let o=i.dataViewViewModeCounts??={},s=o[r.dataViewKey]??={};s[r.viewMode]=(s[r.viewMode]??0)+1}break}default:break}}),i}async clear(){this.events=[]}}function K(e,t){return e.filter((i)=>{if(t.tenantId&&i.tenantId!==t.tenantId)return!1;if(t.userId&&i.userId!==t.userId)return!1;if(t.role&&i.role!==t.role&&!i.roles?.includes(t.role))return!1;if(t.roles?.length&&!t.roles.some((r)=>i.roles?.includes(r)||i.role===r))return!1;if(t.permission&&!i.permissions?.includes(t.permission))return!1;if(t.permissions?.length&&!t.permissions.every((r)=>i.permissions?.includes(r)))return!1;if(t.since&&i.timestamp<t.since)return!1;if(t.until&&i.timestamp>t.until)return!1;if(t.operation&&i.type==="field_access"&&i.operation!==t.operation)return!1;if(t.feature&&i.type==="feature_usage"&&i.feature!==t.feature)return!1;if(t.workflow&&i.type==="workflow_step"&&i.workflow!==t.workflow)return!1;if(t.dataViewKey&&i.type==="data_view_interaction"&&i.dataViewKey!==t.dataViewKey)return!1;if(t.dataViewAction&&i.type==="data_view_interaction"&&i.action!==t.dataViewAction)return!1;return!0})}function Q(e,t){Object.values(t.policyDecisions??{}).forEach((i)=>{if(i.effect!=="deny")return;i.fields?.forEach((r)=>{let a=e.deniedFieldCounts??={};a[r]=(a[r]??0)+1}),i.actions?.forEach((r)=>{let a=e.deniedActionCounts??={};a[r]=(a[r]??0)+1})})}import{metrics as H,trace as W}from"@opentelemetry/api";var $=25;class B{store;context;tracer=W.getTracer("lssm.personalization","1.0.0");counter=H.getMeter("lssm.personalization","1.0.0").createCounter("lssm.personalization.events",{description:"Behavior events tracked for personalization"});buffer=[];bufferSize;flushTimer;constructor(e){if(this.store=e.store,this.context=e.context,this.bufferSize=e.bufferSize??$,e.autoFlushIntervalMs)this.flushTimer=setInterval(()=>{this.flush()},e.autoFlushIntervalMs)}trackFieldAccess(e){let t={type:"field_access",operation:e.operation,field:e.field,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackFeatureUsage(e){let t={type:"feature_usage",feature:e.feature,action:e.action,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackWorkflowStep(e){let t={type:"workflow_step",workflow:e.workflow,step:e.step,status:e.status,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackDataViewInteraction(e){let t={type:"data_view_interaction",dataViewKey:e.dataViewKey,dataViewVersion:e.dataViewVersion,action:e.action,viewMode:e.viewMode,density:e.density,dataDepth:e.dataDepth,filterKey:e.filterKey,sortField:e.sortField,page:e.page,pageSize:e.pageSize,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}async flush(){if(!this.buffer.length)return;let e=this.buffer;this.buffer=[],await this.store.bulkRecord(e)}async dispose(){if(this.flushTimer)clearInterval(this.flushTimer);await this.flush()}enqueue(e){if(this.buffer.push(e),this.counter.add(1,{tenantId:this.context.tenantId,type:e.type}),this.tracer.startActiveSpan(`personalization.${e.type}`,(t)=>{if(t.setAttribute("tenant.id",this.context.tenantId),this.context.userId)t.setAttribute("user.id",this.context.userId);t.setAttribute("personalization.event_type",e.type),t.end()}),this.buffer.length>=this.bufferSize)this.flush()}}var fe=(e)=>new B(e);import{defineFeature as Z}from"@contractspec/lib.contracts-spec/features";var me=Z({meta:{key:"libs.personalization",version:"1.0.0",title:"Personalization",description:"Behavior tracking, analysis, and adaptation helpers for ContractSpec personalization.",domain:"personalization",owners:["@contractspec-core"],tags:["package","libs","personalization"],stability:"experimental"}});export{Y as resolveDataViewPreferences,S as preferenceDimensionsToDataViewDensity,J as insightsToWorkflowAdaptations,G as insightsToOverlaySuggestion,L as dataViewModeToPreferencePatch,N as dataViewDensityToPreferencePatch,fe as createBehaviorTracker,me as PersonalizationFeature,j as InMemoryBehaviorStore,B as BehaviorTracker,I as BehaviorAnalyzer};
@@ -1 +1 @@
1
- class i{events=[];async record(o){this.events.push(o)}async bulkRecord(o){this.events.push(...o)}async query(o){return s(this.events,o)}async summarize(o){let e=await this.query(o),r={fieldCounts:{},featureCounts:{},workflowStepCounts:{},totalEvents:e.length};return e.forEach((t)=>{switch(t.type){case"field_access":r.fieldCounts[t.field]=(r.fieldCounts[t.field]??0)+1;break;case"feature_usage":r.featureCounts[t.feature]=(r.featureCounts[t.feature]??0)+1;break;case"workflow_step":{let a=r.workflowStepCounts[t.workflow]??={};a[t.step]=(a[t.step]??0)+1;break}default:break}}),r}async clear(){this.events=[]}}function s(o,e){return o.filter((r)=>{if(e.tenantId&&r.tenantId!==e.tenantId)return!1;if(e.userId&&r.userId!==e.userId)return!1;if(e.role&&r.role!==e.role)return!1;if(e.since&&r.timestamp<e.since)return!1;if(e.until&&r.timestamp>e.until)return!1;if(e.operation&&r.type==="field_access"&&r.operation!==e.operation)return!1;if(e.feature&&r.type==="feature_usage"&&r.feature!==e.feature)return!1;if(e.workflow&&r.type==="workflow_step"&&r.workflow!==e.workflow)return!1;return!0})}export{i as InMemoryBehaviorStore};
1
+ class f{events=[];async record(r){this.events.push(r)}async bulkRecord(r){this.events.push(...r)}async query(r){return c(this.events,r)}async summarize(r){let o=await this.query(r),e={fieldCounts:{},featureCounts:{},workflowStepCounts:{},dataViewInteractionCounts:{},dataViewViewModeCounts:{},totalEvents:o.length,deniedFieldCounts:{},deniedActionCounts:{}};return o.forEach((i)=>{switch(d(e,i),i.type){case"field_access":e.fieldCounts[i.field]=(e.fieldCounts[i.field]??0)+1;break;case"feature_usage":e.featureCounts[i.feature]=(e.featureCounts[i.feature]??0)+1;break;case"workflow_step":{let t=e.workflowStepCounts[i.workflow]??={};t[i.step]=(t[i.step]??0)+1;break}case"data_view_interaction":{let t=e.dataViewInteractionCounts??={},a=t[i.dataViewKey]??={};if(a[i.action]=(a[i.action]??0)+1,i.viewMode){let n=e.dataViewViewModeCounts??={},s=n[i.dataViewKey]??={};s[i.viewMode]=(s[i.viewMode]??0)+1}break}default:break}}),e}async clear(){this.events=[]}}function c(r,o){return r.filter((e)=>{if(o.tenantId&&e.tenantId!==o.tenantId)return!1;if(o.userId&&e.userId!==o.userId)return!1;if(o.role&&e.role!==o.role&&!e.roles?.includes(o.role))return!1;if(o.roles?.length&&!o.roles.some((i)=>e.roles?.includes(i)||e.role===i))return!1;if(o.permission&&!e.permissions?.includes(o.permission))return!1;if(o.permissions?.length&&!o.permissions.every((i)=>e.permissions?.includes(i)))return!1;if(o.since&&e.timestamp<o.since)return!1;if(o.until&&e.timestamp>o.until)return!1;if(o.operation&&e.type==="field_access"&&e.operation!==o.operation)return!1;if(o.feature&&e.type==="feature_usage"&&e.feature!==o.feature)return!1;if(o.workflow&&e.type==="workflow_step"&&e.workflow!==o.workflow)return!1;if(o.dataViewKey&&e.type==="data_view_interaction"&&e.dataViewKey!==o.dataViewKey)return!1;if(o.dataViewAction&&e.type==="data_view_interaction"&&e.action!==o.dataViewAction)return!1;return!0})}function d(r,o){Object.values(o.policyDecisions??{}).forEach((e)=>{if(e.effect!=="deny")return;e.fields?.forEach((i)=>{let t=r.deniedFieldCounts??={};t[i]=(t[i]??0)+1}),e.actions?.forEach((i)=>{let t=r.deniedActionCounts??={};t[i]=(t[i]??0)+1})})}export{f as InMemoryBehaviorStore};
@@ -1 +1 @@
1
- import{metrics as a,trace as s}from"@opentelemetry/api";var i=25;class r{store;context;tracer=s.getTracer("lssm.personalization","1.0.0");counter=a.getMeter("lssm.personalization","1.0.0").createCounter("lssm.personalization.events",{description:"Behavior events tracked for personalization"});buffer=[];bufferSize;flushTimer;constructor(e){if(this.store=e.store,this.context=e.context,this.bufferSize=e.bufferSize??i,e.autoFlushIntervalMs)this.flushTimer=setInterval(()=>{this.flush()},e.autoFlushIntervalMs)}trackFieldAccess(e){let t={type:"field_access",operation:e.operation,field:e.field,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackFeatureUsage(e){let t={type:"feature_usage",feature:e.feature,action:e.action,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackWorkflowStep(e){let t={type:"workflow_step",workflow:e.workflow,step:e.step,status:e.status,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}async flush(){if(!this.buffer.length)return;let e=this.buffer;this.buffer=[],await this.store.bulkRecord(e)}async dispose(){if(this.flushTimer)clearInterval(this.flushTimer);await this.flush()}enqueue(e){if(this.buffer.push(e),this.counter.add(1,{tenantId:this.context.tenantId,type:e.type}),this.tracer.startActiveSpan(`personalization.${e.type}`,(t)=>{if(t.setAttribute("tenant.id",this.context.tenantId),this.context.userId)t.setAttribute("user.id",this.context.userId);t.setAttribute("personalization.event_type",e.type),t.end()}),this.buffer.length>=this.bufferSize)this.flush()}}var n=(e)=>new r(e);export{n as createBehaviorTracker,r as BehaviorTracker};
1
+ import{metrics as r,trace as i}from"@opentelemetry/api";var o=25;class a{store;context;tracer=i.getTracer("lssm.personalization","1.0.0");counter=r.getMeter("lssm.personalization","1.0.0").createCounter("lssm.personalization.events",{description:"Behavior events tracked for personalization"});buffer=[];bufferSize;flushTimer;constructor(e){if(this.store=e.store,this.context=e.context,this.bufferSize=e.bufferSize??o,e.autoFlushIntervalMs)this.flushTimer=setInterval(()=>{this.flush()},e.autoFlushIntervalMs)}trackFieldAccess(e){let t={type:"field_access",operation:e.operation,field:e.field,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackFeatureUsage(e){let t={type:"feature_usage",feature:e.feature,action:e.action,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackWorkflowStep(e){let t={type:"workflow_step",workflow:e.workflow,step:e.step,status:e.status,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackDataViewInteraction(e){let t={type:"data_view_interaction",dataViewKey:e.dataViewKey,dataViewVersion:e.dataViewVersion,action:e.action,viewMode:e.viewMode,density:e.density,dataDepth:e.dataDepth,filterKey:e.filterKey,sortField:e.sortField,page:e.page,pageSize:e.pageSize,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}async flush(){if(!this.buffer.length)return;let e=this.buffer;this.buffer=[],await this.store.bulkRecord(e)}async dispose(){if(this.flushTimer)clearInterval(this.flushTimer);await this.flush()}enqueue(e){if(this.buffer.push(e),this.counter.add(1,{tenantId:this.context.tenantId,type:e.type}),this.tracer.startActiveSpan(`personalization.${e.type}`,(t)=>{if(t.setAttribute("tenant.id",this.context.tenantId),this.context.userId)t.setAttribute("user.id",this.context.userId);t.setAttribute("personalization.event_type",e.type),t.end()}),this.buffer.length>=this.bufferSize)this.flush()}}var n=(e)=>new a(e);export{n as createBehaviorTracker,a as BehaviorTracker};
package/dist/store.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- class i{events=[];async record(o){this.events.push(o)}async bulkRecord(o){this.events.push(...o)}async query(o){return s(this.events,o)}async summarize(o){let e=await this.query(o),r={fieldCounts:{},featureCounts:{},workflowStepCounts:{},totalEvents:e.length};return e.forEach((t)=>{switch(t.type){case"field_access":r.fieldCounts[t.field]=(r.fieldCounts[t.field]??0)+1;break;case"feature_usage":r.featureCounts[t.feature]=(r.featureCounts[t.feature]??0)+1;break;case"workflow_step":{let a=r.workflowStepCounts[t.workflow]??={};a[t.step]=(a[t.step]??0)+1;break}default:break}}),r}async clear(){this.events=[]}}function s(o,e){return o.filter((r)=>{if(e.tenantId&&r.tenantId!==e.tenantId)return!1;if(e.userId&&r.userId!==e.userId)return!1;if(e.role&&r.role!==e.role)return!1;if(e.since&&r.timestamp<e.since)return!1;if(e.until&&r.timestamp>e.until)return!1;if(e.operation&&r.type==="field_access"&&r.operation!==e.operation)return!1;if(e.feature&&r.type==="feature_usage"&&r.feature!==e.feature)return!1;if(e.workflow&&r.type==="workflow_step"&&r.workflow!==e.workflow)return!1;return!0})}export{i as InMemoryBehaviorStore};
2
+ class f{events=[];async record(r){this.events.push(r)}async bulkRecord(r){this.events.push(...r)}async query(r){return c(this.events,r)}async summarize(r){let o=await this.query(r),e={fieldCounts:{},featureCounts:{},workflowStepCounts:{},dataViewInteractionCounts:{},dataViewViewModeCounts:{},totalEvents:o.length,deniedFieldCounts:{},deniedActionCounts:{}};return o.forEach((i)=>{switch(d(e,i),i.type){case"field_access":e.fieldCounts[i.field]=(e.fieldCounts[i.field]??0)+1;break;case"feature_usage":e.featureCounts[i.feature]=(e.featureCounts[i.feature]??0)+1;break;case"workflow_step":{let t=e.workflowStepCounts[i.workflow]??={};t[i.step]=(t[i.step]??0)+1;break}case"data_view_interaction":{let t=e.dataViewInteractionCounts??={},a=t[i.dataViewKey]??={};if(a[i.action]=(a[i.action]??0)+1,i.viewMode){let n=e.dataViewViewModeCounts??={},s=n[i.dataViewKey]??={};s[i.viewMode]=(s[i.viewMode]??0)+1}break}default:break}}),e}async clear(){this.events=[]}}function c(r,o){return r.filter((e)=>{if(o.tenantId&&e.tenantId!==o.tenantId)return!1;if(o.userId&&e.userId!==o.userId)return!1;if(o.role&&e.role!==o.role&&!e.roles?.includes(o.role))return!1;if(o.roles?.length&&!o.roles.some((i)=>e.roles?.includes(i)||e.role===i))return!1;if(o.permission&&!e.permissions?.includes(o.permission))return!1;if(o.permissions?.length&&!o.permissions.every((i)=>e.permissions?.includes(i)))return!1;if(o.since&&e.timestamp<o.since)return!1;if(o.until&&e.timestamp>o.until)return!1;if(o.operation&&e.type==="field_access"&&e.operation!==o.operation)return!1;if(o.feature&&e.type==="feature_usage"&&e.feature!==o.feature)return!1;if(o.workflow&&e.type==="workflow_step"&&e.workflow!==o.workflow)return!1;if(o.dataViewKey&&e.type==="data_view_interaction"&&e.dataViewKey!==o.dataViewKey)return!1;if(o.dataViewAction&&e.type==="data_view_interaction"&&e.action!==o.dataViewAction)return!1;return!0})}function d(r,o){Object.values(o.policyDecisions??{}).forEach((e)=>{if(e.effect!=="deny")return;e.fields?.forEach((i)=>{let t=r.deniedFieldCounts??={};t[i]=(t[i]??0)+1}),e.actions?.forEach((i)=>{let t=r.deniedActionCounts??={};t[i]=(t[i]??0)+1})})}export{f as InMemoryBehaviorStore};
package/dist/tracker.d.ts CHANGED
@@ -1,8 +1,13 @@
1
+ import type { DataViewCollectionMode, DataViewDataDepth, DataViewDensity } from '@contractspec/lib.contracts-spec/data-views';
1
2
  import type { BehaviorStore } from './store';
3
+ import type { AuthorizationDecisionSummary, DataViewInteractionAction } from './types';
2
4
  export interface BehaviorTrackerContext {
3
5
  tenantId: string;
4
6
  userId?: string;
5
7
  role?: string;
8
+ roles?: string[];
9
+ permissions?: string[];
10
+ policyDecisions?: Record<string, AuthorizationDecisionSummary>;
6
11
  device?: string;
7
12
  metadata?: Record<string, unknown>;
8
13
  }
@@ -29,6 +34,19 @@ export interface TrackWorkflowStepInput {
29
34
  status: 'entered' | 'completed' | 'skipped' | 'errored';
30
35
  metadata?: Record<string, unknown>;
31
36
  }
37
+ export interface TrackDataViewInteractionInput {
38
+ dataViewKey: string;
39
+ dataViewVersion?: string;
40
+ action: DataViewInteractionAction;
41
+ viewMode?: DataViewCollectionMode;
42
+ density?: DataViewDensity;
43
+ dataDepth?: DataViewDataDepth;
44
+ filterKey?: string;
45
+ sortField?: string;
46
+ page?: number;
47
+ pageSize?: number;
48
+ metadata?: Record<string, unknown>;
49
+ }
32
50
  export declare class BehaviorTracker {
33
51
  private readonly store;
34
52
  private readonly context;
@@ -41,6 +59,7 @@ export declare class BehaviorTracker {
41
59
  trackFieldAccess(input: TrackFieldAccessInput): void;
42
60
  trackFeatureUsage(input: TrackFeatureUsageInput): void;
43
61
  trackWorkflowStep(input: TrackWorkflowStepInput): void;
62
+ trackDataViewInteraction(input: TrackDataViewInteractionInput): void;
44
63
  flush(): Promise<void>;
45
64
  dispose(): Promise<void>;
46
65
  private enqueue;
package/dist/tracker.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{metrics as a,trace as s}from"@opentelemetry/api";var i=25;class r{store;context;tracer=s.getTracer("lssm.personalization","1.0.0");counter=a.getMeter("lssm.personalization","1.0.0").createCounter("lssm.personalization.events",{description:"Behavior events tracked for personalization"});buffer=[];bufferSize;flushTimer;constructor(e){if(this.store=e.store,this.context=e.context,this.bufferSize=e.bufferSize??i,e.autoFlushIntervalMs)this.flushTimer=setInterval(()=>{this.flush()},e.autoFlushIntervalMs)}trackFieldAccess(e){let t={type:"field_access",operation:e.operation,field:e.field,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackFeatureUsage(e){let t={type:"feature_usage",feature:e.feature,action:e.action,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackWorkflowStep(e){let t={type:"workflow_step",workflow:e.workflow,step:e.step,status:e.status,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}async flush(){if(!this.buffer.length)return;let e=this.buffer;this.buffer=[],await this.store.bulkRecord(e)}async dispose(){if(this.flushTimer)clearInterval(this.flushTimer);await this.flush()}enqueue(e){if(this.buffer.push(e),this.counter.add(1,{tenantId:this.context.tenantId,type:e.type}),this.tracer.startActiveSpan(`personalization.${e.type}`,(t)=>{if(t.setAttribute("tenant.id",this.context.tenantId),this.context.userId)t.setAttribute("user.id",this.context.userId);t.setAttribute("personalization.event_type",e.type),t.end()}),this.buffer.length>=this.bufferSize)this.flush()}}var n=(e)=>new r(e);export{n as createBehaviorTracker,r as BehaviorTracker};
2
+ import{metrics as r,trace as i}from"@opentelemetry/api";var o=25;class a{store;context;tracer=i.getTracer("lssm.personalization","1.0.0");counter=r.getMeter("lssm.personalization","1.0.0").createCounter("lssm.personalization.events",{description:"Behavior events tracked for personalization"});buffer=[];bufferSize;flushTimer;constructor(e){if(this.store=e.store,this.context=e.context,this.bufferSize=e.bufferSize??o,e.autoFlushIntervalMs)this.flushTimer=setInterval(()=>{this.flush()},e.autoFlushIntervalMs)}trackFieldAccess(e){let t={type:"field_access",operation:e.operation,field:e.field,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackFeatureUsage(e){let t={type:"feature_usage",feature:e.feature,action:e.action,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackWorkflowStep(e){let t={type:"workflow_step",workflow:e.workflow,step:e.step,status:e.status,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}trackDataViewInteraction(e){let t={type:"data_view_interaction",dataViewKey:e.dataViewKey,dataViewVersion:e.dataViewVersion,action:e.action,viewMode:e.viewMode,density:e.density,dataDepth:e.dataDepth,filterKey:e.filterKey,sortField:e.sortField,page:e.page,pageSize:e.pageSize,timestamp:Date.now(),...this.context,metadata:{...this.context.metadata,...e.metadata}};this.enqueue(t)}async flush(){if(!this.buffer.length)return;let e=this.buffer;this.buffer=[],await this.store.bulkRecord(e)}async dispose(){if(this.flushTimer)clearInterval(this.flushTimer);await this.flush()}enqueue(e){if(this.buffer.push(e),this.counter.add(1,{tenantId:this.context.tenantId,type:e.type}),this.tracer.startActiveSpan(`personalization.${e.type}`,(t)=>{if(t.setAttribute("tenant.id",this.context.tenantId),this.context.userId)t.setAttribute("user.id",this.context.userId);t.setAttribute("personalization.event_type",e.type),t.end()}),this.buffer.length>=this.bufferSize)this.flush()}}var n=(e)=>new a(e);export{n as createBehaviorTracker,a as BehaviorTracker};
package/dist/types.d.ts CHANGED
@@ -1,9 +1,18 @@
1
- export type BehaviorEventType = 'field_access' | 'feature_usage' | 'workflow_step';
1
+ import type { PolicyDecision } from '@contractspec/lib.contracts-spec';
2
+ import type { DataViewCollectionMode, DataViewDataDepth, DataViewDensity } from '@contractspec/lib.contracts-spec/data-views';
3
+ export type BehaviorEventType = 'field_access' | 'feature_usage' | 'workflow_step' | 'data_view_interaction';
4
+ export interface AuthorizationDecisionSummary extends Pick<PolicyDecision, 'effect' | 'reason' | 'missing' | 'matched' | 'source'> {
5
+ fields?: string[];
6
+ actions?: string[];
7
+ }
2
8
  export interface BehaviorEventBase {
3
9
  id?: string;
4
10
  tenantId: string;
5
11
  userId?: string;
6
12
  role?: string;
13
+ roles?: string[];
14
+ permissions?: string[];
15
+ policyDecisions?: Record<string, AuthorizationDecisionSummary>;
7
16
  device?: string;
8
17
  timestamp: number;
9
18
  metadata?: Record<string, unknown>;
@@ -24,14 +33,33 @@ export interface WorkflowStepEvent extends BehaviorEventBase {
24
33
  step: string;
25
34
  status: 'entered' | 'completed' | 'skipped' | 'errored';
26
35
  }
27
- export type BehaviorEvent = FieldAccessEvent | FeatureUsageEvent | WorkflowStepEvent;
36
+ export type DataViewInteractionAction = 'opened' | 'view_mode_changed' | 'density_changed' | 'data_depth_changed' | 'search_changed' | 'filter_changed' | 'sort_changed' | 'page_changed';
37
+ export interface DataViewInteractionEvent extends BehaviorEventBase {
38
+ type: 'data_view_interaction';
39
+ dataViewKey: string;
40
+ dataViewVersion?: string;
41
+ action: DataViewInteractionAction;
42
+ viewMode?: DataViewCollectionMode;
43
+ density?: DataViewDensity;
44
+ dataDepth?: DataViewDataDepth;
45
+ filterKey?: string;
46
+ sortField?: string;
47
+ page?: number;
48
+ pageSize?: number;
49
+ }
50
+ export type BehaviorEvent = FieldAccessEvent | FeatureUsageEvent | WorkflowStepEvent | DataViewInteractionEvent;
28
51
  export interface BehaviorQuery {
29
52
  tenantId?: string;
30
53
  userId?: string;
31
54
  role?: string;
55
+ roles?: string[];
56
+ permission?: string;
57
+ permissions?: string[];
32
58
  feature?: string;
33
59
  operation?: string;
34
60
  workflow?: string;
61
+ dataViewKey?: string;
62
+ dataViewAction?: DataViewInteractionAction;
35
63
  since?: number;
36
64
  until?: number;
37
65
  limit?: number;
@@ -40,16 +68,26 @@ export interface BehaviorSummary {
40
68
  fieldCounts: Record<string, number>;
41
69
  featureCounts: Record<string, number>;
42
70
  workflowStepCounts: Record<string, Record<string, number>>;
71
+ dataViewInteractionCounts?: Record<string, Partial<Record<DataViewInteractionAction, number>>>;
72
+ dataViewViewModeCounts?: Record<string, Partial<Record<DataViewCollectionMode, number>>>;
43
73
  totalEvents: number;
74
+ deniedFieldCounts?: Record<string, number>;
75
+ deniedActionCounts?: Record<string, number>;
76
+ }
77
+ export interface DataViewPreferenceInsights {
78
+ preferredViewMode?: DataViewCollectionMode;
44
79
  }
45
80
  export interface BehaviorInsights {
46
81
  unusedFields: string[];
47
82
  frequentlyUsedFields: string[];
48
83
  suggestedHiddenFields: string[];
84
+ deniedFields?: string[];
85
+ deniedActions?: string[];
49
86
  workflowBottlenecks: {
50
87
  workflow: string;
51
88
  step: string;
52
89
  dropRate: number;
53
90
  }[];
54
91
  layoutPreference?: 'form' | 'grid' | 'list' | 'table';
92
+ dataViewPreferences?: Record<string, DataViewPreferenceInsights>;
55
93
  }