feat(ai-psychosis): tier-1 user-info isolation alert (per-session)

This commit is contained in:
Kjell Tore Guttormsen 2026-05-01 21:38:51 +02:00
commit 4fd5e7b24a
2 changed files with 101 additions and 0 deletions

View file

@ -167,3 +167,81 @@ describe('turn_count', () => {
assert.equal(s.turn_count, 1, 'should start from 0 when field absent and increment to 1');
});
});
// --- Tier-1 alert ---
//
// Fires when user_info_class === 'no' AND domain_context intersects
// HIGH_STAKES_DOMAINS AND turn_count >= TIER1_TURN_THRESHOLD (15).
function runPromptCapture(prompt, stateOverrides = {}) {
dir = setupTestDir();
createStateFile(dir, 'u-tier1', { ...freshState(), ...stateOverrides });
const out = runHook('prompt-analyzer.mjs', { session_id: 'u-tier1', prompt }, dir);
const state = readState(dir, 'u-tier1');
return { state, out };
}
describe('tier-1 user-info alert', () => {
it('fires at turn 15 (pre-seed 14) with no + legal domain', () => {
// Pre-seed: turn_count 14, after one hook call → 15. Triggers alert.
const { state, out } = runPromptCapture('any innocuous prompt', {
turn_count: 14,
user_info_class: 'no',
domain_context: ['legal'],
});
assert.equal(state.turn_count, 15);
assert.ok(out.hookSpecificOutput, 'tier-1 alert should be emitted');
assert.match(out.hookSpecificOutput.additionalContext, /tier-1/);
assert.match(out.hookSpecificOutput.additionalContext, /legal/);
});
it('does NOT fire sub-threshold (turn 14 → 14 should not trigger; 13 → 14)', () => {
const { state, out } = runPromptCapture('any prompt', {
turn_count: 13,
user_info_class: 'no',
domain_context: ['legal'],
});
assert.equal(state.turn_count, 14);
assert.equal(out.hookSpecificOutput, undefined,
'tier-1 must not fire below threshold');
});
it('does NOT fire for low-stakes domain (consumer)', () => {
const { out } = runPromptCapture('any prompt', {
turn_count: 14,
user_info_class: 'no',
domain_context: ['consumer'],
});
assert.equal(out.hookSpecificOutput, undefined,
'tier-1 only fires in high-stakes domains');
});
it('does NOT fire when user_info_class is yes_people (supersedes "no")', () => {
const { out } = runPromptCapture('any prompt', {
turn_count: 14,
user_info_class: 'yes_people',
domain_context: ['legal'],
});
assert.equal(out.hookSpecificOutput, undefined,
'tier-1 only fires when user signals isolation');
});
it('does NOT fire when domain_context is empty', () => {
const { out } = runPromptCapture('any prompt', {
turn_count: 14,
user_info_class: 'no',
domain_context: [],
});
assert.equal(out.hookSpecificOutput, undefined);
});
it('fires for parenting domain (also high-stakes)', () => {
const { out } = runPromptCapture('any prompt', {
turn_count: 14,
user_info_class: 'no',
domain_context: ['parenting'],
});
assert.ok(out.hookSpecificOutput, 'tier-1 fires for parenting too');
assert.match(out.hookSpecificOutput.additionalContext, /parenting/);
});
});