timmy-talking-turd/tests/escalation-boundary.test.js
Timmy 693e023758
All checks were successful
Quality gates / quality (pull_request) Successful in 3m31s
fix: replace urgent regex patches with auditable expression grammar
Second hostile review of 1aadca91 found nine ordinary urgent phrasings
bypassing the deterministic gate at detector and service layers, plus
punctuation-fragile contextual exclusions. Replace the accumulated
narrow regex table with a structured, versioned, frozen urgent-expression
grammar and one shared surface normalizer:

- normalizeUrgentText: case folding, apostrophe unification, contraction
  expansion (can't/cant/can not -> cannot, haven't -> have not, ...),
  hyphen splitting, punctuation stripping, whitespace collapse
- URGENT_EXPRESSION_GRAMMAR v2.0: per-flag ordered match expressions with
  bounded nonclinical anchor exclusions; anchors veto only the occurrence
  they sit beside (36-char window), so arbitrary future symptom language
  keeps escalating with no continuation-word allowlist
- new RED->GREEN coverage at every layer: 9 review phrases + 5 prior
  phrases with tense/plural/pronoun/word-order/case/contraction/
  punctuation variants, normalization-equivalence groups, anti-allowlist
  continuation sweep (147 combos), grammar structure audit, service
  zero-Hermes-call interception, and live-HTTP wiring proof with the
  bounded fake adapter (tests/escalation-http.test.js)

Gates: npm test 99/99, check:syntax, check:diff, audit 0 vulns,
staging-deploy 20/20, test:ui/test:photo/test:sleek against this
checkout. No merge, no deploy.
2026-08-22 23:24:51 +00:00

