Compare commits

..

1 Commits

Author SHA1 Message Date
3ae2a72b67 fix: recognize past-tense vomiting in chat safety
All checks were successful
Quality gates / quality (pull_request) Successful in 1m19s
2026-08-20 17:04:10 +00:00
7 changed files with 13 additions and 5 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 KiB

After

Width:  |  Height:  |  Size: 311 KiB

View File

@ -122,7 +122,7 @@ await page.locator('#chat-message').fill('What pattern do you see?');
await tap('#send-chat', 500);
await page.getByText(/mostly Type 4/i).waitFor();
await caption('Hermes keeps credentials, tools, and session continuity server-side. Photos stay out of chat.', 1600);
await page.locator('#chat-message').fill('I threw up');
await page.locator('#chat-message').fill('I barfed');
await tap('#send-chat', 450);
await page.getByText(/Pause and get medical help/i).waitFor();
if (hermesCalls !== 1) throw new Error('Urgent chat must be intercepted before Hermes');

View File

@ -4,7 +4,7 @@ const URGENT_TEXT_PATTERNS = Object.freeze([
['blood', /\b(?:rectal bleeding|bleeding from (?:the )?(?:rectum|bottom)|blood(?:y)? (?:in|on|with) (?:my |the )?(?:stool|poop|bowel movement)|(?:stool|poop) (?:has|contains|with) blood)\b/i],
['blackOrDarkRed', /\b(?:(?:black|dark[- ]?red) (?:stool|poop|bowel movement)|(?:stool|poop|bowel movement) (?:is|looks?) (?:black|dark[- ]?red))s?\b/i],
['severePain', /\b(?:severe|constant|unrelenting) (?:abdominal|stomach|belly) pain\b/i],
['vomiting', /\b(?:vomit(?:ing|ed|s)?|throw(?:ing|s)? up|threw up|thrown up|puk(?:e|ed|ing|es)|emesis)\b/i],
['vomiting', /\b(?:vomit(?:ing|ed|s)?|throw(?:ing|s)? up|threw up|thrown up|puk(?:e|ed|ing|es)|barf(?:ed|ing|s)?|hurl(?:ed|ing|s)?|upchuck(?:ed|ing|s)?|spew(?:ed|ing|s)?|toss(?:ed|ing|es)? (?:my|the) cookies|los(?:e|t|ing|es) (?:my|the) lunch|emesis)\b/i],
['fever', /\bfever(?:ish)?\b/i],
['cannotPassGas', /\b(?:cannot|can[']?t|cant|unable to|not able to) pass gas\b/i],
]);

View File

@ -52,6 +52,13 @@ test('detects common urgent symptom language without matching unrelated blood wo
'I am throwing up and have a fever',
'I threw up',
'I puked twice',
'I am barfing',
'I barfed',
'I hurled',
'I am upchucking',
'I spewed',
'I tossed my cookies',
'I lost my lunch',
'I have emesis',
'I am unable to pass gas',
]) assert.equal(detectUrgentText(message).urgent, true, message);

View File

@ -129,6 +129,7 @@ test('authoritative chat service intercepts urgent message and ledger symptoms b
const messageResult = await service.chat({ origin, cookieToken: 'safety-cookie', payload: { message: 'I have rectal bleeding', ledger: [] } });
const pastTenseVomitingResult = await service.chat({ origin, cookieToken: 'safety-cookie', payload: { message: 'I threw up', ledger: [] } });
const slangVomitingResult = await service.chat({ origin, cookieToken: 'safety-cookie', payload: { message: 'I barfed', ledger: [] } });
const ledgerResult = await service.chat({ origin, cookieToken: 'safety-cookie', payload: {
message: 'What does my journal show?',
ledger: [{ bristolType: 4, color: 'brown', note: '', symptoms: { cannotPassGas: true } }],
@ -139,7 +140,7 @@ test('authoritative chat service intercepts urgent message and ledger symptoms b
} });
assert.equal(calls.length, 0);
for (const result of [messageResult, pastTenseVomitingResult, ledgerResult, noteResult]) {
for (const result of [messageResult, pastTenseVomitingResult, slangVomitingResult, ledgerResult, noteResult]) {
assert.equal(result.safetyOverride, true);
assert.match(result.reply, /medical help/i);
assert.doesNotMatch(result.reply, /unsafe upstream/i);

View File

@ -14,7 +14,7 @@ test('release demo visibly explains the CI-protected browser path without overst
assert.match(demo, /AI may suggest visible form, broad color, and image quality — never symptoms or diagnosis/);
assert.match(demo, /Hermes Agent connected/);
assert.match(demo, /Photos stay out of chat/);
assert.match(demo, /I threw up/);
assert.match(demo, /I barfed/);
assert.match(demo, /Urgent language is intercepted deterministically before Hermes/);
assert.match(demo, /SLEEK\. SIMPLE\.<br>HERMES-POWERED\./);
});

View File

@ -38,7 +38,7 @@ assert.equal(chatRequest.message, 'What pattern do you see?');
assert.ok(Array.isArray(chatRequest.ledger));
assert.equal(JSON.stringify(chatRequest).includes('photoDataUrl'), false, 'photos never enter chat context');
const previousRequest = chatRequest;
await page.locator('#chat-message').fill('I threw up');
await page.locator('#chat-message').fill('I barfed');
await page.locator('#send-chat').click();
await page.waitForSelector('.bubble.timmy >> text=Pause and get medical help');
assert.equal(chatRequest, previousRequest, 'urgent language must be intercepted before the Hermes request');