All checks were successful
Quality gates / quality (pull_request) Successful in 3m31s
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.
279 lines
12 KiB
JavaScript
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);
|
|
});
|