feat(vm-core): restore move-only Continuation — eliminate Arc/share_handle (K1, SPEC-VM-021)#475
Merged
proboscis merged 3 commits intoJun 12, 2026
Conversation
…andle (K1, SPEC-VM-021) Restore the constructive move-only law for Continuation (SPEC-VM-021 invariants 1-4) that was eroded by PR #404's Arc<Mutex<Option<>>> backup mechanism. The #404 exception-propagation semantics are preserved using a Py<PyK> handle (a Python reference to the K object, not a continuation copy). Changes: - continuation.rs: Remove Arc<Mutex<Option<DetachedFiberChain>>> → plain Option<DetachedFiberChain>. Remove share_handle(). - vm.rs: Replace pending_handler_chain_backup: Option<Continuation> → pending_handler_k_handle: Option<Py<PyK>>. - frame.rs: Replace chain_backup → handler_k_handle: Option<Py<PyK>>. - step.rs: Add is_generator_handler() branching in eval_perform and eval_perform_with_k. Generator handlers (Python @Do) use PyK wrapping + backup handle for exception recovery. Synchronous handlers (Rust CallableRef) receive Value::Continuation directly with no backup. Fix stale-backup-leak: only restore handle for DoCtrl::Expand results. - value.rs: Add Callable::is_generator_handler() trait method. - python_generator_stream.rs: Override is_generator_handler()=true for PythonCallable. - invariants.rs: Rewrite to scan Py<PyK> handles instead of Arc cells. - vm_tests.rs: Add stale-backup-leak regression test. - test_move_semantics_architecture.py: Rewrite guard layer to assert SPEC-VM-021 invariants (6 tests, all passing). - decision-records.md: D19 documenting the erosion and restoration. - constraint-graph.md: Update B3 row evidence with new line numbers. Fixes: vm-k1-restore-move-only-continuation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ove-only-continuation/run-20260612-014853
…est green
The guard layer in packages/doeff-vm-core/tests/ was CI-invisible:
not in pytest testpaths. This is the dead-layer pattern that buried
the K1 erosion for 6 weeks.
Changes:
pyproject.toml — add packages/doeff-vm-core/tests to testpaths.
Deleted files (guarding removed architecture — vacuously true):
test_exception_enrichment_architecture.py
Both tests reference vm_trace.rs which no longer exists.
test_interceptor_chain_architecture.py
Sole test references fn current_interceptor_chain which was removed.
Deleted tests within surviving files:
test_dispatch_id_elimination_architecture.py
- test_trace_state_runtime_has_no_preserved_dispatch_side_buffers
trace_state.rs was removed in prior refactors.
test_dispatch_origin_architecture.py (5 tests removed):
- test_vm_runtime_has_no_dispatch_side_table_left
References dispatch.rs and dispatch_state.rs (both removed).
- test_current_interceptor_chain_hot_path_skips_dispatch_origin_view_materialization
fn current_interceptor_chain no longer exists.
- test_current_handler_identity_hot_path_skips_full_handler_chain_materialization
References vm_trace.rs (removed).
- test_start_dispatch_hot_path_skips_full_handler_chain_materialization
fn start_dispatch no longer exists.
- test_dispatch_resume_does_not_mutate_current_handler_caller_chain
fn handle_dispatch_resume no longer exists.
test_frame_based_traceback_architecture.py (3 tests removed):
- test_trace_state_has_no_active_chain_assembly_state_wrapper
trace_state.rs removed.
- test_dispatch_display_lives_on_frame_snapshots_not_frame_dispatch_side_map
trace_state.rs removed.
- test_capture_module_has_no_capture_event_enum
capture.rs removed.
test_lexical_scope_architecture.py (1 test removed):
- test_scheduler_spawn_path_no_longer_requests_get_handlers
Rust scheduler module removed; scheduler is Python now.
Fixed tests:
test_lexical_scope_architecture.py
- test_handler_lookup_walks_parent_chain: pattern updated from
"let next = seg.parent;" to "cursor = seg.parent;" matching
actual dispatch.rs code.
- test_spawn_reuses_live_fiber_chain_without_scope_cloning: removed
stale ReturnToContinuation assertion (dispatch.rs no longer uses
this variant for spawn).
test_vm_module_split.py
- Updated module list: removed vm_trace.rs, added handler.rs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Restores the move-only
Continuationlaw (SPEC-VM-021) that was eroded by PR #404'sArc<Mutex<Option<>>>+share_handle()mechanism. Preserves #404's exception-propagation semantics via a lighter-weightPy<PyK>handle approach.Core changes (K1 coupling core — B2 ⇔ B3 ⇔ B6 ⇔ B10 ⇔ B12):
Continuation.chainis now plainOption<DetachedFiberChain>— no Arc, no Mutex.share_handle()removed entirely.take()isself.chain.take()(Option::take, not lock+take).is_generator_handler()trait method toCallable— differentiates Python generator handlers (need PyK backup for exception recovery across yields) from synchronous Rust handlers (consume continuation directly viaValue::Continuation).eval_perform/eval_perform_with_kbranch onis_generator_handler():@do): wraps k in PyK, stashesPy<PyK>handle for exception recovery, passesValue::Opaque(PyShared(PyK))tocall_handlerValue::Continuation(k)directly, no backup neededpending_handler_chain_backup: Option<Continuation>→pending_handler_k_handle: Option<Py<PyK>>Frame::Program.chain_backup→handler_k_handle: Option<Py<PyK>>PythonCallable::is_generator_handler()returnstruePy<PyK>handles instead of Arc cellsBug fix: Stale-backup-leak — when a generator handler returns non-Expand DoCtrl, the backup handle now drops instead of persisting to corrupt the next Program frame.
Documentation: Decision record D19, constraint graph B3 row updated.
Acceptance Criteria Evidence
1. Move-only Continuation restored
continuation.rs:chain: Option<DetachedFiberChain>(line ~252), noArc/Mutexanywhereshare_handle()removed — grep confirms zero occurrencestake()usesself.chain.take()(line ~277)2. PR #404 exception-propagation preserved
All 6 regression tests pass:
3. Guard layer tests resurrected (6 tests)
4. Stale-backup-leak regression test
5. Full test suites
6. Decision record & constraint graph
docs/crystallization/decision-records.mddocs/crystallization/constraint-graph.mdFiles Changed (11 files, +549 −335)
continuation.rsvalue.rsis_generator_handler()trait methodstep.rsvm.rspending_handler_k_handle: Option<Py<PyK>>frame.rshandler_k_handle: Option<Py<PyK>>python_generator_stream.rsis_generator_handler() → trueinvariants.rsvm_tests.rstest_move_semantics_architecture.pydecision-records.mdconstraint-graph.mdReference
Issue:
vm-k1-restore-move-only-continuation🤖 Generated with Claude Code