strop-engine 0.31.1

strop editor engine: documents, grammar dispatch, services, sessions — no terminal
Documentation
use super::*;
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RecoveryGuard {
    pub expected: LocatedObservation,
}
impl RecoveryGuard {
    pub(super) fn accepts(&self, batch: &strop_fs::batch::PreparedBatch) -> bool {
        batch
            .steps
            .iter()
            .filter_map(|step| step.source.as_ref())
            .any(|source| {
                source.location == self.expected.location
                    && source
                        .value
                        .as_ref()
                        .zip(self.expected.value.as_ref())
                        .is_some_and(|(now, before)| now.same_metadata(before))
            })
    }
}
impl Editor {
    pub(super) fn filesystem_receipt_key(
        &self,
        argument: &str,
    ) -> Result<(WorkerId, usize), String> {
        let (operation, step) = argument
            .split_once(' ')
            .ok_or("specify OPERATION STEP from :fs operations")?;
        let id: u64 = operation
            .parse()
            .map_err(|_| "invalid filesystem operation ID")?;
        let step = step
            .parse::<usize>()
            .ok()
            .and_then(|step| step.checked_sub(1))
            .ok_or("step numbers start at 1")?;
        let operation = self
            .filesystem
            .history
            .iter()
            .find(|attempt| attempt.ticket.request.get() == id)
            .map(|attempt| attempt.ticket.request)
            .ok_or("filesystem operation not found")?;
        Ok((operation, step))
    }
    pub(super) fn recover_filesystem_step(
        &mut self,
        operation: WorkerId,
        step: usize,
    ) -> Result<(), String> {
        let attempt = self
            .filesystem
            .history
            .iter()
            .find(|attempt| attempt.ticket.request == operation)
            .ok_or("filesystem operation not found")?;
        let receipt = attempt
            .receipts
            .get(step)
            .ok_or("filesystem step not found")?;
        let StepOutcome::Committed {
            recovery,
            publication,
            destination_after,
            ..
        } = &receipt.outcome
        else {
            return Err("only a confirmed committed step can prepare recovery; verify unconfirmed outcomes first".into());
        };
        let source = receipt.operation.source.as_ref();
        let destination = receipt.operation.destination.as_ref();
        if receipt.operation.intent.kind == OperationKind::Remove {
            return Err(
                "permanent removal has no filesystem undo; detached drafts remain in memory".into(),
            );
        }
        let owned = publication
            .as_ref()
            .ok_or("receipt has no owned publication witness")?;
        let expected_kind = match receipt.operation.intent.kind {
            OperationKind::CreateDirectory => Some(strop_workspace::EntryKind::Directory),
            OperationKind::CreateFile | OperationKind::Copy => {
                Some(strop_workspace::EntryKind::File)
            }
            _ => source
                .and_then(|value| value.value.as_ref())
                .map(|value| value.kind),
        };
        let matches = |value: &&strop_workspace::Observation| {
            owned.matches_metadata(value) && Some(value.kind) == expected_kind
        };
        let expected = attempt
            .publication
            .get(step)
            .and_then(|value| value.as_ref())
            .filter(matches)
            .or_else(|| destination_after.as_ref().filter(matches))
            .cloned();
        let expected_content = owned.content;
        let (kind, from, to) = match receipt.operation.intent.kind {
            OperationKind::Rename => (
                OperationKind::Rename,
                destination.map(|value| value.location.clone()),
                source.map(|value| value.location.clone()),
            ),
            OperationKind::Trash => (
                OperationKind::Restore,
                recovery.clone(),
                source.map(|value| value.location.clone()),
            ),
            OperationKind::Restore => (
                OperationKind::Trash,
                destination.map(|value| value.location.clone()),
                None,
            ),
            OperationKind::CreateFile | OperationKind::CreateDirectory | OperationKind::Copy => {
                if receipt.operation.intent.kind != OperationKind::CreateDirectory
                    && expected_content.is_none()
                {
                    return Err("receipt lacks the intended content witness; recovery cannot infer ownership".into());
                }
                (
                    OperationKind::Remove,
                    destination.map(|value| value.location.clone()),
                    None,
                )
            }
            OperationKind::Remove => return Err("permanent removal has no filesystem undo".into()),
        };
        let from = from.ok_or("receipt has no recoverable source location")?;
        let expected = expected.ok_or(
            "receipt lacks the original publication witness; recovery cannot infer ownership",
        )?;
        let guard = RecoveryGuard {
            expected: LocatedObservation {
                location: from.clone(),
                value: Some(expected),
            },
        };
        self.prepare_filesystem(
            vec![OperationIntent {
                kind,
                source: Some(from),
                destination: to,
                copy_version: CopyVersion::Stored,
                expected_content,
            }],
            Some(guard),
        )
    }
}