Files
hermes-webui/tests/test_stable_assistant_turn_anchor_normalizer.py

341 lines
12 KiB
Python

"""Normalizer tests for Stable Assistant Turn Anchors (#3926)."""
from __future__ import annotations
import json
import shutil
import subprocess
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
ANCHORS_JS = REPO / "static" / "assistant_turn_anchors.js"
MESSAGES_JS = REPO / "static" / "messages.js"
UI_JS = REPO / "static" / "ui.js"
SESSIONS_JS = REPO / "static" / "sessions.js"
NODE = shutil.which("node")
def _read(path: Path) -> str:
return path.read_text(encoding="utf-8")
def _normalizer_snapshot() -> dict:
assert NODE, "node is required for assistant_turn_anchors.js normalizer tests"
script = f"""
const fs = require('fs');
const vm = require('vm');
const src = fs.readFileSync({json.dumps(str(ANCHORS_JS))}, 'utf8');
const sandbox = {{window:{{}}}};
vm.createContext(sandbox);
vm.runInContext(src, sandbox, {{filename:'assistant_turn_anchors.js'}});
const api = sandbox.window.HermesAssistantTurnAnchors;
const context = {{
session_id:'sid-1',
turn_id:'turn-1',
run_id:'run-1',
stream_id:'stream-1',
}};
const liveToken = api.normalizeAssistantTurnAnchorSourceEvent({{
type:'token',
data:JSON.stringify({{text:'hello', session_id:'sid-1'}}),
lastEventId:'run-1:7',
}}, context);
const replayToken = api.normalizeAssistantTurnAnchorSourceEvent({{
event:'token',
payload:{{text:'hello', session_id:'sid-1'}},
event_id:'run-1:7',
seq:7,
}}, context);
const settled = api.normalizeAssistantTurnAnchorSourceEvent({{
source_type:'settled_message',
payload:{{
role:'assistant',
content:'final',
reasoning:'trace',
_turnUsage:{{input_tokens:3, output_tokens:5}},
}},
}}, context);
const artifact = api.normalizeAssistantTurnAnchorSourceEvent({{
source_type:'artifact_reference',
payload:{{path:'result.txt', kind:'workspace_file'}},
event_id:'run-1:8',
}}, context);
const sideEffect = api.normalizeAssistantTurnAnchorSourceEvent({{
type:'state_saved',
payload:{{kind:'memory', name:'saved-state'}},
event_id:'run-1:9',
}}, context);
const transport = api.normalizeAssistantTurnAnchorSourceEvent({{
type:'stream_end',
data:'{{"session_id":"sid-1"}}',
event_id:'run-1:10',
}}, context);
const unknown = api.normalizeAssistantTurnAnchorSourceEvent({{
type:'unknown_future_event',
payload:{{text:'ignored'}},
}}, context);
const deduped = api.normalizeAssistantTurnAnchorSourceEvents([
{{type:'token', data:'{{"text":"live"}}', lastEventId:'run-1:11'}},
{{event:'token', payload:{{text:'replay'}}, event_id:'run-1:11', seq:11}},
{{event:'reasoning', payload:{{text:'thinking'}}, event_id:'run-1:12', seq:12}},
], context);
const invalidPayload = api.normalizeAssistantTurnAnchorSourceEvent({{
type:'token',
data:'plain text chunk',
event_id:'run-1:13',
}}, context);
const directParsedPayload = api.normalizeAssistantTurnAnchorSourceEvent({{
source_type:'token',
text:'direct parsed payload',
event_id:'run-1:14',
seq:14,
}}, context);
const eventTypePayload = api.normalizeAssistantTurnAnchorSourceEvent({{
event_type:'tool',
event_id:'run-1:15',
tool_call_id:'tool-1',
someField:'x',
}}, context);
const nonPlainPayload = api.normalizeAssistantTurnAnchorSourceEvent({{
type:'token',
payload:{{text:'date payload', meta:new Date('2026-06-11T00:00:00Z')}},
event_id:'run-1:16',
}}, context);
const promotedIdentityPayload = api.normalizeAssistantTurnAnchorSourceEvent({{
type:'token',
payload:{{
text:'identity fields should be promoted only',
session_id:'payload-session',
turn_id:'payload-turn',
run_id:'payload-run',
stream_id:'payload-stream',
event_id:'payload-event',
seq:99,
}},
event_id:'run-1:17',
}}, context);
const pollutionPayload = JSON.parse('{{"text":"clean","__proto__":{{"session_id":"polluted-session","run_id":"polluted-run"}},"constructor":{{"bad":true}},"prototype":{{"bad":true}}}}');
const pollutionAttempt = api.normalizeAssistantTurnAnchorSourceEvent({{
type:'token',
payload:pollutionPayload,
event_id:'run-1:18',
}}, context);
const inheritedEvent = Object.create({{
event_id:'evil:1',
session_id:'evil-session',
run_id:'evil-run',
stream_id:'evil-stream',
seq:666,
created_at:'evil-time',
}});
inheritedEvent.type = 'token';
inheritedEvent.payload = {{text:'own event only'}};
const inheritedEnvelopeIdentity = api.normalizeAssistantTurnAnchorSourceEvent(inheritedEvent, context);
const inheritedContext = Object.create({{
session_id:'evil-context-session',
turn_id:'evil-context-turn',
run_id:'evil-context-run',
stream_id:'evil-context-stream',
seq:777,
created_at:'evil-context-time',
}});
const inheritedContextIdentity = api.normalizeAssistantTurnAnchorSourceEvent({{
type:'token',
payload:{{text:'own context only'}},
}}, inheritedContext);
const runSeqCollisionA = api.assistantTurnAnchorEventDedupeKey({{run_id:'a:b', seq:'c'}});
const runSeqCollisionB = api.assistantTurnAnchorEventDedupeKey({{run_id:'a', seq:'b:c'}});
const localCollisionA = api.assistantTurnAnchorEventDedupeKey({{session_id:'a:b', source_event_type:'tool', local_id:'c', seq:'d'}});
const localCollisionB = api.assistantTurnAnchorEventDedupeKey({{session_id:'a', source_event_type:'b:tool', local_id:'c', seq:'d'}});
const localNoSeqKey = api.assistantTurnAnchorEventDedupeKey({{session_id:'a', source_event_type:'token', local_id:'b'}});
console.log(JSON.stringify({{
version: api.version,
liveToken,
replayToken,
settled,
artifact,
sideEffect,
transport,
unknown,
deduped,
invalidPayload,
directParsedPayload,
eventTypePayload,
nonPlainPayload,
promotedIdentityPayload,
pollutionAttempt,
inheritedEnvelopeIdentity,
inheritedContextIdentity,
runSeqCollisionA,
runSeqCollisionB,
localCollisionA,
localCollisionB,
localNoSeqKey,
}}));
"""
result = subprocess.run([NODE, "-e", script], text=True, capture_output=True, check=False)
assert result.returncode == 0, result.stderr
return json.loads(result.stdout)
def test_normalizer_maps_live_and_replay_to_same_anchor_event_identity():
data = _normalizer_snapshot()
live = data["liveToken"]
replay = data["replayToken"]
assert data["version"] == "slice8-renderer-snapshot-adapter"
assert live["classification"] == "activity"
assert live["dedupe_key"] == 'event_id:"run-1:7"'
assert replay["dedupe_key"] == live["dedupe_key"]
assert live["anchor_event"]["kind"] == "process_prose"
assert live["anchor_event"]["source_event_type"] == "token"
assert live["anchor_event"]["session_id"] == "sid-1"
assert live["anchor_event"]["turn_id"] == "turn-1"
assert live["anchor_event"]["run_id"] == "run-1"
assert live["anchor_event"]["stream_id"] == "stream-1"
assert live["anchor_event"]["seq"] == 7
assert live["anchor_event"]["payload"] == {"text": "hello"}
def test_normalizer_keeps_artifact_side_effect_metadata_and_transport_distinct():
data = _normalizer_snapshot()
settled = data["settled"]
assert settled["classification"] == "metadata"
assert settled["anchor_event"]["kind"] is None
assert settled["anchor_event"]["source_event_type"] == "settled_message"
assert settled["anchor_event"]["payload"]["role"] == "assistant"
assert settled["anchor_event"]["payload"]["_turnUsage"] == {"input_tokens": 3, "output_tokens": 5}
artifact = data["artifact"]
assert artifact["classification"] == "artifact"
assert artifact["anchor_event"]["kind"] == "artifact_reference"
assert artifact["anchor_event"]["payload"] == {"kind": "workspace_file", "path": "result.txt"}
side_effect = data["sideEffect"]
assert side_effect["classification"] == "side_effect"
assert side_effect["anchor_event"]["kind"] is None
assert side_effect["anchor_event"]["payload"] == {"kind": "memory", "name": "saved-state"}
transport = data["transport"]
assert transport["classification"] == "transport"
assert transport["anchor_event"]["kind"] is None
assert transport["anchor_event"]["status"] == "transport_closed"
def test_normalizer_excludes_unknown_sources_and_sanitizes_non_json_data():
data = _normalizer_snapshot()
assert data["unknown"] == {
"classification": "excluded",
"source_event_type": "unknown_future_event",
"anchor_event": None,
"dedupe_key": "",
}
assert data["invalidPayload"]["anchor_event"]["payload"] == {"text": "plain text chunk"}
assert data["invalidPayload"]["dedupe_key"] == 'event_id:"run-1:13"'
assert data["directParsedPayload"]["anchor_event"]["payload"] == {"text": "direct parsed payload"}
assert data["directParsedPayload"]["anchor_event"]["seq"] == 14
def test_normalizer_strips_discriminators_and_promoted_identity_fields_from_payload():
data = _normalizer_snapshot()
event_type_payload = data["eventTypePayload"]
assert event_type_payload["anchor_event"]["source_event_type"] == "tool"
assert event_type_payload["anchor_event"]["payload"] == {
"someField": "x",
"tool_call_id": "tool-1",
}
promoted = data["promotedIdentityPayload"]
assert promoted["anchor_event"]["event_id"] == "run-1:17"
assert promoted["anchor_event"]["session_id"] == "payload-session"
assert promoted["anchor_event"]["turn_id"] == "payload-turn"
assert promoted["anchor_event"]["run_id"] == "payload-run"
assert promoted["anchor_event"]["stream_id"] == "payload-stream"
assert promoted["anchor_event"]["seq"] == 99
assert promoted["anchor_event"]["payload"] == {
"text": "identity fields should be promoted only"
}
def test_normalizer_rejects_pollution_keys_and_inherited_identity_fields():
data = _normalizer_snapshot()
pollution = data["pollutionAttempt"]
assert pollution["dedupe_key"] == 'event_id:"run-1:18"'
assert pollution["anchor_event"]["session_id"] == "sid-1"
assert pollution["anchor_event"]["run_id"] == "run-1"
assert pollution["anchor_event"]["payload"] == {"text": "clean"}
inherited_event = data["inheritedEnvelopeIdentity"]["anchor_event"]
assert inherited_event["event_id"] is None
assert inherited_event["session_id"] == "sid-1"
assert inherited_event["run_id"] == "run-1"
assert inherited_event["stream_id"] == "stream-1"
assert inherited_event["seq"] is None
assert inherited_event["created_at"] is None
assert inherited_event["payload"] == {"text": "own event only"}
inherited_context = data["inheritedContextIdentity"]["anchor_event"]
assert inherited_context["event_id"] is None
assert inherited_context["session_id"] is None
assert inherited_context["turn_id"] is None
assert inherited_context["run_id"] is None
assert inherited_context["stream_id"] is None
assert inherited_context["seq"] is None
assert inherited_context["created_at"] is None
assert inherited_context["payload"] == {"text": "own context only"}
def test_normalizer_marks_non_plain_payload_objects_explicitly():
data = _normalizer_snapshot()
assert data["nonPlainPayload"]["anchor_event"]["payload"] == {
"meta": "[Object]",
"text": "date payload",
}
def test_batch_normalizer_dedupes_live_plus_replay_by_event_envelope():
data = _normalizer_snapshot()
deduped = data["deduped"]
assert [item["dedupe_key"] for item in deduped] == [
'event_id:"run-1:11"',
'event_id:"run-1:12"',
]
assert [item["anchor_event"]["kind"] for item in deduped] == [
"process_prose",
"reasoning",
]
assert deduped[0]["anchor_event"]["payload"] == {"text": "live"}
def test_structured_fallback_dedupe_keys_do_not_collide_on_delimiters():
data = _normalizer_snapshot()
assert data["runSeqCollisionA"] == 'run_seq:["a:b","c"]'
assert data["runSeqCollisionB"] == 'run_seq:["a","b:c"]'
assert data["runSeqCollisionA"] != data["runSeqCollisionB"]
assert data["localCollisionA"] == 'local:["a:b","tool","c","d"]'
assert data["localCollisionB"] == 'local:["a","b:tool","c","d"]'
assert data["localCollisionA"] != data["localCollisionB"]
assert data["localNoSeqKey"] == ""
def test_normalizer_helpers_remain_unwired_from_rendering_and_live_hot_paths():
helper_names = [
"normalizeAssistantTurnAnchorSourceEvent",
"normalizeAssistantTurnAnchorSourceEvents",
"applyAssistantTurnAnchorSourceEvents",
]
for helper in helper_names:
assert helper not in _read(UI_JS)
assert helper not in _read(SESSIONS_JS)
assert helper not in _read(MESSAGES_JS)
messages_src = _read(MESSAGES_JS)
assert "createAssistantTurnAnchorRegistry" in messages_src
assert "applyAssistantTurnAnchorSourceEvent" in messages_src