commit 5667824c6f254a3e7cf8ea0680a38ada9f32fc9e
parent c0cdadea2712312c5d4ed25c1e55b1217572dafa
Author: triesap <tyson@radroots.org>
Date: Sun, 9 Aug 2026 17:31:56 +0000
mobile_core: restore actionable media draft state
- Derive aggregate draft state from every persisted media prerequisite.
- Leave byte-verified uploads editable until the authored operation is queued.
- Return failed media to the retryable preparation surface with orphan evidence intact.
- Lock successful and failed real-I/O Blossom transitions with regression assertions.
Diffstat:
2 files changed, 7 insertions(+), 21 deletions(-)
diff --git a/crates/mobile_core/src/runtime/product_surface/outbox.rs b/crates/mobile_core/src/runtime/product_surface/outbox.rs
@@ -884,15 +884,7 @@ impl RadrootsRuntime {
media.failure_code = None;
}
media.validate()?;
- let next_stage = match stage {
- Phase1MediaStage::Pending | Phase1MediaStage::Preparing => {
- AuthoredDraftStage::MediaPreparing
- }
- Phase1MediaStage::Uploading
- | Phase1MediaStage::Verified
- | Phase1MediaStage::Failed
- | Phase1MediaStage::Orphaned => AuthoredDraftStage::MediaUploading,
- };
+ let next_stage = draft_stage_for_media(&payload.media);
let next = head
.successor(payload.encode()?, next_stage, None, updated_at_unix_ms)
.map_err(|_| Phase1DraftError::RevisionConflict)?;
@@ -946,13 +938,9 @@ impl RadrootsRuntime {
media.verified_at_unix_ms = Some(receipt.verified_at_unix_ms());
media.orphan = None;
media.validate()?;
+ let next_stage = draft_stage_for_media(&payload.media);
let next = head
- .successor(
- payload.encode()?,
- AuthoredDraftStage::MediaUploading,
- None,
- updated_at_unix_ms,
- )
+ .successor(payload.encode()?, next_stage, None, updated_at_unix_ms)
.map_err(|_| Phase1DraftError::RevisionConflict)?;
let receipt = storage
.append_authored_draft(next, Some(expected))
@@ -1016,13 +1004,9 @@ impl RadrootsRuntime {
recorded_at_unix_ms: updated_at_unix_ms,
});
media.validate()?;
+ let next_stage = draft_stage_for_media(&payload.media);
let next = head
- .successor(
- payload.encode()?,
- AuthoredDraftStage::MediaUploading,
- None,
- updated_at_unix_ms,
- )
+ .successor(payload.encode()?, next_stage, None, updated_at_unix_ms)
.map_err(|_| Phase1DraftError::RevisionConflict)?;
let receipt = storage
.append_authored_draft(next, Some(expected))
diff --git a/crates/mobile_ffi/tests/local_mvp_real_io.rs b/crates/mobile_ffi/tests/local_mvp_real_io.rs
@@ -233,6 +233,7 @@ async fn public_runtime_completes_the_local_mvp_against_real_protocol_services()
.await
.expect("upload and re-fetch exact media");
assert_eq!(uploaded.media[0].stage, FfiMediaStage::Verified);
+ assert_eq!(uploaded.state, FfiOutboxState::MediaPreparing);
let evidence = publisher
.sdk_blossom_evidence()
.expect("Blossom evidence")
@@ -818,6 +819,7 @@ async fn prove_corrupted_media_fails(
.await
.expect("durable corrupt-media status");
assert_eq!(failed.media[0].stage, FfiMediaStage::Failed);
+ assert_eq!(failed.state, FfiOutboxState::MediaPreparing);
assert!(failed.media[0].possible_orphan);
let evidence = runtime
.sdk_blossom_evidence()