Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions openadapt_flow/runtime/durable/resume.py
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,23 @@ def _resume_under_lease(
ContinuationCoordinator(run_dir, key=key).bind_approval(
continuation_token, approved
)
resumed_screenshots_may_leave_box = bool(
getattr(replayer, "_screenshots_may_leave_box", False)
)
if resumed_screenshots_may_leave_box and not manifest.screenshots_may_leave_box:
continuation_guard = getattr(replayer, "_durable_continuation_guard", None)
if continuation_guard is None:
raise StateDiverged(
"durable continuation lost its audit-evidence authority guard"
)
updated_manifest = manifest.model_copy(
update={"screenshots_may_leave_box": True}
)
store.cas_manifest(store.model_digest(manifest), updated_manifest)
# Commit the sticky privacy posture to the same continuation authority
# before retained-state checks or resumed execution can call a model.
continuation_guard.acknowledge_progress()
manifest = updated_manifest
if pending.delivery_uncertainty is not None:
last_linear = store.last_checkpoint()
last_program = store.last_program_checkpoint()
Expand Down
43 changes: 43 additions & 0 deletions tests/test_durable_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,49 @@ def test_resume_egress_posture_stays_sticky_across_a_second_resume(tmp_path):
assert manifest.screenshots_may_leave_box is True


def test_resume_persists_new_egress_posture_before_an_early_exit(tmp_path):
"""A crashed resumed leg cannot hide its newly admitted egress posture."""

_report, run_dir, bundle, _backend, verifier = _run_to_halt(tmp_path)
store = CheckpointStore(run_dir)
manifest = store.read_manifest()
assert manifest is not None
assert manifest.screenshots_may_leave_box is False

verifier.refute.clear()
resumed_backend = FakeBackend()

class CrashBeforeResumedStep(Replayer):
def _run_step(self, *args, **kwargs):
del args, kwargs
retained = store.read_manifest()
assert retained is not None
assert retained.screenshots_may_leave_box is True
raise KeyboardInterrupt("simulated early exit before resumed execution")

with pytest.raises(KeyboardInterrupt, match="simulated early exit"):
resume(
run_dir,
CrashBeforeResumedStep(
resumed_backend,
vision=_vision_ok(),
grounder=_EgressGrounder(),
allow_model_grounding=True,
effect_verifier=verifier,
poll_interval_s=0.01,
),
approval=_approval(bundle),
)

retained = store.read_manifest()
assert retained is not None
assert retained.screenshots_may_leave_box is True
assert resumed_backend.actions == []
authority = DurableAuthority(run_dir, store).validate(retained)
assert authority.progress_digest == store.continuation_state_digest()
assert store.read_pending() is not None


def test_durable_resume_projection_failure_keeps_fail_closed_terminal_evidence(
tmp_path,
):
Expand Down