Create a forked session from a parent at a turn boundary. The child inherits the parent's turn records through `fork_turn` as an immutable ledger prefix. No provenance graphs or session rows are copied — the child manifest references the parent by content hash. Returns `(parent_manifest_hash, child_manifest_hash)`.
(
&self,
parent_session_id: &str,
fork_turn: u32,
child_session_id: &str,
)
| 634 | /// |
| 635 | /// Returns `(parent_manifest_hash, child_manifest_hash)`. |
| 636 | pub fn fork_session( |
| 637 | &self, |
| 638 | parent_session_id: &str, |
| 639 | fork_turn: u32, |
| 640 | child_session_id: &str, |
| 641 | ) -> Result<(Hash, Hash), RepositoryError> { |
| 642 | // Resolve the parent ledger and manifest. |
| 643 | let (parent_record, parent_turns) = |
| 644 | self.get_session_ledger(parent_session_id)?.ok_or_else(|| { |
| 645 | RepositoryError::Database(format!( |
| 646 | "parent session not found: {}", |
| 647 | parent_session_id |
| 648 | )) |
| 649 | })?; |
| 650 | |
| 651 | let last_parent_turn = parent_record.turn_count.saturating_sub(1); |
| 652 | if fork_turn > last_parent_turn && parent_record.turn_count > 0 { |
| 653 | return Err(RepositoryError::Database(format!( |
| 654 | "fork turn {} exceeds parent turn {} for session {}", |
| 655 | fork_turn, last_parent_turn, parent_session_id |
| 656 | ))); |
| 657 | } |
| 658 | |
| 659 | let parent_manifest = self |
| 660 | .publish_session_manifest(parent_session_id) |
| 661 | .map_err(|e| RepositoryError::Database(format!("parent manifest: {}", e)))?; |
| 662 | |
| 663 | // Seed the child session record with the inherited ledger prefix: |
| 664 | // turns 0..=fork_turn from the parent. The child reuses the parent's |
| 665 | // immutable turn rows — we copy the references, not the provenance. |
| 666 | let inherited: Vec<atomic_core::change::session::SessionTurn> = parent_turns |
| 667 | .into_iter() |
| 668 | .filter(|t| t.turn_number <= fork_turn) |
| 669 | .map(|mut t| { |
| 670 | t.session_id = child_session_id.to_string(); |
| 671 | t |
| 672 | }) |
| 673 | .collect(); |
| 674 | |
| 675 | let child_json_path = self |
| 676 | .dot_dir |
| 677 | .join("sessions") |
| 678 | .join(format!("{}.json", child_session_id)) |
| 679 | .to_string_lossy() |
| 680 | .to_string(); |
| 681 | |
| 682 | let mut txn = self |
| 683 | .pristine |
| 684 | .write_txn() |
| 685 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 686 | for turn in &inherited { |
| 687 | txn.index_inherited_turn(child_session_id, &child_json_path, turn) |
| 688 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 689 | } |
| 690 | // Create the child record even when no turns are inherited. |
| 691 | if inherited.is_empty() { |
| 692 | txn.index_empty_session( |
| 693 | child_session_id, |