279 lines
12 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
AgentGatewayError,
buildHermesEnvironment,
createHermesAgentService,
parseHermesCliOutput,
resolveHermesAgentConfig,
runHermesCliTurn,
} from '../src/hermes-agent-service.js';
import { detectUrgentText, urgentSymptomCopy } from '../src/domain.js';
// Pinned literally so any drift in the authoritative copy fails this suite.
const URGENT_MESSAGE_COPY = 'These reported symptoms can need prompt medical care. Contact a clinician or urgent service now; call emergency services for heavy or nonstop bleeding, fainting, or severe worsening symptoms.';
assert.equal(urgentSymptomCopy.flagsMessage, URGENT_MESSAGE_COPY);
const origin = 'http://127.0.0.1:4173';
const configured = () => resolveHermesAgentConfig({
TIMMY_AGENT_ENABLED: 'true',
TIMMY_AGENT_ACCESS_TOKEN: 'test-agent-access-code-2026',
TIMMY_PUBLIC_ORIGIN: origin,
TIMMY_AGENT_WORKDIR: '/tmp/timmy-agent-workspace',
TIMMY_AGENT_TIMEOUT_MS: '45000',
});
async function rejectsStatus(fn, status) {
await assert.rejects(fn, error => error instanceof AgentGatewayError && error.status === status);
}
test('every red flag phrase is intercepted at the service boundary with zero Hermes calls', async () => {
const phrases = {
blood: 'There is blood in my stool',
blackOrDarkRed: 'My stool is black',
severePain: 'I have severe stomach pain',
vomiting: 'I threw up',
fever: 'I have a fever',
cannotPassGas: 'I am unable to pass gas',
};
for (const [key, phrase] of Object.entries(phrases)) {
const calls = [];
const service = createHermesAgentService({
config: configured(),
randomToken: () => 'zero-call-cookie',
runTurn: async input => { calls.push(input); return { reply: 'unsafe upstream reply', sessionId: 'unsafe-session' }; },
});
await service.unlock({ origin, accessCode: 'test-agent-access-code-2026' });
const result = await service.chat({ origin, cookieToken: 'zero-call-cookie', payload: { message: phrase, ledger: [] } });
assert.equal(calls.length, 0, `${key} must never reach Hermes`);
assert.equal(result.safetyOverride, true, phrase);
assert.match(result.reply, /medical help/i, phrase);
assert.doesNotMatch(result.reply, /unsafe upstream/i);
assert.equal(detectUrgentText(phrase).flags.includes(key), true, phrase);
}
});
// The review-reported false negatives plus their case/punctuation/contraction
// variants. Each phrase is proven twice: once against the detector and once
// through the authoritative service gate, which must override before any
// Hermes call happens.
const REVIEW_REGRESSION_PHRASES = [
'I have a fever right now',
'I HAVE A FEVER RIGHT NOW!',
'my fever is 103',
'My fever is 103.',
'my fever is 103.5 degrees',
'fever started this morning',
'Fever started this morning?',
'severe pain in the abdomen.',
'Severe pain in THE abdomen!!',
'severe pain around the abdomen',
"I've had a fever since monday",
];
test('review-reported false negatives escalate at the detector for every variant', async () => {
for (const phrase of REVIEW_REGRESSION_PHRASES) {
const result = detectUrgentText(phrase);
assert.equal(result.urgent, true, JSON.stringify(phrase));
assert.equal(result.message, URGENT_MESSAGE_COPY, phrase);
}
});
// Second hostile review (exact head 1aadca91): nine more ordinary urgent
// phrasings that bypassed both gates. Each must escalate at the detector AND
// be intercepted at the service boundary with zero Hermes calls.
const SECOND_REVIEW_PHRASES = [
'My stool had blood.',
'My stools are bloody.',
'My stools are black.',
'My stool has turned black.',
'My abdominal pain is severe.',
'Pain in my abdomen is severe.',
'I threw my lunch up.',
'I can not pass gas.',
"I haven't been able to pass gas.",
];
test('second-review false negatives escalate at the detector for every phrase', () => {
for (const phrase of SECOND_REVIEW_PHRASES) {
const result = detectUrgentText(phrase);
assert.equal(result.urgent, true, JSON.stringify(phrase));
assert.equal(result.message, URGENT_MESSAGE_COPY, phrase);
assert.equal(detectUrgentText(`please help, ${phrase.toLowerCase()}`).urgent, true, phrase);
}
});
test('second-review false negatives are intercepted at the service boundary with zero Hermes calls', async () => {
for (const phrase of SECOND_REVIEW_PHRASES) {
const calls = [];
const service = createHermesAgentService({
config: configured(),
randomToken: () => 'second-review-cookie',
runTurn: async input => { calls.push(input); return { reply: 'unsafe upstream reply', sessionId: 'unsafe-session' }; },
});
await service.unlock({ origin, accessCode: 'test-agent-access-code-2026' });
const result = await service.chat({ origin, cookieToken: 'second-review-cookie', payload: { message: phrase, ledger: [] } });
assert.equal(calls.length, 0, `${JSON.stringify(phrase)} must never reach Hermes`);
assert.equal(result.safetyOverride, true, phrase);
assert.match(result.reply, /medical help/i, phrase);
assert.doesNotMatch(result.reply, /unsafe upstream/i);
}
});
test('review-reported false negatives are intercepted at the service boundary with zero Hermes calls', async () => {
for (const phrase of REVIEW_REGRESSION_PHRASES) {
const calls = [];
const service = createHermesAgentService({
config: configured(),
randomToken: () => 'review-regression-cookie',
runTurn: async input => { calls.push(input); return { reply: 'unsafe upstream reply', sessionId: 'unsafe-session' }; },
});
await service.unlock({ origin, accessCode: 'test-agent-access-code-2026' });
const result = await service.chat({ origin, cookieToken: 'review-regression-cookie', payload: { message: phrase, ledger: [] } });
assert.equal(calls.length, 0, `${JSON.stringify(phrase)} must never reach Hermes`);
assert.equal(result.safetyOverride, true, phrase);
assert.match(result.reply, /medical help/i, phrase);
assert.doesNotMatch(result.reply, /unsafe upstream/i);
}
});
test('confirmed ledger symptoms and note language override chat before Hermes is called', async () => {
for (const ledger of [
[{ bristolType: 4, symptoms: { blood: true } }],
[{ bristolType: 4, symptoms: { blackOrDarkRed: true } }],
[{ bristolType: 4, symptoms: { severePain: true } }],
[{ bristolType: 4, symptoms: { vomiting: true } }],
[{ bristolType: 4, symptoms: { fever: true } }],
[{ bristolType: 4, symptoms: { cannotPassGas: true } }],
[{ bristolType: 4, symptoms: {}, note: 'I threw up' }],
[{ bristolType: 4, symptoms: {}, note: 'severe abdominal pain' }],
[{ bristolType: 4, symptoms: {}, note: 'black stool' }],
[{ bristolType: 4, symptoms: {}, note: 'fever' }],
[{ bristolType: 4, symptoms: {}, note: 'blood in my stool' }],
[{ bristolType: 4, symptoms: {}, note: 'unable to pass gas' }],
]) {
const calls = [];
const service = createHermesAgentService({
config: configured(),
randomToken: () => 'ledger-cookie',
runTurn: async input => { calls.push(input); return { reply: 'unsafe upstream reply', sessionId: 'unsafe-session' }; },
});
await service.unlock({ origin, accessCode: 'test-agent-access-code-2026' });
const result = await service.chat({
origin,
cookieToken: 'ledger-cookie',
payload: { message: 'What does my journal show?', ledger },
});
assert.equal(calls.length, 0, JSON.stringify(ledger));
assert.equal(result.safetyOverride, true, JSON.stringify(ledger));
assert.match(result.reply, /medical help/i);
}
});
test('malicious or broken provider output cannot weaken the deterministic urgent reply', async () => {
const hostileReplies = [
undefined,
null,
'',
'All clear, nothing to worry about.',
'You are fine, no medical care needed.',
];
for (const reply of hostileReplies) {
const calls = [];
const service = createHermesAgentService({
config: configured(),
randomToken: () => 'hostile-cookie',
runTurn: async input => { calls.push(input); return { reply, sessionId: 'hostile-session' }; },
});
await service.unlock({ origin, accessCode: 'test-agent-access-code-2026' });
const result = await service.chat({
origin,
cookieToken: 'hostile-cookie',
payload: { message: 'There is blood in my stool', ledger: [] },
});
assert.equal(calls.length, 0, 'urgent text must be resolved deterministically');
assert.equal(result.safetyOverride, true);
assert.match(result.reply, /medical help/i);
assert.doesNotMatch(result.reply, /all clear|fine|worry/i);
}
});
test('model-shaped junk in ledger symptoms can neither fabricate nor suppress escalation', async () => {
// Truthy junk must not fabricate an override...
const junkCalls = [];
const junkService = createHermesAgentService({
config: configured(),
randomToken: () => 'junk-cookie',
runTurn: async input => { junkCalls.push(input); return { reply: 'pattern reply', sessionId: 'junk-session' }; },
});
await junkService.unlock({ origin, accessCode: 'test-agent-access-code-2026' });
const junkResult = await junkService.chat({
origin,
cookieToken: 'junk-cookie',
payload: { message: 'Summarize my journal', ledger: [{ bristolType: 4, symptoms: { blood: 'yes', fever: 1, vomiting: { forced: true }, cannotPassGas: [true] } }] },
});
assert.equal(junkResult.safetyOverride, undefined, 'truthy junk must not fabricate an urgent override');
assert.match(junkResult.reply, /pattern reply/);
// ...and a forged true flag must still suppress the Hermes call.
const forgedCalls = [];
const forgedService = createHermesAgentService({
config: configured(),
randomToken: () => 'forged-cookie',
runTurn: async input => { forgedCalls.push(input); return { reply: 'pattern reply', sessionId: 'forged-session' }; },
});
await forgedService.unlock({ origin, accessCode: 'test-agent-access-code-2026' });
const forged = await forgedService.chat({
origin,
cookieToken: 'forged-cookie',
payload: { message: 'Summarize my journal', ledger: [{ bristolType: 4, symptoms: { blood: true } }] },
});
assert.equal(forgedCalls.length, 0, 'a forged true flag must still override before Hermes');
assert.equal(forged.safetyOverride, true);
assert.match(forged.reply, /medical help/i);
});
test('non-urgent journal questions still reach the agent exactly once', async () => {
const calls = [];
const service = createHermesAgentService({
config: configured(),
randomToken: () => 'normal-cookie',
runTurn: async input => { calls.push(input); return { reply: 'Two confirmed logs this week.', sessionId: 'normal-session' }; },
});
await service.unlock({ origin, accessCode: 'test-agent-access-code-2026' });
const result = await service.chat({
origin,
cookieToken: 'normal-cookie',
payload: { message: 'What pattern do you see?', ledger: [{ bristolType: 4, symptoms: {}, note: 'ordinary entry' }] },
});
assert.equal(calls.length, 1);
assert.equal(result.safetyOverride, undefined);
assert.equal(result.reply, 'Two confirmed logs this week.');
});
test('Hermes CLI adapter contract is unchanged by escalation work', async () => {
let captured;
const result = await runHermesCliTurn({
prompt: 'hello',
hermesSessionId: null,
config: configured(),
execImpl: async (command, args, options) => {
captured = { command, args, options };
return { stdout: 'bounded answer\n', stderr: 'session_id: 20260820_fixture\n' };
},
});
assert.deepEqual(result, { sessionId: '20260820_fixture', reply: 'bounded answer' });
assert.equal(captured.command, 'hermes');
assert.equal(captured.options.cwd, '/tmp/timmy-agent-workspace');
const childEnv = buildHermesEnvironment({ TIMMY_AGENT_ACCESS_TOKEN: 'must-not-leak' });
assert.equal(childEnv.TIMMY_AGENT_ACCESS_TOKEN, undefined);
assert.equal(childEnv.TIMMY_AGENT_BROWSER_REQUEST, '1');
});
test('parseHermesCliOutput still rejects malformed provider output', () => {
assert.throws(() => parseHermesCliOutput('answer only'), /session metadata/i);
assert.throws(() => parseHermesCliOutput('session_id: private\nsession_id: leaked'), /unsafe/i);
});