harden(#4411): read-side anchor-scene ref-ambiguity guard (Opus finding)

Mirror the write-side _find_anchor_scene_message ambiguity guard on the read side:
_hydrate_anchor_activity_scenes now counts ref occurrences and skips ref-based
attachment for any ref resolving to >1 assistant message (falls through to the
positional index match), so a duplicate content-digest ref can't double-render the
same worklog scene. Low-severity defense-in-depth on the crown-jewel render surface.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
This commit is contained in:
nesquena-hermes committed 2026-06-20 22:05:01 +00:00
1 parent 001148d208
commit 81a4945be8
2 files changed
+48 -1

No files matched your search

+15 -1
View File
@@ -3490,11 +3490,25 @@ def _hydrate_anchor_activity_scenes(messages, records, *, message_offset=0, tool
if idx is not None:
by_index[idx] = record
out = list(messages)
# Read-side ref-ambiguity guard (parity with the write-side
# _find_anchor_scene_message, which returns None when a ref matches >1
# message). If two assistant messages ever share a ref (byte-identical
# whitespace-normalized content + identical _ts), attaching the same scene
# to both would render duplicate worklog groups. Count ref occurrences and
# fall through to the index-based match (which is positional, unambiguous)
# for any ref that resolves to more than one assistant message.
_ref_counts: dict[str, int] = {}
for _m in messages:
if isinstance(_m, dict) and _m.get("role") == "assistant":
_r = _assistant_anchor_scene_message_ref(_m)
if _r:
_ref_counts[_r] = _ref_counts.get(_r, 0) + 1
for local_idx, message in enumerate(messages):
if not isinstance(message, dict) or message.get("role") != "assistant":
continue
absolute_idx = int(message_offset or 0) + local_idx
record = by_ref.get(_assistant_anchor_scene_message_ref(message))
_msg_ref = _assistant_anchor_scene_message_ref(message)
record = by_ref.get(_msg_ref) if _ref_counts.get(_msg_ref, 0) <= 1 else None
if not record:
candidate = by_index.get(absolute_idx)
if candidate and _anchor_scene_candidate_matches_scene(message, candidate.get("scene") or {}):
+33
View File
@@ -187,6 +187,39 @@ def test_anchor_scene_persistence_rejects_cross_profile_write(tmp_path, monkeypa
)
def test_anchor_scene_hydration_skips_ambiguous_ref_match(monkeypatch):
"""#4411 defense-in-depth: when two assistant messages share a ref (byte-identical
content + identical _ts), the read-side hydration must NOT attach the same scene to
both (which would render duplicate worklog groups) — mirroring the write-side
_find_anchor_scene_message ambiguity guard. The ambiguous ref falls through to the
positional index match instead."""
from api import routes
# Two assistant messages that normalize to the SAME ref.
messages = [
{"role": "assistant", "content": "dup answer", "timestamp": 5.0},
{"role": "assistant", "content": "dup answer", "timestamp": 5.0},
]
ref = routes._assistant_anchor_scene_message_ref(messages[0])
assert ref == routes._assistant_anchor_scene_message_ref(messages[1]), "refs must collide for this test"
# A single record keyed by that ambiguous ref, index-targeted at message 0.
records = {
ref: {
"version": "anchor_activity_scene_record_v1",
"message_index": 0,
"message_ref": ref,
"scene": {"version": "activity_scene_v1", "activity_rows": [], "final_answer": "dup answer"},
}
}
out = routes._hydrate_anchor_activity_scenes(messages, records, message_offset=0)
attached = [("_anchor_activity_scene" in m) for m in out]
# The ambiguous ref must NOT fan the scene out to BOTH messages.
assert attached.count(True) <= 1, (
f"ambiguous ref must not double-attach the scene; got {attached}"
)
def test_anchor_scene_hydration_rejects_stale_index_fallback_when_final_answer_mismatches():
from api import routes