fix(session-export): lineage timings span the merged messages; import ignores derived size
export_session_lineage spread segments[-1] over the merged dict, so the top-level `timings` described only the last compression segment while `messages` spanned the whole lineage — a reader would see a 500 ms wall clock over a lineage that ran for hours. Compute the block over the merged message list (segments keep their own). _validate_import_session measured the raw session JSON, so the derived `timings.intervals` (one entry per message pair) counted toward the 5 MiB per-session limit and could reject a long lineage whose actual content fits. Strip `timings` before measuring; it is rebuilt from the messages on the next export anyway.
This commit is contained in:
@@ -82,7 +82,7 @@ _PROMPT_RESOLVED_SQL = "COALESCE(sp.prompt, s.system_prompt) AS _system_prompt_r
|
||||
|
||||
|
||||
def _export_timings(messages: List[Dict[str, Any]], session_id: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Text-free timing evidence for a session export (port of nearai/ironclaw#7735).
|
||||
"""Text-free timing evidence for a session export.
|
||||
|
||||
Exports get attached to bug reports; a reader should not have to infer from raw
|
||||
timestamps whether a slow turn was one long model gap or many small tool
|
||||
@@ -297,7 +297,7 @@ class SessionPortabilityMixin:
|
||||
return {
|
||||
**segments[-1], "segments": segments,
|
||||
"lineage_session_ids": [seg["id"] for seg in segments], "message_count": len(messages),
|
||||
"messages": messages,
|
||||
"messages": messages, "timings": _export_timings(messages, session_id),
|
||||
}
|
||||
|
||||
def export_all(self, source: str = None) -> List[Dict[str, Any]]:
|
||||
@@ -494,7 +494,10 @@ class SessionPortabilityMixin:
|
||||
if any(not isinstance(msg, dict) for msg in messages):
|
||||
raise ValueError("messages must contain only objects")
|
||||
try:
|
||||
session_bytes = len(json.dumps(raw, ensure_ascii=False, separators=(",", ":")).encode("utf-8"))
|
||||
# `timings` is derived from the messages at export time and rebuilt on the next export;
|
||||
# it must not eat into the size budget of the content it merely describes.
|
||||
measured = {k: v for k, v in raw.items() if k != "timings"}
|
||||
session_bytes = len(json.dumps(measured, ensure_ascii=False, separators=(",", ":")).encode("utf-8"))
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError("session must be JSON serializable") from None
|
||||
if session_bytes > self._IMPORT_MAX_SESSION_BYTES:
|
||||
|
||||
Reference in New Issue
Block a user