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,
)
| 614 | /// |
| 615 | /// Returns `(parent_manifest_hash, child_manifest_hash)`. |
| 616 | pub fn fork_session( |
| 617 | &self, |
| 618 | parent_session_id: &str, |
| 619 | fork_turn: u32, |
| 620 | child_session_id: &str, |
| 621 | ) -> Result<(Hash, Hash), RepositoryError> { |
| 622 | // Resolve the parent ledger and manifest. |
| 623 | let (parent_record, parent_turns) = |
| 624 | self.get_session_ledger(parent_session_id)?.ok_or_else(|| { |
| 625 | RepositoryError::Database(format!( |
| 626 | "parent session not found: {}", |
| 627 | parent_session_id |
| 628 | )) |
| 629 | })?; |
| 630 | |
| 631 | let last_parent_turn = parent_record.turn_count.saturating_sub(1); |
| 632 | if fork_turn > last_parent_turn && parent_record.turn_count > 0 { |
| 633 | return Err(RepositoryError::Database(format!( |
| 634 | "fork turn {} exceeds parent turn {} for session {}", |
| 635 | fork_turn, last_parent_turn, parent_session_id |
| 636 | ))); |
| 637 | } |
| 638 | |
| 639 | let parent_manifest = self |
| 640 | .publish_session_manifest(parent_session_id) |
| 641 | .map_err(|e| RepositoryError::Database(format!("parent manifest: {}", e)))?; |
| 642 | |
| 643 | // Seed the child session record with the inherited ledger prefix: |
| 644 | // turns 0..=fork_turn from the parent. The child reuses the parent's |
| 645 | // immutable turn rows — we copy the references, not the provenance. |
| 646 | let inherited: Vec<atomic_core::change::session::SessionTurn> = parent_turns |
| 647 | .into_iter() |
| 648 | .filter(|t| t.turn_number <= fork_turn) |
| 649 | .map(|mut t| { |
| 650 | t.session_id = child_session_id.to_string(); |
| 651 | t |
| 652 | }) |
| 653 | .collect(); |
| 654 | |
| 655 | let child_json_path = self |
| 656 | .dot_dir |
| 657 | .join("sessions") |
| 658 | .join(format!("{}.json", child_session_id)) |
| 659 | .to_string_lossy() |
| 660 | .to_string(); |
| 661 | |
| 662 | let mut txn = self |
| 663 | .pristine |
| 664 | .write_txn() |
| 665 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 666 | for turn in &inherited { |
| 667 | txn.index_inherited_turn(child_session_id, &child_json_path, turn) |
| 668 | .map_err(|e| RepositoryError::Database(e.to_string()))?; |
| 669 | } |
| 670 | // Create the child record even when no turns are inherited. |
| 671 | if inherited.is_empty() { |
| 672 | txn.index_empty_session( |
| 673 | child_session_id, |