fix: address 4th review - don't clear normalized parent, guard malformed node

- Dual-write sends parent_id only when parentId is known; parent_id=None
  would wipe a correct parent link on the normalized row from a stale
  embedded snapshot (the inverse of last round's repair gap).
- Treat a non-dict existing node as the synthesis path instead of letting
  {**existing} TypeError, consistent with the container guard.
- build_assistant_message_update: structural keys last so callers can't
  override id/role.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Classic298 2026-05-16 00:00:51 +02:00
parent b7bd853e38
commit 466883ad18
2 changed files with 14 additions and 11 deletions

View file

@ -584,13 +584,15 @@ class ChatTable:
return node
existing = messages.get(message_id)
if existing is not None:
if isinstance(existing, dict):
# {**existing, **message}: omitted keys keep existing, explicit
# values (incl. parentId=None, childrenIds=[]) win.
messages[message_id] = enforce_node_invariants({**existing, **message})
else:
# Node missing: a concurrent whole-chat write likely dropped the
# placeholder. Warn only when the payload is itself partial.
if existing is not None:
log.warning(f'upsert: discarding malformed existing node {message_id}')
# Node missing/unusable: a concurrent whole-chat write likely
# dropped the placeholder. Warn only when the payload is partial.
is_partial = not all(k in message for k in ('id', 'parentId', 'childrenIds', 'role'))
if is_partial:
log.warning(
@ -623,16 +625,18 @@ class ChatTable:
chat['history'] = history
# Dual-write to chat_message table. Alias camelCase parentId to
# snake_case so the normalized row's parent link is repaired too
# (callee gates its update on 'parent_id').
# Send parent_id only when known; parent_id=None would clear a real
# link on the normalized row from a possibly-stale snapshot.
node = history['messages'][message_id]
cm_data = {**node}
if node.get('parentId') is not None:
cm_data['parent_id'] = node['parentId']
try:
await ChatMessages.upsert_message(
message_id=message_id,
chat_id=id,
user_id=user_id,
data={**node, 'parent_id': node.get('parentId')},
data=cm_data,
)
except Exception as e:
log.warning(f'Failed to write to chat_message table: {e}')

View file

@ -3583,13 +3583,12 @@ async def streaming_chat_response_handler(response, ctx):
model_id = form_data.get('model', '')
def build_assistant_message_update(**fields):
# Only placeholder-repair fields, and each only when authoritative,
# so the upsert merge can't clobber an existing node (chats.py
# synthesizes what's missing).
# Placeholder-repair fields only, each when authoritative;
# structural keys last so callers can't override them.
update = {
**fields,
'id': metadata['message_id'],
'role': 'assistant',
**fields,
}
if model_id:
update['model'] = model_id