From e9055ad2baeddd9e184b2b0a48f1f855480b09b1 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 30 Jul 2026 19:13:57 -0400 Subject: [PATCH] Restore persisted ObjectState timeline state on load --- pyproject.toml | 2 +- src/objectstate/__init__.py | 2 +- src/objectstate/object_state_registry.py | 11 +++++++++- tests/test_history_persistence.py | 26 ++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c7a3442..55d3968 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "objectstate" -version = "1.0.23" +version = "1.1.0" description = "Generic lazy dataclass configuration framework with dual-axis inheritance and contextvars-based resolution" authors = [{name = "Tristan Simas", email = "tristan.simas@mail.mcgill.ca"}] license = {text = "MIT"} diff --git a/src/objectstate/__init__.py b/src/objectstate/__init__.py index ed07fac..53174f6 100644 --- a/src/objectstate/__init__.py +++ b/src/objectstate/__init__.py @@ -214,7 +214,7 @@ 'ObjectStateEditSession', ] -__version__ = "1.0.23" +__version__ = "1.1.0" __author__ = 'OpenHCS Team' __description__ = 'Generic configuration framework for lazy dataclass resolution' diff --git a/src/objectstate/object_state_registry.py b/src/objectstate/object_state_registry.py index de9787f..5da7605 100644 --- a/src/objectstate/object_state_registry.py +++ b/src/objectstate/object_state_registry.py @@ -2172,7 +2172,7 @@ def save_history_to_file(cls, filepath: str) -> None: @classmethod def load_history_from_file(cls, filepath: str) -> None: - """Load a trusted type-preserving ObjectState history document. + """Load and materialize a trusted ObjectState history document. Args: filepath: Path to the binary ObjectState history document. @@ -2181,4 +2181,13 @@ def load_history_from_file(cls, filepath: str) -> None: with open(filepath, "rb") as history_file: cls.import_history_from_dict(dill.load(history_file)) + restored_at_head = cls._current_head is None + snapshot_id = cls._current_snapshot_id() + if snapshot_id is not None: + if not cls.time_travel_to_snapshot(snapshot_id): + raise RuntimeError( + "Loaded ObjectState history could not restore its current snapshot." + ) + if restored_at_head: + cls._current_head = None logger.info(f"⏱️ Loaded {len(cls._snapshots)} snapshots from {filepath}") diff --git a/tests/test_history_persistence.py b/tests/test_history_persistence.py index 234ff9f..d342c18 100644 --- a/tests/test_history_persistence.py +++ b/tests/test_history_persistence.py @@ -58,3 +58,29 @@ def test_history_file_round_trips_typed_values(tmp_path: Path) -> None: assert parameters["mode"] is HistoryMode.FAST assert parameters["transform"] is identity assert history_path.stat().st_size > 0 + + +def test_history_file_materializes_current_snapshot_into_registered_state( + tmp_path: Path, +) -> None: + state = ObjectState( + HistoryConfig( + output_path=tmp_path / "before", + mode=HistoryMode.FAST, + transform=identity, + ), + scope_id="restored_history", + ) + ObjectStateRegistry.register(state, _skip_snapshot=True) + ObjectStateRegistry.record_snapshot("baseline", scope_id=state.scope_id) + state.update_parameter("output_path", tmp_path / "persisted") + ObjectStateRegistry.record_snapshot("persisted edit", scope_id=state.scope_id) + history_path = tmp_path / "history.objectstate" + ObjectStateRegistry.save_history_to_file(str(history_path)) + + state.update_parameter("output_path", tmp_path / "after-save") + ObjectStateRegistry.load_history_from_file(str(history_path)) + + assert state.parameters["output_path"] == tmp_path / "persisted" + assert state.to_resolved_object().output_path == tmp_path / "persisted" + assert ObjectStateRegistry.get_current_snapshot_index() == -1