@atlasent/sdk 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +133 -0
- package/dist/behavior.cjs +175 -0
- package/dist/behavior.cjs.map +1 -0
- package/dist/behavior.d.cts +241 -0
- package/dist/behavior.d.ts +241 -0
- package/dist/behavior.js +143 -0
- package/dist/behavior.js.map +1 -0
- package/dist/hono.cjs +580 -0
- package/dist/hono.cjs.map +1 -0
- package/dist/hono.d.cts +109 -0
- package/dist/hono.d.ts +109 -0
- package/dist/hono.js +550 -0
- package/dist/hono.js.map +1 -0
- package/dist/index.cjs +763 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +620 -0
- package/dist/index.d.ts +620 -0
- package/dist/index.js +723 -0
- package/dist/index.js.map +1 -0
- package/dist/protect-BKxcoR_2.d.cts +159 -0
- package/dist/protect-BKxcoR_2.d.ts +159 -0
- package/package.json +101 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlasent/sdk/behavior — consent + redaction helpers for the v2
|
|
3
|
+
* Behavior Conditioning Layer.
|
|
4
|
+
*
|
|
5
|
+
* See `atlasent-docs/docs/V2_BEHAVIOR_CONDITIONING_LAYER.md` for the
|
|
6
|
+
* architecture this module implements.
|
|
7
|
+
*
|
|
8
|
+
* Quick start:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import {
|
|
12
|
+
* ConsentManager,
|
|
13
|
+
* InMemoryBehaviorLedger,
|
|
14
|
+
* redactStateSnapshot,
|
|
15
|
+
* type StateSnapshot,
|
|
16
|
+
* } from "@atlasent/sdk/behavior";
|
|
17
|
+
*
|
|
18
|
+
* const consent = new ConsentManager({ userId: "u_123" });
|
|
19
|
+
* const ledger = new InMemoryBehaviorLedger();
|
|
20
|
+
*
|
|
21
|
+
* const snapshot: StateSnapshot = { ... };
|
|
22
|
+
* const summary = redactStateSnapshot(snapshot);
|
|
23
|
+
*
|
|
24
|
+
* if (consent.canEmit("ledgers-me", "behavior.health.mental")) {
|
|
25
|
+
* await ledger.emit({
|
|
26
|
+
* user_id: snapshot.user_id,
|
|
27
|
+
* source: "hicoach",
|
|
28
|
+
* category: "behavior.health.mental",
|
|
29
|
+
* entry_state_summary: summary,
|
|
30
|
+
* exit_state_summary: null,
|
|
31
|
+
* relief_delta: null,
|
|
32
|
+
* confidence_score: 1,
|
|
33
|
+
* timestamp: new Date().toISOString(),
|
|
34
|
+
* });
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* The MVP is pure and on-device — no HTTP calls. A future
|
|
39
|
+
* `RemoteBehaviorLedger` will POST to `/v1/behavior/events` once
|
|
40
|
+
* the atlasent-api endpoint ships.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Sensitive-category slugs used by behavior policies. Keep in sync
|
|
44
|
+
* with `gxp-starter/packs/hipaa/` rules and
|
|
45
|
+
* `atlasent-api`'s `target.category` field.
|
|
46
|
+
*/
|
|
47
|
+
type SensitiveCategory = "behavior.health.mental" | "behavior.health.adherence" | "behavior.financial" | "behavior.minor";
|
|
48
|
+
declare const SENSITIVE_CATEGORIES: readonly SensitiveCategory[];
|
|
49
|
+
type EmotionalState = "tense" | "anxious" | "overwhelmed" | "flat" | "frustrated" | "uncertain" | "tired" | "okay";
|
|
50
|
+
type BodyState = "tight" | "heavy" | "restless" | "numb" | "buzzing" | "settled";
|
|
51
|
+
type ReadinessLevel = "low" | "medium" | "high";
|
|
52
|
+
/**
|
|
53
|
+
* A single moment captured before/after a regulation session.
|
|
54
|
+
*
|
|
55
|
+
* This is the **on-device** shape with all raw fields. It is never
|
|
56
|
+
* emitted to a ledger; only the {@link StateEventSummary} projection
|
|
57
|
+
* crosses an app boundary.
|
|
58
|
+
*/
|
|
59
|
+
interface StateSnapshot {
|
|
60
|
+
id: string;
|
|
61
|
+
user_id: string;
|
|
62
|
+
emotional_state: EmotionalState;
|
|
63
|
+
/** 0..10 */
|
|
64
|
+
intensity: number;
|
|
65
|
+
/** 0..10 */
|
|
66
|
+
stress_level: number;
|
|
67
|
+
/** 0..10 */
|
|
68
|
+
pressure_level: number;
|
|
69
|
+
body_state: BodyState;
|
|
70
|
+
/** 0..10 */
|
|
71
|
+
cognitive_load: number;
|
|
72
|
+
readiness_level: ReadinessLevel;
|
|
73
|
+
/** 0..1 — how sure the user feels about the rating */
|
|
74
|
+
confidence_score: number;
|
|
75
|
+
/** ISO 8601 */
|
|
76
|
+
created_at: string;
|
|
77
|
+
/** Optional free-form note. NEVER part of the redacted summary. */
|
|
78
|
+
note?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Redacted summary projection — the only shape that crosses an app
|
|
82
|
+
* boundary. Contains no raw text, no IDs, no timestamps.
|
|
83
|
+
*/
|
|
84
|
+
interface StateEventSummary {
|
|
85
|
+
emotional_state: EmotionalState;
|
|
86
|
+
intensity: number;
|
|
87
|
+
stress_level: number;
|
|
88
|
+
pressure_level: number;
|
|
89
|
+
body_state: BodyState;
|
|
90
|
+
cognitive_load: number;
|
|
91
|
+
readiness_level: ReadinessLevel;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A behavior event written to the cross-app ledger (LedgersMe).
|
|
95
|
+
* The atlasent-api `/v1/behavior/events` endpoint accepts this shape.
|
|
96
|
+
*/
|
|
97
|
+
interface BehaviorEvent {
|
|
98
|
+
user_id: string;
|
|
99
|
+
source: "hicoach" | "echobloom" | "ledgers-me" | string;
|
|
100
|
+
category: SensitiveCategory;
|
|
101
|
+
entry_state_summary: StateEventSummary;
|
|
102
|
+
exit_state_summary: StateEventSummary | null;
|
|
103
|
+
relief_delta: number | null;
|
|
104
|
+
/** 0..1 */
|
|
105
|
+
confidence_score: number;
|
|
106
|
+
/** ISO 8601 */
|
|
107
|
+
timestamp: string;
|
|
108
|
+
/** Optional list of safety signals that fired during the event. Never raw text. */
|
|
109
|
+
safety_signals?: string[];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Per-user consent settings. Privacy-first defaults: nothing leaves
|
|
113
|
+
* the device unless the user explicitly opts in.
|
|
114
|
+
*/
|
|
115
|
+
interface ConsentSettings {
|
|
116
|
+
/** Default false. Opt-in to emit summaries to the ledger. */
|
|
117
|
+
share_state_summaries: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Default false. When true, suppresses ALL outbound emissions
|
|
120
|
+
* regardless of any other setting. Acts as a global circuit
|
|
121
|
+
* breaker.
|
|
122
|
+
*/
|
|
123
|
+
private_only_mode: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Optional per-receiver allowlist. When non-empty, an event is
|
|
126
|
+
* only emitted to a receiver whose name appears here AND for a
|
|
127
|
+
* category whose slug appears in the receiver's allowed set.
|
|
128
|
+
*
|
|
129
|
+
* Example:
|
|
130
|
+
* ```ts
|
|
131
|
+
* { "ledgers-me": ["behavior.health.mental"] }
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
receivers?: Record<string, SensitiveCategory[]>;
|
|
135
|
+
}
|
|
136
|
+
declare const DEFAULT_CONSENT: ConsentSettings;
|
|
137
|
+
/**
|
|
138
|
+
* Storage abstraction so the helper works in browser
|
|
139
|
+
* (`window.localStorage`), Node, and tests.
|
|
140
|
+
*/
|
|
141
|
+
interface ConsentStorage {
|
|
142
|
+
get(key: string): string | null;
|
|
143
|
+
set(key: string, value: string): void;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Default in-memory storage (for Node + tests). In a browser, pass
|
|
147
|
+
* `window.localStorage`.
|
|
148
|
+
*/
|
|
149
|
+
declare class MemoryStorage implements ConsentStorage {
|
|
150
|
+
private store;
|
|
151
|
+
get(key: string): string | null;
|
|
152
|
+
set(key: string, value: string): void;
|
|
153
|
+
}
|
|
154
|
+
interface ConsentManagerOpts {
|
|
155
|
+
userId: string;
|
|
156
|
+
/** Defaults to {@link MemoryStorage}. Pass `localStorage` in browsers. */
|
|
157
|
+
storage?: ConsentStorage;
|
|
158
|
+
/** Defaults to {@link DEFAULT_CONSENT}. */
|
|
159
|
+
defaults?: ConsentSettings;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Read/write consent settings; gate emissions through `canEmit`.
|
|
163
|
+
*
|
|
164
|
+
* Apps NEVER hand-roll consent checks. This is the only correct way
|
|
165
|
+
* to decide whether a `BehaviorEvent` may leave the device.
|
|
166
|
+
*/
|
|
167
|
+
declare class ConsentManager {
|
|
168
|
+
private readonly key;
|
|
169
|
+
private readonly storage;
|
|
170
|
+
private readonly defaults;
|
|
171
|
+
constructor(opts: ConsentManagerOpts);
|
|
172
|
+
get(): ConsentSettings;
|
|
173
|
+
set(patch: Partial<ConsentSettings>): ConsentSettings;
|
|
174
|
+
/**
|
|
175
|
+
* The single decision point: may we emit a `BehaviorEvent` for
|
|
176
|
+
* this `category` to this `receiver`? Returns `false` whenever
|
|
177
|
+
* any of the following is true:
|
|
178
|
+
*
|
|
179
|
+
* - `private_only_mode` is on.
|
|
180
|
+
* - `share_state_summaries` is off.
|
|
181
|
+
* - A `receivers` allowlist exists and the `(receiver, category)`
|
|
182
|
+
* pair is not in it.
|
|
183
|
+
*/
|
|
184
|
+
canEmit(receiver: string, category: SensitiveCategory): boolean;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Project a {@link StateSnapshot} down to the redacted
|
|
188
|
+
* {@link StateEventSummary} shape. Drops `id`, `user_id`,
|
|
189
|
+
* `created_at`, `confidence_score`, and any `note` field. The
|
|
190
|
+
* remaining fields are bounded numeric ranges or closed enums and
|
|
191
|
+
* carry no free-form text.
|
|
192
|
+
*/
|
|
193
|
+
declare function redactStateSnapshot(s: StateSnapshot): StateEventSummary;
|
|
194
|
+
interface BehaviorLedger {
|
|
195
|
+
/**
|
|
196
|
+
* Emit a behavior event. Implementations MUST validate `consent`
|
|
197
|
+
* before persisting and throw `ConsentDeniedError` when an event
|
|
198
|
+
* would be persisted in violation of the user's settings.
|
|
199
|
+
*/
|
|
200
|
+
emit(event: BehaviorEvent): Promise<void>;
|
|
201
|
+
}
|
|
202
|
+
declare class ConsentDeniedError extends Error {
|
|
203
|
+
readonly receiver: string;
|
|
204
|
+
readonly category: SensitiveCategory;
|
|
205
|
+
readonly code: "consent_denied";
|
|
206
|
+
constructor(receiver: string, category: SensitiveCategory);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* On-device ledger for development and demos. A future
|
|
210
|
+
* `RemoteBehaviorLedger` will POST to atlasent-api's
|
|
211
|
+
* `/v1/behavior/events` once that endpoint ships.
|
|
212
|
+
*/
|
|
213
|
+
declare class InMemoryBehaviorLedger implements BehaviorLedger {
|
|
214
|
+
private readonly opts;
|
|
215
|
+
private readonly events;
|
|
216
|
+
constructor(opts: {
|
|
217
|
+
consent: ConsentManager;
|
|
218
|
+
receiver?: string;
|
|
219
|
+
});
|
|
220
|
+
emit(event: BehaviorEvent): Promise<void>;
|
|
221
|
+
/** Read all events accepted so far. Test/demo helper. */
|
|
222
|
+
list(): readonly BehaviorEvent[];
|
|
223
|
+
/** Clear the in-memory store. Test helper. */
|
|
224
|
+
clear(): void;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Bounded in-memory ring buffer of recent {@link StateEventSummary}
|
|
228
|
+
* values. The LangChain/LlamaIndex middleware (and similar wrappers)
|
|
229
|
+
* read this to attach `context.session_history` to evaluate calls
|
|
230
|
+
* without ever touching raw snapshots.
|
|
231
|
+
*/
|
|
232
|
+
declare class StateEventCache {
|
|
233
|
+
private readonly capacity;
|
|
234
|
+
private readonly buf;
|
|
235
|
+
constructor(capacity?: number);
|
|
236
|
+
add(summary: StateEventSummary): void;
|
|
237
|
+
recent(n?: number): readonly StateEventSummary[];
|
|
238
|
+
clear(): void;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export { type BehaviorEvent, type BehaviorLedger, type BodyState, ConsentDeniedError, ConsentManager, type ConsentManagerOpts, type ConsentSettings, type ConsentStorage, DEFAULT_CONSENT, type EmotionalState, InMemoryBehaviorLedger, MemoryStorage, type ReadinessLevel, SENSITIVE_CATEGORIES, type SensitiveCategory, StateEventCache, type StateEventSummary, type StateSnapshot, redactStateSnapshot };
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlasent/sdk/behavior — consent + redaction helpers for the v2
|
|
3
|
+
* Behavior Conditioning Layer.
|
|
4
|
+
*
|
|
5
|
+
* See `atlasent-docs/docs/V2_BEHAVIOR_CONDITIONING_LAYER.md` for the
|
|
6
|
+
* architecture this module implements.
|
|
7
|
+
*
|
|
8
|
+
* Quick start:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import {
|
|
12
|
+
* ConsentManager,
|
|
13
|
+
* InMemoryBehaviorLedger,
|
|
14
|
+
* redactStateSnapshot,
|
|
15
|
+
* type StateSnapshot,
|
|
16
|
+
* } from "@atlasent/sdk/behavior";
|
|
17
|
+
*
|
|
18
|
+
* const consent = new ConsentManager({ userId: "u_123" });
|
|
19
|
+
* const ledger = new InMemoryBehaviorLedger();
|
|
20
|
+
*
|
|
21
|
+
* const snapshot: StateSnapshot = { ... };
|
|
22
|
+
* const summary = redactStateSnapshot(snapshot);
|
|
23
|
+
*
|
|
24
|
+
* if (consent.canEmit("ledgers-me", "behavior.health.mental")) {
|
|
25
|
+
* await ledger.emit({
|
|
26
|
+
* user_id: snapshot.user_id,
|
|
27
|
+
* source: "hicoach",
|
|
28
|
+
* category: "behavior.health.mental",
|
|
29
|
+
* entry_state_summary: summary,
|
|
30
|
+
* exit_state_summary: null,
|
|
31
|
+
* relief_delta: null,
|
|
32
|
+
* confidence_score: 1,
|
|
33
|
+
* timestamp: new Date().toISOString(),
|
|
34
|
+
* });
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* The MVP is pure and on-device — no HTTP calls. A future
|
|
39
|
+
* `RemoteBehaviorLedger` will POST to `/v1/behavior/events` once
|
|
40
|
+
* the atlasent-api endpoint ships.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Sensitive-category slugs used by behavior policies. Keep in sync
|
|
44
|
+
* with `gxp-starter/packs/hipaa/` rules and
|
|
45
|
+
* `atlasent-api`'s `target.category` field.
|
|
46
|
+
*/
|
|
47
|
+
type SensitiveCategory = "behavior.health.mental" | "behavior.health.adherence" | "behavior.financial" | "behavior.minor";
|
|
48
|
+
declare const SENSITIVE_CATEGORIES: readonly SensitiveCategory[];
|
|
49
|
+
type EmotionalState = "tense" | "anxious" | "overwhelmed" | "flat" | "frustrated" | "uncertain" | "tired" | "okay";
|
|
50
|
+
type BodyState = "tight" | "heavy" | "restless" | "numb" | "buzzing" | "settled";
|
|
51
|
+
type ReadinessLevel = "low" | "medium" | "high";
|
|
52
|
+
/**
|
|
53
|
+
* A single moment captured before/after a regulation session.
|
|
54
|
+
*
|
|
55
|
+
* This is the **on-device** shape with all raw fields. It is never
|
|
56
|
+
* emitted to a ledger; only the {@link StateEventSummary} projection
|
|
57
|
+
* crosses an app boundary.
|
|
58
|
+
*/
|
|
59
|
+
interface StateSnapshot {
|
|
60
|
+
id: string;
|
|
61
|
+
user_id: string;
|
|
62
|
+
emotional_state: EmotionalState;
|
|
63
|
+
/** 0..10 */
|
|
64
|
+
intensity: number;
|
|
65
|
+
/** 0..10 */
|
|
66
|
+
stress_level: number;
|
|
67
|
+
/** 0..10 */
|
|
68
|
+
pressure_level: number;
|
|
69
|
+
body_state: BodyState;
|
|
70
|
+
/** 0..10 */
|
|
71
|
+
cognitive_load: number;
|
|
72
|
+
readiness_level: ReadinessLevel;
|
|
73
|
+
/** 0..1 — how sure the user feels about the rating */
|
|
74
|
+
confidence_score: number;
|
|
75
|
+
/** ISO 8601 */
|
|
76
|
+
created_at: string;
|
|
77
|
+
/** Optional free-form note. NEVER part of the redacted summary. */
|
|
78
|
+
note?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Redacted summary projection — the only shape that crosses an app
|
|
82
|
+
* boundary. Contains no raw text, no IDs, no timestamps.
|
|
83
|
+
*/
|
|
84
|
+
interface StateEventSummary {
|
|
85
|
+
emotional_state: EmotionalState;
|
|
86
|
+
intensity: number;
|
|
87
|
+
stress_level: number;
|
|
88
|
+
pressure_level: number;
|
|
89
|
+
body_state: BodyState;
|
|
90
|
+
cognitive_load: number;
|
|
91
|
+
readiness_level: ReadinessLevel;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A behavior event written to the cross-app ledger (LedgersMe).
|
|
95
|
+
* The atlasent-api `/v1/behavior/events` endpoint accepts this shape.
|
|
96
|
+
*/
|
|
97
|
+
interface BehaviorEvent {
|
|
98
|
+
user_id: string;
|
|
99
|
+
source: "hicoach" | "echobloom" | "ledgers-me" | string;
|
|
100
|
+
category: SensitiveCategory;
|
|
101
|
+
entry_state_summary: StateEventSummary;
|
|
102
|
+
exit_state_summary: StateEventSummary | null;
|
|
103
|
+
relief_delta: number | null;
|
|
104
|
+
/** 0..1 */
|
|
105
|
+
confidence_score: number;
|
|
106
|
+
/** ISO 8601 */
|
|
107
|
+
timestamp: string;
|
|
108
|
+
/** Optional list of safety signals that fired during the event. Never raw text. */
|
|
109
|
+
safety_signals?: string[];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Per-user consent settings. Privacy-first defaults: nothing leaves
|
|
113
|
+
* the device unless the user explicitly opts in.
|
|
114
|
+
*/
|
|
115
|
+
interface ConsentSettings {
|
|
116
|
+
/** Default false. Opt-in to emit summaries to the ledger. */
|
|
117
|
+
share_state_summaries: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Default false. When true, suppresses ALL outbound emissions
|
|
120
|
+
* regardless of any other setting. Acts as a global circuit
|
|
121
|
+
* breaker.
|
|
122
|
+
*/
|
|
123
|
+
private_only_mode: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Optional per-receiver allowlist. When non-empty, an event is
|
|
126
|
+
* only emitted to a receiver whose name appears here AND for a
|
|
127
|
+
* category whose slug appears in the receiver's allowed set.
|
|
128
|
+
*
|
|
129
|
+
* Example:
|
|
130
|
+
* ```ts
|
|
131
|
+
* { "ledgers-me": ["behavior.health.mental"] }
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
receivers?: Record<string, SensitiveCategory[]>;
|
|
135
|
+
}
|
|
136
|
+
declare const DEFAULT_CONSENT: ConsentSettings;
|
|
137
|
+
/**
|
|
138
|
+
* Storage abstraction so the helper works in browser
|
|
139
|
+
* (`window.localStorage`), Node, and tests.
|
|
140
|
+
*/
|
|
141
|
+
interface ConsentStorage {
|
|
142
|
+
get(key: string): string | null;
|
|
143
|
+
set(key: string, value: string): void;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Default in-memory storage (for Node + tests). In a browser, pass
|
|
147
|
+
* `window.localStorage`.
|
|
148
|
+
*/
|
|
149
|
+
declare class MemoryStorage implements ConsentStorage {
|
|
150
|
+
private store;
|
|
151
|
+
get(key: string): string | null;
|
|
152
|
+
set(key: string, value: string): void;
|
|
153
|
+
}
|
|
154
|
+
interface ConsentManagerOpts {
|
|
155
|
+
userId: string;
|
|
156
|
+
/** Defaults to {@link MemoryStorage}. Pass `localStorage` in browsers. */
|
|
157
|
+
storage?: ConsentStorage;
|
|
158
|
+
/** Defaults to {@link DEFAULT_CONSENT}. */
|
|
159
|
+
defaults?: ConsentSettings;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Read/write consent settings; gate emissions through `canEmit`.
|
|
163
|
+
*
|
|
164
|
+
* Apps NEVER hand-roll consent checks. This is the only correct way
|
|
165
|
+
* to decide whether a `BehaviorEvent` may leave the device.
|
|
166
|
+
*/
|
|
167
|
+
declare class ConsentManager {
|
|
168
|
+
private readonly key;
|
|
169
|
+
private readonly storage;
|
|
170
|
+
private readonly defaults;
|
|
171
|
+
constructor(opts: ConsentManagerOpts);
|
|
172
|
+
get(): ConsentSettings;
|
|
173
|
+
set(patch: Partial<ConsentSettings>): ConsentSettings;
|
|
174
|
+
/**
|
|
175
|
+
* The single decision point: may we emit a `BehaviorEvent` for
|
|
176
|
+
* this `category` to this `receiver`? Returns `false` whenever
|
|
177
|
+
* any of the following is true:
|
|
178
|
+
*
|
|
179
|
+
* - `private_only_mode` is on.
|
|
180
|
+
* - `share_state_summaries` is off.
|
|
181
|
+
* - A `receivers` allowlist exists and the `(receiver, category)`
|
|
182
|
+
* pair is not in it.
|
|
183
|
+
*/
|
|
184
|
+
canEmit(receiver: string, category: SensitiveCategory): boolean;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Project a {@link StateSnapshot} down to the redacted
|
|
188
|
+
* {@link StateEventSummary} shape. Drops `id`, `user_id`,
|
|
189
|
+
* `created_at`, `confidence_score`, and any `note` field. The
|
|
190
|
+
* remaining fields are bounded numeric ranges or closed enums and
|
|
191
|
+
* carry no free-form text.
|
|
192
|
+
*/
|
|
193
|
+
declare function redactStateSnapshot(s: StateSnapshot): StateEventSummary;
|
|
194
|
+
interface BehaviorLedger {
|
|
195
|
+
/**
|
|
196
|
+
* Emit a behavior event. Implementations MUST validate `consent`
|
|
197
|
+
* before persisting and throw `ConsentDeniedError` when an event
|
|
198
|
+
* would be persisted in violation of the user's settings.
|
|
199
|
+
*/
|
|
200
|
+
emit(event: BehaviorEvent): Promise<void>;
|
|
201
|
+
}
|
|
202
|
+
declare class ConsentDeniedError extends Error {
|
|
203
|
+
readonly receiver: string;
|
|
204
|
+
readonly category: SensitiveCategory;
|
|
205
|
+
readonly code: "consent_denied";
|
|
206
|
+
constructor(receiver: string, category: SensitiveCategory);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* On-device ledger for development and demos. A future
|
|
210
|
+
* `RemoteBehaviorLedger` will POST to atlasent-api's
|
|
211
|
+
* `/v1/behavior/events` once that endpoint ships.
|
|
212
|
+
*/
|
|
213
|
+
declare class InMemoryBehaviorLedger implements BehaviorLedger {
|
|
214
|
+
private readonly opts;
|
|
215
|
+
private readonly events;
|
|
216
|
+
constructor(opts: {
|
|
217
|
+
consent: ConsentManager;
|
|
218
|
+
receiver?: string;
|
|
219
|
+
});
|
|
220
|
+
emit(event: BehaviorEvent): Promise<void>;
|
|
221
|
+
/** Read all events accepted so far. Test/demo helper. */
|
|
222
|
+
list(): readonly BehaviorEvent[];
|
|
223
|
+
/** Clear the in-memory store. Test helper. */
|
|
224
|
+
clear(): void;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Bounded in-memory ring buffer of recent {@link StateEventSummary}
|
|
228
|
+
* values. The LangChain/LlamaIndex middleware (and similar wrappers)
|
|
229
|
+
* read this to attach `context.session_history` to evaluate calls
|
|
230
|
+
* without ever touching raw snapshots.
|
|
231
|
+
*/
|
|
232
|
+
declare class StateEventCache {
|
|
233
|
+
private readonly capacity;
|
|
234
|
+
private readonly buf;
|
|
235
|
+
constructor(capacity?: number);
|
|
236
|
+
add(summary: StateEventSummary): void;
|
|
237
|
+
recent(n?: number): readonly StateEventSummary[];
|
|
238
|
+
clear(): void;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export { type BehaviorEvent, type BehaviorLedger, type BodyState, ConsentDeniedError, ConsentManager, type ConsentManagerOpts, type ConsentSettings, type ConsentStorage, DEFAULT_CONSENT, type EmotionalState, InMemoryBehaviorLedger, MemoryStorage, type ReadinessLevel, SENSITIVE_CATEGORIES, type SensitiveCategory, StateEventCache, type StateEventSummary, type StateSnapshot, redactStateSnapshot };
|
package/dist/behavior.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// src/behavior.ts
|
|
2
|
+
var SENSITIVE_CATEGORIES = [
|
|
3
|
+
"behavior.health.mental",
|
|
4
|
+
"behavior.health.adherence",
|
|
5
|
+
"behavior.financial",
|
|
6
|
+
"behavior.minor"
|
|
7
|
+
];
|
|
8
|
+
var DEFAULT_CONSENT = Object.freeze({
|
|
9
|
+
share_state_summaries: false,
|
|
10
|
+
private_only_mode: false
|
|
11
|
+
});
|
|
12
|
+
var MemoryStorage = class {
|
|
13
|
+
store = /* @__PURE__ */ new Map();
|
|
14
|
+
get(key) {
|
|
15
|
+
return this.store.get(key) ?? null;
|
|
16
|
+
}
|
|
17
|
+
set(key, value) {
|
|
18
|
+
this.store.set(key, value);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
var ConsentManager = class {
|
|
22
|
+
key;
|
|
23
|
+
storage;
|
|
24
|
+
defaults;
|
|
25
|
+
constructor(opts) {
|
|
26
|
+
this.key = `atlasent.behavior.consent.${opts.userId}`;
|
|
27
|
+
this.storage = opts.storage ?? new MemoryStorage();
|
|
28
|
+
this.defaults = opts.defaults ?? DEFAULT_CONSENT;
|
|
29
|
+
}
|
|
30
|
+
get() {
|
|
31
|
+
const raw = this.storage.get(this.key);
|
|
32
|
+
if (!raw) return { ...this.defaults };
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(raw);
|
|
35
|
+
return { ...this.defaults, ...parsed };
|
|
36
|
+
} catch {
|
|
37
|
+
return { ...this.defaults };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
set(patch) {
|
|
41
|
+
const next = { ...this.get(), ...patch };
|
|
42
|
+
this.storage.set(this.key, JSON.stringify(next));
|
|
43
|
+
return next;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The single decision point: may we emit a `BehaviorEvent` for
|
|
47
|
+
* this `category` to this `receiver`? Returns `false` whenever
|
|
48
|
+
* any of the following is true:
|
|
49
|
+
*
|
|
50
|
+
* - `private_only_mode` is on.
|
|
51
|
+
* - `share_state_summaries` is off.
|
|
52
|
+
* - A `receivers` allowlist exists and the `(receiver, category)`
|
|
53
|
+
* pair is not in it.
|
|
54
|
+
*/
|
|
55
|
+
canEmit(receiver, category) {
|
|
56
|
+
const c = this.get();
|
|
57
|
+
if (c.private_only_mode) return false;
|
|
58
|
+
if (!c.share_state_summaries) return false;
|
|
59
|
+
if (c.receivers) {
|
|
60
|
+
const allowed = c.receivers[receiver] ?? [];
|
|
61
|
+
if (!allowed.includes(category)) return false;
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
function redactStateSnapshot(s) {
|
|
67
|
+
return {
|
|
68
|
+
emotional_state: s.emotional_state,
|
|
69
|
+
intensity: s.intensity,
|
|
70
|
+
stress_level: s.stress_level,
|
|
71
|
+
pressure_level: s.pressure_level,
|
|
72
|
+
body_state: s.body_state,
|
|
73
|
+
cognitive_load: s.cognitive_load,
|
|
74
|
+
readiness_level: s.readiness_level
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
var ConsentDeniedError = class extends Error {
|
|
78
|
+
constructor(receiver, category) {
|
|
79
|
+
super(
|
|
80
|
+
`Consent denies emit to receiver=${receiver} category=${category}`
|
|
81
|
+
);
|
|
82
|
+
this.receiver = receiver;
|
|
83
|
+
this.category = category;
|
|
84
|
+
this.name = "ConsentDeniedError";
|
|
85
|
+
}
|
|
86
|
+
receiver;
|
|
87
|
+
category;
|
|
88
|
+
code = "consent_denied";
|
|
89
|
+
};
|
|
90
|
+
var InMemoryBehaviorLedger = class {
|
|
91
|
+
constructor(opts) {
|
|
92
|
+
this.opts = opts;
|
|
93
|
+
}
|
|
94
|
+
opts;
|
|
95
|
+
events = [];
|
|
96
|
+
async emit(event) {
|
|
97
|
+
const receiver = this.opts.receiver ?? "in-memory";
|
|
98
|
+
if (!this.opts.consent.canEmit(receiver, event.category)) {
|
|
99
|
+
throw new ConsentDeniedError(receiver, event.category);
|
|
100
|
+
}
|
|
101
|
+
this.events.push(event);
|
|
102
|
+
}
|
|
103
|
+
/** Read all events accepted so far. Test/demo helper. */
|
|
104
|
+
list() {
|
|
105
|
+
return [...this.events];
|
|
106
|
+
}
|
|
107
|
+
/** Clear the in-memory store. Test helper. */
|
|
108
|
+
clear() {
|
|
109
|
+
this.events.length = 0;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
var StateEventCache = class {
|
|
113
|
+
constructor(capacity = 10) {
|
|
114
|
+
this.capacity = capacity;
|
|
115
|
+
if (capacity <= 0) {
|
|
116
|
+
throw new RangeError("capacity must be > 0");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
capacity;
|
|
120
|
+
buf = [];
|
|
121
|
+
add(summary) {
|
|
122
|
+
this.buf.push(summary);
|
|
123
|
+
if (this.buf.length > this.capacity) this.buf.shift();
|
|
124
|
+
}
|
|
125
|
+
recent(n) {
|
|
126
|
+
const k = n ?? this.buf.length;
|
|
127
|
+
return this.buf.slice(-k);
|
|
128
|
+
}
|
|
129
|
+
clear() {
|
|
130
|
+
this.buf.length = 0;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
export {
|
|
134
|
+
ConsentDeniedError,
|
|
135
|
+
ConsentManager,
|
|
136
|
+
DEFAULT_CONSENT,
|
|
137
|
+
InMemoryBehaviorLedger,
|
|
138
|
+
MemoryStorage,
|
|
139
|
+
SENSITIVE_CATEGORIES,
|
|
140
|
+
StateEventCache,
|
|
141
|
+
redactStateSnapshot
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=behavior.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/behavior.ts"],"sourcesContent":["/**\n * @atlasent/sdk/behavior — consent + redaction helpers for the v2\n * Behavior Conditioning Layer.\n *\n * See `atlasent-docs/docs/V2_BEHAVIOR_CONDITIONING_LAYER.md` for the\n * architecture this module implements.\n *\n * Quick start:\n *\n * ```ts\n * import {\n * ConsentManager,\n * InMemoryBehaviorLedger,\n * redactStateSnapshot,\n * type StateSnapshot,\n * } from \"@atlasent/sdk/behavior\";\n *\n * const consent = new ConsentManager({ userId: \"u_123\" });\n * const ledger = new InMemoryBehaviorLedger();\n *\n * const snapshot: StateSnapshot = { ... };\n * const summary = redactStateSnapshot(snapshot);\n *\n * if (consent.canEmit(\"ledgers-me\", \"behavior.health.mental\")) {\n * await ledger.emit({\n * user_id: snapshot.user_id,\n * source: \"hicoach\",\n * category: \"behavior.health.mental\",\n * entry_state_summary: summary,\n * exit_state_summary: null,\n * relief_delta: null,\n * confidence_score: 1,\n * timestamp: new Date().toISOString(),\n * });\n * }\n * ```\n *\n * The MVP is pure and on-device — no HTTP calls. A future\n * `RemoteBehaviorLedger` will POST to `/v1/behavior/events` once\n * the atlasent-api endpoint ships.\n */\n\n// ---------------------------------------------------------------------------\n// Sensitive-category vocabulary\n// ---------------------------------------------------------------------------\n\n/**\n * Sensitive-category slugs used by behavior policies. Keep in sync\n * with `gxp-starter/packs/hipaa/` rules and\n * `atlasent-api`'s `target.category` field.\n */\nexport type SensitiveCategory =\n | \"behavior.health.mental\"\n | \"behavior.health.adherence\"\n | \"behavior.financial\"\n | \"behavior.minor\";\n\nexport const SENSITIVE_CATEGORIES: readonly SensitiveCategory[] = [\n \"behavior.health.mental\",\n \"behavior.health.adherence\",\n \"behavior.financial\",\n \"behavior.minor\",\n] as const;\n\n// ---------------------------------------------------------------------------\n// Domain types — mirror the model in `bettyc925/hicoach/lib/hicoach/types.ts`.\n// ---------------------------------------------------------------------------\n\nexport type EmotionalState =\n | \"tense\"\n | \"anxious\"\n | \"overwhelmed\"\n | \"flat\"\n | \"frustrated\"\n | \"uncertain\"\n | \"tired\"\n | \"okay\";\n\nexport type BodyState =\n | \"tight\"\n | \"heavy\"\n | \"restless\"\n | \"numb\"\n | \"buzzing\"\n | \"settled\";\n\nexport type ReadinessLevel = \"low\" | \"medium\" | \"high\";\n\n/**\n * A single moment captured before/after a regulation session.\n *\n * This is the **on-device** shape with all raw fields. It is never\n * emitted to a ledger; only the {@link StateEventSummary} projection\n * crosses an app boundary.\n */\nexport interface StateSnapshot {\n id: string;\n user_id: string;\n emotional_state: EmotionalState;\n /** 0..10 */\n intensity: number;\n /** 0..10 */\n stress_level: number;\n /** 0..10 */\n pressure_level: number;\n body_state: BodyState;\n /** 0..10 */\n cognitive_load: number;\n readiness_level: ReadinessLevel;\n /** 0..1 — how sure the user feels about the rating */\n confidence_score: number;\n /** ISO 8601 */\n created_at: string;\n /** Optional free-form note. NEVER part of the redacted summary. */\n note?: string;\n}\n\n/**\n * Redacted summary projection — the only shape that crosses an app\n * boundary. Contains no raw text, no IDs, no timestamps.\n */\nexport interface StateEventSummary {\n emotional_state: EmotionalState;\n intensity: number;\n stress_level: number;\n pressure_level: number;\n body_state: BodyState;\n cognitive_load: number;\n readiness_level: ReadinessLevel;\n}\n\n/**\n * A behavior event written to the cross-app ledger (LedgersMe).\n * The atlasent-api `/v1/behavior/events` endpoint accepts this shape.\n */\nexport interface BehaviorEvent {\n user_id: string;\n source: \"hicoach\" | \"echobloom\" | \"ledgers-me\" | string;\n category: SensitiveCategory;\n entry_state_summary: StateEventSummary;\n exit_state_summary: StateEventSummary | null;\n relief_delta: number | null;\n /** 0..1 */\n confidence_score: number;\n /** ISO 8601 */\n timestamp: string;\n /** Optional list of safety signals that fired during the event. Never raw text. */\n safety_signals?: string[];\n}\n\n// ---------------------------------------------------------------------------\n// Consent\n// ---------------------------------------------------------------------------\n\n/**\n * Per-user consent settings. Privacy-first defaults: nothing leaves\n * the device unless the user explicitly opts in.\n */\nexport interface ConsentSettings {\n /** Default false. Opt-in to emit summaries to the ledger. */\n share_state_summaries: boolean;\n /**\n * Default false. When true, suppresses ALL outbound emissions\n * regardless of any other setting. Acts as a global circuit\n * breaker.\n */\n private_only_mode: boolean;\n /**\n * Optional per-receiver allowlist. When non-empty, an event is\n * only emitted to a receiver whose name appears here AND for a\n * category whose slug appears in the receiver's allowed set.\n *\n * Example:\n * ```ts\n * { \"ledgers-me\": [\"behavior.health.mental\"] }\n * ```\n */\n receivers?: Record<string, SensitiveCategory[]>;\n}\n\nexport const DEFAULT_CONSENT: ConsentSettings = Object.freeze({\n share_state_summaries: false,\n private_only_mode: false,\n});\n\n/**\n * Storage abstraction so the helper works in browser\n * (`window.localStorage`), Node, and tests.\n */\nexport interface ConsentStorage {\n get(key: string): string | null;\n set(key: string, value: string): void;\n}\n\n/**\n * Default in-memory storage (for Node + tests). In a browser, pass\n * `window.localStorage`.\n */\nexport class MemoryStorage implements ConsentStorage {\n private store = new Map<string, string>();\n get(key: string): string | null {\n return this.store.get(key) ?? null;\n }\n set(key: string, value: string): void {\n this.store.set(key, value);\n }\n}\n\nexport interface ConsentManagerOpts {\n userId: string;\n /** Defaults to {@link MemoryStorage}. Pass `localStorage` in browsers. */\n storage?: ConsentStorage;\n /** Defaults to {@link DEFAULT_CONSENT}. */\n defaults?: ConsentSettings;\n}\n\n/**\n * Read/write consent settings; gate emissions through `canEmit`.\n *\n * Apps NEVER hand-roll consent checks. This is the only correct way\n * to decide whether a `BehaviorEvent` may leave the device.\n */\nexport class ConsentManager {\n private readonly key: string;\n private readonly storage: ConsentStorage;\n private readonly defaults: ConsentSettings;\n\n constructor(opts: ConsentManagerOpts) {\n this.key = `atlasent.behavior.consent.${opts.userId}`;\n this.storage = opts.storage ?? new MemoryStorage();\n this.defaults = opts.defaults ?? DEFAULT_CONSENT;\n }\n\n get(): ConsentSettings {\n const raw = this.storage.get(this.key);\n if (!raw) return { ...this.defaults };\n try {\n const parsed = JSON.parse(raw) as Partial<ConsentSettings>;\n return { ...this.defaults, ...parsed };\n } catch {\n return { ...this.defaults };\n }\n }\n\n set(patch: Partial<ConsentSettings>): ConsentSettings {\n const next = { ...this.get(), ...patch };\n this.storage.set(this.key, JSON.stringify(next));\n return next;\n }\n\n /**\n * The single decision point: may we emit a `BehaviorEvent` for\n * this `category` to this `receiver`? Returns `false` whenever\n * any of the following is true:\n *\n * - `private_only_mode` is on.\n * - `share_state_summaries` is off.\n * - A `receivers` allowlist exists and the `(receiver, category)`\n * pair is not in it.\n */\n canEmit(receiver: string, category: SensitiveCategory): boolean {\n const c = this.get();\n if (c.private_only_mode) return false;\n if (!c.share_state_summaries) return false;\n if (c.receivers) {\n const allowed = c.receivers[receiver] ?? [];\n if (!allowed.includes(category)) return false;\n }\n return true;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Redaction\n// ---------------------------------------------------------------------------\n\n/**\n * Project a {@link StateSnapshot} down to the redacted\n * {@link StateEventSummary} shape. Drops `id`, `user_id`,\n * `created_at`, `confidence_score`, and any `note` field. The\n * remaining fields are bounded numeric ranges or closed enums and\n * carry no free-form text.\n */\nexport function redactStateSnapshot(s: StateSnapshot): StateEventSummary {\n return {\n emotional_state: s.emotional_state,\n intensity: s.intensity,\n stress_level: s.stress_level,\n pressure_level: s.pressure_level,\n body_state: s.body_state,\n cognitive_load: s.cognitive_load,\n readiness_level: s.readiness_level,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Ledger\n// ---------------------------------------------------------------------------\n\nexport interface BehaviorLedger {\n /**\n * Emit a behavior event. Implementations MUST validate `consent`\n * before persisting and throw `ConsentDeniedError` when an event\n * would be persisted in violation of the user's settings.\n */\n emit(event: BehaviorEvent): Promise<void>;\n}\n\nexport class ConsentDeniedError extends Error {\n readonly code = \"consent_denied\" as const;\n constructor(\n public readonly receiver: string,\n public readonly category: SensitiveCategory,\n ) {\n super(\n `Consent denies emit to receiver=${receiver} category=${category}`,\n );\n this.name = \"ConsentDeniedError\";\n }\n}\n\n/**\n * On-device ledger for development and demos. A future\n * `RemoteBehaviorLedger` will POST to atlasent-api's\n * `/v1/behavior/events` once that endpoint ships.\n */\nexport class InMemoryBehaviorLedger implements BehaviorLedger {\n private readonly events: BehaviorEvent[] = [];\n constructor(\n private readonly opts: {\n consent: ConsentManager;\n receiver?: string;\n },\n ) {}\n\n async emit(event: BehaviorEvent): Promise<void> {\n const receiver = this.opts.receiver ?? \"in-memory\";\n if (!this.opts.consent.canEmit(receiver, event.category)) {\n throw new ConsentDeniedError(receiver, event.category);\n }\n this.events.push(event);\n }\n\n /** Read all events accepted so far. Test/demo helper. */\n list(): readonly BehaviorEvent[] {\n return [...this.events];\n }\n\n /** Clear the in-memory store. Test helper. */\n clear(): void {\n this.events.length = 0;\n }\n}\n\n// ---------------------------------------------------------------------------\n// State-event cache (for biasing AI suggestions with recent context)\n// ---------------------------------------------------------------------------\n\n/**\n * Bounded in-memory ring buffer of recent {@link StateEventSummary}\n * values. The LangChain/LlamaIndex middleware (and similar wrappers)\n * read this to attach `context.session_history` to evaluate calls\n * without ever touching raw snapshots.\n */\nexport class StateEventCache {\n private readonly buf: StateEventSummary[] = [];\n constructor(private readonly capacity: number = 10) {\n if (capacity <= 0) {\n throw new RangeError(\"capacity must be > 0\");\n }\n }\n\n add(summary: StateEventSummary): void {\n this.buf.push(summary);\n if (this.buf.length > this.capacity) this.buf.shift();\n }\n\n recent(n?: number): readonly StateEventSummary[] {\n const k = n ?? this.buf.length;\n return this.buf.slice(-k);\n }\n\n clear(): void {\n this.buf.length = 0;\n }\n}\n"],"mappings":"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","names":[]}
|