instar 1.3.448 → 1.3.449
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/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +15 -2
- package/dist/commands/server.js.map +1 -1
- package/dist/permissions/RelationshipAnomalyScorer.d.ts +24 -0
- package/dist/permissions/RelationshipAnomalyScorer.d.ts.map +1 -1
- package/dist/permissions/RelationshipAnomalyScorer.js +100 -35
- package/dist/permissions/RelationshipAnomalyScorer.js.map +1 -1
- package/dist/permissions/RelationshipBehaviorStore.d.ts +110 -1
- package/dist/permissions/RelationshipBehaviorStore.d.ts.map +1 -1
- package/dist/permissions/RelationshipBehaviorStore.js +193 -4
- package/dist/permissions/RelationshipBehaviorStore.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.449.md +26 -0
- package/upgrades/side-effects/slack-anomaly-poisoning-resistance.md +52 -0
|
@@ -64,6 +64,30 @@ export interface RelationshipAnomalyConfig {
|
|
|
64
64
|
* (no character yet → no out-of-character). Default 5.
|
|
65
65
|
*/
|
|
66
66
|
establishedMin?: number;
|
|
67
|
+
/**
|
|
68
|
+
* Minimum baseline calendar AGE (days) before a baseline counts as "established"
|
|
69
|
+
* (poisoning-resistance #3a). "Established" now requires BOTH this age AND
|
|
70
|
+
* `establishedMin` count — so a patient attacker can't rapidly manufacture a
|
|
71
|
+
* trusted baseline in a single burst (a high COUNT accrued in minutes is still
|
|
72
|
+
* YOUNG → not established → the action/tier/style signals stay suppressed, and the
|
|
73
|
+
* scorer never gains false confidence from a rapid burst). Default 7 days.
|
|
74
|
+
* Set to 0 to require count only (legacy behavior). Backward-compat: a profile
|
|
75
|
+
* whose firstSeen predates the request by ≥ this age satisfies it automatically.
|
|
76
|
+
*/
|
|
77
|
+
minBaselineAgeDays?: number;
|
|
78
|
+
/**
|
|
79
|
+
* Decay half-life in bucket-windows for the recency weighting (#2). A recent burst
|
|
80
|
+
* of attacker observations decays relative to long-standing behavior. Default 30
|
|
81
|
+
* (≈30 days at the store's 1-day bucket). Larger = slower decay (older behavior
|
|
82
|
+
* keeps weight longer). Read-time only; the store records raw counts.
|
|
83
|
+
*/
|
|
84
|
+
decayHalfLifeWindows?: number;
|
|
85
|
+
/**
|
|
86
|
+
* Bucket-window length (ms) the baseline was recorded with. Must match the store's
|
|
87
|
+
* `bucketWindowMs`. Default reads from the store; falls back to 1 day. Exposed for
|
|
88
|
+
* tests that inject a custom window.
|
|
89
|
+
*/
|
|
90
|
+
bucketMs?: number;
|
|
67
91
|
/**
|
|
68
92
|
* Share floor for the out-of-character-action signal (poisoning resistance).
|
|
69
93
|
* The signal fires when the requested action's SHARE of the principal's history is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RelationshipAnomalyScorer.d.ts","sourceRoot":"","sources":["../../src/permissions/RelationshipAnomalyScorer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EACL,yBAAyB,
|
|
1
|
+
{"version":3,"file":"RelationshipAnomalyScorer.d.ts","sourceRoot":"","sources":["../../src/permissions/RelationshipAnomalyScorer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EACL,yBAAyB,EAOzB,KAAK,wBAAwB,EAE9B,MAAM,gCAAgC,CAAC;AAKxC,+FAA+F;AAC/F,MAAM,WAAW,yBAAyB;IACxC,kEAAkE;IAClE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,yDAAyD;IACzD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gFAAgF;IAChF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mFAAmF;IACnF,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB;;;OAGG;IACH,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,2CAA2C;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAQD,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4B;IAClD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAEsE;gBAE9E,KAAK,EAAE,yBAAyB,EAAE,MAAM,GAAE,yBAA8B;IAwBpF;;;;;;;;OAQG;IACG,MAAM,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA6BnG;;;;OAIG;IACH,kBAAkB,CAChB,OAAO,EAAE,wBAAwB,GAAG,SAAS,EAC7C,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,MAAM,GACX;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAA;KAAE;IAgJvF;;;;OAIG;YACW,WAAW;CAyC1B"}
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
*
|
|
44
44
|
* Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md §7.1–7.4, §7.6.
|
|
45
45
|
*/
|
|
46
|
-
import {
|
|
46
|
+
import { baselineAgeMs, decayedView, decayedMeanLength, decayedStdLength, decayedHourFraction, DEFAULT_BUCKET_MS, } from './RelationshipBehaviorStore.js';
|
|
47
47
|
const URGENCY = /\b(urgent|urgently|asap|right now|immediately|before eod|by eod|end of day|emergency|hurry|quickly|can'?t wait)\b/i;
|
|
48
48
|
export class RelationshipAnomalyScorer {
|
|
49
49
|
store;
|
|
@@ -57,6 +57,11 @@ export class RelationshipAnomalyScorer {
|
|
|
57
57
|
urgencyWeight: config.urgencyWeight ?? 0.25,
|
|
58
58
|
styleWeight: config.styleWeight ?? 0.2,
|
|
59
59
|
establishedMin: config.establishedMin ?? 5,
|
|
60
|
+
// #3a: default 7 calendar days. Nullish-coalescing so a deliberate 0 means
|
|
61
|
+
// "count only" (the legacy pre-hardening behavior).
|
|
62
|
+
minBaselineAgeDays: config.minBaselineAgeDays ?? 7,
|
|
63
|
+
decayHalfLifeWindows: config.decayHalfLifeWindows && config.decayHalfLifeWindows > 0 ? config.decayHalfLifeWindows : 30,
|
|
64
|
+
bucketMs: config.bucketMs && config.bucketMs > 0 ? config.bucketMs : store.bucketWindowMs ?? DEFAULT_BUCKET_MS,
|
|
60
65
|
rareActionShareFloor: config.rareActionShareFloor ?? 0.1,
|
|
61
66
|
styleZThreshold: config.styleZThreshold ?? 2.5,
|
|
62
67
|
offCadenceHourFraction: config.offCadenceHourFraction ?? 0.02,
|
|
@@ -110,7 +115,43 @@ export class RelationshipAnomalyScorer {
|
|
|
110
115
|
if (!profile || profile.interactionCount <= 0) {
|
|
111
116
|
return { score: 0, reasons: [], confidence: 'none' };
|
|
112
117
|
}
|
|
113
|
-
const
|
|
118
|
+
const nowMs = this.cfg.now().getTime();
|
|
119
|
+
// ── Recency/decay view (#2): a decay-weighted SHAPE so a recent attacker burst that
|
|
120
|
+
// tries to NORMALIZE a rare action is caught — under decay the burst can't durably
|
|
121
|
+
// dominate, and once it ages out the genuine baseline re-asserts. A pre-hardening
|
|
122
|
+
// profile (no buckets) yields a decayed view IDENTICAL to its cumulative form. ──
|
|
123
|
+
const view = decayedView(profile, {
|
|
124
|
+
nowMs,
|
|
125
|
+
bucketMs: this.cfg.bucketMs ?? DEFAULT_BUCKET_MS,
|
|
126
|
+
halfLifeWindows: this.cfg.decayHalfLifeWindows,
|
|
127
|
+
});
|
|
128
|
+
// ── The CUMULATIVE view (as a DecayedProfileView with weight 1.0 everywhere) ──
|
|
129
|
+
// Each established signal evaluates BOTH views and fires on the MORE anomalous one.
|
|
130
|
+
// This is the load-bearing poisoning-resistance invariant: the hardening only ever
|
|
131
|
+
// ADDS resistance — it must never DISARM a signal the pre-hardening cumulative baseline
|
|
132
|
+
// would have fired. A recent rate-capped burst can raise a rare action's *decayed*
|
|
133
|
+
// share above the floor, but it cannot erase its rarity across the WHOLE relationship
|
|
134
|
+
// (the cap bounds how much it can inject); the cumulative view preserves that. The
|
|
135
|
+
// decayed view, conversely, catches "this used to be normal but the recent pattern is
|
|
136
|
+
// off". Taking the max of the two is strictly stronger than either alone. ──
|
|
137
|
+
const cumulative = {
|
|
138
|
+
effectiveCount: profile.interactionCount,
|
|
139
|
+
actionCounts: profile.actionCounts,
|
|
140
|
+
tierCounts: profile.tierCounts,
|
|
141
|
+
hourCounts: profile.hourCounts,
|
|
142
|
+
lengthSum: profile.lengthSum,
|
|
143
|
+
lengthSqSum: profile.lengthSqSum,
|
|
144
|
+
urgentCount: profile.urgentCount,
|
|
145
|
+
};
|
|
146
|
+
// ── "Established" now requires BOTH count AND calendar age (#3a) ──
|
|
147
|
+
// A high-COUNT but YOUNG baseline (a rapid burst) is NOT established → the
|
|
148
|
+
// action/tier/style signals stay suppressed (no false confidence from a burst).
|
|
149
|
+
const countEstablished = profile.interactionCount >= this.cfg.establishedMin;
|
|
150
|
+
const ageDays = baselineAgeMs(profile, nowMs) / (24 * 60 * 60 * 1000);
|
|
151
|
+
const ageEstablished = this.cfg.minBaselineAgeDays <= 0 || ageDays >= this.cfg.minBaselineAgeDays;
|
|
152
|
+
const established = countEstablished && ageEstablished;
|
|
153
|
+
// Confidence reflects depth, but a young baseline is capped at 'low' (we don't trust
|
|
154
|
+
// a burst-built baseline even if its count is high).
|
|
114
155
|
const confidence = !established
|
|
115
156
|
? 'low'
|
|
116
157
|
: profile.interactionCount >= this.cfg.establishedMin * 4
|
|
@@ -119,30 +160,34 @@ export class RelationshipAnomalyScorer {
|
|
|
119
160
|
const hits = [];
|
|
120
161
|
// ── 1. Out-of-character ACTION (only when the baseline is established) ──
|
|
121
162
|
if (established) {
|
|
122
|
-
const seen = profile.actionCounts[intent.action] ?? 0;
|
|
123
|
-
const total = profile.interactionCount || 1;
|
|
124
|
-
const share = seen / total;
|
|
125
163
|
const floor = this.cfg.rareActionShareFloor;
|
|
126
|
-
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
164
|
+
const neverSeen = (profile.actionCounts[intent.action] ?? 0) === 0;
|
|
165
|
+
// Rarity in each view; fire on the MORE anomalous (lower share = higher rarity).
|
|
166
|
+
const shareCum = (cumulative.actionCounts[intent.action] ?? 0) / (cumulative.effectiveCount || 1);
|
|
167
|
+
const shareDec = (view.actionCounts[intent.action] ?? 0) / (view.effectiveCount || 1);
|
|
168
|
+
const minShare = Math.min(shareCum, shareDec);
|
|
169
|
+
// Poisoning resistance (#1 share-floor + #2 dual-view max): the action is flagged if it
|
|
170
|
+
// is rare in EITHER the whole-relationship history OR the recent-weighted window. A
|
|
171
|
+
// patient attacker can seed a prior observation (defeats `seen===0`) or burst recently
|
|
172
|
+
// (raises the decayed share), but cannot make the action non-rare in BOTH views without
|
|
173
|
+
// a sustained campaign the rate cap (#3b) throttles. Weight scales by how far below the
|
|
174
|
+
// floor the more-anomalous share sits, so a genuinely-routine action never trips it.
|
|
175
|
+
if (minShare < floor) {
|
|
176
|
+
const rarity = 1 - minShare / floor; // 1.0 when never seen; →0 as share→floor
|
|
135
177
|
hits.push({
|
|
136
178
|
weight: this.cfg.atypicalActionWeight * rarity,
|
|
137
|
-
reason:
|
|
179
|
+
reason: neverSeen
|
|
138
180
|
? `out-of-character: established history (${profile.interactionCount} interactions) but never requested "${intent.action}"`
|
|
139
|
-
: `out-of-character: "${intent.action}" is rare for this principal (
|
|
181
|
+
: `out-of-character: "${intent.action}" is rare for this principal (≈ ${Math.round(minShare * 100)}% of history, below the ${Math.round(floor * 100)}% floor)`,
|
|
140
182
|
});
|
|
141
183
|
}
|
|
142
184
|
}
|
|
143
185
|
// ── 2. Tier ESCALATION — request tier far above the principal's normal ceiling ──
|
|
144
186
|
if (established) {
|
|
145
|
-
|
|
187
|
+
// Use the LOWER ceiling across both views: a recent burst of high-tier obs can't
|
|
188
|
+
// silently raise the "normal ceiling" and disarm this signal (it stays armed as long
|
|
189
|
+
// as EITHER the whole-relationship OR the recent-weighted ceiling is low).
|
|
190
|
+
const normalMaxTier = Math.min(highestRoutineTier(view), highestRoutineTier(cumulative));
|
|
146
191
|
if (intent.tier >= 4 && normalMaxTier <= 2) {
|
|
147
192
|
hits.push({
|
|
148
193
|
weight: this.cfg.tierEscalationWeight,
|
|
@@ -153,7 +198,10 @@ export class RelationshipAnomalyScorer {
|
|
|
153
198
|
// ── 3. Off-CADENCE timing — arrival hour this principal almost never uses ──
|
|
154
199
|
if (established) {
|
|
155
200
|
const hour = this.cfg.now().getHours();
|
|
156
|
-
if (
|
|
201
|
+
// Off-cadence if the hour is rare in EITHER view (a burst at an odd hour can't make
|
|
202
|
+
// that hour look routine across the whole relationship).
|
|
203
|
+
const minHourFrac = Math.min(decayedHourFraction(view, hour), decayedHourFraction(cumulative, hour));
|
|
204
|
+
if (minHourFrac <= this.cfg.offCadenceHourFraction) {
|
|
157
205
|
hits.push({
|
|
158
206
|
weight: this.cfg.offCadenceWeight,
|
|
159
207
|
reason: `off-cadence: request at ${pad2(hour)}:00, an hour this principal almost never operates in`,
|
|
@@ -162,8 +210,12 @@ export class RelationshipAnomalyScorer {
|
|
|
162
210
|
}
|
|
163
211
|
// ── 4. Sudden URGENCY/pressure from someone normally calm ──
|
|
164
212
|
if (isUrgent) {
|
|
165
|
-
|
|
166
|
-
|
|
213
|
+
// Use the LOWER urgent rate across both views — a recent burst of urgent obs can't
|
|
214
|
+
// raise the baseline urgent rate enough to mask sudden pressure from a normally-calm
|
|
215
|
+
// principal (the whole-relationship rate stays low).
|
|
216
|
+
const urgentRateDec = view.effectiveCount > 0 ? view.urgentCount / view.effectiveCount : 0;
|
|
217
|
+
const urgentRateCum = cumulative.effectiveCount > 0 ? cumulative.urgentCount / cumulative.effectiveCount : 0;
|
|
218
|
+
if (Math.min(urgentRateDec, urgentRateCum) <= 0.15) {
|
|
167
219
|
hits.push({
|
|
168
220
|
weight: this.cfg.urgencyWeight,
|
|
169
221
|
reason: 'sudden urgency/pressure language from a normally-calm principal',
|
|
@@ -171,17 +223,17 @@ export class RelationshipAnomalyScorer {
|
|
|
171
223
|
}
|
|
172
224
|
}
|
|
173
225
|
// ── 5. STYLE deviation — message length far outside the normal envelope ──
|
|
226
|
+
// Fire if the message length is deviant in EITHER view (a recent burst of off-style
|
|
227
|
+
// messages can't widen the envelope enough to hide a deviation from the whole history).
|
|
174
228
|
if (established) {
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
});
|
|
184
|
-
}
|
|
229
|
+
const zDec = lengthZ(text, decayedMeanLength(view), decayedStdLength(view));
|
|
230
|
+
const zCum = lengthZ(text, decayedMeanLength(cumulative), decayedStdLength(cumulative));
|
|
231
|
+
const z = Math.max(zDec ?? 0, zCum ?? 0);
|
|
232
|
+
if (z >= this.cfg.styleZThreshold) {
|
|
233
|
+
hits.push({
|
|
234
|
+
weight: this.cfg.styleWeight,
|
|
235
|
+
reason: `style deviation: message length is ${z.toFixed(1)}σ from this principal's norm`,
|
|
236
|
+
});
|
|
185
237
|
}
|
|
186
238
|
}
|
|
187
239
|
const score = clamp01(hits.reduce((s, h) => s + h.weight, 0));
|
|
@@ -197,7 +249,9 @@ export class RelationshipAnomalyScorer {
|
|
|
197
249
|
if (!intel)
|
|
198
250
|
return null; // no provider → fail closed (no contribution)
|
|
199
251
|
try {
|
|
200
|
-
|
|
252
|
+
// Coarse style summary for the LLM — cumulative mean is fine here (it's a prose hint,
|
|
253
|
+
// not a scoring input; the deterministic signals already use the decayed view).
|
|
254
|
+
const mean = profile.interactionCount > 0 ? profile.lengthSum / profile.interactionCount : undefined;
|
|
201
255
|
const topActions = Object.entries(profile.actionCounts)
|
|
202
256
|
.sort((a, b) => b[1] - a[1])
|
|
203
257
|
.slice(0, 5)
|
|
@@ -230,16 +284,27 @@ export class RelationshipAnomalyScorer {
|
|
|
230
284
|
}
|
|
231
285
|
}
|
|
232
286
|
}
|
|
233
|
-
/**
|
|
234
|
-
|
|
287
|
+
/**
|
|
288
|
+
* The highest tier this principal has used in more than a trivial share of interactions,
|
|
289
|
+
* computed over the DECAYED view so a recent attacker burst of high-tier observations
|
|
290
|
+
* doesn't silently raise the "normal ceiling" and disarm tier-escalation (#2).
|
|
291
|
+
*/
|
|
292
|
+
function highestRoutineTier(view) {
|
|
235
293
|
let max = 0;
|
|
294
|
+
const total = view.effectiveCount || 1;
|
|
236
295
|
for (let t = 0; t <= 4; t++) {
|
|
237
|
-
const c =
|
|
238
|
-
if (c > 0 && c /
|
|
296
|
+
const c = view.tierCounts[t] ?? 0;
|
|
297
|
+
if (c > 0 && c / total > 0.02)
|
|
239
298
|
max = t;
|
|
240
299
|
}
|
|
241
300
|
return max;
|
|
242
301
|
}
|
|
302
|
+
/** |z| of a message length against a view's mean/std, or undefined when not computable. */
|
|
303
|
+
function lengthZ(text, mean, std) {
|
|
304
|
+
if (mean === undefined || std === undefined || std <= 0)
|
|
305
|
+
return undefined;
|
|
306
|
+
return Math.abs(((text || '').length - mean) / std);
|
|
307
|
+
}
|
|
243
308
|
function clamp01(x) {
|
|
244
309
|
return Math.max(0, Math.min(1, x));
|
|
245
310
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RelationshipAnomalyScorer.js","sourceRoot":"","sources":["../../src/permissions/RelationshipAnomalyScorer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAIH,OAAO,EAEL,
|
|
1
|
+
{"version":3,"file":"RelationshipAnomalyScorer.js","sourceRoot":"","sources":["../../src/permissions/RelationshipAnomalyScorer.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,6 +33,31 @@ export interface BehaviorObservation {
|
|
|
33
33
|
/** Whether the message carried urgency/pressure language (cheap style signal). */
|
|
34
34
|
urgent: boolean;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* A time-bucketed slice of a principal's history (poisoning-resistance, Phase-3
|
|
38
|
+
* follow-ups #2/#3b). One bucket per rolling window (`bucketMs`, default 1 day).
|
|
39
|
+
* Holds the SAME SHAPE counts as the cumulative profile, scoped to a window — so
|
|
40
|
+
* the scorer can apply recency/decay weighting (a RECENT attacker burst can't durably
|
|
41
|
+
* dominate the histogram) and the store can enforce a per-window observation-rate cap
|
|
42
|
+
* (one session can't hammer the histogram to reshape it).
|
|
43
|
+
*
|
|
44
|
+
* Buckets are ADDITIVE: the cumulative `actionCounts`/`tierCounts`/`hourCounts`/length
|
|
45
|
+
* sums on the profile are kept in lock-step so an old reader (or a downgrade) still sees
|
|
46
|
+
* the same numbers it always did. A profile written before this hardening simply has no
|
|
47
|
+
* `buckets` field and degrades to its cumulative form everywhere.
|
|
48
|
+
*/
|
|
49
|
+
export interface BehaviorBucket {
|
|
50
|
+
/** Window start (epoch ms, floored to the bucket boundary) — drives decay weighting. */
|
|
51
|
+
startMs: number;
|
|
52
|
+
/** Observations recorded in this window (drives the per-window rate cap). */
|
|
53
|
+
count: number;
|
|
54
|
+
actionCounts: Record<string, number>;
|
|
55
|
+
tierCounts: number[];
|
|
56
|
+
hourCounts: number[];
|
|
57
|
+
lengthSum: number;
|
|
58
|
+
lengthSqSum: number;
|
|
59
|
+
urgentCount: number;
|
|
60
|
+
}
|
|
36
61
|
/**
|
|
37
62
|
* The aggregated, privacy-respecting baseline for one principal. All counts; no
|
|
38
63
|
* content. Persisted as JSON; cheap to read and update.
|
|
@@ -55,11 +80,47 @@ export interface PrincipalBehaviorProfile {
|
|
|
55
80
|
/** First / last observation ISO timestamps. */
|
|
56
81
|
firstSeen: string;
|
|
57
82
|
lastSeen: string;
|
|
83
|
+
/**
|
|
84
|
+
* OPTIONAL time-bucketed history (poisoning-resistance #2/#3b). Newest bucket last.
|
|
85
|
+
* Absent on a pre-hardening profile — the scorer degrades to the cumulative counts.
|
|
86
|
+
* When present, the cumulative counts above remain the exact sum of all buckets'
|
|
87
|
+
* counts (backward-compat invariant), so old readers are unaffected.
|
|
88
|
+
*/
|
|
89
|
+
buckets?: BehaviorBucket[];
|
|
90
|
+
}
|
|
91
|
+
/** Recording knobs for the rate-cap (#3b) + decay bucket sizing (#2). All optional. */
|
|
92
|
+
export interface BehaviorStoreOptions {
|
|
93
|
+
/**
|
|
94
|
+
* Length of one decay/rate-cap bucket window (ms). Default 1 day. The recency-decay
|
|
95
|
+
* weighting (applied at scoring time) and the per-window observation cap are both
|
|
96
|
+
* keyed off this window.
|
|
97
|
+
*/
|
|
98
|
+
bucketMs?: number;
|
|
99
|
+
/**
|
|
100
|
+
* Per-principal observation-rate cap (#3b): the MAX observations RECORDED into a
|
|
101
|
+
* single bucket window. Excess observations in the window are DROPPED (logged via
|
|
102
|
+
* `onCapDrop`), not recorded — so one session can't hammer the histogram to reshape
|
|
103
|
+
* it. Default 50/day. Set to a falsy/non-positive value to disable the cap.
|
|
104
|
+
*/
|
|
105
|
+
maxObservationsPerWindow?: number;
|
|
106
|
+
/** Cap how many buckets are retained (bounds file growth). Default 90 (≈90 days). */
|
|
107
|
+
maxBuckets?: number;
|
|
108
|
+
/** Best-effort callback when an observation is dropped by the rate cap (for logging). */
|
|
109
|
+
onCapDrop?: (slackUserId: string, windowStartMs: number, droppedCount: number) => void;
|
|
58
110
|
}
|
|
111
|
+
export declare const DEFAULT_BUCKET_MS: number;
|
|
112
|
+
export declare const DEFAULT_MAX_OBSERVATIONS_PER_WINDOW = 50;
|
|
113
|
+
export declare const DEFAULT_MAX_BUCKETS = 90;
|
|
59
114
|
export declare class RelationshipBehaviorStore {
|
|
60
115
|
private readonly file;
|
|
61
116
|
private readonly now;
|
|
62
|
-
|
|
117
|
+
private readonly bucketMs;
|
|
118
|
+
private readonly maxObservationsPerWindow;
|
|
119
|
+
private readonly maxBuckets;
|
|
120
|
+
private readonly onCapDrop?;
|
|
121
|
+
constructor(stateDir: string, now?: () => string, options?: BehaviorStoreOptions);
|
|
122
|
+
/** The bucket-window length (ms) this store records into — read by the scorer for decay. */
|
|
123
|
+
get bucketWindowMs(): number;
|
|
63
124
|
get path(): string;
|
|
64
125
|
/**
|
|
65
126
|
* Record an observation for a principal, growing their baseline. Best-effort:
|
|
@@ -67,6 +128,14 @@ export declare class RelationshipBehaviorStore {
|
|
|
67
128
|
* the message path). No-op for an unsafe key (defensive).
|
|
68
129
|
*/
|
|
69
130
|
record(slackUserId: string, obs: BehaviorObservation): void;
|
|
131
|
+
/**
|
|
132
|
+
* Resolve (and lazily migrate) the bucket covering `nowMs`, pruning to `maxBuckets`.
|
|
133
|
+
* Backfills a missing `buckets` array on a pre-hardening profile WITHOUT moving its
|
|
134
|
+
* cumulative counts into a bucket (those counts predate bucketing and have unknown
|
|
135
|
+
* timestamps — they keep weight as the un-decayable "legacy" base; see the scorer's
|
|
136
|
+
* decay logic). New observations land in fresh, timestamped buckets.
|
|
137
|
+
*/
|
|
138
|
+
private currentBucket;
|
|
70
139
|
/** The current baseline for a principal, or undefined if none recorded yet. */
|
|
71
140
|
profileFor(slackUserId: string | undefined): PrincipalBehaviorProfile | undefined;
|
|
72
141
|
/** All profiles (for the read route / inspection). */
|
|
@@ -80,6 +149,46 @@ export declare function meanLength(prof: PrincipalBehaviorProfile): number | und
|
|
|
80
149
|
export declare function stdLength(prof: PrincipalBehaviorProfile): number | undefined;
|
|
81
150
|
/** Fraction of baseline interactions in a given hour (0..1). */
|
|
82
151
|
export declare function hourFraction(prof: PrincipalBehaviorProfile, hour: number): number;
|
|
152
|
+
/** Baseline calendar age in milliseconds (now − firstSeen), or 0 when unknown. */
|
|
153
|
+
export declare function baselineAgeMs(prof: PrincipalBehaviorProfile, nowMs: number): number;
|
|
154
|
+
/**
|
|
155
|
+
* The decayed-effective SHAPE view a scorer should read instead of raw cumulative
|
|
156
|
+
* counts. Same fields as the profile, but every count is the decay-weighted sum across
|
|
157
|
+
* buckets PLUS the legacy (pre-bucketing) base at full weight.
|
|
158
|
+
*/
|
|
159
|
+
export interface DecayedProfileView {
|
|
160
|
+
effectiveCount: number;
|
|
161
|
+
actionCounts: Record<string, number>;
|
|
162
|
+
tierCounts: number[];
|
|
163
|
+
hourCounts: number[];
|
|
164
|
+
lengthSum: number;
|
|
165
|
+
lengthSqSum: number;
|
|
166
|
+
urgentCount: number;
|
|
167
|
+
}
|
|
168
|
+
export interface DecayOptions {
|
|
169
|
+
/** Now (epoch ms) — drives bucket age. */
|
|
170
|
+
nowMs: number;
|
|
171
|
+
/** One bucket window length (ms). Must match the store's bucketMs. */
|
|
172
|
+
bucketMs: number;
|
|
173
|
+
/** Decay half-life in bucket-windows. Default 30 (≈30 days at a 1-day bucket). */
|
|
174
|
+
halfLifeWindows?: number;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Compute the decay-weighted effective baseline. The legacy base = cumulative totals
|
|
178
|
+
* MINUS the sum of all buckets (i.e. the part of history that predates bucketing). That
|
|
179
|
+
* base keeps full weight (weight 1.0); each bucket is weighted by 0.5^(ageWindows/halfLife).
|
|
180
|
+
*
|
|
181
|
+
* A profile with no buckets → its entire cumulative form is the legacy base at full
|
|
182
|
+
* weight → the decayed view equals the cumulative view (perfect backward-compat). A
|
|
183
|
+
* fully-bucketed profile decays purely on bucket age.
|
|
184
|
+
*/
|
|
185
|
+
export declare function decayedView(prof: PrincipalBehaviorProfile, opts: DecayOptions): DecayedProfileView;
|
|
186
|
+
/** Mean message length from a decayed view, or undefined when there is no weight. */
|
|
187
|
+
export declare function decayedMeanLength(view: DecayedProfileView): number | undefined;
|
|
188
|
+
/** Population std of message length from a decayed view, or undefined when <2 effective. */
|
|
189
|
+
export declare function decayedStdLength(view: DecayedProfileView): number | undefined;
|
|
190
|
+
/** Fraction of decayed interactions in a given hour (0..1). */
|
|
191
|
+
export declare function decayedHourFraction(view: DecayedProfileView, hour: number): number;
|
|
83
192
|
import type { BaselineProvider, PrincipalBaseline } from './AnomalyScorer.js';
|
|
84
193
|
import type { Principal } from './types.js';
|
|
85
194
|
export declare class StoreBaselineProvider implements BaselineProvider {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RelationshipBehaviorStore.d.ts","sourceRoot":"","sources":["../../src/permissions/RelationshipBehaviorStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,6DAA6D;AAC7D,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,mFAAmF;IACnF,gBAAgB,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,+CAA+C;IAC/C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,iFAAiF;IACjF,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,0GAA0G;IAC1G,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,4FAA4F;IAC5F,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"RelationshipBehaviorStore.d.ts","sourceRoot":"","sources":["../../src/permissions/RelationshipBehaviorStore.ts"],"names":[],"mappings":"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"}
|