Skip to main content

app_kit/storage/
mod.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::future::Future;
3use std::num::NonZeroUsize;
4use std::pin::Pin;
5use std::sync::{Arc, Mutex};
6
7use serde::de::DeserializeOwned;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use storage_kit::state::record::{
11    StateKey, StateKeyPrefix, StateListRequest, StateRecord, StateRecordSnapshot,
12};
13use storage_kit::{StateStoreBackend, StateStoreError, StateTransactionBackend};
14use uuid::Uuid;
15
16use crate::app::{AppRecord, AppTrace, MAX_AGENTS, MAX_RUNS, ShutdownRecord};
17use crate::workflow::MAX_FRONTIER;
18use crate::workflow::definition::{CompiledDefinition, SchemaText};
19use crate::workflow::run::{
20    CheckpointPhaseRecord, ControlRecordState, InteractionSettlement, MAX_INTERACTION_SETTLEMENTS,
21    MAX_LIST_LIMIT, RunKey, RunRecord, RuntimeOperationFailure,
22};
23use crate::workflow::{
24    CheckpointKind, CheckpointSelector, HistoryCursor, RunId, RunSelector, StagedOutcomeRecord,
25    WorkflowId,
26};
27use crate::workspace::{MAX_WORKSPACES, WorkspaceRecord};
28use crate::{AppError, WorkspaceId};
29
30mod migration;
31mod occurrence;
32
33pub(crate) use migration::AppMigration;
34pub(crate) use occurrence::RunCopy;
35use occurrence::{RunIndex, RunProjection};
36
37pub(crate) const EPOCH: &str = "app/v7";
38const LEGACY_EPOCH: &str = "app/v6";
39const NAMESPACE: &str = "app-kit";
40const PAGE_SIZE: usize = 256;
41pub(crate) const MAX_RECORD_BYTES: usize = 4 * 1_048_576;
42
43pub(crate) struct AppStore<CS: StateStoreBackend> {
44    inner: CS,
45    decoded_runs: Mutex<Vec<DecodedRun>>,
46    retired_receipts: Mutex<Vec<String>>,
47}
48
49/// Intrinsic decoding evidence only; callers still freshly read every referenced row.
50struct DecodedRun {
51    source: Arc<Value>,
52    pages: Vec<Arc<Value>>,
53    record: RunRecord,
54}
55
56impl DecodedRun {
57    fn matches(&self, source: &Arc<Value>, pages: &[Arc<Value>]) -> bool {
58        Arc::ptr_eq(&self.source, source)
59            && self.pages.len() == pages.len()
60            && self
61                .pages
62                .iter()
63                .zip(pages)
64                .all(|(left, right)| Arc::ptr_eq(left, right))
65    }
66}
67
68pub(crate) struct AppMutation {
69    id: String,
70    kind: AppMutationKind,
71}
72
73pub(crate) enum StorageFailure {
74    Recoverable(AppError),
75    Permanent(AppError),
76    CleanupFailed(AppError),
77}
78
79pub(crate) enum AppCommitOutcome {
80    Applied,
81    Rejected(StorageFailure),
82    Unknown(StorageFailure),
83}
84
85enum AppMutationKind {
86    ClaimApp {
87        fence: AppClaimFence,
88        app: AppStorageRecord,
89        definitions: Vec<AppStorageRecord>,
90    },
91    AdvanceLifecycle {
92        expected_app_revision: u64,
93        generation: u64,
94        shutdown: ShutdownRecord,
95    },
96    AdmitWorkspace {
97        expected_app_revision: u64,
98        app: AppStorageRecord,
99        workspace: AppStorageRecord,
100    },
101    AdvanceWorkspace {
102        expected_app_revision: u64,
103        expected_workspace_revision: u64,
104        workspace: AppStorageRecord,
105    },
106    AdvanceWorkspaceWithApp {
107        expected_app_revision: u64,
108        expected_workspace_revision: u64,
109        app: AppStorageRecord,
110        workspace: AppStorageRecord,
111    },
112    DeleteWorkspace {
113        expected_app_revision: u64,
114        expected_workspace_revision: u64,
115        workspace: RecordKey,
116    },
117    AdmitRun {
118        expected_app_revision: u64,
119        expected_workspace_revision: u64,
120        app: AppStorageRecord,
121        workspace: AppStorageRecord,
122        run: AppStorageRecord,
123        head: AppStorageRecord,
124        source: Option<Box<RunCopy>>,
125    },
126    AdvanceRun {
127        expected_app_revision: u64,
128        expected_run_revision: u64,
129        app: AppStorageRecord,
130        run: AppStorageRecord,
131        predecessor: RunRecord,
132        children: Vec<(RunKey, String)>,
133    },
134    AdmitChildren {
135        expected_app_revision: u64,
136        expected_workspace_revision: u64,
137        expected_parent_revision: u64,
138        app: AppStorageRecord,
139        workspace: AppStorageRecord,
140        parent: AppStorageRecord,
141        predecessor: RunRecord,
142        children: Vec<(AppStorageRecord, AppStorageRecord, Option<RunCopy>)>,
143    },
144    RetainRun {
145        expected_app_revision: u64,
146        expected_run_revision: u64,
147        app: AppStorageRecord,
148        run: AppStorageRecord,
149    },
150    ReadmitRun {
151        expected_app_revision: u64,
152        expected_workspace_revision: u64,
153        expected_run_revision: u64,
154        app: AppStorageRecord,
155        workspace: AppStorageRecord,
156        run: AppStorageRecord,
157        predecessor: RunRecord,
158    },
159    SealRun {
160        expected_app_revision: u64,
161        expected_run_revision: u64,
162        run: AppStorageRecord,
163        parent: Option<(AppStorageRecord, u64)>,
164    },
165    DeleteRunPage {
166        expected_app_revision: u64,
167        expected_run_revision: u64,
168        head: AppStorageRecord,
169        keys: Vec<RecordKey>,
170        complete: bool,
171    },
172}
173
174enum AppClaimFence {
175    Missing,
176    Revision(u64),
177}
178
179enum AppStorageRecord {
180    Value {
181        key: RecordKey,
182        value: Value,
183    },
184    Checkpoint {
185        key: RecordKey,
186        value: Value,
187        record: Box<RunRecord>,
188        pages: Vec<StateRecord>,
189    },
190}
191
192#[derive(Deserialize, Serialize)]
193#[serde(deny_unknown_fields)]
194struct SettlementPages {
195    count: usize,
196    pages: Vec<u64>,
197}
198
199#[derive(Serialize)]
200struct SettlementPage<'record> {
201    epoch: String,
202    workspace_id: WorkspaceId,
203    workflow_id: WorkflowId,
204    id: RunId,
205    occurrence: String,
206    index: usize,
207    sequence: u64,
208    entries: &'record [InteractionSettlement],
209}
210
211#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
212enum RecordKey {
213    App,
214    Definition {
215        workflow: WorkflowId,
216        revision: String,
217    },
218    Workspace(WorkspaceId),
219    Run {
220        workspace: WorkspaceId,
221        workflow: WorkflowId,
222        run: RunId,
223        occurrence: String,
224    },
225    Checkpoint {
226        workspace: WorkspaceId,
227        workflow: WorkflowId,
228        run: RunId,
229        occurrence: String,
230        sequence: u64,
231    },
232    Ledger {
233        workspace: WorkspaceId,
234        workflow: WorkflowId,
235        run: RunId,
236        occurrence: String,
237        index: usize,
238        sequence: u64,
239    },
240    ActionObservation {
241        workspace: WorkspaceId,
242        workflow: WorkflowId,
243        run: RunId,
244        occurrence: String,
245        ordinal: u64,
246        sequence: u64,
247    },
248    ActiveRun {
249        workspace: WorkspaceId,
250        workflow: WorkflowId,
251        run: RunId,
252        occurrence: String,
253    },
254    ProjectionTail {
255        workspace: WorkspaceId,
256        workflow: WorkflowId,
257        run: RunId,
258        projection: RunProjection,
259    },
260    ProjectionLink {
261        key: RunKey,
262        projection: RunProjection,
263        sequence: u64,
264    },
265    Receipt(String),
266}
267
268#[derive(Deserialize, Serialize)]
269#[serde(deny_unknown_fields)]
270struct DefinitionRecord {
271    epoch: String,
272    workflow_id: WorkflowId,
273    revision: String,
274    canonical: String,
275}
276
277impl SettlementPages {
278    fn validate(&self, sequence: u64) -> Result<(), AppError> {
279        if self.count > MAX_INTERACTION_SETTLEMENTS
280            || self.pages.len() != self.count.div_ceil(64)
281            || self.pages.iter().any(|published| *published > sequence)
282        {
283            return Err(AppError::Integration(
284                "settlement page references are invalid".into(),
285            ));
286        }
287        Ok(())
288    }
289}
290
291impl SettlementPage<'_> {
292    fn matches(key: &RecordKey, value: &Value) -> bool {
293        let RecordKey::Ledger {
294            workspace,
295            workflow,
296            run,
297            occurrence,
298            index,
299            sequence,
300        } = key
301        else {
302            return false;
303        };
304        value.as_object().is_some_and(|value| value.len() == 8)
305            && value.get("epoch").and_then(Value::as_str) == Some(EPOCH)
306            && value.get("workspace_id").and_then(Value::as_str) == Some(workspace.as_str())
307            && value.get("workflow_id").and_then(Value::as_str) == Some(workflow.as_str())
308            && value.get("id").and_then(Value::as_str) == Some(run.as_str())
309            && value.get("occurrence").and_then(Value::as_str) == Some(occurrence.as_str())
310            && value.get("index").and_then(Value::as_u64) == Some(*index as u64)
311            && value.get("sequence").and_then(Value::as_u64) == Some(*sequence)
312            && value
313                .get("entries")
314                .and_then(Value::as_array)
315                .is_some_and(|entries| !entries.is_empty() && entries.len() <= 64)
316    }
317}
318
319impl DefinitionRecord {
320    fn key(&self) -> RecordKey {
321        RecordKey::Definition {
322            workflow: self.workflow_id.clone(),
323            revision: self.revision.clone(),
324        }
325    }
326
327    fn validate(&self) -> Result<(), AppError> {
328        let compiled = CompiledDefinition::parse(&self.canonical)
329            .map_err(|_| AppError::Integration("durable workflow definition is corrupt".into()))?;
330        if self.epoch != EPOCH
331            || self.workflow_id.as_str() != compiled.id()
332            || self.revision != compiled.revision()
333            || self.canonical != compiled.fingerprint()
334        {
335            return Err(AppError::Integration(
336                "durable workflow definition identity is inconsistent".into(),
337            ));
338        }
339        Ok(())
340    }
341}
342
343#[derive(Deserialize, Serialize)]
344#[serde(deny_unknown_fields)]
345struct RunHead {
346    epoch: String,
347    id: RunId,
348    workspace_id: WorkspaceId,
349    workflow_id: WorkflowId,
350    definition_revision: String,
351    child_revisions: BTreeMap<WorkflowId, String>,
352    agent_revisions: BTreeMap<String, String>,
353    creation: String,
354    sequence: u64,
355    unresolved_failures: BTreeMap<u64, u64>,
356    retention: RunRetention,
357}
358
359#[derive(Deserialize, Serialize)]
360#[serde(tag = "state", rename_all = "snake_case", deny_unknown_fields)]
361enum RunRetention {
362    Active {},
363    Retained {},
364    Deleting { after: Option<String> },
365}
366
367#[derive(Deserialize, Serialize)]
368struct ReceiptRecord {
369    epoch: String,
370    id: String,
371}
372
373pub(crate) struct RecordCodec {
374    bytes: usize,
375}
376
377impl RunHead {
378    fn definition_pins(&self) -> impl Iterator<Item = (&WorkflowId, &str)> {
379        std::iter::once((&self.workflow_id, self.definition_revision.as_str())).chain(
380            self.child_revisions
381                .iter()
382                .map(|(id, revision)| (id, revision.as_str())),
383        )
384    }
385
386    fn advance(&mut self, record: &RunRecord, prior: &RunRecord) -> Result<(), AppError> {
387        if !matches!(self.retention, RunRetention::Active {})
388            || self.sequence.checked_add(1) != Some(record.checkpoint.sequence)
389            || self.id != record.id
390            || self.creation != record.occurrence
391            || self.workspace_id != record.workspace_id
392            || self.workflow_id != record.workflow_id
393        {
394            return Err(AppError::Conflict(
395                "run head cannot accept this checkpoint".into(),
396            ));
397        }
398        if let Some(committed) = record.checkpoint.committed_ordinal() {
399            self.unresolved_failures
400                .retain(|ordinal, _| *ordinal > committed);
401        }
402        if let Some(observation) = record.checkpoint.failed_observation()
403            && prior.checkpoint.failed_observation() != Some(observation)
404        {
405            self.unresolved_failures
406                .insert(observation.ordinal, record.checkpoint.sequence);
407        }
408        self.sequence = record.checkpoint.sequence;
409        self.validate()?;
410        self.validate_row(record)
411    }
412
413    fn head_key(&self) -> RecordKey {
414        RecordKey::Run {
415            workspace: self.workspace_id.clone(),
416            workflow: self.workflow_id.clone(),
417            run: self.id.clone(),
418            occurrence: self.creation.clone(),
419        }
420    }
421
422    fn checkpoint_key(&self, sequence: u64) -> RecordKey {
423        RecordKey::Checkpoint {
424            workspace: self.workspace_id.clone(),
425            workflow: self.workflow_id.clone(),
426            run: self.id.clone(),
427            occurrence: self.creation.clone(),
428            sequence,
429        }
430    }
431
432    fn active_key(&self) -> RecordKey {
433        RecordKey::ActiveRun {
434            workspace: self.workspace_id.clone(),
435            workflow: self.workflow_id.clone(),
436            run: self.id.clone(),
437            occurrence: self.creation.clone(),
438        }
439    }
440
441    fn history_prefix(&self) -> String {
442        format!(
443            "history/{}/{}/{}/{}/",
444            self.workspace_id, self.workflow_id, self.id, self.creation
445        )
446    }
447
448    fn ensure_readable(&self) -> Result<(), AppError> {
449        if matches!(self.retention, RunRetention::Deleting { .. }) {
450            Err(AppError::Closed(
451                "run history deletion has been admitted".into(),
452            ))
453        } else {
454            Ok(())
455        }
456    }
457
458    fn admitted(record: &RunRecord) -> Result<Self, AppError> {
459        if record.checkpoint.sequence != 0 || record.previous.is_some() {
460            return Err(AppError::Integration(
461                "run admission must publish checkpoint zero".into(),
462            ));
463        }
464        let head = Self {
465            epoch: record.epoch.clone(),
466            id: record.id.clone(),
467            workspace_id: record.workspace_id.clone(),
468            workflow_id: record.workflow_id.clone(),
469            definition_revision: record.definition_revision.clone(),
470            child_revisions: record.child_revisions.clone(),
471            agent_revisions: record.agent_revisions.clone(),
472            creation: record.occurrence.clone(),
473            sequence: record.checkpoint.sequence,
474            unresolved_failures: BTreeMap::new(),
475            retention: RunRetention::Active {},
476        };
477        head.validate()?;
478        head.validate_row(record)?;
479        Ok(head)
480    }
481
482    fn decode(value: &Value) -> Result<Self, AppError> {
483        RecordCodec::preflight_epoch(value)?;
484        let head: Self = RecordCodec::decode(value)?;
485        head.validate()?;
486        Ok(head)
487    }
488
489    fn validate(&self) -> Result<(), AppError> {
490        if self.epoch != EPOCH
491            || self.definition_pins().any(|(_, revision)| {
492                revision.is_empty()
493                    || revision.len() > 256
494                    || revision.trim() != revision
495                    || revision.chars().any(char::is_control)
496            })
497            || self.agent_revisions.len() > MAX_AGENTS
498            || self.agent_revisions.iter().any(|(key, revision)| {
499                SchemaText::node_or_state("agent key", key).is_err()
500                    || SchemaText::revision(revision).is_err()
501            })
502            || self.creation.len() != 32
503            || !self
504                .creation
505                .bytes()
506                .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
507        {
508            return Err(AppError::Integration("durable run head is invalid".into()));
509        }
510        if self.unresolved_failures.len() > MAX_FRONTIER
511            || self
512                .unresolved_failures
513                .values()
514                .any(|sequence| *sequence == 0 || *sequence > self.sequence)
515            || self
516                .unresolved_failures
517                .values()
518                .collect::<BTreeSet<_>>()
519                .len()
520                != self.unresolved_failures.len()
521        {
522            return Err(AppError::Integration(
523                "durable failed observation evidence is invalid".into(),
524            ));
525        }
526        if let RunRetention::Deleting { after: Some(after) } = &self.retention {
527            match RecordKey::history_key(after)? {
528                RecordKey::Checkpoint {
529                    workspace,
530                    workflow,
531                    run,
532                    occurrence,
533                    sequence,
534                }
535                | RecordKey::Ledger {
536                    workspace,
537                    workflow,
538                    run,
539                    occurrence,
540                    sequence,
541                    ..
542                }
543                | RecordKey::ActionObservation {
544                    workspace,
545                    workflow,
546                    run,
547                    occurrence,
548                    sequence,
549                    ..
550                } if workspace == self.workspace_id
551                    && workflow == self.workflow_id
552                    && run == self.id
553                    && occurrence == self.creation
554                    && sequence <= self.sequence => {}
555                RecordKey::ProjectionLink { key, sequence, .. }
556                    if key.workspace() == &self.workspace_id
557                        && key.workflow() == &self.workflow_id
558                        && key.run() == &self.id
559                        && key.occurrence() == self.creation
560                        && sequence <= self.sequence => {}
561                _ => {
562                    return Err(AppError::Integration(
563                        "run deletion cursor has foreign scope".into(),
564                    ));
565                }
566            }
567        }
568        Ok(())
569    }
570
571    fn validate_row(&self, record: &RunRecord) -> Result<(), AppError> {
572        if record.epoch != self.epoch
573            || record.id != self.id
574            || record.occurrence != self.creation
575            || record.workspace_id != self.workspace_id
576            || record.workflow_id != self.workflow_id
577            || self.definition_revision != record.definition_revision
578            || self.child_revisions != record.child_revisions
579            || self.agent_revisions != record.agent_revisions
580            || record.checkpoint.sequence != self.sequence
581            || self.unresolved_failures.keys().any(|ordinal| {
582                match record.checkpoint.committed_ordinal() {
583                    Some(committed) => ordinal
584                        .checked_sub(committed)
585                        .is_none_or(|offset| offset == 0 || offset > MAX_FRONTIER as u64),
586                    None => *ordinal >= MAX_FRONTIER as u64,
587                }
588            })
589            || record
590                .checkpoint
591                .failed_observation()
592                .is_some_and(|observation| {
593                    !self.unresolved_failures.contains_key(&observation.ordinal)
594                })
595            || (self.sequence == 0 && !self.unresolved_failures.is_empty())
596            || (!matches!(self.retention, RunRetention::Active {})
597                && !record.checkpoint.is_terminal())
598        {
599            return Err(AppError::Integration(
600                "durable run head does not identify its checkpoint".into(),
601            ));
602        }
603        Ok(())
604    }
605
606    fn record(&self) -> Result<StateRecord, AppError> {
607        self.validate()?;
608        self.head_key().record(self)
609    }
610}
611
612impl StorageFailure {
613    fn from_app(error: AppError) -> Self {
614        if matches!(error, AppError::Busy(_) | AppError::Timeout(_)) {
615            Self::Recoverable(error)
616        } else {
617            Self::Permanent(error)
618        }
619    }
620
621    pub(crate) fn permanent(error: AppError) -> Self {
622        Self::Permanent(error)
623    }
624
625    pub(crate) fn is_recoverable(&self) -> bool {
626        matches!(self, Self::Recoverable(_))
627    }
628
629    pub(crate) fn is_cleanup_failed(&self) -> bool {
630        matches!(self, Self::CleanupFailed(_))
631    }
632
633    pub(crate) fn is_retryable_conflict(&self) -> bool {
634        !self.is_cleanup_failed() && matches!(self.error(), AppError::Conflict(_))
635    }
636
637    pub(crate) fn error(&self) -> &AppError {
638        match self {
639            Self::Recoverable(error) | Self::Permanent(error) | Self::CleanupFailed(error) => error,
640        }
641    }
642
643    pub(crate) fn into_error(self) -> AppError {
644        match self {
645            Self::Recoverable(error) | Self::Permanent(error) | Self::CleanupFailed(error) => error,
646        }
647    }
648}
649
650impl<CS: StateStoreBackend> AppStore<CS> {
651    pub(crate) fn new(inner: CS) -> Self {
652        Self {
653            inner,
654            decoded_runs: Mutex::new(Vec::new()),
655            retired_receipts: Mutex::new(Vec::new()),
656        }
657    }
658
659    async fn decode_run<T: StateTransactionBackend>(
660        &self,
661        transaction: &mut T,
662        snapshot: StateRecordSnapshot,
663        admit: bool,
664        observed_pages: &mut Vec<StateRecordSnapshot>,
665    ) -> Result<RunRecord, StorageFailure> {
666        let key = snapshot.record.key;
667        let value = snapshot.record.value;
668        RecordCodec::preflight_epoch(&value).map_err(StorageFailure::permanent)?;
669        RecordKey::validate_value(key.key(), &value).map_err(StorageFailure::permanent)?;
670        let RecordKey::Checkpoint {
671            workspace,
672            workflow,
673            run,
674            occurrence,
675            sequence,
676        } = RecordKey::history_key(key.key()).map_err(StorageFailure::permanent)?
677        else {
678            return Err(StorageFailure::permanent(AppError::Integration(
679                "expected checkpoint evidence".into(),
680            )));
681        };
682        let descriptor: SettlementPages = RecordCodec::decode(&value["checkpoint"]["settlements"])
683            .map_err(StorageFailure::permanent)?;
684        descriptor
685            .validate(sequence)
686            .map_err(StorageFailure::permanent)?;
687        let mut pages = Vec::with_capacity(descriptor.pages.len());
688        for (index, sequence) in descriptor.pages.iter().copied().enumerate() {
689            let key = RecordKey::Ledger {
690                workspace: workspace.clone(),
691                workflow: workflow.clone(),
692                run: run.clone(),
693                occurrence: occurrence.clone(),
694                index,
695                sequence,
696            }
697            .state_key()
698            .map_err(StorageFailure::permanent)?;
699            let snapshot = match observed_pages
700                .iter()
701                .find(|snapshot| snapshot.record.key == key)
702            {
703                Some(snapshot) => snapshot.clone(),
704                None => {
705                    let snapshot = transaction
706                        .load(key.clone())
707                        .await
708                        .map_err(StorageFailure::from)?
709                        .ok_or_else(|| {
710                            StorageFailure::permanent(AppError::Integration(
711                                "referenced settlement page is missing".into(),
712                            ))
713                        })?;
714                    // Current and predecessor reference at most 128 distinct
715                    // leaves. Longer history/child batches evict old observations;
716                    // this is a reuse bound, never an operation or record limit.
717                    if observed_pages.len() == 128 {
718                        observed_pages.remove(0);
719                    }
720                    observed_pages.push(snapshot.clone());
721                    snapshot
722                }
723            };
724            if snapshot.record.key != key {
725                return Err(StorageFailure::permanent(AppError::Integration(
726                    "settlement page has foreign storage identity".into(),
727                )));
728            }
729            RecordKey::validate_value(key.key(), &snapshot.record.value)
730                .map_err(StorageFailure::permanent)?;
731            if snapshot
732                .record
733                .value
734                .get("entries")
735                .and_then(Value::as_array)
736                .map(Vec::len)
737                != Some((descriptor.count - index * 64).min(64))
738            {
739                return Err(StorageFailure::permanent(AppError::Integration(
740                    "settlement page count is inconsistent".into(),
741                )));
742            }
743            pages.push(snapshot.record.value);
744        }
745        let cached = {
746            let mut rows = self
747                .decoded_runs
748                .lock()
749                .unwrap_or_else(|poisoned| poisoned.into_inner());
750            rows.iter()
751                .position(|row| row.matches(&value, &pages))
752                .map(|index| {
753                    let row = rows.remove(index);
754                    let cached = row.record.clone();
755                    rows.push(row);
756                    cached
757                })
758        };
759        if let Some(record) = cached {
760            return Ok(record);
761        }
762        // Reconstitute the original inline byte charge without allocating its
763        // JSON array. Descriptor and leaf metadata are physical bookkeeping,
764        // not a reason to narrow the unchanged logical four-MiB contract.
765        let mut entries_size = 2usize;
766        for (index, page) in pages.iter().enumerate() {
767            let size =
768                RecordCodec::value_len(&page["entries"]).map_err(StorageFailure::permanent)?;
769            entries_size = entries_size
770                .checked_add(size.saturating_sub(2))
771                .and_then(|size| size.checked_add(usize::from(index > 0)))
772                .ok_or_else(|| {
773                    StorageFailure::permanent(AppError::Integration(
774                        "app durable record encoding failed".into(),
775                    ))
776                })?;
777        }
778        let size = RecordCodec::value_len(&value)
779            .map_err(StorageFailure::permanent)?
780            .checked_sub(
781                RecordCodec::value_len(&value["checkpoint"]["settlements"])
782                    .map_err(StorageFailure::permanent)?,
783            )
784            .and_then(|size| size.checked_add(entries_size))
785            .ok_or_else(|| {
786                StorageFailure::permanent(AppError::Integration(
787                    "app durable record encoding failed".into(),
788                ))
789            })?;
790        if size > MAX_RECORD_BYTES {
791            return Err(StorageFailure::permanent(AppError::Integration(
792                "app durable record exceeds the private byte bound".into(),
793            )));
794        }
795        let mut record = RecordCodec::parse_run(&value).map_err(StorageFailure::permanent)?;
796        record
797            .checkpoint
798            .hydrate_settlements(&pages, descriptor.pages, descriptor.count)
799            .map_err(StorageFailure::permanent)?;
800        RecordCodec::validate_run(&record, size).map_err(StorageFailure::permanent)?;
801        record
802            .checkpoint
803            .validate_settlements()
804            .map_err(StorageFailure::permanent)?;
805        if admit {
806            let mut rows = self
807                .decoded_runs
808                .lock()
809                .unwrap_or_else(|poisoned| poisoned.into_inner());
810            if !rows.iter().any(|row| row.matches(&value, &pages)) {
811                if rows.len() == 4 {
812                    rows.remove(0);
813                }
814                rows.push(DecodedRun {
815                    source: value,
816                    pages,
817                    record: record.clone(),
818                });
819            }
820        }
821        Ok(record)
822    }
823
824    pub(crate) async fn validate_definition_inventory(
825        &self,
826        definitions: &BTreeMap<WorkflowId, Vec<Arc<CompiledDefinition>>>,
827        application: Option<&AppRecord>,
828        configured_agents: &BTreeSet<(&str, &str)>,
829    ) -> Result<(), AppError> {
830        for workspace in self.list_workspaces().await? {
831            workspace.validate(&workspace.id, configured_agents)?;
832        }
833        if let Some(application) = application {
834            for (id, revision) in &application.definitions {
835                let key = RecordKey::Definition {
836                    workflow: id.clone(),
837                    revision: revision.clone(),
838                };
839                if self
840                    .load_record::<DefinitionRecord>(key)
841                    .await
842                    .map_err(StorageFailure::into_error)?
843                    .is_none()
844                {
845                    return Err(AppError::Integration(
846                        "current workflow revision has no durable definition".into(),
847                    ));
848                }
849            }
850        }
851        // Configured pairs are checked once, independently of how many retained
852        // runs pin them. Unused durable pairs never become supply obligations.
853        let mut catalogued = BTreeSet::new();
854        for (id, revisions) in definitions {
855            for definition in revisions {
856                let key = RecordKey::Definition {
857                    workflow: id.clone(),
858                    revision: definition.revision().to_owned(),
859                };
860                if let Some((record, _)) = self
861                    .load_record::<DefinitionRecord>(key)
862                    .await
863                    .map_err(StorageFailure::into_error)?
864                {
865                    if record.canonical != definition.fingerprint() {
866                        return Err(AppError::Conflict(
867                            "configured workflow revision differs from its durable definition"
868                                .into(),
869                        ));
870                    }
871                    catalogued.insert((id, definition.revision()));
872                }
873            }
874        }
875        let mut after = None;
876        loop {
877            let (page, next) = self
878                .list_page(Some("run/"), after.as_deref(), PAGE_SIZE)
879                .await
880                .map_err(StorageFailure::into_error)?;
881            for snapshot in page {
882                let head = RunHead::decode(&snapshot.record.value)?;
883                if matches!(head.retention, RunRetention::Deleting { .. }) {
884                    continue;
885                }
886                if head.agent_revisions.iter().any(|(key, revision)| {
887                    !configured_agents.contains(&(key.as_str(), revision.as_str()))
888                }) {
889                    return Err(AppError::Conflict(
890                        "retained agent revision is not configured".into(),
891                    ));
892                }
893                let latest = self
894                    .load_checkpoint(&head, head.sequence)
895                    .await
896                    .map_err(StorageFailure::into_error)?;
897                head.validate_row(&latest)?;
898                // Validate exact selected task bindings from the same already
899                // required latest row; explicit history reads validate older rows.
900                for (id, revision) in latest.definition_pins() {
901                    if !catalogued.contains(&(id, revision)) {
902                        let key = RecordKey::Definition {
903                            workflow: id.clone(),
904                            revision: revision.to_owned(),
905                        };
906                        if self
907                            .load_record::<DefinitionRecord>(key)
908                            .await
909                            .map_err(StorageFailure::into_error)?
910                            .is_none()
911                        {
912                            return Err(AppError::Integration(
913                                "retained workflow revision has no durable definition".into(),
914                            ));
915                        }
916                        return Err(AppError::Conflict(
917                            "retained workflow revision is not configured".into(),
918                        ));
919                    }
920                }
921                let definition = definitions
922                    .get(&head.workflow_id)
923                    .and_then(|revisions| {
924                        revisions
925                            .iter()
926                            .find(|definition| definition.revision() == head.definition_revision)
927                    })
928                    .ok_or_else(|| {
929                        AppError::Conflict("retained workflow revision is not configured".into())
930                    })?;
931                definition
932                    .validate_child_relationships(definitions, &head.child_revisions)
933                    .map_err(|_| {
934                        AppError::Integration(
935                            "retained workflow child revision relationships are inconsistent"
936                                .into(),
937                        )
938                    })?;
939                latest.validate_recovery(definition)?;
940                if !self
941                    .head_unchanged(&head, snapshot.revision.get())
942                    .await
943                    .map_err(StorageFailure::into_error)?
944                {
945                    return Err(AppError::Busy(
946                        "retained run changed during startup validation".into(),
947                    ));
948                }
949            }
950            match next {
951                Some(next) => after = Some(next),
952                None => return Ok(()),
953            }
954        }
955    }
956
957    pub(crate) async fn load_app(&self) -> Result<Option<(AppRecord, u64)>, AppError> {
958        self.load_app_for_recovery()
959            .await
960            .map_err(StorageFailure::into_error)
961    }
962
963    pub(crate) async fn load_app_for_recovery(
964        &self,
965    ) -> Result<Option<(AppRecord, u64)>, StorageFailure> {
966        self.load_record(RecordKey::App).await
967    }
968
969    pub(crate) async fn load_workspace(
970        &self,
971        id: &WorkspaceId,
972    ) -> Result<Option<(WorkspaceRecord, u64)>, AppError> {
973        self.load_workspace_for_recovery(id)
974            .await
975            .map_err(StorageFailure::into_error)
976    }
977
978    pub(crate) async fn load_workspace_for_recovery(
979        &self,
980        id: &WorkspaceId,
981    ) -> Result<Option<(WorkspaceRecord, u64)>, StorageFailure> {
982        self.load_record(RecordKey::Workspace(id.clone())).await
983    }
984
985    pub(crate) async fn list_workspaces(&self) -> Result<Vec<WorkspaceRecord>, AppError> {
986        let mut after = None;
987        let mut records = Vec::new();
988        loop {
989            let (page, next) = self
990                .list_page(Some("workspace/"), after.as_deref(), PAGE_SIZE)
991                .await
992                .map_err(StorageFailure::into_error)?;
993            for snapshot in page {
994                if records.len() == MAX_WORKSPACES {
995                    return Err(AppError::Integration(
996                        "durable workspace inventory exceeds the private bound".into(),
997                    ));
998                }
999                records.push(RecordCodec::decode(&snapshot.record.value)?);
1000            }
1001            match next {
1002                Some(next) => after = Some(next),
1003                None => return Ok(records),
1004            }
1005        }
1006    }
1007
1008    pub(crate) async fn load_run(
1009        &self,
1010        key: &RunKey,
1011    ) -> Result<Option<(RunRecord, u64)>, AppError> {
1012        self.load_run_for_recovery(key)
1013            .await
1014            .map_err(StorageFailure::into_error)
1015    }
1016
1017    pub(crate) async fn load_run_for_recovery(
1018        &self,
1019        key: &RunKey,
1020    ) -> Result<Option<(RunRecord, u64)>, StorageFailure> {
1021        let state_key = RecordKey::exact_run(key)
1022            .state_key()
1023            .map_err(StorageFailure::permanent)?;
1024        for _ in 0..8 {
1025            let mut transaction = self
1026                .inner
1027                .begin_transaction()
1028                .await
1029                .map_err(StorageFailure::from)?;
1030            let mut observed_pages = Vec::new();
1031            let mut missing_row_head = None;
1032            let observed: Result<Option<(RunHead, u64, RunRecord)>, StorageFailure> = async {
1033                let Some(snapshot) = transaction
1034                    .load(state_key.clone())
1035                    .await
1036                    .map_err(StorageFailure::from)?
1037                else {
1038                    return Ok(None);
1039                };
1040                RecordKey::validate_value(state_key.key(), &snapshot.record.value)
1041                    .map_err(StorageFailure::permanent)?;
1042                let head =
1043                    RunHead::decode(&snapshot.record.value).map_err(StorageFailure::permanent)?;
1044                let revision = snapshot.revision.get();
1045                head.ensure_readable().map_err(StorageFailure::permanent)?;
1046                let mut current: Option<RunRecord> = None;
1047                for selected in std::iter::once(head.sequence).chain(head.sequence.checked_sub(1)) {
1048                    let key = head
1049                        .checkpoint_key(selected)
1050                        .state_key()
1051                        .map_err(StorageFailure::permanent)?;
1052                    let Some(snapshot) = transaction
1053                        .load(key.clone())
1054                        .await
1055                        .map_err(StorageFailure::from)?
1056                    else {
1057                        missing_row_head = Some(head);
1058                        return Err(StorageFailure::permanent(AppError::Integration(
1059                            if current.is_some() {
1060                                "retained checkpoint predecessor is missing"
1061                            } else {
1062                                "retained checkpoint is missing"
1063                            }
1064                            .into(),
1065                        )));
1066                    };
1067                    current = Some(
1068                        self.decode_checkpoint_row(
1069                            &mut transaction,
1070                            &key,
1071                            snapshot,
1072                            current,
1073                            &mut observed_pages,
1074                        )
1075                        .await?,
1076                    );
1077                }
1078                let record = current.expect("the selected checkpoint is always observed");
1079                head.validate_row(&record)
1080                    .map_err(StorageFailure::permanent)?;
1081                Ok(Some((head, revision, record)))
1082            }
1083            .await;
1084            let observed = match observed {
1085                Ok(observed) => match transaction.commit().await {
1086                    Ok(()) => observed,
1087                    // A concurrent run transition can invalidate this pure
1088                    // snapshot. Discard it and observe again within the bound.
1089                    Err(StateStoreError::TransactionConflict { .. }) => continue,
1090                    Err(error) => return Err(StorageFailure::from(error)),
1091                },
1092                Err(primary) => {
1093                    if transaction.rollback().await.is_err() {
1094                        AppTrace::event(
1095                            || tracing::warn!(target: "app_kit", event = "app.storage.read_rollback_failed"),
1096                        );
1097                        // Failed cleanup vetoes retry without replacing the primary public error.
1098                        return Err(StorageFailure::CleanupFailed(primary.into_error()));
1099                    }
1100                    if let Some(head) = missing_row_head {
1101                        self.validate_history_head(&head).await?;
1102                    }
1103                    return Err(primary);
1104                }
1105            };
1106            let Some((head, revision, record)) = observed else {
1107                return Ok(None);
1108            };
1109            // Snapshot consistency does not replace this fresh deletion/advance fence.
1110            if self.head_unchanged(&head, revision).await? {
1111                return Ok(Some((record, revision)));
1112            }
1113        }
1114        Err(StorageFailure::Recoverable(AppError::Busy(
1115            "run head changed during observation".into(),
1116        )))
1117    }
1118
1119    async fn load_head(&self, key: RecordKey) -> Result<Option<(RunHead, u64)>, StorageFailure> {
1120        let state_key = key.state_key().map_err(StorageFailure::permanent)?;
1121        let snapshot = self
1122            .inner
1123            .load(state_key.clone())
1124            .await
1125            .map_err(StorageFailure::from)?;
1126        snapshot
1127            .map(|snapshot| {
1128                RecordKey::validate_value(state_key.key(), &snapshot.record.value)
1129                    .map_err(StorageFailure::permanent)?;
1130                RunHead::decode(&snapshot.record.value)
1131                    .map(|head| (head, snapshot.revision.get()))
1132                    .map_err(StorageFailure::permanent)
1133            })
1134            .transpose()
1135    }
1136
1137    async fn head_unchanged(&self, head: &RunHead, revision: u64) -> Result<bool, StorageFailure> {
1138        let Some((current, observed)) = self.load_head(head.head_key()).await? else {
1139            return Err(StorageFailure::permanent(AppError::Closed(
1140                "workflow run was removed during observation".into(),
1141            )));
1142        };
1143        current
1144            .ensure_readable()
1145            .map_err(StorageFailure::permanent)?;
1146        Ok(observed == revision
1147            && current.creation == head.creation
1148            && current.sequence == head.sequence)
1149    }
1150
1151    async fn validate_history_head(&self, head: &RunHead) -> Result<(), StorageFailure> {
1152        let Some((current, _)) = self.load_head(head.head_key()).await? else {
1153            return Err(StorageFailure::permanent(AppError::Closed(
1154                "workflow run was removed during observation".into(),
1155            )));
1156        };
1157        current
1158            .ensure_readable()
1159            .map_err(StorageFailure::permanent)?;
1160        if current.creation != head.creation || current.sequence < head.sequence {
1161            return Err(StorageFailure::permanent(AppError::Conflict(
1162                "run history scope changed during observation".into(),
1163            )));
1164        }
1165        Ok(())
1166    }
1167
1168    async fn decode_checkpoint_row<T: StateTransactionBackend>(
1169        &self,
1170        transaction: &mut T,
1171        key: &StateKey,
1172        snapshot: StateRecordSnapshot,
1173        current: Option<RunRecord>,
1174        observed_pages: &mut Vec<StateRecordSnapshot>,
1175    ) -> Result<RunRecord, StorageFailure> {
1176        RecordCodec::preflight_epoch(&snapshot.record.value).map_err(StorageFailure::permanent)?;
1177        RecordKey::validate_value(key.key(), &snapshot.record.value)
1178            .map_err(StorageFailure::permanent)?;
1179        let record = self
1180            .decode_run(transaction, snapshot, true, observed_pages)
1181            .await?;
1182        match current {
1183            Some(mut current) => {
1184                if !current.same_run(&record) {
1185                    return Err(StorageFailure::permanent(AppError::Integration(
1186                        "retained checkpoint predecessor has foreign identity".into(),
1187                    )));
1188                }
1189                current.previous = Some(record.checkpoint);
1190                Ok(current)
1191            }
1192            None => Ok(record),
1193        }
1194    }
1195
1196    async fn load_checkpoint(
1197        &self,
1198        head: &RunHead,
1199        sequence: u64,
1200    ) -> Result<RunRecord, StorageFailure> {
1201        let mut transaction = self
1202            .inner
1203            .begin_transaction()
1204            .await
1205            .map_err(StorageFailure::from)?;
1206        let mut observed_pages = Vec::new();
1207        let mut missing = false;
1208        let observed = async {
1209            let mut current: Option<RunRecord> = None;
1210            for selected in std::iter::once(sequence).chain(sequence.checked_sub(1)) {
1211                let key = head
1212                    .checkpoint_key(selected)
1213                    .state_key()
1214                    .map_err(StorageFailure::permanent)?;
1215                let Some(snapshot) = transaction
1216                    .load(key.clone())
1217                    .await
1218                    .map_err(StorageFailure::from)?
1219                else {
1220                    missing = true;
1221                    return Err(StorageFailure::permanent(AppError::Integration(
1222                        if current.is_some() {
1223                            "retained checkpoint predecessor is missing"
1224                        } else {
1225                            "retained checkpoint is missing"
1226                        }
1227                        .into(),
1228                    )));
1229                };
1230                current = Some(
1231                    self.decode_checkpoint_row(
1232                        &mut transaction,
1233                        &key,
1234                        snapshot,
1235                        current,
1236                        &mut observed_pages,
1237                    )
1238                    .await?,
1239                );
1240            }
1241            Ok(current.expect("the selected checkpoint is always observed"))
1242        }
1243        .await;
1244        match observed {
1245            Ok(record) => {
1246                transaction.commit().await.map_err(StorageFailure::from)?;
1247                Ok(record)
1248            }
1249            Err(primary) => {
1250                if transaction.rollback().await.is_err() {
1251                    AppTrace::event(
1252                        || tracing::warn!(target: "app_kit", event = "app.storage.read_rollback_failed"),
1253                    );
1254                    return Err(StorageFailure::CleanupFailed(primary.into_error()));
1255                }
1256                if missing {
1257                    self.validate_history_head(head).await?;
1258                }
1259                Err(primary)
1260            }
1261        }
1262    }
1263
1264    pub(crate) async fn run_is_active(&self, record: &RunRecord) -> Result<bool, StorageFailure> {
1265        let Some((head, revision)) = self.load_head(RecordKey::run(record)).await? else {
1266            return Ok(false);
1267        };
1268        head.ensure_readable().map_err(StorageFailure::permanent)?;
1269        let active = matches!(head.retention, RunRetention::Active {});
1270        let marker: Option<(Value, u64)> = self.load_record(head.active_key()).await?;
1271        if !self.head_unchanged(&head, revision).await? {
1272            return Err(StorageFailure::Recoverable(AppError::Busy(
1273                "run capacity changed during observation".into(),
1274            )));
1275        }
1276        if active != marker.is_some() {
1277            return Err(StorageFailure::permanent(AppError::Integration(
1278                "run capacity index is inconsistent".into(),
1279            )));
1280        }
1281        Ok(active)
1282    }
1283
1284    pub(crate) async fn checkpoint(
1285        &self,
1286        current: &RunRecord,
1287        selector: &CheckpointSelector,
1288        definition: &CompiledDefinition,
1289    ) -> Result<Option<(RunRecord, u64)>, RuntimeOperationFailure> {
1290        let Some((head, revision)) = self.load_head(RecordKey::run(current)).await? else {
1291            return Ok(None);
1292        };
1293        head.ensure_readable()?;
1294        let validate = |record: &RunRecord| {
1295            if !record.same_run(current) {
1296                return Err(AppError::Integration(
1297                    "retained checkpoint has foreign identity".into(),
1298                ));
1299            }
1300            record.validate_recovery(definition)
1301        };
1302        let latest = self.load_checkpoint(&head, head.sequence).await?;
1303        head.validate_row(&latest)?;
1304        validate(&latest)?;
1305        let selected = match selector {
1306            CheckpointSelector::Latest => Some(latest),
1307            CheckpointSelector::Sequence(sequence) if *sequence == head.sequence => Some(latest),
1308            CheckpointSelector::Sequence(sequence) if *sequence <= head.sequence => {
1309                Some(self.load_checkpoint(&head, *sequence).await?)
1310            }
1311            CheckpointSelector::Sequence(_) => None,
1312            CheckpointSelector::Action(ordinal) => {
1313                self.action_checkpoint(&head, &latest, *ordinal, definition)
1314                    .await?
1315            }
1316        };
1317        if let Some(record) = &selected {
1318            validate(record)?;
1319        }
1320        self.validate_history_head(&head).await?;
1321        Ok(selected.map(|record| (record, revision)))
1322    }
1323
1324    async fn action_checkpoint(
1325        &self,
1326        head: &RunHead,
1327        latest: &RunRecord,
1328        ordinal: u64,
1329        definition: &CompiledDefinition,
1330    ) -> Result<Option<RunRecord>, RuntimeOperationFailure> {
1331        let ordinal = &ordinal;
1332        let validate = |record: &RunRecord| -> Result<(), RuntimeOperationFailure> {
1333            if !record.same_run(latest) {
1334                return Err(StorageFailure::permanent(AppError::Integration(
1335                    "retained checkpoint has foreign identity".into(),
1336                ))
1337                .into());
1338            }
1339            record
1340                .validate_recovery(definition)
1341                .map_err(|error| StorageFailure::permanent(error).into())
1342        };
1343
1344        let prefix = format!("{}action/{ordinal:020}/", head.history_prefix());
1345        let reverse = u64::MAX - head.sequence;
1346        let after = (reverse > 0).then(|| format!("{prefix}{:020}", reverse - 1));
1347        let (rows, _) = self.list_page(Some(&prefix), after.as_deref(), 1).await?;
1348        let indexed = rows
1349            .first()
1350            .map(
1351                |snapshot| match RecordKey::history_key(snapshot.record.key.key())? {
1352                    RecordKey::ActionObservation {
1353                        workspace,
1354                        workflow,
1355                        run,
1356                        occurrence,
1357                        ordinal: actual,
1358                        sequence,
1359                    } if workspace == head.workspace_id
1360                        && workflow == head.workflow_id
1361                        && run == head.id
1362                        && occurrence == head.creation
1363                        && actual == *ordinal
1364                        && sequence <= head.sequence =>
1365                    {
1366                        Ok(sequence)
1367                    }
1368                    _ => Err(AppError::Integration(
1369                        "action observation index is invalid".into(),
1370                    )),
1371                },
1372            )
1373            .transpose()
1374            .map_err(StorageFailure::permanent)?;
1375        let committed = latest
1376            .checkpoint
1377            .committed_ordinal()
1378            .is_some_and(|position| *ordinal <= position);
1379        let expected = if committed {
1380            let initial = self.load_checkpoint(head, 0).await?;
1381            validate(&initial)?;
1382            if initial
1383                .checkpoint
1384                .committed_ordinal()
1385                .is_some_and(|inherited| *ordinal <= inherited)
1386            {
1387                // A restored initial position is not a local observation.
1388                None
1389            } else {
1390                // Committed positions are monotone and local successful
1391                // ordinals are contiguous. Find their first publication;
1392                // even u64::MAX checkpoints require at most 64 probes.
1393                let mut lower = 1;
1394                let mut upper = head.sequence;
1395                let mut boundary = latest.clone();
1396                for _ in 0..u64::BITS {
1397                    if lower >= upper {
1398                        break;
1399                    }
1400                    let middle = lower + (upper - lower) / 2;
1401                    let record = self.load_checkpoint(head, middle).await?;
1402                    validate(&record)?;
1403                    if record
1404                        .checkpoint
1405                        .committed_ordinal()
1406                        .is_some_and(|position| position >= *ordinal)
1407                    {
1408                        upper = middle;
1409                        boundary = record;
1410                    } else {
1411                        lower = middle + 1;
1412                    }
1413                }
1414                if lower != upper
1415                    || boundary.checkpoint.sequence != lower
1416                    || !matches!(boundary.checkpoint_kind(), CheckpointKind::Action)
1417                    || !boundary
1418                        .checkpoint
1419                        .outcomes()
1420                        .iter()
1421                        .any(|outcome| outcome.ordinal == *ordinal)
1422                {
1423                    return Err(StorageFailure::permanent(AppError::Integration(
1424                        "committed action has no valid observation boundary".into(),
1425                    ))
1426                    .into());
1427                }
1428                Some(boundary)
1429            }
1430        } else if let Some(sequence) = head.unresolved_failures.get(ordinal) {
1431            let record = self.load_checkpoint(head, *sequence).await?;
1432            validate(&record)?;
1433            if !matches!(record.checkpoint_kind(),
1434                        CheckpointKind::Failed { ordinal: actual, .. } if actual == *ordinal)
1435            {
1436                return Err(StorageFailure::permanent(AppError::Integration(
1437                    "failed observation evidence identifies another checkpoint".into(),
1438                ))
1439                .into());
1440            }
1441            Some(record)
1442        } else {
1443            None
1444        };
1445        if indexed != expected.as_ref().map(|record| record.checkpoint.sequence) {
1446            // A legitimate seal/removal outranks an apparent missing
1447            // index in this captured, otherwise immutable history.
1448            self.validate_history_head(head).await?;
1449            return Err(StorageFailure::permanent(AppError::Integration(
1450                "action observation index is inconsistent".into(),
1451            ))
1452            .into());
1453        }
1454        Ok(expected)
1455    }
1456
1457    pub(crate) async fn history(
1458        &self,
1459        current: &RunRecord,
1460        generation: u64,
1461        after: Option<&HistoryCursor>,
1462        limit: NonZeroUsize,
1463    ) -> Result<(Vec<RunRecord>, Option<HistoryCursor>), AppError> {
1464        if limit.get() > MAX_LIST_LIMIT {
1465            return Err(AppError::InvalidInput(
1466                "history page limit exceeds the supported bound".into(),
1467            ));
1468        }
1469        let Some((head, _)) = self
1470            .load_head(RecordKey::run(current))
1471            .await
1472            .map_err(StorageFailure::into_error)?
1473        else {
1474            return Err(AppError::NotFound("workflow run does not exist".into()));
1475        };
1476        head.ensure_readable()?;
1477        let latest = self
1478            .load_checkpoint(&head, head.sequence)
1479            .await
1480            .map_err(StorageFailure::into_error)?;
1481        head.validate_row(&latest)?;
1482        if !latest.same_run(current) {
1483            return Err(AppError::Integration(
1484                "run history has foreign identity".into(),
1485            ));
1486        }
1487        let (cutoff, mut next) = match after {
1488            Some(cursor) => {
1489                let (cutoff, after) = cursor.continue_from(&latest, generation, &head.creation)?;
1490                (
1491                    cutoff,
1492                    after.checked_add(1).ok_or_else(|| {
1493                        AppError::InvalidInput("history cursor is exhausted".into())
1494                    })?,
1495                )
1496            }
1497            None => (head.sequence, 0),
1498        };
1499        let prefix = format!("{}checkpoint/", head.history_prefix());
1500        let mut rows: Vec<RunRecord> = Vec::new();
1501        let mut previous = if next > 0 {
1502            Some(
1503                self.load_checkpoint(&head, next - 1)
1504                    .await
1505                    .map_err(StorageFailure::into_error)?
1506                    .checkpoint,
1507            )
1508        } else {
1509            None
1510        };
1511        while next <= cutoff && rows.len() < limit.get() {
1512            let after_key = (next > 0).then(|| format!("{prefix}{:020}", next - 1));
1513            let count = (limit.get() - rows.len()).min(PAGE_SIZE).min(
1514                usize::try_from(cutoff - next)
1515                    .unwrap_or(usize::MAX)
1516                    .saturating_add(1),
1517            );
1518            let (page, _) = self
1519                .list_page(Some(&prefix), after_key.as_deref(), count)
1520                .await
1521                .map_err(StorageFailure::into_error)?;
1522            if page.is_empty() {
1523                self.validate_history_head(&head)
1524                    .await
1525                    .map_err(StorageFailure::into_error)?;
1526                return Err(AppError::Integration(
1527                    "retained history contains a gap".into(),
1528                ));
1529            }
1530            let mut transaction = self
1531                .inner
1532                .begin_transaction()
1533                .await
1534                .map_err(AppError::from)?;
1535            let mut observed_pages = Vec::new();
1536            let mut missing = false;
1537            let hydrated = async {
1538                for listed in page {
1539                    let Some(snapshot) = transaction
1540                        .load(listed.record.key.clone())
1541                        .await
1542                        .map_err(AppError::from)?
1543                    else {
1544                        missing = true;
1545                        return Err(AppError::Integration(
1546                            "retained history contains a gap".into(),
1547                        ));
1548                    };
1549                    RecordKey::validate_value(snapshot.record.key.key(), &snapshot.record.value)?;
1550                    let mut record = self
1551                        .decode_run(&mut transaction, snapshot, true, &mut observed_pages)
1552                        .await
1553                        .map_err(StorageFailure::into_error)?;
1554                    if record.checkpoint.sequence != next || !record.same_run(current) {
1555                        return Err(AppError::Integration(
1556                            "retained history is not contiguous".into(),
1557                        ));
1558                    }
1559                    record.previous = previous.take();
1560                    previous = Some(record.checkpoint.clone());
1561                    rows.push(record);
1562                    if next == cutoff {
1563                        break;
1564                    }
1565                    next += 1;
1566                }
1567                Ok(())
1568            }
1569            .await;
1570            match hydrated {
1571                Ok(()) => transaction.commit().await.map_err(AppError::from)?,
1572                Err(primary) => {
1573                    if transaction.rollback().await.is_err() {
1574                        AppTrace::event(
1575                            || tracing::warn!(target: "app_kit", event = "app.storage.read_rollback_failed"),
1576                        );
1577                        return Err(primary);
1578                    }
1579                    if missing {
1580                        self.validate_history_head(&head)
1581                            .await
1582                            .map_err(StorageFailure::into_error)?;
1583                    }
1584                    return Err(primary);
1585                }
1586            }
1587            if rows
1588                .last()
1589                .is_some_and(|record| record.checkpoint.sequence == cutoff)
1590            {
1591                break;
1592            }
1593        }
1594        self.validate_history_head(&head)
1595            .await
1596            .map_err(StorageFailure::into_error)?;
1597        let last = rows.last().map(|record| record.checkpoint.sequence);
1598        let cursor = match last {
1599            Some(last) if last < cutoff => Some(match after {
1600                Some(cursor) => cursor.advance_to(last)?,
1601                None => HistoryCursor::issue(&latest, generation, &head.creation, last)?,
1602            }),
1603            _ => None,
1604        };
1605        Ok((rows, cursor))
1606    }
1607
1608    async fn list_page(
1609        &self,
1610        prefix: Option<&str>,
1611        after: Option<&str>,
1612        limit: usize,
1613    ) -> Result<(Vec<StateRecordSnapshot>, Option<String>), StorageFailure> {
1614        let page = self.list_scoped_page(prefix, after, limit).await?;
1615        for snapshot in &page.0 {
1616            RecordCodec::preflight_epoch(&snapshot.record.value)
1617                .map_err(StorageFailure::permanent)?;
1618            RecordKey::validate_value(snapshot.record.key.key(), &snapshot.record.value)
1619                .map_err(StorageFailure::permanent)?;
1620        }
1621        Ok(page)
1622    }
1623
1624    async fn list_scoped_page(
1625        &self,
1626        prefix: Option<&str>,
1627        after: Option<&str>,
1628        limit: usize,
1629    ) -> Result<(Vec<StateRecordSnapshot>, Option<String>), StorageFailure> {
1630        let mut request = StateListRequest::new(NAMESPACE)
1631            .map_err(StorageFailure::from)?
1632            .with_limit(limit)
1633            .map_err(StorageFailure::from)?;
1634        if let Some(prefix) = prefix {
1635            request =
1636                request.with_key_prefix(StateKeyPrefix::new(prefix).map_err(StorageFailure::from)?);
1637        }
1638        if let Some(after) = after {
1639            request = request.start_after(after).map_err(StorageFailure::from)?;
1640        }
1641        let page = self
1642            .inner
1643            .list(request)
1644            .await
1645            .map_err(StorageFailure::from)?;
1646        if page.records.len() > limit {
1647            return Err(StorageFailure::permanent(AppError::Integration(
1648                "storage page exceeds its requested bound".into(),
1649            )));
1650        }
1651        let mut previous = after;
1652        for snapshot in &page.records {
1653            if snapshot.record.key.namespace().as_str() != NAMESPACE
1654                || prefix.is_some_and(|prefix| !snapshot.record.key.key().starts_with(prefix))
1655                || previous.is_some_and(|previous| previous >= snapshot.record.key.key())
1656            {
1657                return Err(StorageFailure::permanent(AppError::Integration(
1658                    "storage page has invalid scope or order".into(),
1659                )));
1660            }
1661            previous = Some(snapshot.record.key.key());
1662        }
1663        if page
1664            .next_start_after
1665            .as_deref()
1666            .is_some_and(|next| Some(next) != previous)
1667            || (page.records.is_empty() && page.next_start_after.is_some())
1668        {
1669            return Err(StorageFailure::permanent(AppError::Integration(
1670                "storage page cursor does not advance".into(),
1671            )));
1672        }
1673        Ok((page.records, page.next_start_after))
1674    }
1675
1676    pub(crate) async fn select_run(
1677        &self,
1678        workspace: &WorkspaceId,
1679        workflow: &WorkflowId,
1680        selector: &RunSelector,
1681    ) -> Result<Option<RunKey>, AppError> {
1682        selector.validate()?;
1683        let occurrence = match selector {
1684            RunSelector::Exact { occurrence, .. } => occurrence.clone(),
1685            RunSelector::Latest(id) => {
1686                let Some(last) = self
1687                    .projection_tail(workspace, workflow, id, RunProjection::Checkpoint)
1688                    .await
1689                    .map_err(StorageFailure::into_error)?
1690                else {
1691                    return Ok(None);
1692                };
1693                last.occurrence
1694            }
1695        };
1696        let key = RunKey::new(
1697            workspace.clone(),
1698            workflow.clone(),
1699            selector.id().clone(),
1700            occurrence,
1701        );
1702        let Some((head, _)) = self
1703            .load_head(RecordKey::exact_run(&key))
1704            .await
1705            .map_err(StorageFailure::into_error)?
1706        else {
1707            return match selector {
1708                RunSelector::Exact { .. } => Ok(None),
1709                RunSelector::Latest(_) => Err(AppError::Integration(
1710                    "saved logical run points to a missing occurrence".into(),
1711                )),
1712            };
1713        };
1714        head.ensure_readable()?;
1715        Ok(Some(key))
1716    }
1717
1718    pub(crate) async fn list_runs(
1719        &self,
1720        workspace: &WorkspaceId,
1721        workflow: &WorkflowId,
1722        after: Option<&RunSelector>,
1723        limit: NonZeroUsize,
1724    ) -> Result<Vec<RunRecord>, AppError> {
1725        if limit.get() > MAX_LIST_LIMIT {
1726            return Err(AppError::InvalidInput(
1727                "run list limit exceeds the supported bound".into(),
1728            ));
1729        }
1730        let prefix = format!("run/{workspace}/{workflow}/");
1731        let mut cursor = match after {
1732            Some(selector) => {
1733                self.select_run(workspace, workflow, selector)
1734                    .await?
1735                    .ok_or_else(|| AppError::NotFound("run cursor does not exist".into()))?;
1736                Some(match selector {
1737                    RunSelector::Latest(id) => format!("{prefix}{id}/~"),
1738                    RunSelector::Exact { id, occurrence } => format!("{prefix}{id}/{occurrence}"),
1739                })
1740            }
1741            None => None,
1742        };
1743        let mut records = Vec::new();
1744        loop {
1745            let (page, next) = self
1746                .list_page(
1747                    Some(&prefix),
1748                    cursor.as_deref(),
1749                    (limit.get() - records.len()).min(PAGE_SIZE),
1750                )
1751                .await
1752                .map_err(StorageFailure::into_error)?;
1753            for snapshot in page {
1754                let head = RunHead::decode(&snapshot.record.value)?;
1755                if matches!(head.retention, RunRetention::Deleting { .. }) {
1756                    continue;
1757                }
1758                if let Some((record, _)) = self
1759                    .load_run(&RunKey::new(
1760                        workspace.clone(),
1761                        workflow.clone(),
1762                        head.id.clone(),
1763                        head.creation.clone(),
1764                    ))
1765                    .await?
1766                {
1767                    records.push(record);
1768                }
1769            }
1770            if records.len() == limit.get() || next.is_none() {
1771                return Ok(records);
1772            }
1773            cursor = next;
1774        }
1775    }
1776
1777    pub(crate) async fn has_retained_history(
1778        &self,
1779        workspace: &WorkspaceId,
1780        workflow: Option<&WorkflowId>,
1781    ) -> Result<bool, AppError> {
1782        let prefix = match workflow {
1783            Some(workflow) => format!("run/{workspace}/{workflow}/"),
1784            None => format!("run/{workspace}/"),
1785        };
1786        let mut after = None;
1787        loop {
1788            let (page, next) = self
1789                .list_page(Some(&prefix), after.as_deref(), PAGE_SIZE)
1790                .await
1791                .map_err(StorageFailure::into_error)?;
1792            for snapshot in page {
1793                let revision = snapshot.revision.get();
1794                let head = RunHead::decode(&snapshot.record.value)?;
1795                if !matches!(head.retention, RunRetention::Retained {}) {
1796                    continue;
1797                }
1798                let record = self
1799                    .load_checkpoint(&head, head.sequence)
1800                    .await
1801                    .map_err(StorageFailure::into_error)?;
1802                head.validate_row(&record)?;
1803                if !self
1804                    .head_unchanged(&head, revision)
1805                    .await
1806                    .map_err(StorageFailure::into_error)?
1807                {
1808                    return Err(AppError::Busy(
1809                        "workspace history changed during observation".into(),
1810                    ));
1811                }
1812                return Ok(true);
1813            }
1814            match next {
1815                Some(next) => after = Some(next),
1816                None => return Ok(false),
1817            }
1818        }
1819    }
1820
1821    pub(crate) async fn list_all_runs(&self) -> Result<Vec<RunRecord>, AppError> {
1822        self.list_all_runs_for_recovery()
1823            .await
1824            .map_err(StorageFailure::into_error)
1825    }
1826
1827    pub(crate) async fn list_all_runs_for_recovery(
1828        &self,
1829    ) -> Result<Vec<RunRecord>, StorageFailure> {
1830        let mut after = None;
1831        let mut records = Vec::new();
1832        loop {
1833            let (page, next) = self
1834                .list_page(Some("active/"), after.as_deref(), PAGE_SIZE)
1835                .await?;
1836            for snapshot in page {
1837                if records.len() == MAX_RUNS {
1838                    return Err(StorageFailure::permanent(AppError::Integration(
1839                        "durable run inventory exceeds the private bound".into(),
1840                    )));
1841                }
1842                let RecordKey::ActiveRun {
1843                    workspace,
1844                    workflow,
1845                    run,
1846                    occurrence,
1847                } = RecordKey::history_key(snapshot.record.key.key())
1848                    .map_err(StorageFailure::permanent)?
1849                else {
1850                    unreachable!()
1851                };
1852                let observed = async {
1853                    let Some((record, _)) = self
1854                        .load_run_for_recovery(&RunKey::new(
1855                            workspace.clone(),
1856                            workflow.clone(),
1857                            run.clone(),
1858                            occurrence.clone(),
1859                        ))
1860                        .await?
1861                    else {
1862                        return Ok(None);
1863                    };
1864                    if !self.run_is_active(&record).await? {
1865                        return Err(StorageFailure::Recoverable(AppError::Busy(
1866                            "active run inventory changed".into(),
1867                        )));
1868                    }
1869                    Ok(Some(record))
1870                }
1871                .await;
1872                match observed {
1873                    Ok(Some(record)) => records.push(record),
1874                    Ok(None)
1875                    | Err(StorageFailure::Permanent(AppError::Closed(_) | AppError::NotFound(_))) =>
1876                    {
1877                        // A captured list page can outlive both capacity
1878                        // release and explicit deletion. Disappearance or
1879                        // replacement of this exact marker proves inventory movement.
1880                        let marker: Option<(Value, u64)> = self
1881                            .load_record(RecordKey::ActiveRun {
1882                                workspace,
1883                                workflow,
1884                                run,
1885                                occurrence,
1886                            })
1887                            .await?;
1888                        if marker.is_some_and(|(_, revision)| revision == snapshot.revision.get()) {
1889                            return Err(StorageFailure::permanent(AppError::Integration(
1890                                "active run head is missing or inconsistent".into(),
1891                            )));
1892                        }
1893                        return Err(StorageFailure::Recoverable(AppError::Busy(
1894                            "active run inventory changed".into(),
1895                        )));
1896                    }
1897                    Err(failure) => return Err(failure),
1898                }
1899            }
1900            match next {
1901                Some(next) => after = Some(next),
1902                None => return Ok(records),
1903            }
1904        }
1905    }
1906
1907    pub(crate) async fn preflight_records(&self) -> Result<(), AppError> {
1908        let mut after = None;
1909        loop {
1910            let (page, next) = self
1911                .list_page(None, after.as_deref(), PAGE_SIZE)
1912                .await
1913                .map_err(StorageFailure::into_error)?;
1914            for snapshot in page {
1915                if snapshot.record.key.key().starts_with("run/") {
1916                    RunHead::decode(&snapshot.record.value)?;
1917                }
1918            }
1919            match next {
1920                Some(next) => after = Some(next),
1921                None => return Ok(()),
1922            }
1923        }
1924    }
1925
1926    pub(crate) async fn pending_deletions(
1927        &self,
1928        after: Option<&str>,
1929    ) -> Result<(Vec<RunKey>, Option<String>), AppError> {
1930        let (page, next) = self
1931            .list_page(Some("run/"), after, PAGE_SIZE)
1932            .await
1933            .map_err(StorageFailure::into_error)?;
1934        let mut deletions = Vec::new();
1935        for snapshot in page {
1936            let head = RunHead::decode(&snapshot.record.value)?;
1937            if matches!(head.retention, RunRetention::Deleting { .. }) {
1938                deletions.push(RunKey::new(
1939                    head.workspace_id,
1940                    head.workflow_id,
1941                    head.id,
1942                    head.creation,
1943                ));
1944            }
1945        }
1946        Ok((deletions, next))
1947    }
1948
1949    pub(crate) async fn deletion_checkpoint(
1950        &self,
1951        key: &RunKey,
1952    ) -> Result<Option<u64>, StorageFailure> {
1953        let key = RecordKey::exact_run(key);
1954        Ok(self.load_head(key).await?.and_then(|(head, _)| {
1955            matches!(head.retention, RunRetention::Deleting { .. }).then_some(head.sequence)
1956        }))
1957    }
1958
1959    pub(crate) async fn delete_run_page(
1960        &self,
1961        expected_app_revision: u64,
1962        run: &RunKey,
1963    ) -> Result<Option<AppMutation>, StorageFailure> {
1964        let key = RecordKey::exact_run(run);
1965        let Some((head, revision)) = self.load_head(key.clone()).await? else {
1966            return Ok(None);
1967        };
1968        let RunRetention::Deleting { after } = &head.retention else {
1969            return Err(StorageFailure::permanent(AppError::Conflict(
1970                "run deletion has not been sealed".into(),
1971            )));
1972        };
1973        // Listing the first remaining page also detects a forged progress key
1974        // which would otherwise hide undeleted earlier records.
1975        let (page, next) = self
1976            .list_page(Some(&head.history_prefix()), None, PAGE_SIZE)
1977            .await?;
1978        let mut keys = Vec::with_capacity(page.len());
1979        for snapshot in page {
1980            if after
1981                .as_ref()
1982                .is_some_and(|after| snapshot.record.key.key() <= after.as_str())
1983            {
1984                return Err(StorageFailure::permanent(AppError::Integration(
1985                    "run deletion progress skipped a retained record".into(),
1986                )));
1987            }
1988            keys.push(
1989                RecordKey::history_key(snapshot.record.key.key())
1990                    .map_err(StorageFailure::permanent)?,
1991            );
1992        }
1993        Ok(Some(AppMutation::new(AppMutationKind::DeleteRunPage {
1994            expected_app_revision,
1995            expected_run_revision: revision,
1996            head: AppStorageRecord::new(key, &head, 0).map_err(StorageFailure::permanent)?,
1997            keys,
1998            complete: next.is_none(),
1999        })))
2000    }
2001
2002    pub(crate) async fn commit(&self, mutation: AppMutation) -> AppCommitOutcome {
2003        let AppMutation { id, kind } = mutation;
2004        let receipt = match RecordKey::Receipt(id.clone()).record(&ReceiptRecord {
2005            epoch: EPOCH.to_owned(),
2006            id: id.clone(),
2007        }) {
2008            Ok(receipt) => receipt,
2009            Err(error) => {
2010                return AppCommitOutcome::Rejected(StorageFailure::permanent(error));
2011            }
2012        };
2013        let mut transaction = match self.inner.begin_transaction().await {
2014            Ok(transaction) => transaction,
2015            Err(error) => return AppCommitOutcome::Rejected(StorageFailure::from(error)),
2016        };
2017        let prepared: Result<(), StorageFailure> = async {
2018            if transaction
2019                .load(receipt.key.clone())
2020                .await
2021                .map_err(StorageFailure::from)?
2022                .is_some()
2023            {
2024                return Err(StorageFailure::permanent(AppError::Conflict(
2025                    "mutation identity already exists".to_owned(),
2026                )));
2027            }
2028            kind.stage(&mut transaction, self).await?;
2029            transaction.put(receipt).await.map_err(StorageFailure::from)
2030        }
2031        .await;
2032        if let Err(primary) = prepared {
2033            if transaction.rollback().await.is_err() {
2034                AppTrace::event(
2035                    || tracing::warn!(target: "app_kit", event = "app.storage.mutation_rollback_failed"),
2036                );
2037                return AppCommitOutcome::Rejected(StorageFailure::CleanupFailed(
2038                    primary.into_error(),
2039                ));
2040            }
2041            return AppCommitOutcome::Rejected(primary);
2042        }
2043        match transaction.commit().await {
2044            Ok(()) => {
2045                self.cleanup_receipt(&id).await;
2046                AppCommitOutcome::Applied
2047            }
2048            Err(
2049                error @ (StateStoreError::TransactionConflict { .. }
2050                | StateStoreError::InvalidConfig(_)
2051                | StateStoreError::InvalidRequest(_)
2052                | StateStoreError::Serialization(_)),
2053            ) => AppCommitOutcome::Rejected(StorageFailure::from(error)),
2054            Err(
2055                error @ (StateStoreError::Unavailable { .. }
2056                | StateStoreError::BackendOperation { .. }
2057                | StateStoreError::TimedOut { .. }),
2058            ) => {
2059                let unknown = StorageFailure::from(error);
2060                let key = match RecordKey::Receipt(id.clone()).state_key() {
2061                    Ok(key) => key,
2062                    Err(error) => {
2063                        return AppCommitOutcome::Unknown(StorageFailure::permanent(error));
2064                    }
2065                };
2066                let snapshot = match self.inner.load(key).await {
2067                    Ok(Some(snapshot)) => snapshot,
2068                    Ok(None) => return AppCommitOutcome::Unknown(unknown),
2069                    Err(error) => {
2070                        return AppCommitOutcome::Unknown(StorageFailure::from(error));
2071                    }
2072                };
2073                if let Err(error) = RecordCodec::preflight_epoch(&snapshot.record.value) {
2074                    return AppCommitOutcome::Unknown(StorageFailure::permanent(error));
2075                }
2076                match RecordCodec::decode::<ReceiptRecord>(&snapshot.record.value) {
2077                    Ok(receipt) if receipt.id == id => {
2078                        self.cleanup_receipt(&receipt.id).await;
2079                        AppCommitOutcome::Applied
2080                    }
2081                    Ok(_) | Err(_) => AppCommitOutcome::Unknown(StorageFailure::permanent(
2082                        AppError::Integration("mutation receipt is invalid".to_owned()),
2083                    )),
2084                }
2085            }
2086        }
2087    }
2088
2089    async fn load_record<T>(&self, key: RecordKey) -> Result<Option<(T, u64)>, StorageFailure>
2090    where
2091        T: DeserializeOwned,
2092    {
2093        let state_key = key.state_key().map_err(StorageFailure::permanent)?;
2094        let snapshot = self
2095            .inner
2096            .load(state_key.clone())
2097            .await
2098            .map_err(StorageFailure::from)?;
2099        snapshot
2100            .map(|snapshot| {
2101                RecordCodec::preflight_epoch(&snapshot.record.value)
2102                    .map_err(StorageFailure::permanent)?;
2103                RecordKey::validate_value(state_key.key(), &snapshot.record.value)
2104                    .map_err(StorageFailure::permanent)?;
2105                let revision = snapshot.revision.get();
2106                RecordCodec::decode(&snapshot.record.value)
2107                    .map(|record| (record, revision))
2108                    .map_err(StorageFailure::permanent)
2109            })
2110            .transpose()
2111    }
2112
2113    pub(crate) async fn cleanup_receipts(&self) -> Result<(), AppError> {
2114        let mut after = None;
2115        loop {
2116            let (page, next) = self
2117                .list_page(Some("receipt/"), after.as_deref(), PAGE_SIZE)
2118                .await
2119                .map_err(StorageFailure::into_error)?;
2120            if !page.is_empty() {
2121                let mut transaction = self
2122                    .inner
2123                    .begin_transaction()
2124                    .await
2125                    .map_err(AppError::from)?;
2126                for snapshot in page {
2127                    transaction
2128                        .delete(snapshot.record.key)
2129                        .await
2130                        .map_err(AppError::from)?;
2131                }
2132                transaction.commit().await.map_err(AppError::from)?;
2133            }
2134            match next {
2135                Some(next) => after = Some(next),
2136                None => return Ok(()),
2137            }
2138        }
2139    }
2140
2141    async fn cleanup_receipt(&self, id: &str) {
2142        // Only Applied mutations retire evidence. An idle partial page may
2143        // remain until reopen; uncertain owners' receipts never enter this queue.
2144        let retired = {
2145            let Ok(mut retired) = self.retired_receipts.lock() else {
2146                return;
2147            };
2148            retired.push(id.to_owned());
2149            if retired.len() < PAGE_SIZE {
2150                return;
2151            }
2152            std::mem::take(&mut *retired)
2153        };
2154        let Ok(mut transaction) = self.inner.begin_transaction().await else {
2155            return;
2156        };
2157        for id in retired {
2158            let Ok(key) = RecordKey::Receipt(id.clone()).state_key() else {
2159                continue;
2160            };
2161            let snapshot = match transaction.load(key.clone()).await {
2162                Ok(Some(snapshot)) => snapshot,
2163                Ok(None) => continue,
2164                Err(_) => {
2165                    let _ = transaction.rollback().await;
2166                    return;
2167                }
2168            };
2169            if RecordCodec::preflight_epoch(&snapshot.record.value).is_err() {
2170                continue;
2171            }
2172            let Ok(receipt) = RecordCodec::decode::<ReceiptRecord>(&snapshot.record.value) else {
2173                continue;
2174            };
2175            if receipt.id != id {
2176                continue;
2177            }
2178            if transaction.delete(key).await.is_err() {
2179                let _ = transaction.rollback().await;
2180                return;
2181            }
2182        }
2183        // This separate best-effort transaction cannot reject the already
2184        // applied mutation. Failed or cancelled cleanup is retried on reopen.
2185        let _ = transaction.commit().await;
2186    }
2187}
2188
2189impl AppMutationKind {
2190    fn stage<'a, T: StateTransactionBackend, CS: StateStoreBackend>(
2191        self,
2192        transaction: &'a mut T,
2193        store: &'a AppStore<CS>,
2194    ) -> Pin<Box<impl Future<Output = Result<(), StorageFailure>> + 'a>> {
2195        Box::pin(async move {
2196            let mut observed_pages = Vec::new();
2197            match self {
2198                Self::ClaimApp {
2199                    fence,
2200                    app,
2201                    definitions,
2202                } => {
2203                    match fence {
2204                        AppClaimFence::Missing => {
2205                            app.key().expect_missing(transaction).await?;
2206                            let intended = RecordCodec::decode::<AppRecord>(app.value())
2207                                .map_err(StorageFailure::permanent)?;
2208                            if intended.epoch != EPOCH
2209                                || intended.generation != 2
2210                                || intended.run_count != 0
2211                                || !matches!(&intended.shutdown, ShutdownRecord::Active)
2212                            {
2213                                return Err(StorageFailure::permanent(AppError::Conflict(
2214                                    "initial application generation claim is invalid".to_owned(),
2215                                )));
2216                            }
2217                        }
2218                        AppClaimFence::Revision(expected) => {
2219                            let snapshot = transaction
2220                                .load(app.key().state_key().map_err(StorageFailure::permanent)?)
2221                                .await
2222                                .map_err(StorageFailure::from)?
2223                                .ok_or_else(|| {
2224                                    StorageFailure::permanent(AppError::NotFound(
2225                                        "durable record does not exist".to_owned(),
2226                                    ))
2227                                })?;
2228                            if snapshot.revision.get() != expected {
2229                                return Err(StorageFailure::permanent(AppError::Conflict(
2230                                    "durable record revision fence is stale".to_owned(),
2231                                )));
2232                            }
2233                            RecordCodec::preflight_epoch(&snapshot.record.value)
2234                                .map_err(StorageFailure::permanent)?;
2235                            RecordKey::validate_value(
2236                                snapshot.record.key.key(),
2237                                &snapshot.record.value,
2238                            )
2239                            .map_err(StorageFailure::permanent)?;
2240                            let current = RecordCodec::decode::<AppRecord>(&snapshot.record.value)
2241                                .map_err(StorageFailure::permanent)?;
2242                            let intended = RecordCodec::decode::<AppRecord>(app.value())
2243                                .map_err(StorageFailure::permanent)?;
2244                            let next_generation =
2245                                current.generation.checked_add(1).ok_or_else(|| {
2246                                    StorageFailure::permanent(AppError::Conflict(
2247                                        "application generation is exhausted".to_owned(),
2248                                    ))
2249                                })?;
2250                            let next_shutdown =
2251                                if matches!(&current.shutdown, ShutdownRecord::Closed) {
2252                                    ShutdownRecord::Active
2253                                } else {
2254                                    current.shutdown.clone()
2255                                };
2256                            if intended.epoch != current.epoch
2257                                || intended.generation != next_generation
2258                                || intended.run_count != current.run_count
2259                                || intended.shutdown != next_shutdown
2260                            {
2261                                return Err(StorageFailure::permanent(AppError::Conflict(
2262                                    "application generation claim changed durable authority"
2263                                        .to_owned(),
2264                                )));
2265                            }
2266                        }
2267                    }
2268                    for definition in definitions {
2269                        definition.catalog(transaction).await?;
2270                    }
2271                    app.put(transaction).await
2272                }
2273                Self::AdvanceLifecycle {
2274                    expected_app_revision,
2275                    generation,
2276                    shutdown,
2277                } => {
2278                    let key = RecordKey::App
2279                        .state_key()
2280                        .map_err(StorageFailure::permanent)?;
2281                    let snapshot = transaction
2282                        .load(key.clone())
2283                        .await
2284                        .map_err(StorageFailure::from)?
2285                        .ok_or_else(|| {
2286                            StorageFailure::permanent(AppError::NotFound(
2287                                "durable record does not exist".to_owned(),
2288                            ))
2289                        })?;
2290                    if snapshot.revision.get() != expected_app_revision {
2291                        return Err(StorageFailure::permanent(AppError::Conflict(
2292                            "durable record revision fence is stale".to_owned(),
2293                        )));
2294                    }
2295                    RecordCodec::preflight_epoch(&snapshot.record.value)
2296                        .map_err(StorageFailure::permanent)?;
2297                    RecordKey::validate_value(key.key(), &snapshot.record.value)
2298                        .map_err(StorageFailure::permanent)?;
2299                    let mut record = RecordCodec::decode::<AppRecord>(&snapshot.record.value)
2300                        .map_err(StorageFailure::permanent)?;
2301                    if record.generation != generation {
2302                        return Err(StorageFailure::permanent(AppError::Closed(
2303                            "application generation has been superseded".to_owned(),
2304                        )));
2305                    }
2306                    let legal = matches!(
2307                        (&record.shutdown, &shutdown),
2308                        (ShutdownRecord::Active, ShutdownRecord::Draining)
2309                            | (ShutdownRecord::Active, ShutdownRecord::Cancelling)
2310                            | (ShutdownRecord::Draining, ShutdownRecord::Cancelling)
2311                            | (ShutdownRecord::Draining, ShutdownRecord::Closed)
2312                            | (ShutdownRecord::Cancelling, ShutdownRecord::Closed)
2313                    );
2314                    if !legal {
2315                        return Err(StorageFailure::permanent(AppError::Conflict(
2316                            "application lifecycle transition is invalid".to_owned(),
2317                        )));
2318                    }
2319                    record.shutdown = shutdown;
2320                    AppStorageRecord::app(&record)
2321                        .map_err(StorageFailure::permanent)?
2322                        .put(transaction)
2323                        .await
2324                }
2325                Self::AdmitWorkspace {
2326                    expected_app_revision,
2327                    app,
2328                    workspace,
2329                } => {
2330                    app.key()
2331                        .expect_revision(transaction, expected_app_revision)
2332                        .await?;
2333                    workspace.key().expect_missing(transaction).await?;
2334                    app.put(transaction).await?;
2335                    workspace.put(transaction).await
2336                }
2337                Self::AdvanceWorkspace {
2338                    expected_app_revision,
2339                    expected_workspace_revision,
2340                    workspace,
2341                } => {
2342                    RecordKey::App
2343                        .expect_revision(transaction, expected_app_revision)
2344                        .await?;
2345                    workspace
2346                        .key()
2347                        .expect_revision(transaction, expected_workspace_revision)
2348                        .await?;
2349                    workspace.put(transaction).await
2350                }
2351                Self::AdvanceWorkspaceWithApp {
2352                    expected_app_revision,
2353                    expected_workspace_revision,
2354                    app,
2355                    workspace,
2356                } => {
2357                    app.key()
2358                        .expect_revision(transaction, expected_app_revision)
2359                        .await?;
2360                    workspace
2361                        .key()
2362                        .expect_revision(transaction, expected_workspace_revision)
2363                        .await?;
2364                    app.put(transaction).await?;
2365                    workspace.put(transaction).await
2366                }
2367                Self::DeleteWorkspace {
2368                    expected_app_revision,
2369                    expected_workspace_revision,
2370                    workspace,
2371                } => {
2372                    RecordKey::App
2373                        .expect_revision(transaction, expected_app_revision)
2374                        .await?;
2375                    workspace
2376                        .expect_revision(transaction, expected_workspace_revision)
2377                        .await?;
2378                    workspace.delete(transaction).await
2379                }
2380                Self::AdmitRun {
2381                    expected_app_revision,
2382                    expected_workspace_revision,
2383                    mut app,
2384                    workspace,
2385                    run,
2386                    head,
2387                    source,
2388                } => {
2389                    app.rebase_capacity(transaction, expected_app_revision, 1)
2390                        .await?;
2391                    workspace
2392                        .key()
2393                        .expect_revision(transaction, expected_workspace_revision)
2394                        .await?;
2395                    if let Some(source) = source {
2396                        source.validate(transaction, store).await?;
2397                    }
2398                    app.put(transaction).await?;
2399                    run.admit(transaction, head).await
2400                }
2401                Self::AdvanceRun {
2402                    expected_app_revision,
2403                    expected_run_revision,
2404                    mut app,
2405                    run,
2406                    predecessor,
2407                    children,
2408                } => {
2409                    app.rebase_capacity(transaction, expected_app_revision, 0)
2410                        .await?;
2411                    let head = run.load_head(transaction, expected_run_revision).await?;
2412                    let mut released = 0_usize;
2413                    if !children.is_empty() {
2414                        let parent: RunRecord =
2415                            run.decode_run().map_err(StorageFailure::permanent)?;
2416                        let previous = &predecessor;
2417                        let deleting = matches!(
2418                            parent.checkpoint.phase,
2419                            CheckpointPhaseRecord::Routing { .. }
2420                        ) && parent.checkpoint.base_sequence
2421                            == parent.checkpoint.sequence;
2422                        let mut admitted = BTreeMap::new();
2423                        if deleting {
2424                            for activation in &previous.checkpoint.staged {
2425                                if let Some((sequence, members)) = &activation.children {
2426                                    for (index, member) in members.iter().enumerate() {
2427                                        admitted.insert(
2428                                            &member.key,
2429                                            (
2430                                                *sequence,
2431                                                index,
2432                                                member.revision.as_str(),
2433                                                member.history,
2434                                            ),
2435                                        );
2436                                    }
2437                                }
2438                            }
2439                        } else {
2440                            // Release only the original complete Child group
2441                            // transferred by this Stage, before its parent barrier.
2442                            for activation in &parent.checkpoint.staged {
2443                                if !matches!(
2444                                    activation.outcome,
2445                                    StagedOutcomeRecord::Complete { .. }
2446                                ) {
2447                                    continue;
2448                                }
2449                                let Some((sequence, members)) = &activation.children else {
2450                                    continue;
2451                                };
2452                                let Some(control) = previous.checkpoint.control(*sequence) else {
2453                                    continue;
2454                                };
2455                                let ControlRecordState::Child {
2456                                    inputs,
2457                                    admitted: original,
2458                                } = &control.state
2459                                else {
2460                                    continue;
2461                                };
2462                                if activation.node != control.node
2463                                    || activation.lineage != control.lineage
2464                                    || members != original
2465                                    || members.len() != inputs.len()
2466                                    || parent.checkpoint.control(*sequence).is_some()
2467                                    || parent.checkpoint.base_sequence
2468                                        != previous.checkpoint.base_sequence
2469                                {
2470                                    return Err(StorageFailure::permanent(AppError::Conflict(
2471                                        "child capacity requires its completed original Stage"
2472                                            .into(),
2473                                    )));
2474                                }
2475                                for (index, member) in members.iter().enumerate() {
2476                                    admitted.insert(
2477                                        &member.key,
2478                                        (
2479                                            *sequence,
2480                                            index,
2481                                            member.revision.as_str(),
2482                                            member.history,
2483                                        ),
2484                                    );
2485                                }
2486                            }
2487                        }
2488                        for (child, revision) in children {
2489                            let key = RecordKey::Run {
2490                                workspace: child.workspace().clone(),
2491                                workflow: child.workflow().clone(),
2492                                run: child.run().clone(),
2493                                occurrence: child.occurrence().to_owned(),
2494                            }
2495                            .state_key()
2496                            .map_err(StorageFailure::permanent)?;
2497                            let snapshot = transaction
2498                                .load(key.clone())
2499                                .await
2500                                .map_err(StorageFailure::from)?
2501                                .ok_or_else(|| {
2502                                    StorageFailure::permanent(AppError::Integration(
2503                                        "copied child head is missing".into(),
2504                                    ))
2505                                })?;
2506                            RecordKey::validate_value(key.key(), &snapshot.record.value)
2507                                .map_err(StorageFailure::permanent)?;
2508                            let mut child_head = RunHead::decode(&snapshot.record.value)
2509                                .map_err(StorageFailure::permanent)?;
2510                            let key = child_head
2511                                .checkpoint_key(child_head.sequence)
2512                                .state_key()
2513                                .map_err(StorageFailure::permanent)?;
2514                            let snapshot = transaction
2515                                .load(key.clone())
2516                                .await
2517                                .map_err(StorageFailure::from)?
2518                                .ok_or_else(|| {
2519                                    StorageFailure::permanent(AppError::Integration(
2520                                        "copied child checkpoint is missing".into(),
2521                                    ))
2522                                })?;
2523                            RecordKey::validate_value(key.key(), &snapshot.record.value)
2524                                .map_err(StorageFailure::permanent)?;
2525                            let record: RunRecord = store
2526                                .decode_run(transaction, snapshot, false, &mut observed_pages)
2527                                .await?;
2528                            child_head
2529                                .validate_row(&record)
2530                                .map_err(StorageFailure::permanent)?;
2531                            let relation = record.parent.as_ref().ok_or_else(|| {
2532                                StorageFailure::permanent(AppError::Conflict(
2533                                    "copied child has no original parent".into(),
2534                                ))
2535                            })?;
2536                            let membership = admitted.remove(&child);
2537                            if !matches!(record.checkpoint.phase, CheckpointPhaseRecord::Succeeded)
2538                                || record.workspace_id != parent.workspace_id
2539                                || relation.workflow_id != parent.workflow_id || relation.run_id != parent.id
2540                                || relation.occurrence != parent.occurrence
2541                                || relation.base_checkpoint != previous.checkpoint.base_sequence
2542                                || previous.parent.as_ref().map_or(0, |parent| parent.depth).checked_add(1) != Some(relation.depth)
2543                                || relation.depth > crate::workflow::run::MAX_CHILD_DEPTH
2544                                || membership.is_none_or(|(sequence, index, selected, history)| {
2545                                    sequence != relation.control_sequence || index != relation.index || selected != revision
2546                                        || deleting && history != crate::workflow::definition::ChildHistory::DeleteAfterReduction
2547                                })
2548                                || record.definition_revision != revision
2549                                || record.child_revisions != previous.child_revisions
2550                                || record.agent_revisions.iter().any(|(key, revision)| previous.agent_revisions.get(key) != Some(revision))
2551                            {
2552                                return Err(StorageFailure::permanent(AppError::Conflict("copied child authority changed".into())));
2553                            }
2554                            match child_head.retention {
2555                                RunRetention::Active {} => {
2556                                    child_head.active_key().expect_present(transaction).await?;
2557                                    child_head.active_key().delete(transaction).await?;
2558                                    released += 1;
2559                                }
2560                                RunRetention::Retained {} => {
2561                                    child_head.active_key().expect_missing(transaction).await?;
2562                                    if !deleting {
2563                                        continue;
2564                                    }
2565                                }
2566                                RunRetention::Deleting { .. } => {
2567                                    return Err(StorageFailure::permanent(AppError::Conflict(
2568                                        "copied child is already sealed".into(),
2569                                    )));
2570                                }
2571                            }
2572                            child_head.retention = if deleting {
2573                                RunRetention::Deleting { after: None }
2574                            } else {
2575                                RunRetention::Retained {}
2576                            };
2577                            transaction
2578                                .put(child_head.record().map_err(StorageFailure::permanent)?)
2579                                .await
2580                                .map_err(StorageFailure::from)?;
2581                        }
2582                    }
2583                    if released != 0 {
2584                        let mut app: AppRecord =
2585                            RecordCodec::decode(app.value()).map_err(StorageFailure::permanent)?;
2586                        app.run_count = app.run_count.checked_sub(released).ok_or_else(|| {
2587                            StorageFailure::permanent(AppError::Integration(
2588                                "copied child capacity is inconsistent".into(),
2589                            ))
2590                        })?;
2591                        AppStorageRecord::app(&app)
2592                            .map_err(StorageFailure::permanent)?
2593                            .put(transaction)
2594                            .await?;
2595                    }
2596                    run.append(transaction, head, predecessor, store, &mut observed_pages)
2597                        .await
2598                }
2599                Self::AdmitChildren {
2600                    expected_app_revision,
2601                    expected_workspace_revision,
2602                    expected_parent_revision,
2603                    mut app,
2604                    workspace,
2605                    parent,
2606                    predecessor,
2607                    children,
2608                } => {
2609                    app.rebase_capacity(
2610                        transaction,
2611                        expected_app_revision,
2612                        children.len() as isize,
2613                    )
2614                    .await?;
2615                    workspace
2616                        .key()
2617                        .expect_revision(transaction, expected_workspace_revision)
2618                        .await?;
2619                    let head = parent
2620                        .load_head(transaction, expected_parent_revision)
2621                        .await?;
2622                    for (_, _, source) in &children {
2623                        if let Some(source) = source {
2624                            source.validate(transaction, store).await?;
2625                        }
2626                    }
2627                    app.put(transaction).await?;
2628                    parent
2629                        .append(transaction, head, predecessor, store, &mut observed_pages)
2630                        .await?;
2631                    for (child, head, _) in children {
2632                        child.admit(transaction, head).await?;
2633                    }
2634                    Ok(())
2635                }
2636                Self::RetainRun {
2637                    expected_app_revision,
2638                    expected_run_revision,
2639                    mut app,
2640                    run,
2641                } => {
2642                    app.rebase_capacity(transaction, expected_app_revision, -1)
2643                        .await?;
2644                    let mut head = run.load_head(transaction, expected_run_revision).await?;
2645                    let record: RunRecord = run.decode_run().map_err(StorageFailure::permanent)?;
2646                    head.validate_row(&record)
2647                        .map_err(StorageFailure::permanent)?;
2648                    if !record.checkpoint.is_terminal()
2649                        || !matches!(head.retention, RunRetention::Active {})
2650                    {
2651                        return Err(StorageFailure::permanent(AppError::Conflict(
2652                            "run capacity is not eligible for release".into(),
2653                        )));
2654                    }
2655                    run.expect_value(transaction, store, &mut observed_pages)
2656                        .await?;
2657                    head.active_key().expect_present(transaction).await?;
2658                    head.retention = RunRetention::Retained {};
2659                    app.put(transaction).await?;
2660                    head.active_key().delete(transaction).await?;
2661                    transaction
2662                        .put(head.record().map_err(StorageFailure::permanent)?)
2663                        .await
2664                        .map_err(StorageFailure::from)
2665                }
2666                Self::ReadmitRun {
2667                    expected_app_revision,
2668                    expected_workspace_revision,
2669                    expected_run_revision,
2670                    mut app,
2671                    workspace,
2672                    run,
2673                    predecessor,
2674                } => {
2675                    app.rebase_capacity(transaction, expected_app_revision, 1)
2676                        .await?;
2677                    workspace
2678                        .key()
2679                        .expect_revision(transaction, expected_workspace_revision)
2680                        .await?;
2681                    let mut head = run.load_head(transaction, expected_run_revision).await?;
2682                    if !matches!(head.retention, RunRetention::Retained {}) {
2683                        return Err(StorageFailure::permanent(AppError::Conflict(
2684                            "run capacity is already admitted".into(),
2685                        )));
2686                    }
2687                    let prior = transaction
2688                        .load(
2689                            head.checkpoint_key(head.sequence)
2690                                .state_key()
2691                                .map_err(StorageFailure::permanent)?,
2692                        )
2693                        .await
2694                        .map_err(StorageFailure::from)?
2695                        .ok_or_else(|| {
2696                            StorageFailure::permanent(AppError::Integration(
2697                                "retained run checkpoint is missing".into(),
2698                            ))
2699                        })?;
2700                    RecordCodec::preflight_epoch(&prior.record.value)
2701                        .map_err(StorageFailure::permanent)?;
2702                    RecordKey::validate_value(prior.record.key.key(), &prior.record.value)
2703                        .map_err(StorageFailure::permanent)?;
2704                    let prior: RunRecord = store
2705                        .decode_run(transaction, prior, false, &mut observed_pages)
2706                        .await?;
2707                    head.validate_row(&prior)
2708                        .map_err(StorageFailure::permanent)?;
2709                    head.active_key().expect_missing(transaction).await?;
2710                    head.retention = RunRetention::Active {};
2711                    let active = head
2712                        .active_key()
2713                        .index_record()
2714                        .map_err(StorageFailure::permanent)?;
2715                    app.put(transaction).await?;
2716                    run.append(transaction, head, predecessor, store, &mut observed_pages)
2717                        .await?;
2718                    transaction.put(active).await.map_err(StorageFailure::from)
2719                }
2720                Self::SealRun {
2721                    expected_app_revision,
2722                    expected_run_revision,
2723                    run,
2724                    parent,
2725                } => {
2726                    RecordKey::App
2727                        .expect_revision(transaction, expected_app_revision)
2728                        .await?;
2729                    let mut head = run.load_head(transaction, expected_run_revision).await?;
2730                    let record: RunRecord = run.decode_run().map_err(StorageFailure::permanent)?;
2731                    head.validate_row(&record)
2732                        .map_err(StorageFailure::permanent)?;
2733                    if !matches!(head.retention, RunRetention::Retained {})
2734                        || !record.checkpoint.is_terminal()
2735                    {
2736                        return Err(StorageFailure::permanent(AppError::Conflict(
2737                            "run history is not eligible for deletion".into(),
2738                        )));
2739                    }
2740                    run.expect_value(transaction, store, &mut observed_pages)
2741                        .await?;
2742                    match (&record.parent, parent) {
2743                        (Some(relation), Some((parent, revision))) => {
2744                            let parent_record: RunRecord =
2745                                parent.decode_run().map_err(StorageFailure::permanent)?;
2746                            if parent_record.workspace_id != record.workspace_id
2747                                || parent_record.workflow_id != relation.workflow_id
2748                                || parent_record.id != relation.run_id
2749                                || parent_record.occurrence != relation.occurrence
2750                            {
2751                                return Err(StorageFailure::permanent(AppError::Conflict(
2752                                    "deletion parent fence has foreign identity".into(),
2753                                )));
2754                            }
2755                            let parent_head = parent.load_head(transaction, revision).await?;
2756                            parent_head
2757                                .ensure_readable()
2758                                .map_err(StorageFailure::permanent)?;
2759                            parent_head
2760                                .validate_row(&parent_record)
2761                                .map_err(StorageFailure::permanent)?;
2762                            parent
2763                                .expect_value(transaction, store, &mut observed_pages)
2764                                .await?;
2765                        }
2766                        (Some(relation), None) => {
2767                            RecordKey::Run {
2768                                workspace: record.workspace_id.clone(),
2769                                workflow: relation.workflow_id.clone(),
2770                                run: relation.run_id.clone(),
2771                                occurrence: relation.occurrence.clone(),
2772                            }
2773                            .expect_missing(transaction)
2774                            .await?;
2775                        }
2776                        (None, None) => {}
2777                        (None, Some(_)) => {
2778                            return Err(StorageFailure::permanent(AppError::Conflict(
2779                                "root deletion cannot fence a parent".into(),
2780                            )));
2781                        }
2782                    }
2783                    head.active_key().expect_missing(transaction).await?;
2784                    head.retention = RunRetention::Deleting { after: None };
2785                    transaction
2786                        .put(head.record().map_err(StorageFailure::permanent)?)
2787                        .await
2788                        .map_err(StorageFailure::from)
2789                }
2790                Self::DeleteRunPage {
2791                    expected_app_revision,
2792                    expected_run_revision,
2793                    head,
2794                    keys,
2795                    complete,
2796                } => {
2797                    RecordKey::App
2798                        .expect_revision(transaction, expected_app_revision)
2799                        .await?;
2800                    head.key()
2801                        .expect_revision(transaction, expected_run_revision)
2802                        .await?;
2803                    head.expect_value(transaction, store, &mut observed_pages)
2804                        .await?;
2805                    let mut decoded =
2806                        RunHead::decode(head.value()).map_err(StorageFailure::permanent)?;
2807                    let RunRetention::Deleting { after } = &decoded.retention else {
2808                        return Err(StorageFailure::permanent(AppError::Conflict(
2809                            "run history deletion is not admitted".into(),
2810                        )));
2811                    };
2812                    let mut previous = after.clone();
2813                    for key in &keys {
2814                        let text = key
2815                            .state_key()
2816                            .map_err(StorageFailure::permanent)?
2817                            .key()
2818                            .to_owned();
2819                        let valid = match key {
2820                            RecordKey::Checkpoint {
2821                                workspace,
2822                                workflow,
2823                                run,
2824                                occurrence,
2825                                sequence,
2826                            }
2827                            | RecordKey::Ledger {
2828                                workspace,
2829                                workflow,
2830                                run,
2831                                occurrence,
2832                                sequence,
2833                                ..
2834                            }
2835                            | RecordKey::ActionObservation {
2836                                workspace,
2837                                workflow,
2838                                run,
2839                                occurrence,
2840                                sequence,
2841                                ..
2842                            } => {
2843                                workspace == &decoded.workspace_id
2844                                    && workflow == &decoded.workflow_id
2845                                    && run == &decoded.id
2846                                    && occurrence == &decoded.creation
2847                                    && *sequence <= decoded.sequence
2848                            }
2849                            RecordKey::ProjectionLink { key, sequence, .. } => {
2850                                key.workspace() == &decoded.workspace_id
2851                                    && key.workflow() == &decoded.workflow_id
2852                                    && key.run() == &decoded.id
2853                                    && key.occurrence() == decoded.creation
2854                                    && *sequence <= decoded.sequence
2855                            }
2856                            _ => false,
2857                        };
2858                        if !valid || previous.as_ref().is_some_and(|previous| previous >= &text) {
2859                            return Err(StorageFailure::permanent(AppError::Integration(
2860                                "run deletion page is invalid".into(),
2861                            )));
2862                        }
2863                        previous = Some(text);
2864                    }
2865                    for key in keys {
2866                        match &key {
2867                            RecordKey::ActionObservation {
2868                                workspace,
2869                                workflow,
2870                                run,
2871                                occurrence,
2872                                ordinal,
2873                                sequence,
2874                            } => {
2875                                RunProjection::Action(*ordinal)
2876                                    .remove(
2877                                        transaction,
2878                                        &RunKey::new(
2879                                            workspace.clone(),
2880                                            workflow.clone(),
2881                                            run.clone(),
2882                                            occurrence.clone(),
2883                                        ),
2884                                        *sequence,
2885                                        EPOCH,
2886                                    )
2887                                    .await?;
2888                            }
2889                            RecordKey::ProjectionLink {
2890                                key,
2891                                projection,
2892                                sequence,
2893                            } => {
2894                                projection
2895                                    .remove(transaction, key, *sequence, EPOCH)
2896                                    .await?;
2897                            }
2898                            _ => key.delete(transaction).await?,
2899                        }
2900                    }
2901                    if complete {
2902                        decoded.head_key().delete(transaction).await
2903                    } else {
2904                        decoded.retention = RunRetention::Deleting { after: previous };
2905                        transaction
2906                            .put(decoded.record().map_err(StorageFailure::permanent)?)
2907                            .await
2908                            .map_err(StorageFailure::from)
2909                    }
2910                }
2911            }
2912        })
2913    }
2914}
2915
2916impl AppStorageRecord {
2917    fn key(&self) -> &RecordKey {
2918        match self {
2919            Self::Value { key, .. } | Self::Checkpoint { key, .. } => key,
2920        }
2921    }
2922
2923    fn value(&self) -> &Value {
2924        match self {
2925            Self::Value { value, .. } | Self::Checkpoint { value, .. } => value,
2926        }
2927    }
2928
2929    fn definitions(
2930        definitions: &BTreeMap<WorkflowId, Vec<Arc<CompiledDefinition>>>,
2931    ) -> Result<Vec<Self>, AppError> {
2932        definitions
2933            .iter()
2934            .flat_map(|(id, revisions)| {
2935                revisions.iter().map(move |definition| {
2936                    let record = DefinitionRecord {
2937                        epoch: EPOCH.into(),
2938                        workflow_id: id.clone(),
2939                        revision: definition.revision().to_owned(),
2940                        canonical: definition.fingerprint().to_owned(),
2941                    };
2942                    Self::new(record.key(), &record, 0)
2943                })
2944            })
2945            .collect()
2946    }
2947
2948    async fn catalog<T: StateTransactionBackend>(
2949        self,
2950        transaction: &mut T,
2951    ) -> Result<(), StorageFailure> {
2952        let key = self.key().state_key().map_err(StorageFailure::permanent)?;
2953        if let Some(current) = transaction
2954            .load(key.clone())
2955            .await
2956            .map_err(StorageFailure::from)?
2957        {
2958            RecordKey::validate_value(key.key(), &current.record.value)
2959                .map_err(StorageFailure::permanent)?;
2960            let current: DefinitionRecord =
2961                RecordCodec::decode(&current.record.value).map_err(StorageFailure::permanent)?;
2962            let intended: DefinitionRecord =
2963                RecordCodec::decode(self.value()).map_err(StorageFailure::permanent)?;
2964            if current.canonical != intended.canonical {
2965                return Err(StorageFailure::permanent(AppError::Conflict(
2966                    "configured workflow revision differs from its durable definition".into(),
2967                )));
2968            }
2969            Ok(())
2970        } else {
2971            self.put(transaction).await
2972        }
2973    }
2974
2975    async fn rebase_capacity<T: StateTransactionBackend>(
2976        &mut self,
2977        transaction: &mut T,
2978        expected: u64,
2979        change: isize,
2980    ) -> Result<(), StorageFailure> {
2981        let snapshot = transaction
2982            .load(self.key().state_key().map_err(StorageFailure::permanent)?)
2983            .await
2984            .map_err(StorageFailure::from)?
2985            .ok_or_else(|| {
2986                StorageFailure::permanent(AppError::NotFound(
2987                    "durable record does not exist".into(),
2988                ))
2989            })?;
2990        RecordCodec::preflight_epoch(&snapshot.record.value).map_err(StorageFailure::permanent)?;
2991        RecordKey::validate_value("app", &snapshot.record.value)
2992            .map_err(StorageFailure::permanent)?;
2993        let mut current: AppRecord =
2994            RecordCodec::decode(&snapshot.record.value).map_err(StorageFailure::permanent)?;
2995        let intended = AppRecord::deserialize(self.value()).map_err(|_| {
2996            StorageFailure::permanent(AppError::Integration(
2997                "app durable record is corrupt".into(),
2998            ))
2999        })?;
3000        let count = current.run_count;
3001        // Only the shared count can commute with another accepted run mutation.
3002        // Generation, lifecycle and every retained pin remain exact authority.
3003        current.run_count = intended.run_count;
3004        if current != intended {
3005            return Err(StorageFailure::permanent(AppError::Conflict(
3006                "run capacity mutation changes unrelated application authority".into(),
3007            )));
3008        }
3009        let count = count.checked_add_signed(change).ok_or_else(|| {
3010            StorageFailure::permanent(AppError::Conflict(
3011                "run capacity transition is invalid".into(),
3012            ))
3013        })?;
3014        if snapshot.revision.get() < expected
3015            || snapshot.revision.get() == expected && count != intended.run_count
3016        {
3017            return Err(StorageFailure::permanent(AppError::Conflict(
3018                "run capacity revision or count fence is stale".into(),
3019            )));
3020        }
3021        if count > MAX_RUNS {
3022            return Err(StorageFailure::permanent(AppError::Busy(
3023                "application run capacity is exhausted".into(),
3024            )));
3025        }
3026        current.run_count = count;
3027        *self = Self::app(&current).map_err(StorageFailure::permanent)?;
3028        Ok(())
3029    }
3030
3031    fn app(record: &AppRecord) -> Result<Self, AppError> {
3032        Self::new(RecordKey::App, record, 0)
3033    }
3034
3035    fn workspace(record: &WorkspaceRecord) -> Result<Self, AppError> {
3036        Self::new(RecordKey::Workspace(record.id.clone()), record, 0)
3037    }
3038
3039    fn predecessor(record: &RunRecord) -> Result<RunRecord, AppError> {
3040        let predecessor = record.observed_predecessor()?;
3041        let key = RecordKey::checkpoint(&predecessor);
3042        let reserved_bytes = predecessor.checkpoint.prospective_settlement_bytes()?;
3043        let state_key = key.state_key()?;
3044        let size = RecordCodec::logical_run_len(&predecessor)?;
3045        if size.saturating_add(reserved_bytes) > MAX_RECORD_BYTES {
3046            return Err(AppError::InvalidInput(
3047                "app durable record exceeds the private byte bound".into(),
3048            ));
3049        }
3050        // The derived key and typed predecessor share their exact identity.
3051        // Keep key validation after the complete encoded-size check; `previous`
3052        // is omitted by RunRecord serialization and is never stored here.
3053        if RecordKey::history_key(state_key.key())? != key {
3054            return Err(AppError::Integration(
3055                "durable history value does not match its key".into(),
3056            ));
3057        }
3058        Ok(predecessor)
3059    }
3060
3061    fn head(record: &RunRecord) -> Result<Self, AppError> {
3062        let head = RunHead::admitted(record)?;
3063        Self::new(head.head_key(), &head, 0)
3064    }
3065
3066    async fn load_head<T: StateTransactionBackend>(
3067        &self,
3068        transaction: &mut T,
3069        expected: u64,
3070    ) -> Result<RunHead, StorageFailure> {
3071        let key = self
3072            .key()
3073            .head_key()
3074            .map_err(StorageFailure::permanent)?
3075            .state_key()
3076            .map_err(StorageFailure::permanent)?;
3077        let snapshot = transaction
3078            .load(key.clone())
3079            .await
3080            .map_err(StorageFailure::from)?
3081            .ok_or_else(|| {
3082                StorageFailure::permanent(AppError::NotFound("workflow run does not exist".into()))
3083            })?;
3084        if snapshot.revision.get() != expected {
3085            return Err(StorageFailure::permanent(AppError::Conflict(
3086                "run head revision fence is stale".into(),
3087            )));
3088        }
3089        RecordKey::validate_value(key.key(), &snapshot.record.value)
3090            .map_err(StorageFailure::permanent)?;
3091        RunHead::decode(&snapshot.record.value).map_err(StorageFailure::permanent)
3092    }
3093
3094    async fn expect_value<T: StateTransactionBackend, CS: StateStoreBackend>(
3095        &self,
3096        transaction: &mut T,
3097        store: &AppStore<CS>,
3098        observed_pages: &mut Vec<StateRecordSnapshot>,
3099    ) -> Result<(), StorageFailure> {
3100        let snapshot = transaction
3101            .load(self.key().state_key().map_err(StorageFailure::permanent)?)
3102            .await
3103            .map_err(StorageFailure::from)?;
3104        let matches = match snapshot {
3105            Some(snapshot) if matches!(self.key(), RecordKey::Checkpoint { .. }) => {
3106                let actual: RunRecord = store
3107                    .decode_run(transaction, snapshot, false, observed_pages)
3108                    .await?;
3109                self.matches_run(&actual)
3110                    .map_err(StorageFailure::permanent)?
3111            }
3112            Some(snapshot) => snapshot.record.value.as_ref() == self.value(),
3113            None => false,
3114        };
3115        if !matches {
3116            return Err(StorageFailure::permanent(AppError::Conflict(
3117                "immutable checkpoint evidence changed".into(),
3118            )));
3119        }
3120        Ok(())
3121    }
3122
3123    fn decode_run(&self) -> Result<RunRecord, AppError> {
3124        match self {
3125            Self::Checkpoint { record, .. } => Ok(record.as_ref().clone()),
3126            Self::Value { .. } => Err(AppError::Integration("expected checkpoint evidence".into())),
3127        }
3128    }
3129
3130    fn matches_run(&self, actual: &RunRecord) -> Result<bool, AppError> {
3131        match self {
3132            Self::Checkpoint { record, value, .. } => {
3133                let expected: SettlementPages =
3134                    RecordCodec::decode(&value["checkpoint"]["settlements"])?;
3135                Ok(actual.same_persisted_row(record)?
3136                    && actual.checkpoint.settlement_references() == expected.pages)
3137            }
3138            Self::Value { .. } => Err(AppError::Integration("expected checkpoint evidence".into())),
3139        }
3140    }
3141
3142    fn append<'a, T: StateTransactionBackend, CS: StateStoreBackend>(
3143        self,
3144        transaction: &'a mut T,
3145        mut head: RunHead,
3146        predecessor: RunRecord,
3147        store: &'a AppStore<CS>,
3148        observed_pages: &'a mut Vec<StateRecordSnapshot>,
3149    ) -> Pin<Box<impl Future<Output = Result<(), StorageFailure>> + 'a>> {
3150        Box::pin(async move {
3151            let record: RunRecord = self.decode_run().map_err(StorageFailure::permanent)?;
3152            let prior = transaction
3153                .load(
3154                    head.checkpoint_key(head.sequence)
3155                        .state_key()
3156                        .map_err(StorageFailure::permanent)?,
3157                )
3158                .await
3159                .map_err(StorageFailure::from)?
3160                .ok_or_else(|| {
3161                    StorageFailure::permanent(AppError::Integration(
3162                        "current checkpoint is missing".into(),
3163                    ))
3164                })?;
3165            RecordCodec::preflight_epoch(&prior.record.value).map_err(StorageFailure::permanent)?;
3166            RecordKey::validate_value(prior.record.key.key(), &prior.record.value)
3167                .map_err(StorageFailure::permanent)?;
3168            let prior: RunRecord = store
3169                .decode_run(transaction, prior, false, observed_pages)
3170                .await?;
3171            if RecordKey::checkpoint(&predecessor) != head.checkpoint_key(head.sequence)
3172                || prior.checkpoint.settlement_references()
3173                    != predecessor.checkpoint.settlement_references()
3174                || !prior
3175                    .same_persisted_row(&predecessor)
3176                    .map_err(StorageFailure::permanent)?
3177            {
3178                return Err(StorageFailure::permanent(AppError::Conflict(
3179                    "immutable checkpoint predecessor changed".into(),
3180                )));
3181            }
3182            head.validate_row(&prior)
3183                .map_err(StorageFailure::permanent)?;
3184            if !record.same_run(&prior) {
3185                return Err(StorageFailure::permanent(AppError::Integration(
3186                    "checkpoint changes immutable run identity".into(),
3187                )));
3188            }
3189            head.advance(&record, &prior)
3190                .map_err(StorageFailure::permanent)?;
3191            self.key().expect_missing(transaction).await?;
3192            for projection in RunProjection::for_checkpoint(&record, Some(&prior)) {
3193                projection
3194                    .append(transaction, &record, record.checkpoint.sequence)
3195                    .await?;
3196            }
3197            let before = prior.checkpoint.is_copy_clean();
3198            let after = record.checkpoint.is_copy_clean();
3199            if before && !after {
3200                RunProjection::Unsettled
3201                    .append(transaction, &record, 0)
3202                    .await?;
3203            } else if !before && after {
3204                RunProjection::Unsettled
3205                    .remove(transaction, &record.key(), 0, EPOCH)
3206                    .await?;
3207            }
3208            self.put(transaction).await?;
3209            transaction
3210                .put(head.record().map_err(StorageFailure::permanent)?)
3211                .await
3212                .map_err(StorageFailure::from)
3213        })
3214    }
3215
3216    async fn admit<T: StateTransactionBackend>(
3217        self,
3218        transaction: &mut T,
3219        head: Self,
3220    ) -> Result<(), StorageFailure> {
3221        let decoded = RunHead::decode(head.value()).map_err(StorageFailure::permanent)?;
3222        let record: RunRecord = self.decode_run().map_err(StorageFailure::permanent)?;
3223        decoded
3224            .validate_row(&record)
3225            .map_err(StorageFailure::permanent)?;
3226        head.key().expect_missing(transaction).await?;
3227        self.key().expect_missing(transaction).await?;
3228        decoded.active_key().expect_missing(transaction).await?;
3229        for projection in RunProjection::for_checkpoint(&record, None) {
3230            projection
3231                .append(transaction, &record, record.checkpoint.sequence)
3232                .await?;
3233        }
3234        if !record.checkpoint.is_copy_clean() {
3235            RunProjection::Unsettled
3236                .append(transaction, &record, 0)
3237                .await?;
3238        }
3239        self.put(transaction).await?;
3240        head.put(transaction).await?;
3241        transaction
3242            .put(
3243                decoded
3244                    .active_key()
3245                    .index_record()
3246                    .map_err(StorageFailure::permanent)?,
3247            )
3248            .await
3249            .map_err(StorageFailure::from)
3250    }
3251
3252    fn new<T>(key: RecordKey, record: &T, reserved_bytes: usize) -> Result<Self, AppError>
3253    where
3254        T: Serialize,
3255    {
3256        let state_key = key.state_key()?;
3257        let value = RecordCodec::encode(record, reserved_bytes)?;
3258        RecordKey::validate_value(state_key.key(), &value)?;
3259        Ok(Self::Value { key, value })
3260    }
3261
3262    async fn put<T: StateTransactionBackend>(
3263        self,
3264        transaction: &mut T,
3265    ) -> Result<(), StorageFailure> {
3266        let (key, value) = match self {
3267            Self::Value { key, value } => (key, value),
3268            Self::Checkpoint {
3269                key, value, pages, ..
3270            } => {
3271                for page in pages {
3272                    if transaction
3273                        .load(page.key.clone())
3274                        .await
3275                        .map_err(StorageFailure::from)?
3276                        .is_some()
3277                    {
3278                        return Err(StorageFailure::permanent(AppError::Conflict(
3279                            "immutable settlement page already exists".into(),
3280                        )));
3281                    }
3282                    transaction.put(page).await.map_err(StorageFailure::from)?;
3283                }
3284                (key, value)
3285            }
3286        };
3287        transaction
3288            .put(StateRecord::new(
3289                key.state_key().map_err(StorageFailure::permanent)?,
3290                value,
3291            ))
3292            .await
3293            .map_err(StorageFailure::from)
3294    }
3295}
3296
3297impl AppMutation {
3298    pub(crate) fn claim_new_app(
3299        record: &AppRecord,
3300        definitions: &BTreeMap<WorkflowId, Vec<Arc<CompiledDefinition>>>,
3301    ) -> Result<Self, AppError> {
3302        Ok(Self::new(AppMutationKind::ClaimApp {
3303            fence: AppClaimFence::Missing,
3304            app: AppStorageRecord::app(record)?,
3305            definitions: AppStorageRecord::definitions(definitions)?,
3306        }))
3307    }
3308
3309    pub(crate) fn claim_app_revision(
3310        expected_app_revision: u64,
3311        record: &AppRecord,
3312        definitions: &BTreeMap<WorkflowId, Vec<Arc<CompiledDefinition>>>,
3313    ) -> Result<Self, AppError> {
3314        Ok(Self::new(AppMutationKind::ClaimApp {
3315            fence: AppClaimFence::Revision(expected_app_revision),
3316            app: AppStorageRecord::app(record)?,
3317            definitions: AppStorageRecord::definitions(definitions)?,
3318        }))
3319    }
3320
3321    pub(crate) fn advance_lifecycle(
3322        expected_app_revision: u64,
3323        generation: u64,
3324        shutdown: ShutdownRecord,
3325    ) -> Result<Self, AppError> {
3326        if matches!(&shutdown, ShutdownRecord::Active) {
3327            return Err(AppError::Integration(
3328                "application lifecycle mutation cannot publish active state".to_owned(),
3329            ));
3330        }
3331        Ok(Self::new(AppMutationKind::AdvanceLifecycle {
3332            expected_app_revision,
3333            generation,
3334            shutdown,
3335        }))
3336    }
3337
3338    pub(crate) fn admit_workspace(
3339        expected_app_revision: u64,
3340        app: &AppRecord,
3341        workspace: &WorkspaceRecord,
3342    ) -> Result<Self, AppError> {
3343        Ok(Self::new(AppMutationKind::AdmitWorkspace {
3344            expected_app_revision,
3345            app: AppStorageRecord::app(app)?,
3346            workspace: AppStorageRecord::workspace(workspace)?,
3347        }))
3348    }
3349
3350    pub(crate) fn advance_workspace(
3351        expected_app_revision: u64,
3352        expected_workspace_revision: u64,
3353        workspace: &WorkspaceRecord,
3354    ) -> Result<Self, AppError> {
3355        Ok(Self::new(AppMutationKind::AdvanceWorkspace {
3356            expected_app_revision,
3357            expected_workspace_revision,
3358            workspace: AppStorageRecord::workspace(workspace)?,
3359        }))
3360    }
3361
3362    pub(crate) fn advance_workspace_with_app(
3363        expected_app_revision: u64,
3364        expected_workspace_revision: u64,
3365        app: &AppRecord,
3366        workspace: &WorkspaceRecord,
3367    ) -> Result<Self, AppError> {
3368        Ok(Self::new(AppMutationKind::AdvanceWorkspaceWithApp {
3369            expected_app_revision,
3370            expected_workspace_revision,
3371            app: AppStorageRecord::app(app)?,
3372            workspace: AppStorageRecord::workspace(workspace)?,
3373        }))
3374    }
3375
3376    pub(crate) fn delete_workspace(
3377        expected_app_revision: u64,
3378        expected_workspace_revision: u64,
3379        workspace: &WorkspaceId,
3380    ) -> Result<Self, AppError> {
3381        RecordKey::Workspace(workspace.clone()).state_key()?;
3382        Ok(Self::new(AppMutationKind::DeleteWorkspace {
3383            expected_app_revision,
3384            expected_workspace_revision,
3385            workspace: RecordKey::Workspace(workspace.clone()),
3386        }))
3387    }
3388
3389    pub(crate) fn admit_run(
3390        expected_app_revision: u64,
3391        expected_workspace_revision: u64,
3392        app: &AppRecord,
3393        workspace: &WorkspaceRecord,
3394        run: &RunRecord,
3395    ) -> Result<Self, AppError> {
3396        if workspace.id != run.workspace_id || workspace.revision != run.workspace_revision {
3397            return Err(AppError::Integration(
3398                "run admission workspace identity is inconsistent".to_owned(),
3399            ));
3400        }
3401        Ok(Self::new(AppMutationKind::AdmitRun {
3402            expected_app_revision,
3403            expected_workspace_revision,
3404            app: AppStorageRecord::app(app)?,
3405            workspace: AppStorageRecord::workspace(workspace)?,
3406            run: RecordCodec::run(run)?,
3407            head: AppStorageRecord::head(run)?,
3408            source: None,
3409        }))
3410    }
3411
3412    pub(crate) fn admit_restore(
3413        expected_app_revision: u64,
3414        expected_workspace_revision: u64,
3415        app: &AppRecord,
3416        workspace: &WorkspaceRecord,
3417        run: &RunRecord,
3418        selected: &RunCopy,
3419    ) -> Result<Self, AppError> {
3420        if selected.source.as_ref().is_some_and(|(source, _)| {
3421            source.workspace_id != run.workspace_id || source.workflow_id != run.workflow_id
3422        }) {
3423            return Err(AppError::InvalidInput(
3424                "restore source is outside the workflow scope".into(),
3425            ));
3426        }
3427        let mut mutation = Self::admit_run(
3428            expected_app_revision,
3429            expected_workspace_revision,
3430            app,
3431            workspace,
3432            run,
3433        )?;
3434        let AppMutationKind::AdmitRun { source, .. } = &mut mutation.kind else {
3435            unreachable!()
3436        };
3437        *source = Some(Box::new(selected.clone()));
3438        Ok(mutation)
3439    }
3440
3441    pub(crate) fn advance_run(
3442        expected_app_revision: u64,
3443        expected_run_revision: u64,
3444        app: &AppRecord,
3445        run: &RunRecord,
3446        children: &[(RunKey, String)],
3447    ) -> Result<Self, AppError> {
3448        let mut children = children.to_vec();
3449        if children.is_empty()
3450            && let Some(previous) = &run.previous
3451        {
3452            for activation in &run.checkpoint.staged {
3453                if matches!(activation.outcome, StagedOutcomeRecord::Complete { .. })
3454                    && let Some((sequence, members)) = &activation.children
3455                    && previous.control(*sequence).is_some()
3456                {
3457                    children.extend(
3458                        members
3459                            .iter()
3460                            .map(|member| (member.key.clone(), member.revision.clone())),
3461                    );
3462                }
3463            }
3464        }
3465        Ok(Self::new(AppMutationKind::AdvanceRun {
3466            expected_app_revision,
3467            expected_run_revision,
3468            app: AppStorageRecord::app(app)?,
3469            run: RecordCodec::run(run)?,
3470            predecessor: AppStorageRecord::predecessor(run)?,
3471            children,
3472        }))
3473    }
3474
3475    pub(crate) fn admit_children<'a>(
3476        expected_app_revision: u64,
3477        expected_workspace_revision: u64,
3478        expected_parent_revision: u64,
3479        app: &AppRecord,
3480        workspace: &WorkspaceRecord,
3481        parent: &RunRecord,
3482        children: impl ExactSizeIterator<Item = (&'a RunRecord, Option<RunCopy>)>,
3483    ) -> Result<Self, AppError> {
3484        if children.len() == 0 {
3485            return Err(AppError::Integration(
3486                "child admission mutation requires at least one child".to_owned(),
3487            ));
3488        }
3489        if workspace.id != parent.workspace_id || workspace.revision != parent.workspace_revision {
3490            return Err(AppError::Integration(
3491                "child admission workspace identity is inconsistent".to_owned(),
3492            ));
3493        }
3494        let mut identities = BTreeSet::new();
3495        let mut stored_children = Vec::with_capacity(children.len());
3496        for (child, source) in children {
3497            let relation_matches = child.parent.as_ref().is_some_and(|relation| {
3498                relation.workflow_id == parent.workflow_id
3499                    && relation.run_id == parent.id
3500                    && relation.occurrence == parent.occurrence
3501                    && parent
3502                        .parent
3503                        .as_ref()
3504                        .map_or(0, |parent| parent.depth)
3505                        .checked_add(1)
3506                        == Some(relation.depth)
3507                    && relation.depth <= crate::workflow::run::MAX_CHILD_DEPTH
3508            });
3509            if child.workspace_id != parent.workspace_id
3510                || child.workspace_revision != parent.workspace_revision
3511                || !relation_matches
3512            {
3513                return Err(AppError::Integration(
3514                    "child admission run identity is inconsistent".to_owned(),
3515                ));
3516            }
3517            let stored = RecordCodec::run(child)?;
3518            if !identities.insert(stored.key().clone()) {
3519                return Err(AppError::Integration(
3520                    "child admission contains duplicate run identities".to_owned(),
3521                ));
3522            }
3523            stored_children.push((stored, AppStorageRecord::head(child)?, source));
3524        }
3525        Ok(Self::new(AppMutationKind::AdmitChildren {
3526            expected_app_revision,
3527            expected_workspace_revision,
3528            expected_parent_revision,
3529            app: AppStorageRecord::app(app)?,
3530            workspace: AppStorageRecord::workspace(workspace)?,
3531            parent: RecordCodec::run(parent)?,
3532            predecessor: AppStorageRecord::predecessor(parent)?,
3533            children: stored_children,
3534        }))
3535    }
3536
3537    pub(crate) fn retain_run(
3538        expected_app_revision: u64,
3539        expected_run_revision: u64,
3540        app: &AppRecord,
3541        run: &RunRecord,
3542    ) -> Result<Self, AppError> {
3543        Ok(Self::new(AppMutationKind::RetainRun {
3544            expected_app_revision,
3545            expected_run_revision,
3546            app: AppStorageRecord::app(app)?,
3547            run: RecordCodec::run(run)?,
3548        }))
3549    }
3550
3551    pub(crate) fn readmit_run(
3552        expected_app_revision: u64,
3553        expected_workspace_revision: u64,
3554        expected_run_revision: u64,
3555        app: &AppRecord,
3556        workspace: &WorkspaceRecord,
3557        run: &RunRecord,
3558    ) -> Result<Self, AppError> {
3559        if workspace.id != run.workspace_id || workspace.revision != run.workspace_revision {
3560            return Err(AppError::Conflict(
3561                "run readmission workspace identity is stale".into(),
3562            ));
3563        }
3564        Ok(Self::new(AppMutationKind::ReadmitRun {
3565            expected_app_revision,
3566            expected_workspace_revision,
3567            expected_run_revision,
3568            app: AppStorageRecord::app(app)?,
3569            workspace: AppStorageRecord::workspace(workspace)?,
3570            run: RecordCodec::run(run)?,
3571            predecessor: AppStorageRecord::predecessor(run)?,
3572        }))
3573    }
3574
3575    pub(crate) fn seal_run(
3576        expected_app_revision: u64,
3577        expected_run_revision: u64,
3578        run: &RunRecord,
3579        parent: Option<(&RunRecord, u64)>,
3580    ) -> Result<Self, AppError> {
3581        Ok(Self::new(AppMutationKind::SealRun {
3582            expected_app_revision,
3583            expected_run_revision,
3584            run: RecordCodec::run(run)?,
3585            parent: parent
3586                .map(|(record, revision)| RecordCodec::run(record).map(|record| (record, revision)))
3587                .transpose()?,
3588        }))
3589    }
3590
3591    fn new(kind: AppMutationKind) -> Self {
3592        Self {
3593            id: Uuid::new_v4().simple().to_string(),
3594            kind,
3595        }
3596    }
3597}
3598
3599impl RecordKey {
3600    fn ledger(record: &RunRecord, index: usize, sequence: u64) -> Self {
3601        Self::Ledger {
3602            workspace: record.workspace_id.clone(),
3603            workflow: record.workflow_id.clone(),
3604            run: record.id.clone(),
3605            occurrence: record.occurrence.clone(),
3606            index,
3607            sequence,
3608        }
3609    }
3610
3611    fn exact_run(key: &RunKey) -> Self {
3612        Self::Run {
3613            workspace: key.workspace().clone(),
3614            workflow: key.workflow().clone(),
3615            run: key.run().clone(),
3616            occurrence: key.occurrence().to_owned(),
3617        }
3618    }
3619
3620    async fn expect_present<T: StateTransactionBackend>(
3621        &self,
3622        transaction: &mut T,
3623    ) -> Result<(), StorageFailure> {
3624        let key = self.state_key().map_err(StorageFailure::permanent)?;
3625        let snapshot = transaction
3626            .load(key.clone())
3627            .await
3628            .map_err(StorageFailure::from)?
3629            .ok_or_else(|| {
3630                StorageFailure::permanent(AppError::Integration("run index is missing".into()))
3631            })?;
3632        RecordCodec::preflight_epoch(&snapshot.record.value).map_err(StorageFailure::permanent)?;
3633        Self::validate_value(key.key(), &snapshot.record.value).map_err(StorageFailure::permanent)
3634    }
3635
3636    fn run(record: &RunRecord) -> Self {
3637        Self::Run {
3638            workspace: record.workspace_id.clone(),
3639            workflow: record.workflow_id.clone(),
3640            run: record.id.clone(),
3641            occurrence: record.occurrence.clone(),
3642        }
3643    }
3644
3645    fn checkpoint(record: &RunRecord) -> Self {
3646        Self::Checkpoint {
3647            workspace: record.workspace_id.clone(),
3648            workflow: record.workflow_id.clone(),
3649            run: record.id.clone(),
3650            occurrence: record.occurrence.clone(),
3651            sequence: record.checkpoint.sequence,
3652        }
3653    }
3654
3655    fn head_key(&self) -> Result<Self, AppError> {
3656        match self {
3657            Self::Run {
3658                workspace,
3659                workflow,
3660                run,
3661                occurrence,
3662            }
3663            | Self::Checkpoint {
3664                workspace,
3665                workflow,
3666                run,
3667                occurrence,
3668                ..
3669            }
3670            | Self::Ledger {
3671                workspace,
3672                workflow,
3673                run,
3674                occurrence,
3675                ..
3676            }
3677            | Self::ActionObservation {
3678                workspace,
3679                workflow,
3680                run,
3681                occurrence,
3682                ..
3683            }
3684            | Self::ActiveRun {
3685                workspace,
3686                workflow,
3687                run,
3688                occurrence,
3689            } => Ok(Self::Run {
3690                workspace: workspace.clone(),
3691                workflow: workflow.clone(),
3692                run: run.clone(),
3693                occurrence: occurrence.clone(),
3694            }),
3695            Self::ProjectionLink { key, .. } => Ok(Self::exact_run(key)),
3696            Self::App
3697            | Self::Definition { .. }
3698            | Self::Workspace(_)
3699            | Self::Receipt(_)
3700            | Self::ProjectionTail { .. } => {
3701                Err(AppError::Integration("record has no run scope".into()))
3702            }
3703        }
3704    }
3705
3706    fn history_key(key: &str) -> Result<Self, AppError> {
3707        let parts = key.split('/').collect::<Vec<_>>();
3708        let parsed = (|| match parts.as_slice() {
3709            [
3710                "history",
3711                workspace,
3712                workflow,
3713                run,
3714                occurrence,
3715                "checkpoint",
3716                sequence,
3717            ] => Some(Self::Checkpoint {
3718                workspace: workspace.parse().ok()?,
3719                workflow: workflow.parse().ok()?,
3720                run: run.parse().ok()?,
3721                occurrence: (*occurrence).to_owned(),
3722                sequence: Self::sequence(sequence)?,
3723            }),
3724            [
3725                "history",
3726                workspace,
3727                workflow,
3728                run,
3729                occurrence,
3730                "ledger",
3731                index,
3732                sequence,
3733            ] if index.len() == 2 && index.bytes().all(|byte| byte.is_ascii_digit()) => {
3734                let index = index.parse::<usize>().ok()?;
3735                if index >= MAX_INTERACTION_SETTLEMENTS.div_ceil(64) {
3736                    return None;
3737                }
3738                Some(Self::Ledger {
3739                    workspace: workspace.parse().ok()?,
3740                    workflow: workflow.parse().ok()?,
3741                    run: run.parse().ok()?,
3742                    occurrence: (*occurrence).to_owned(),
3743                    index,
3744                    sequence: Self::sequence(sequence)?,
3745                })
3746            }
3747            [
3748                "history",
3749                workspace,
3750                workflow,
3751                run,
3752                occurrence,
3753                "action",
3754                ordinal,
3755                reverse,
3756            ] => Some(Self::ActionObservation {
3757                workspace: workspace.parse().ok()?,
3758                workflow: workflow.parse().ok()?,
3759                run: run.parse().ok()?,
3760                occurrence: (*occurrence).to_owned(),
3761                ordinal: Self::sequence(ordinal)?,
3762                sequence: u64::MAX - Self::sequence(reverse)?,
3763            }),
3764            ["active", workspace, workflow, run, occurrence] => Some(Self::ActiveRun {
3765                workspace: workspace.parse().ok()?,
3766                workflow: workflow.parse().ok()?,
3767                run: run.parse().ok()?,
3768                occurrence: (*occurrence).to_owned(),
3769            }),
3770            [
3771                "history",
3772                workspace,
3773                workflow,
3774                run,
3775                occurrence,
3776                "projection",
3777                rest @ ..,
3778            ] if rest.len() >= 2 => {
3779                let (sequence, projection) = rest.split_last()?;
3780                let projection = RunProjection::parse(projection)?;
3781                if matches!(projection, RunProjection::Action(_)) {
3782                    return None;
3783                }
3784                RunId::validate_occurrence(occurrence).ok()?;
3785                Some(Self::ProjectionLink {
3786                    key: RunKey::new(
3787                        workspace.parse().ok()?,
3788                        workflow.parse().ok()?,
3789                        run.parse().ok()?,
3790                        (*occurrence).into(),
3791                    ),
3792                    projection,
3793                    sequence: Self::sequence(sequence)?,
3794                })
3795            }
3796            _ => None,
3797        })();
3798        let parsed =
3799            parsed.ok_or_else(|| AppError::Integration("durable history key is invalid".into()))?;
3800        match &parsed {
3801            Self::Checkpoint { occurrence, .. }
3802            | Self::Ledger { occurrence, .. }
3803            | Self::ActionObservation { occurrence, .. }
3804            | Self::ActiveRun { occurrence, .. } => RunId::validate_occurrence(occurrence)
3805                .map_err(|_| {
3806                    AppError::Integration("durable history occurrence is invalid".into())
3807                })?,
3808            Self::ProjectionLink { .. } => {}
3809            _ => {
3810                return Err(AppError::Integration(
3811                    "durable history key is invalid".into(),
3812                ));
3813            }
3814        }
3815        Ok(parsed)
3816    }
3817
3818    fn sequence(value: &str) -> Option<u64> {
3819        (value.len() == 20 && value.bytes().all(|byte| byte.is_ascii_digit()))
3820            .then(|| value.parse().ok())
3821            .flatten()
3822    }
3823
3824    fn index_record(&self) -> Result<StateRecord, AppError> {
3825        if !matches!(self, Self::ActiveRun { .. }) {
3826            return Err(AppError::Integration(
3827                "record is not a history index".into(),
3828            ));
3829        }
3830        self.record(&serde_json::json!({ "epoch": EPOCH }))
3831    }
3832
3833    async fn expect_missing<T: StateTransactionBackend>(
3834        &self,
3835        transaction: &mut T,
3836    ) -> Result<(), StorageFailure> {
3837        let key = self.state_key().map_err(StorageFailure::permanent)?;
3838        if transaction
3839            .load(key)
3840            .await
3841            .map_err(StorageFailure::from)?
3842            .is_some()
3843        {
3844            Err(StorageFailure::permanent(AppError::Conflict(
3845                "durable record already exists".to_owned(),
3846            )))
3847        } else {
3848            Ok(())
3849        }
3850    }
3851
3852    async fn expect_revision<T: StateTransactionBackend>(
3853        &self,
3854        transaction: &mut T,
3855        expected: u64,
3856    ) -> Result<(), StorageFailure> {
3857        let key = self.state_key().map_err(StorageFailure::permanent)?;
3858        let Some(snapshot) = transaction.load(key).await.map_err(StorageFailure::from)? else {
3859            return Err(StorageFailure::permanent(AppError::NotFound(
3860                "durable record does not exist".to_owned(),
3861            )));
3862        };
3863        if matches!(self, Self::App) {
3864            RecordCodec::preflight_epoch(&snapshot.record.value)
3865                .map_err(StorageFailure::permanent)?;
3866            Self::validate_value("app", &snapshot.record.value)
3867                .map_err(StorageFailure::permanent)?;
3868        }
3869        if snapshot.revision.get() != expected {
3870            return Err(StorageFailure::permanent(AppError::Conflict(
3871                "durable record revision fence is stale".to_owned(),
3872            )));
3873        }
3874        Ok(())
3875    }
3876
3877    async fn delete<T: StateTransactionBackend>(
3878        self,
3879        transaction: &mut T,
3880    ) -> Result<(), StorageFailure> {
3881        let key = self.state_key().map_err(StorageFailure::permanent)?;
3882        transaction.delete(key).await.map_err(StorageFailure::from)
3883    }
3884
3885    fn record<T>(&self, record: &T) -> Result<StateRecord, AppError>
3886    where
3887        T: Serialize,
3888    {
3889        let key = self.state_key()?;
3890        let value = RecordCodec::encode(record, 0)?;
3891        Self::validate_value(key.key(), &value)?;
3892        Ok(StateRecord::new(key, value))
3893    }
3894
3895    fn validate_value(key: &str, value: &Value) -> Result<(), AppError> {
3896        if key.starts_with("definition/") {
3897            let record: DefinitionRecord = RecordCodec::decode(value)?;
3898            record.validate()?;
3899            return if record.key().state_key()?.key() == key {
3900                Ok(())
3901            } else {
3902                Err(AppError::Integration(
3903                    "durable workflow definition key is inconsistent".into(),
3904                ))
3905            };
3906        }
3907        if key.starts_with("projection/") {
3908            let parts = key.split('/').collect::<Vec<_>>();
3909            let ["projection", workspace, workflow, run, projection @ ..] = parts.as_slice() else {
3910                return Err(AppError::Integration(
3911                    "saved projection key is invalid".into(),
3912                ));
3913            };
3914            let parsed = Self::ProjectionTail {
3915                workspace: workspace.parse()?,
3916                workflow: workflow.parse()?,
3917                run: run.parse()?,
3918                projection: RunProjection::parse(projection)
3919                    .ok_or_else(|| AppError::Integration("saved projection is invalid".into()))?,
3920            };
3921            let index: RunIndex = RecordCodec::decode(value)?;
3922            index.validate()?;
3923            let valid = match (&parsed, &index) {
3924                (Self::ProjectionTail { projection, .. }, RunIndex::Tail { last, .. }) => {
3925                    match projection {
3926                        RunProjection::Sequence(sequence) => last.sequence == *sequence,
3927                        RunProjection::Unsettled => last.sequence == 0,
3928                        RunProjection::Checkpoint
3929                        | RunProjection::Saved
3930                        | RunProjection::Action(_) => true,
3931                    }
3932                }
3933                _ => false,
3934            };
3935            if parsed.state_key()?.key() != key || !valid {
3936                return Err(AppError::Integration(
3937                    "saved projection tail is invalid".into(),
3938                ));
3939            }
3940            return Ok(());
3941        }
3942        if key.starts_with("history/") || key.starts_with("active/") {
3943            let matches = match Self::history_key(key)? {
3944                Self::Checkpoint {
3945                    workspace,
3946                    workflow,
3947                    run,
3948                    occurrence,
3949                    sequence,
3950                } => {
3951                    value.get("workspace_id").and_then(Value::as_str) == Some(workspace.as_str())
3952                        && value.get("previous").is_none()
3953                        && value.get("workflow_id").and_then(Value::as_str)
3954                            == Some(workflow.as_str())
3955                        && value.get("id").and_then(Value::as_str) == Some(run.as_str())
3956                        && value.get("occurrence").and_then(Value::as_str)
3957                            == Some(occurrence.as_str())
3958                        && value
3959                            .get("checkpoint")
3960                            .and_then(|checkpoint| checkpoint.get("sequence"))
3961                            .and_then(Value::as_u64)
3962                            == Some(sequence)
3963                }
3964                ledger @ Self::Ledger { .. } => SettlementPage::matches(&ledger, value),
3965                Self::ActionObservation {
3966                    occurrence,
3967                    sequence,
3968                    ..
3969                } => {
3970                    let index: RunIndex = RecordCodec::decode(value)?;
3971                    index.validate()?;
3972                    matches!(index, RunIndex::Link { previous, next, .. }
3973                        if previous.iter().chain(&next).all(|neighbor|
3974                            neighbor.occurrence != occurrence || neighbor.sequence != sequence))
3975                }
3976                Self::ProjectionLink {
3977                    key,
3978                    projection,
3979                    sequence,
3980                } => {
3981                    let index: RunIndex = RecordCodec::decode(value)?;
3982                    index.validate()?;
3983                    let scope = match projection {
3984                        RunProjection::Sequence(expected) => sequence == expected,
3985                        RunProjection::Unsettled => sequence == 0,
3986                        RunProjection::Checkpoint | RunProjection::Saved => true,
3987                        RunProjection::Action(_) => false,
3988                    };
3989                    scope
3990                        && matches!(index, RunIndex::Link { previous, next, .. }
3991                        if previous.iter().chain(&next).all(|neighbor|
3992                            (neighbor.occurrence != key.occurrence() || neighbor.sequence != sequence)
3993                            && match projection {
3994                                RunProjection::Sequence(expected) => neighbor.sequence == expected,
3995                                RunProjection::Unsettled => neighbor.sequence == 0,
3996                                _ => true,
3997                            }))
3998                }
3999                Self::ActiveRun { .. } => {
4000                    value.as_object().is_some_and(|object| object.len() == 1)
4001                        && value.get("epoch").and_then(Value::as_str) == Some(EPOCH)
4002                }
4003                _ => false,
4004            };
4005            return if matches {
4006                Ok(())
4007            } else {
4008                Err(AppError::Integration(
4009                    "durable history value does not match its key".into(),
4010                ))
4011            };
4012        }
4013        let mut parts = key.split('/');
4014        let matches = match (
4015            parts.next(),
4016            parts.next(),
4017            parts.next(),
4018            parts.next(),
4019            parts.next(),
4020            parts.next(),
4021        ) {
4022            (Some("app"), None, None, None, None, None) => {
4023                let migration = value.get("migration").ok_or_else(|| {
4024                    AppError::Integration("app migration state is missing".into())
4025                })?;
4026                matches!(
4027                    RecordCodec::decode::<AppMigration>(migration)?,
4028                    AppMigration::Ready
4029                )
4030            }
4031            (Some("workspace"), Some(id), None, None, None, None) => {
4032                value.get("id").and_then(Value::as_str) == Some(id)
4033            }
4034            (Some("run"), Some(workspace), Some(workflow), Some(run), Some(occurrence), None) => {
4035                value.get("workspace_id").and_then(Value::as_str) == Some(workspace)
4036                    && value.get("workflow_id").and_then(Value::as_str) == Some(workflow)
4037                    && value.get("id").and_then(Value::as_str) == Some(run)
4038                    && value.get("creation").and_then(Value::as_str) == Some(occurrence)
4039            }
4040            (Some("receipt"), Some(id), None, None, None, None) => {
4041                value.get("id").and_then(Value::as_str) == Some(id)
4042            }
4043            _ => false,
4044        };
4045        if matches {
4046            Ok(())
4047        } else {
4048            Err(AppError::Integration(
4049                "durable record identity does not match its storage key".to_owned(),
4050            ))
4051        }
4052    }
4053
4054    fn state_key(&self) -> Result<StateKey, AppError> {
4055        let key = match self {
4056            Self::App => "app".to_owned(),
4057            Self::Definition { workflow, revision } => {
4058                let mut encoded = String::with_capacity(revision.len() * 2);
4059                for byte in revision.bytes() {
4060                    encoded.push(char::from(b"0123456789abcdef"[usize::from(byte >> 4)]));
4061                    encoded.push(char::from(b"0123456789abcdef"[usize::from(byte & 15)]));
4062                }
4063                format!("definition/{workflow}/{encoded}")
4064            }
4065            Self::Workspace(id) => format!("workspace/{id}"),
4066            Self::Run {
4067                workspace,
4068                workflow,
4069                run,
4070                occurrence,
4071            } => format!("run/{workspace}/{workflow}/{run}/{occurrence}"),
4072            Self::Checkpoint {
4073                workspace,
4074                workflow,
4075                run,
4076                occurrence,
4077                sequence,
4078            } => {
4079                format!(
4080                    "history/{workspace}/{workflow}/{run}/{occurrence}/checkpoint/{sequence:020}"
4081                )
4082            }
4083            Self::Ledger {
4084                workspace,
4085                workflow,
4086                run,
4087                occurrence,
4088                index,
4089                sequence,
4090            } => {
4091                format!(
4092                    "history/{workspace}/{workflow}/{run}/{occurrence}/ledger/{index:02}/{sequence:020}"
4093                )
4094            }
4095            Self::ActionObservation {
4096                workspace,
4097                workflow,
4098                run,
4099                occurrence,
4100                ordinal,
4101                sequence,
4102            } => {
4103                format!(
4104                    "history/{workspace}/{workflow}/{run}/{occurrence}/action/{ordinal:020}/{:020}",
4105                    u64::MAX - sequence
4106                )
4107            }
4108            Self::ActiveRun {
4109                workspace,
4110                workflow,
4111                run,
4112                occurrence,
4113            } => {
4114                format!("active/{workspace}/{workflow}/{run}/{occurrence}")
4115            }
4116            Self::ProjectionTail {
4117                workspace,
4118                workflow,
4119                run,
4120                projection,
4121            } => {
4122                format!(
4123                    "projection/{workspace}/{workflow}/{run}/{}",
4124                    projection.text()
4125                )
4126            }
4127            Self::ProjectionLink {
4128                key,
4129                projection,
4130                sequence,
4131            } => {
4132                format!(
4133                    "history/{}/{}/{}/{}/projection/{}/{sequence:020}",
4134                    key.workspace(),
4135                    key.workflow(),
4136                    key.run(),
4137                    key.occurrence(),
4138                    projection.text()
4139                )
4140            }
4141            Self::Receipt(id) => format!("receipt/{id}"),
4142        };
4143        StateKey::new(NAMESPACE, key).map_err(AppError::from)
4144    }
4145}
4146
4147impl std::io::Write for RecordCodec {
4148    fn write(&mut self, bytes: &[u8]) -> Result<usize, std::io::Error> {
4149        self.bytes = self
4150            .bytes
4151            .checked_add(bytes.len())
4152            .ok_or_else(|| std::io::Error::other("app durable record encoded length overflowed"))?;
4153        Ok(bytes.len())
4154    }
4155
4156    fn flush(&mut self) -> Result<(), std::io::Error> {
4157        Ok(())
4158    }
4159}
4160
4161impl RecordCodec {
4162    fn run(record: &RunRecord) -> Result<AppStorageRecord, AppError> {
4163        let reserved = record.checkpoint.prospective_settlement_bytes()?;
4164        let key = RecordKey::checkpoint(record);
4165        let state_key = key.state_key()?;
4166        let size = RecordCodec::logical_run_len(record)?;
4167        if size.saturating_add(reserved) > MAX_RECORD_BYTES {
4168            return Err(AppError::InvalidInput(
4169                "app durable record exceeds the private byte bound".into(),
4170            ));
4171        }
4172        let count = record.checkpoint.settlement_count();
4173        if count > MAX_INTERACTION_SETTLEMENTS {
4174            return Err(AppError::InvalidInput(
4175                "durable interaction settlement capacity is insufficient".into(),
4176            ));
4177        }
4178        let mut normalized = record.clone();
4179        if normalized.checkpoint.settlement_references().len() != count.div_ceil(64) {
4180            normalized
4181                .checkpoint
4182                .prepare_settlement_pages(record.previous.as_ref(), record.checkpoint.sequence)?;
4183        }
4184        // Prepared records previously came from their persisted body, which
4185        // never includes this separate observed predecessor.
4186        normalized.previous = None;
4187        let references = normalized.checkpoint.settlement_references().to_vec();
4188        let mut pages = Vec::new();
4189        for (index, sequence) in references.iter().copied().enumerate() {
4190            if sequence == record.checkpoint.sequence {
4191                let entries = record.checkpoint.settlement_page(index)?;
4192                let page = SettlementPage {
4193                    epoch: EPOCH.into(),
4194                    workspace_id: record.workspace_id.clone(),
4195                    workflow_id: record.workflow_id.clone(),
4196                    id: record.id.clone(),
4197                    occurrence: record.occurrence.clone(),
4198                    index,
4199                    sequence,
4200                    entries,
4201                };
4202                let key = RecordKey::ledger(record, index, sequence);
4203                let value = serde_json::to_value(&page).map_err(|_| {
4204                    AppError::Integration("app durable record encoding failed".into())
4205                })?;
4206                let state_key = key.state_key()?;
4207                RecordKey::validate_value(state_key.key(), &value)?;
4208                pages.push(StateRecord::new(state_key, value));
4209            }
4210        }
4211        let descriptor = SettlementPages {
4212            count,
4213            pages: references,
4214        };
4215        descriptor.validate(record.checkpoint.sequence)?;
4216        let mut value = serde_json::to_value(record)
4217            .map_err(|_| AppError::Integration("app durable record encoding failed".into()))?;
4218        value
4219            .get_mut("checkpoint")
4220            .and_then(Value::as_object_mut)
4221            .ok_or_else(|| AppError::Integration("checkpoint body encoding is invalid".into()))?
4222            .insert(
4223                "settlements".into(),
4224                serde_json::to_value(&descriptor).map_err(|_| {
4225                    AppError::Integration("app durable record encoding failed".into())
4226                })?,
4227            );
4228        RecordKey::validate_value(state_key.key(), &value)?;
4229        Ok(AppStorageRecord::Checkpoint {
4230            key,
4231            value,
4232            record: Box::new(normalized),
4233            pages,
4234        })
4235    }
4236
4237    pub(crate) fn encoded_len<T: Serialize>(record: &T) -> Result<usize, AppError> {
4238        let mut encoded = Self { bytes: 0 };
4239        serde_json::to_writer(&mut encoded, record)
4240            .map_err(|_| AppError::Integration("app durable record encoding failed".into()))?;
4241        Ok(encoded.bytes)
4242    }
4243
4244    fn value_len(value: &Value) -> Result<usize, AppError> {
4245        let checked = |size: Option<usize>| {
4246            size.ok_or_else(|| {
4247                AppError::Integration("app durable record encoding failed".to_owned())
4248            })
4249        };
4250        Ok(match value {
4251            Value::Null => 4,
4252            Value::Bool(true) => 4,
4253            Value::Bool(false) => 5,
4254            Value::Number(number) => {
4255                if let Some(value) = number.as_u64() {
4256                    value.checked_ilog10().unwrap_or(0) as usize + 1
4257                } else if let Some(value) = number.as_i64() {
4258                    value.unsigned_abs().checked_ilog10().unwrap_or(0) as usize + 2
4259                } else {
4260                    Self::encoded_len(number)?
4261                }
4262            }
4263            Value::String(text) => checked(Self::string_len(text))?,
4264            Value::Array(values) => {
4265                let mut size = 2usize;
4266                for (index, value) in values.iter().enumerate() {
4267                    size = checked(size.checked_add(usize::from(index != 0)))?;
4268                    size = checked(size.checked_add(Self::value_len(value)?))?;
4269                }
4270                size
4271            }
4272            Value::Object(values) => {
4273                let mut size = 2usize;
4274                for (index, (key, value)) in values.iter().enumerate() {
4275                    size = checked(size.checked_add(if index == 0 { 1 } else { 2 }))?;
4276                    size = checked(size.checked_add(checked(Self::string_len(key))?))?;
4277                    size = checked(size.checked_add(Self::value_len(value)?))?;
4278                }
4279                size
4280            }
4281        })
4282    }
4283
4284    fn string_len(text: &str) -> Option<usize> {
4285        let mut size = text.len().checked_add(2)?;
4286        for byte in text.bytes() {
4287            let extra = match byte {
4288                b'"' | b'\\' | b'\x08' | b'\t' | b'\n' | b'\x0c' | b'\r' => 1,
4289                0..=0x1f => 5,
4290                _ => continue,
4291            };
4292            size = size.checked_add(extra)?;
4293        }
4294        Some(size)
4295    }
4296
4297    fn parse_run(value: &Value) -> Result<RunRecord, AppError> {
4298        RunRecord::deserialize(value)
4299            .map_err(|_| AppError::Integration("app durable record is corrupt".into()))
4300    }
4301
4302    fn parse_legacy_run(value: &Value) -> Result<RunRecord, AppError> {
4303        if value.get("epoch").and_then(Value::as_str) != Some(LEGACY_EPOCH) {
4304            return Err(AppError::Integration(
4305                "unsupported app record schema version".into(),
4306            ));
4307        }
4308        let size = Self::value_len(value)?;
4309        if size > MAX_RECORD_BYTES {
4310            return Err(AppError::Integration(
4311                "app durable record exceeds the private byte bound".into(),
4312            ));
4313        }
4314        let mut record = Self::parse_run(value)?;
4315        let entries = value
4316            .get("checkpoint")
4317            .and_then(|checkpoint| checkpoint.get("settlements"))
4318            .ok_or_else(|| AppError::Integration("app durable record is corrupt".into()))?;
4319        record.checkpoint.hydrate_legacy_settlements(entries)?;
4320        Self::validate_run(&record, size)?;
4321        record.checkpoint.validate_settlements()?;
4322        Ok(record)
4323    }
4324
4325    pub(crate) fn logical_run_len(record: &RunRecord) -> Result<usize, AppError> {
4326        // The canonical body omits only this final checkpoint field. Counting
4327        // its existing exact array encoding restores the complete inline size
4328        // without materializing that potentially large JSON Value.
4329        let body = Self::encoded_len(record)?;
4330        let entries = record.checkpoint.settlement_bytes()?;
4331        body.checked_add(
4332            Self::string_len("settlements").ok_or_else(|| {
4333                AppError::Integration("app durable record encoding failed".into())
4334            })?,
4335        )
4336        .and_then(|size| size.checked_add(2))
4337        .and_then(|size| size.checked_add(entries))
4338        .ok_or_else(|| AppError::Integration("app durable record encoding failed".into()))
4339    }
4340
4341    pub(crate) fn same_run(left: &RunRecord, right: &RunRecord) -> Result<bool, AppError> {
4342        let encode = |record: &RunRecord| {
4343            serde_json::to_vec(record).map_err(|_| {
4344                AppError::Integration("workflow checkpoint evidence encoding failed".into())
4345            })
4346        };
4347        // Both ledgers are encoded even if the bodies differ, preserving
4348        // validation of exact ordered maps and numeric representations.
4349        let left_body = encode(left)?;
4350        let left_entries = left.checkpoint.settlement_evidence()?;
4351        let right_body = encode(right)?;
4352        let right_entries = right.checkpoint.settlement_evidence()?;
4353        Ok(left_body == right_body && left_entries == right_entries)
4354    }
4355
4356    fn validate_run(record: &RunRecord, size: usize) -> Result<(), AppError> {
4357        RunId::validate_occurrence(&record.occurrence)
4358            .map_err(|_| AppError::Integration("durable run occurrence is invalid".into()))?;
4359        let reserve = record
4360            .checkpoint
4361            .prospective_settlement_bytes()
4362            .map_err(|_| {
4363                AppError::Integration(
4364                    "durable interaction settlement capacity is insufficient".into(),
4365                )
4366            })?;
4367        if size.saturating_add(reserve) > MAX_RECORD_BYTES {
4368            return Err(AppError::Integration(
4369                "durable interaction settlement capacity is insufficient".into(),
4370            ));
4371        }
4372        Ok(())
4373    }
4374
4375    fn preflight_epoch(value: &Value) -> Result<(), AppError> {
4376        if value.get("epoch").and_then(Value::as_str) == Some(EPOCH) {
4377            Ok(())
4378        } else {
4379            Err(AppError::Integration(
4380                "unsupported app record schema version".to_owned(),
4381            ))
4382        }
4383    }
4384
4385    fn encode<T>(record: &T, reserved_bytes: usize) -> Result<Value, AppError>
4386    where
4387        T: Serialize,
4388    {
4389        let value = serde_json::to_value(record)
4390            .map_err(|_| AppError::Integration("app durable record encoding failed".to_owned()))?;
4391        let size = Self::value_len(&value)?;
4392        if size.saturating_add(reserved_bytes) > MAX_RECORD_BYTES {
4393            return Err(AppError::InvalidInput(
4394                "app durable record exceeds the private byte bound".to_owned(),
4395            ));
4396        }
4397        Ok(value)
4398    }
4399
4400    fn decode<T>(value: &Value) -> Result<T, AppError>
4401    where
4402        T: DeserializeOwned,
4403    {
4404        let size = Self::value_len(value)
4405            .map_err(|_| AppError::Integration("app durable record decoding failed".to_owned()))?;
4406        if size > MAX_RECORD_BYTES {
4407            return Err(AppError::Integration(
4408                "app durable record exceeds the private byte bound".to_owned(),
4409            ));
4410        }
4411        T::deserialize(value)
4412            .map_err(|_| AppError::Integration("app durable record is corrupt".to_owned()))
4413    }
4414}
4415
4416impl From<AppError> for StorageFailure {
4417    fn from(error: AppError) -> Self {
4418        Self::from_app(error)
4419    }
4420}
4421
4422impl From<StateStoreError> for StorageFailure {
4423    fn from(error: StateStoreError) -> Self {
4424        let recoverable = matches!(
4425            error,
4426            StateStoreError::TimedOut { .. }
4427                | StateStoreError::Unavailable { .. }
4428                | StateStoreError::BackendOperation { .. }
4429        );
4430        let error = AppError::from(error);
4431        if recoverable {
4432            Self::Recoverable(error)
4433        } else {
4434            Self::Permanent(error)
4435        }
4436    }
4437}
4438
4439impl From<StateStoreError> for AppError {
4440    fn from(error: StateStoreError) -> Self {
4441        match error {
4442            StateStoreError::InvalidConfig(_) | StateStoreError::InvalidRequest(_) => {
4443                Self::InvalidInput("app state-store input is invalid".to_owned())
4444            }
4445            StateStoreError::TransactionConflict { .. } => {
4446                Self::Conflict("app state transaction conflicted".to_owned())
4447            }
4448            StateStoreError::TimedOut { .. } => {
4449                Self::Timeout("app state-store operation timed out".to_owned())
4450            }
4451            StateStoreError::Unavailable { .. }
4452            | StateStoreError::BackendOperation { .. }
4453            | StateStoreError::Serialization(_) => {
4454                Self::Integration("app state-store operation failed".to_owned())
4455            }
4456        }
4457    }
4458}