fix(transfer): preserve JSONB and timestamp provenance#2717
Conversation
|
verified this locally and it's a strong, well-evidenced fix — the three restore regressions are real data corruption (decoded JSON-looking scalar strings coerced to objects, flagging for @nicoloboschi to own the merge rather than self-merging — not a fix-quality concern, purely subsystem coordination:
no objection to the change itself — just want the format commitment + the #2691 ordering to be your call. |
|
@Korayem please rebase |
d87d7ad to
a229d3f
Compare
on it |
a229d3f to
e03f8d3
Compare
Rebased onto current |
|
@nicoloboschi check now |
e03f8d3 to
c4624b4
Compare
Native admin connections decode JSON and JSONB columns before export. Preserve already-decoded string scalars while continuing to parse raw JSON strings so export-bank no longer fails on observation scopes such as combined. Co-Authored-By: OpenAI GPT-5 Codex medium <noreply@openai.com>
Whole-bank archives can contain Python string scalars when their export connection registered JSON codecs. Quote decoded scalars before PostgreSQL casts while preserving already-serialized JSON text and decoded objects. Co-Authored-By: OpenAI GPT-5 Codex medium <noreply@openai.com>
Record bank-row JSON encoding in transfer manifests so decoded scalar strings and serialized objects restore without ambiguous parsing. Preserve archived document and observation timestamps during replay. Co-Authored-By: OpenAI GPT-5 Codex medium <noreply@openai.com>
Prove the codec-enabled admin exporter identifies bank rows as decoded so JSON-looking scalar strings cannot silently regress during restore. Co-Authored-By: OpenAI GPT-5 Codex high <noreply@openai.com>
c4624b4 to
ef7f237
Compare
Summary
Whole-bank export/import now preserves the archive's JSONB representation and carried source timestamps instead of guessing or re-deriving them during restore.
The native admin exporter uses decoded JSON/JSONB values, while raw programmatic connections emit serialized JSON text. The manifest now records that provenance explicitly. Import quotes every string in
decodedmode and preserves JSON text inserializedmode, so a decoded scalar that happens to contain valid JSON can no longer be mistaken for an object. Legacy v1 bank archives without the optional field default todecoded, matching the releasedhindsight-admin export-bankproducer.Transfer replay also reapplies
documents.created_atand observationevent_dateinside the existing import transactions. Normal retain/upsert timestamp semantics are unchanged.Production archive evidence
A restore drill against a real production archive exposed all three regressions:
llm_requests: all 70/70 ids restored, but 5 JSON-looking scalaroutputstrings became JSON objects under the heuristic importer. The other 23 string outputs were correct and 42 were null.retain_paramsmatched, but 2/23 effective dates changed because the importer used restore time fordocuments.created_at.event_datevalues that intentionally differed from both source timestamps were re-derived on restore; none of those 18 source values survived.Validation
decodedin the archive manifest.created_atround-trip coverage.event_dateround-trip coverage.uv run pytest tests/test_document_transfer.py tests/test_admin_bank_transfer.py -qq— 23 passed../scripts/hooks/lint.sh— all lints passed.uv run ty check hindsight_api/— all checks passed.