From 589eddb9de90bdd91e538d1fbb9cc772b8494c96 Mon Sep 17 00:00:00 2001 From: Savva Melnikov Date: Fri, 16 Oct 2026 13:44:16 +0200 Subject: [PATCH] fix(desktop): recover canonical device-home persona coordinates Assisted-by: Codex Signed-off-by: Savva Melnikov --- FORK_PATCHES.md | 12 +- .../managed_agents/device_home_operations.rs | 37 +++- .../device_home_operations/delete.rs | 7 +- .../device_home_operations/durable_tests.rs | 183 ++++++++++++------ .../device_home_operations/label.rs | 37 +++- .../device_home_operations/label_tests.rs | 161 +++++++++++++-- .../device_home_operations/tests.rs | 149 +++++++++++++- .../managed_agents/device_workflow_tests.rs | 10 +- scripts/fork/patches.json | 26 ++- 9 files changed, 513 insertions(+), 109 deletions(-) diff --git a/FORK_PATCHES.md b/FORK_PATCHES.md index 3d98511a9dd..cd6cb539d86 100644 --- a/FORK_PATCHES.md +++ b/FORK_PATCHES.md @@ -1402,7 +1402,7 @@ New module: `true`. ## `desktop/src-tauri/src/managed_agents/device_home_operations.rs` -Device creation policy, default-private sources, atomic claim and durable recovery regression coverage; Task8 durable original-scope home deletion/label publication and retry +Device creation policy, default-private sources, atomic claim and durable recovery regression coverage; Task8 durable original-scope home deletion/label publication and retry; Preserve canonical persona coordinates for builtin claims and durable journal recovery New module: `true`. @@ -1417,22 +1417,23 @@ New module: `true`. - Invocation: `desktop/src-tauri/src/commands/agents.rs` → `commit_home_claim_locked`; exact call `crate::managed_agents::device_home_operations::commit_home_claim_locked(`; behavior test `managed_agents::device_home_operations::tests::released_definition_is_claimed_without_restart`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace managed_agents::device_home_operations::tests::released_definition_is_claimed_without_restart -- --exact` - Invocation: `desktop/src-tauri/src/managed_agents/device_home_operations.rs` → `save_journal`; exact call `save_journal(dir, &operations)?; save(&raw)?;`; behavior test `managed_agents::device_home_operations::tests::failed_claim_save_never_publishes_uncommitted_intent`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace managed_agents::device_home_operations::tests::failed_claim_save_never_publishes_uncommitted_intent -- --exact` -- Invocation: `desktop/src-tauri/src/managed_agents/device_home_operations.rs` → `enqueue_home_events`; exact call `enqueue_home_events(&mut conn, operation)`; behavior test `managed_agents::device_home_operations::tests::claim_transaction_failure_retains_intent_and_never_half_enqueues`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace managed_agents::device_home_operations::tests::claim_transaction_failure_retains_intent_and_never_half_enqueues -- --exact` +- Invocation: `desktop/src-tauri/src/managed_agents/device_home_operations.rs` → `enqueue_home_events`; exact call `enqueue_home_events(dir, &mut conn, operation)`; behavior test `managed_agents::device_home_operations::tests::claim_transaction_failure_retains_intent_and_never_half_enqueues`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace managed_agents::device_home_operations::tests::claim_transaction_failure_retains_intent_and_never_half_enqueues -- --exact` - Invocation: `desktop/src-tauri/src/managed_agents/device_home_operations.rs` → `recover_in_dir`; exact call `recover_in_dir(&dir, verify_binding, |operation| {`; behavior test `commands::workspace::device_home_preparation_tests::recovery_precedes_migration_and_replays_original_scope_after_switch`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace commands::workspace::device_home_preparation_tests::recovery_precedes_migration_and_replays_original_scope_after_switch -- --exact` - Invocation: `desktop/src-tauri/src/managed_agents/device_home_operations.rs` → `load_existing_host_proof`; exact call `crate::device_identity::load_existing_host_proof(`; behavior test `managed_agents::device_home_operations::tests::copied_or_unavailable_proof_never_recovers_claim_and_preserves_intent_bytes`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace managed_agents::device_home_operations::tests::copied_or_unavailable_proof_never_recovers_claim_and_preserves_intent_bytes -- --exact` - Invocation: `desktop/src-tauri/src/commands/workspace.rs` → `recover_home_operations_locked`; exact call `crate::managed_agents::device_home_operations::recover_home_operations_locked(app)`; behavior test `commands::workspace::device_home_preparation_tests::recovery_precedes_migration_and_replays_original_scope_after_switch`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace commands::workspace::device_home_preparation_tests::recovery_precedes_migration_and_replays_original_scope_after_switch -- --exact` - Invocation: `desktop/src-tauri/src/commands/personas/snapshot/import.rs` → `commit_new_pairs_locked`; exact call `crate::managed_agents::device_home_operations::commit_new_pairs_locked(`; behavior test `managed_agents::device_home_operations::tests::new_import_pair_preserves_foreign_records_and_persists_only_new_keys`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace managed_agents::device_home_operations::tests::new_import_pair_preserves_foreign_records_and_persists_only_new_keys -- --exact` - Invocation: `desktop/src-tauri/src/commands/team_snapshot.rs` → `commit_new_pairs_locked`; exact call `crate::managed_agents::device_home_operations::commit_new_pairs_locked(`; behavior test `managed_agents::device_home_operations::tests::new_import_pair_preserves_foreign_records_and_persists_only_new_keys`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace managed_agents::device_home_operations::tests::new_import_pair_preserves_foreign_records_and_persists_only_new_keys -- --exact` -- Invocation: `desktop/src-tauri/src/managed_agents/device_home_operations.rs` → `enqueue_home_events_in_transaction`; exact call `enqueue_home_events_in_transaction(&tx, operation)?;`; behavior test `intent_save_and_each_sql_enqueue_failure_leave_no_partial_batch`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace intent_save_and_each_sql_enqueue_failure_leave_no_partial_batch` +- Invocation: `desktop/src-tauri/src/managed_agents/device_home_operations.rs` → `enqueue_home_events_in_transaction`; exact call `enqueue_home_events_in_transaction(&tx, operation, &definitions)?;`; behavior test `intent_save_and_each_sql_enqueue_failure_leave_no_partial_batch`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace intent_save_and_each_sql_enqueue_failure_leave_no_partial_batch` - Invocation: `desktop/src-tauri/src/managed_agents/device_home_operations.rs` → `apply_delete`; exact call `delete::apply_delete(&mut raw, op, Some(&mut verify_binding))?`; behavior test `new_instance_cancels_obsolete_release`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace new_instance_cancels_obsolete_release` - Invocation: `desktop/src-tauri/src/managed_agents/device_home_operations.rs` → `prepare_label_retries`; exact call `label::prepare_label_retries(`; behavior test `premetadata_label_worklist_resolves_when_original_database_becomes_known`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace premetadata_label_worklist_resolves_when_original_database_becomes_known` - Invocation: `desktop/src-tauri/src/managed_agents/device_home_operations.rs` → `coalesce_label_worklists`; exact call `label::coalesce_label_worklists(&mut operations, label)?;`; behavior test `managed_agents::device_home_operations::label_tests`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace` +- Invocation: `desktop/src-tauri/src/managed_agents/device_home_operations.rs` → `persona_d_tag`; exact call `.map(|d| super::persona_events::persona_d_tag(&d))`; behavior test `builtin_and_source_slug_claims_enqueue_canonical_persona_heads`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib builtin_and_source_slug_claims_enqueue_canonical_persona_heads` - Verify: `just desktop-tauri-test desktop-tauri-check` - Verify: `just desktop-tauri-clippy` ## `desktop/src-tauri/src/managed_agents/device_home_operations/tests.rs` -Device creation policy, default-private sources, atomic claim and durable recovery regression coverage +Device creation policy, default-private sources, atomic claim and durable recovery regression coverage; Canonical builtin/source-slug claims, exact signed-byte legacy recovery, and invalid event rejection New module: `true`. @@ -1897,7 +1898,7 @@ New module: `true`. ## `desktop/src-tauri/src/managed_agents/device_home_operations/label.rs` -Task8: durable secret-free device home deletion, label retries and owning production-adapter coverage; R5 coalesce all outstanding scope label worklists to latest intent with monotonic signed floors +Task8: durable secret-free device home deletion, label retries and owning production-adapter coverage; R5 coalesce all outstanding scope label worklists to latest intent with monotonic signed floors; canonical persona deduplication and superseded timestamp floors preserve builtin and source-slug label retries New module: `true`. @@ -1905,6 +1906,7 @@ New module: `true`. - Invocation: `desktop/src-tauri/src/commands/device_identity.rs` → `set_device_label_locked`; exact call `crate::managed_agents::device_home_operations::label::set_device_label_locked(`; behavior test `native_label_adapter_reports_queued_until_existing_event_sync_acknowledges`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace native_label_adapter_reports_queued_until_existing_event_sync_acknowledges` - Invocation: `desktop/src-tauri/src/managed_agents/device_home_operations/label.rs` → `coalesce_label_worklists`; exact call `coalesce_label_worklists(&mut ops, label)?;`; behavior test `managed_agents::device_home_operations::label_tests`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace` - Invocation: `desktop/src-tauri/src/managed_agents/device_home_operations/label.rs` → `coalesce_label_worklists`; exact call `coalesce_label_worklists(&mut ops, &label)?;`; behavior test `managed_agents::device_home_operations::label_tests`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace` +- Invocation: `desktop/src-tauri/src/managed_agents/device_home_operations/label.rs` → `persona_d_tag`; exact call `.get(&super::super::persona_events::persona_d_tag(&d))`; behavior test `newer_label_is_signed_after_superseded_unenqueued_event`; verify `cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib newer_label_is_signed_after_superseded_unenqueued_event` - Verify: `just desktop-tauri-test desktop-tauri-check` - Verify: `just desktop-tauri-clippy` diff --git a/desktop/src-tauri/src/managed_agents/device_home_operations.rs b/desktop/src-tauri/src/managed_agents/device_home_operations.rs index b40b18af1bf..c8fded1b7c6 100644 --- a/desktop/src-tauri/src/managed_agents/device_home_operations.rs +++ b/desktop/src-tauri/src/managed_agents/device_home_operations.rs @@ -169,7 +169,7 @@ fn prepare_claim_events( .transpose() .map(Option::flatten) }; - let persona_head = prior(30175, &definition.id)?; + let persona_head = prior(30175, &super::persona_events::persona_d_tag(definition))?; let instance_head = prior(30177, &record.pubkey)?; let mut scoped = definition.clone(); scoped.shared = persona_head @@ -193,20 +193,33 @@ fn prepare_claim_events( } /// Atomically retain all signed heads for the original operation scope. pub(crate) fn enqueue_home_events( + dir: &Path, conn: &mut Connection, operation: &HomeOperation, ) -> Result<(), String> { + let definitions = read_policy_records(&dir.join("agents/managed-agents.json"))?; let tx = conn .transaction_with_behavior(rusqlite::TransactionBehavior::Immediate) .map_err(|e| e.to_string())?; - enqueue_home_events_in_transaction(&tx, operation)?; + enqueue_home_events_in_transaction(&tx, operation, &definitions)?; tx.commit().map_err(|e| format!("home events commit: {e}")) } /// All event kinds share the caller-owned transaction; no helper opens nested BEGIN. fn enqueue_home_events_in_transaction( conn: &Connection, operation: &HomeOperation, + definitions: &[ManagedAgentRecord], ) -> Result<(), String> { + // Journals keep local IDs; signed heads use the persona protocol coordinate. + // Retained-only label worklists already contain coordinates and need no local row. + let persona_coordinate = |id: &str| { + definitions + .iter() + .find(|r| r.pubkey.is_empty() && r.slug.as_deref() == Some(id)) + .and_then(ManagedAgentRecord::to_definition_view) + .map(|d| super::persona_events::persona_d_tag(&d)) + .unwrap_or_else(|| id.to_string()) + }; match &operation.kind { HomeOperationKind::Claim { definition_id, @@ -216,8 +229,9 @@ fn enqueue_home_events_in_transaction( if operation.signed_events.len() != 2 { return Err("invalid home claim event count".into()); } + let persona = persona_coordinate(definition_id); for (event, kind, coordinate) in [ - (&operation.signed_events[0], 30175, definition_id.as_str()), + (&operation.signed_events[0], 30175, persona.as_str()), (&operation.signed_events[1], 30177, target_pubkey.as_str()), ] { enqueue_head(conn, operation, event, kind, coordinate)?; @@ -233,7 +247,7 @@ fn enqueue_home_events_in_transaction( let id = persona_id .as_deref() .ok_or_else(|| "release missing definition".to_string())?; - enqueue_head(conn, operation, &events[0], 30175, id)?; + enqueue_head(conn, operation, &events[0], 30175, &persona_coordinate(id))?; } else if events.len() != 2 { return Err("invalid home delete event count".into()); } @@ -255,7 +269,18 @@ fn enqueue_home_events_in_transaction( return Err("unprepared device label worklist".into()); } for (event, id) in operation.signed_events.iter().zip(affected_definition_ids) { - enqueue_head(conn, operation, event, 30175, id)?; + // Retained-only worklists record protocol coordinates, which can + // collide with unrelated local IDs in another owner scope. + let recorded_coordinate = event.tags.iter().any(|tag| { + let values = tag.as_slice(); + values.first().map(String::as_str) == Some("d") && values.get(1) == Some(id) + }); + let coordinate = if recorded_coordinate { + id.clone() + } else { + persona_coordinate(id) + }; + enqueue_head(conn, operation, event, 30175, &coordinate)?; } } } @@ -474,7 +499,7 @@ fn enqueue_in_scope(dir: &Path, operation: &HomeOperation) -> Result<(), String> &operation.relay_url, &operation.owner_pubkey, )?; - enqueue_home_events(&mut conn, operation) + enqueue_home_events(dir, &mut conn, operation) } /// Save new imported definitions and instances together, preserving unrelated rows and keys. pub(crate) fn commit_new_pairs_locked( diff --git a/desktop/src-tauri/src/managed_agents/device_home_operations/delete.rs b/desktop/src-tauri/src/managed_agents/device_home_operations/delete.rs index d0c437c64e2..7b2e93afe74 100644 --- a/desktop/src-tauri/src/managed_agents/device_home_operations/delete.rs +++ b/desktop/src-tauri/src/managed_agents/device_home_operations/delete.rs @@ -81,7 +81,12 @@ fn prepare_authorized_delete( }) { d.origin_released = Some(true); - let prior = get_retained_event(&conn, 30175, &scope.owner_pubkey, &d.id)?; + let prior = get_retained_event( + &conn, + 30175, + &scope.owner_pubkey, + &super::super::persona_events::persona_d_tag(&d), + )?; d.shared = prior .as_ref() .and_then(|r| Event::from_json(&r.raw_event).ok()) diff --git a/desktop/src-tauri/src/managed_agents/device_home_operations/durable_tests.rs b/desktop/src-tauri/src/managed_agents/device_home_operations/durable_tests.rs index ded2fcfc458..adfef6a3a01 100644 --- a/desktop/src-tauri/src/managed_agents/device_home_operations/durable_tests.rs +++ b/desktop/src-tauri/src/managed_agents/device_home_operations/durable_tests.rs @@ -30,7 +30,7 @@ fn last_instance_delete_queues_release_and_tombstone_atomically() { dir.path(), &op, |r| save(dir.path(), r), - |op| enqueue_home_events(&mut conn, op), + |op| enqueue_home_events(dir.path(), &mut conn, op), ) .unwrap(); let r = read_policy_records(&dir.path().join("agents/managed-agents.json")).unwrap(); @@ -92,7 +92,7 @@ fn recovery_reuses_signed_event_ids() { ids ); assert_eq!(op.relay_url, c.scope.relay_url); - enqueue_home_events(&mut conn, op) + enqueue_home_events(dir.path(), &mut conn, op) }, ) .unwrap(); @@ -120,7 +120,7 @@ fn new_instance_cancels_obsolete_release() { recover_in_dir( dir.path(), |b| Ok(c.proof.matches(b)), - |op| enqueue_home_events(&mut conn, op), + |op| enqueue_home_events(dir.path(), &mut conn, op), ) .unwrap(); let r = read_policy_records(&dir.path().join("agents/managed-agents.json")).unwrap(); @@ -244,7 +244,7 @@ fn intent_save_and_each_sql_enqueue_failure_leave_no_partial_batch() { saved.set(true); save(dir.path(), r) }, - |op| enqueue_home_events(&mut conn, op), + |op| enqueue_home_events(dir.path(), &mut conn, op), |ops| { if fail == 0 { Err("intent write failure".into()) @@ -269,7 +269,7 @@ fn intent_save_and_each_sql_enqueue_failure_leave_no_partial_batch() { recover_in_dir( dir.path(), |b| Ok(c.proof.matches(b)), - |op| enqueue_home_events(&mut conn, op), + |op| enqueue_home_events(dir.path(), &mut conn, op), ) .unwrap(); assert_eq!(get_pending_sync(&conn).unwrap().len(), 3); @@ -292,7 +292,7 @@ fn unified_save_failure_restarts_deletion_and_clear_failure_replays_exact_ids() } else { save(dir.path(), r) }, - |op| enqueue_home_events(&mut conn, op), + |op| enqueue_home_events(dir.path(), &mut conn, op), |ops| { writes.set(writes.get() + 1); if writes.get() == 2 { @@ -321,7 +321,7 @@ fn unified_save_failure_restarts_deletion_and_clear_failure_replays_exact_ids() ids, op.signed_events.iter().map(|e| e.id).collect::>() ); - enqueue_home_events(&mut conn, op) + enqueue_home_events(dir.path(), &mut conn, op) }, ) .unwrap(); @@ -386,7 +386,7 @@ fn definition_cascade_recovery_never_resurrects_absent_definition() { recover_in_dir( dir.path(), |_| panic!("absent target proof"), - |op| enqueue_home_events(&mut conn, op), + |op| enqueue_home_events(dir.path(), &mut conn, op), ) .unwrap(); assert!( @@ -464,7 +464,7 @@ fn premetadata_label_worklist_resolves_when_original_database_becomes_known() { recover_in_dir( dir.path(), |_| panic!("label proof"), - |op| enqueue_home_events(&mut conn, op), + |op| enqueue_home_events(dir.path(), &mut conn, op), ) .unwrap(); let pending = get_pending_sync(&conn).unwrap(); @@ -536,7 +536,7 @@ fn label_other_owner_waits_for_its_keys_and_foreign_origins_are_not_rebuilt() { recover_in_dir( dir.path(), |_| panic!("label proof"), - |op| enqueue_home_events(&mut conn, op), + |op| enqueue_home_events(dir.path(), &mut conn, op), ) .unwrap(); let pending = get_pending_sync(&conn).unwrap(); @@ -588,54 +588,64 @@ fn native_label_adapter_reports_queued_until_existing_event_sync_acknowledges() #[test] fn claim_recovery_finishes_enqueue_without_mint() { use tauri::Manager; - let dir = tempfile::tempdir().unwrap(); - let app = super::super::device_home_migration::tests::app(dir.path()); - let state = app.state::(); - let (mut raw, _) = super::super::device_home_migration::tests::records(); - let i = raw.pop().unwrap(); - save(dir.path(), &raw).unwrap(); - let mut c = super::super::device_home_migration::tests::context( - super::super::definition_home::EvidenceReadiness::Ready, - ); - c.scope = super::super::device_home_sync::capture_scope(&state).unwrap(); - assert!(commit_claim_in_dir( - dir.path(), - &c, - "one", - i.clone(), - &state.signing_keys().unwrap(), - |r| save(dir.path(), r), - |_| Err("SQL unavailable".into()) - ) - .is_err()); - let op = read_journal(dir.path()).unwrap().pop().unwrap(); - let ids = op - .signed_events - .iter() - .map(|e| e.id) - .collect::>(); - let original_owner = c.scope.owner_pubkey.clone(); - let original_relay = c.scope.relay_url.clone(); - *state.keys.lock().unwrap() = Keys::generate(); - *state.relay_url_override.lock().unwrap() = Some("wss://switched".into()); - recover_home_operations_locked_with(app.handle(), |b| Ok(c.proof.matches(b))).unwrap(); - let path = super::super::retention::scoped_retention_db_path( - &dir.path().join("agents"), - &original_relay, - &original_owner, - ); - let conn = open_retention_db(&path).unwrap(); - let pending = get_pending_sync(&conn).unwrap(); - assert_eq!( - pending + for id in ["one", "builtin:fizz", "builtin:honey"] { + let dir = tempfile::tempdir().unwrap(); + let app = super::super::device_home_migration::tests::app(dir.path()); + let state = app.state::(); + let (mut raw, _) = super::super::device_home_migration::tests::records(); + let mut i = raw.pop().unwrap(); + if id != "one" { + raw.clear(); + } + i.persona_id = Some(id.into()); + save(dir.path(), &raw).unwrap(); + let mut c = super::super::device_home_migration::tests::context( + super::super::definition_home::EvidenceReadiness::Ready, + ); + c.scope = super::super::device_home_sync::capture_scope(&state).unwrap(); + assert!(commit_claim_in_dir( + dir.path(), + &c, + id, + i.clone(), + &state.signing_keys().unwrap(), + |r| save(dir.path(), r), + |_| Err("SQL unavailable".into()) + ) + .is_err()); + let op = read_journal(dir.path()).unwrap().pop().unwrap(); + let signed: Vec<_> = op.signed_events.iter().map(Event::as_json).collect(); + let ids = op + .signed_events .iter() - .map(|r| Event::from_json(&r.raw_event).unwrap().id) - .collect::>(), - ids - ); - let raw = read_policy_records(&dir.path().join("agents/managed-agents.json")).unwrap(); - assert_eq!(raw.iter().filter(|r| r.pubkey == i.pubkey).count(), 1); - assert!(read_journal(dir.path()).unwrap().is_empty()); + .map(|e| e.id) + .collect::>(); + let original_owner = c.scope.owner_pubkey.clone(); + let original_relay = c.scope.relay_url.clone(); + *state.keys.lock().unwrap() = Keys::generate(); + *state.relay_url_override.lock().unwrap() = Some("wss://switched".into()); + recover_home_operations_locked_with(app.handle(), |b| Ok(c.proof.matches(b))).unwrap(); + let path = super::super::retention::scoped_retention_db_path( + &dir.path().join("agents"), + &original_relay, + &original_owner, + ); + let conn = open_retention_db(&path).unwrap(); + let pending = get_pending_sync(&conn).unwrap(); + assert_eq!( + pending + .iter() + .map(|r| Event::from_json(&r.raw_event).unwrap().id) + .collect::>(), + ids + ); + let raw = read_policy_records(&dir.path().join("agents/managed-agents.json")).unwrap(); + assert_eq!(raw.iter().filter(|r| r.pubkey == i.pubkey).count(), 1); + for event in signed { + assert!(pending.iter().any(|row| row.raw_event == event)); + } + assert!(read_journal(dir.path()).unwrap().is_empty()); + } } #[test] @@ -777,7 +787,7 @@ fn label_batch_rolls_back_all_definitions_and_replays_same_signed_ids() { conn.execute_batch("CREATE TRIGGER reject_second BEFORE INSERT ON persona_events WHEN NEW.d_tag='two' BEGIN SELECT RAISE(ABORT,'second label head failure');END;").unwrap(); assert!( label::set_label_in_dir(dir.path(), c.device.clone(), "New", &scopes, &k, |op| { - enqueue_home_events(&mut conn, op) + enqueue_home_events(dir.path(), &mut conn, op) }) .unwrap() ); @@ -797,7 +807,7 @@ fn label_batch_rolls_back_all_definitions_and_replays_same_signed_ids() { ids, op.signed_events.iter().map(|e| e.id).collect::>() ); - enqueue_home_events(&mut conn, op) + enqueue_home_events(dir.path(), &mut conn, op) }, ) .unwrap(); @@ -907,3 +917,58 @@ fn native_label_retry_never_rebuilds_deleted_or_foreign_scope_from_local_id_coll assert_eq!(current[0].origin_device_label.as_deref(), Some("New")); } } + +#[test] +fn builtin_last_instance_delete_publishes_and_recovers_exact_release() { + for id in ["builtin:fizz", "builtin:honey"] { + for offline in [false, true] { + let (dir, c, mut instance, keys) = setup(); + save(dir.path(), &[]).unwrap(); + instance.persona_id = Some(id.into()); + commit_claim_in_dir( + dir.path(), + &c, + id, + instance.clone(), + &keys, + |raw| save(dir.path(), raw), + |_| Ok(()), + ) + .unwrap(); + let op = + delete::prepare_delete_in_dir(dir.path(), &c, &instance.pubkey, &keys).unwrap(); + assert_eq!(op.signed_events.len(), 3); + let signed: Vec<_> = op.signed_events.iter().map(Event::as_json).collect(); + let mut conn = open_retention_db(&dir.path().join("delete.db")).unwrap(); + let result = delete::commit_delete_in_dir( + dir.path(), + &op, + |raw| save(dir.path(), raw), + |op| { + if offline { + Err("offline".into()) + } else { + enqueue_home_events(dir.path(), &mut conn, op) + } + }, + ); + assert_eq!(result.is_err(), offline); + recover_in_dir( + dir.path(), + |b| Ok(c.proof.matches(b)), + |op| enqueue_home_events(dir.path(), &mut conn, op), + ) + .unwrap(); + let pending = get_pending_sync(&conn).unwrap(); + assert_eq!(pending.len(), 3); + for event in signed { + assert!(pending.iter().any(|row| row.raw_event == event)); + } + let raw = read_policy_records(&dir.path().join("agents/managed-agents.json")).unwrap(); + assert_eq!(raw.len(), 1); + assert_eq!(raw[0].slug.as_deref(), Some(id)); + assert_eq!(raw[0].origin_released, Some(true)); + assert!(read_journal(dir.path()).unwrap().is_empty()); + } + } +} diff --git a/desktop/src-tauri/src/managed_agents/device_home_operations/label.rs b/desktop/src-tauri/src/managed_agents/device_home_operations/label.rs index f83d478e2aa..3a287f7cebd 100644 --- a/desktop/src-tauri/src/managed_agents/device_home_operations/label.rs +++ b/desktop/src-tauri/src/managed_agents/device_home_operations/label.rs @@ -115,7 +115,13 @@ fn scoped_definitions( .filter_map(ManagedAgentRecord::to_definition_view) .filter(|d| d.origin_device_id.as_deref() == Some(device.device_id.as_str())) { - if !definitions.iter().any(|r| r.id == d.id) { + if let Some(retained) = definitions.iter_mut().find(|r| { + super::super::persona_events::persona_d_tag(r) + == super::super::persona_events::persona_d_tag(&d) + }) { + // Keep retained content, but retain local journal identity for retries. + retained.id = d.id; + } else { definitions.push(d); } } @@ -156,18 +162,36 @@ fn sign_label( } else { None }; + // Built-in journals retain local IDs even when replay only has retained coordinates. + let matches_id = |d: &AgentDefinition, id: &String| { + &d.id == id + || super::super::persona_events::persona_d_tag(d) == *id + || super::super::built_in_persona_definition(id, &crate::util::now_iso()).is_some_and( + |builtin| { + super::super::persona_events::persona_d_tag(&builtin) + == super::super::persona_events::persona_d_tag(d) + }, + ) + }; // Keep exactly the recorded worklist; later deleted/moved definitions are dropped. - affected_definition_ids.retain(|id| definitions.iter().any(|d| &d.id == id)); + affected_definition_ids.retain(|id| definitions.iter().any(|d| matches_id(d, id))); for id in affected_definition_ids.iter() { let mut d = definitions .iter() - .find(|d| &d.id == id) + .find(|d| matches_id(d, id)) .cloned() .ok_or_else(|| "label definition missing".to_string())?; d.origin_device_label = Some(new_label.clone()); let prior = conn .as_ref() - .map(|c| super::super::retention::get_retained_event(c, 30175, &op.owner_pubkey, id)) + .map(|c| { + super::super::retention::get_retained_event( + c, + 30175, + &op.owner_pubkey, + &super::super::persona_events::persona_d_tag(&d), + ) + }) .transpose()? .flatten(); let event = super::super::persona_events::build_persona_event(&d)? @@ -177,6 +201,11 @@ fn sign_label( .map(|r| r.created_at) .into_iter() .chain(superseded_created_at.get(id).copied()) + .chain( + superseded_created_at + .get(&super::super::persona_events::persona_d_tag(&d)) + .copied(), + ) .max(), )) .sign_with_keys(keys) diff --git a/desktop/src-tauri/src/managed_agents/device_home_operations/label_tests.rs b/desktop/src-tauri/src/managed_agents/device_home_operations/label_tests.rs index 66f9f65049d..f9b53f25c99 100644 --- a/desktop/src-tauri/src/managed_agents/device_home_operations/label_tests.rs +++ b/desktop/src-tauri/src/managed_agents/device_home_operations/label_tests.rs @@ -44,7 +44,9 @@ fn retain_definition(dir: &Path, relay: &str, keys: &Keys, known: bool) -> Conne &RetainedEvent { kind: 30175, pubkey: owner, - d_tag: "one".into(), + d_tag: super::super::persona_events::persona_d_tag( + &raw[0].to_definition_view().unwrap(), + ), content: event.content.clone(), created_at: event.created_at.as_secs() as i64, raw_event: event.as_json(), @@ -74,14 +76,14 @@ fn newer_successful_label_survives_older_sql_enqueue_failure_and_recovery() { let scopes = [(relay, owner)]; conn.execute_batch("CREATE TRIGGER reject_label BEFORE INSERT ON persona_events BEGIN SELECT RAISE(ABORT,'label enqueue failure'); END;").unwrap(); label::set_label_in_dir(dir.path(), identity.clone(), "A", &scopes, &keys, |op| { - enqueue_home_events(&mut conn, op) + enqueue_home_events(dir.path(), &mut conn, op) }) .unwrap(); assert_eq!(read_journal(dir.path()).unwrap().len(), 1); assert!(get_pending_sync(&conn).unwrap().is_empty()); conn.execute_batch("DROP TRIGGER reject_label").unwrap(); label::set_label_in_dir(dir.path(), identity, "B", &scopes, &keys, |op| { - enqueue_home_events(&mut conn, op) + enqueue_home_events(dir.path(), &mut conn, op) }) .unwrap(); assert_label(dir.path(), "B"); @@ -89,7 +91,7 @@ fn newer_successful_label_survives_older_sql_enqueue_failure_and_recovery() { recover_in_dir( dir.path(), |_| panic!("label proof"), - |op| enqueue_home_events(&mut conn, op), + |op| enqueue_home_events(dir.path(), &mut conn, op), ) .unwrap(); assert_label(dir.path(), "B"); @@ -130,7 +132,7 @@ fn newer_label_preserves_other_relay_owner_and_unresolved_scope_worklists() { &keys, |op| { if op.relay_url == relay { - enqueue_home_events(&mut active, op) + enqueue_home_events(dir.path(), &mut active, op) } else { Err("other relay offline".into()) } @@ -188,21 +190,140 @@ fn newer_label_preserves_other_relay_owner_and_unresolved_scope_worklists() { #[test] fn newer_label_is_signed_after_superseded_unenqueued_event() { - let (dir, identity, keys, relay) = setup_label(); - let mut conn = retain_definition(dir.path(), &relay, &keys, true); - let scopes = [(relay, keys.public_key().to_hex())]; - label::set_label_in_dir(dir.path(), identity.clone(), "A", &scopes, &keys, |_| { - Err("offline".into()) - }) + for (id, source) in [ + ("one", None), + ("builtin:fizz", None), + ("builtin:honey", None), + ("one", Some("CodeReviewer")), + ] { + let (dir, identity, keys, relay) = setup_label(); + let mut raw = read_policy_records(&dir.path().join("agents/managed-agents.json")).unwrap(); + raw[0].slug = Some(id.into()); + raw[0].source_team_persona_slug = source.map(str::to_string); + save(dir.path(), &raw).unwrap(); + let mut conn = retain_definition(dir.path(), &relay, &keys, true); + let scopes = [(relay, keys.public_key().to_hex())]; + label::set_label_in_dir(dir.path(), identity.clone(), "A", &scopes, &keys, |_| { + Err("offline".into()) + }) + .unwrap(); + let old = read_journal(dir.path()).unwrap()[0].signed_events[0].clone(); + label::set_label_in_dir(dir.path(), identity, "B", &scopes, &keys, |op| { + enqueue_home_events(dir.path(), &mut conn, op) + }) + .unwrap(); + let pending = get_pending_sync(&conn).unwrap(); + assert_eq!(pending.len(), 1); + let event = Event::from_json(&pending[0].raw_event).unwrap(); + assert!(event.content.contains("B")); + assert!( + event.created_at > old.created_at, + "newest label must dominate signed pending A for {id}/{source:?}" + ); + } +} + +#[test] +fn builtin_labels_enqueue_and_replay_exact_signed_heads_without_duplicate_coordinates() { + for id in ["builtin:fizz", "builtin:honey"] { + for offline in [false, true] { + let (dir, identity, keys, relay) = setup_label(); + let mut raw = + read_policy_records(&dir.path().join("agents/managed-agents.json")).unwrap(); + raw[0].slug = Some(id.into()); + save(dir.path(), &raw).unwrap(); + let mut conn = retain_definition(dir.path(), &relay, &keys, true); + let scopes = [(relay, keys.public_key().to_hex())]; + label::set_label_in_dir(dir.path(), identity, "After", &scopes, &keys, |op| { + if offline { + Err("offline".into()) + } else { + enqueue_home_events(dir.path(), &mut conn, op) + } + }) + .unwrap(); + let signed: Vec<_> = read_journal(dir.path()) + .unwrap() + .iter() + .flat_map(|op| op.signed_events.iter().map(Event::as_json)) + .collect(); + if offline { + assert_eq!(signed.len(), 1); + } + recover_in_dir( + dir.path(), + |_| panic!("label proof"), + |op| enqueue_home_events(dir.path(), &mut conn, op), + ) + .unwrap(); + let pending = get_pending_sync(&conn).unwrap(); + assert_eq!(pending.len(), 1); + assert_eq!(pending[0].d_tag, id.replace(':', "-")); + assert!(pending[0].content.contains("After")); + for event in signed { + assert_eq!(pending[0].raw_event, event); + } + assert!(read_journal(dir.path()).unwrap().is_empty()); + } + } +} + +#[test] +fn retained_label_coordinate_survives_unrelated_local_id_collision_in_original_owner_scope() { + let (dir, identity, original_keys, relay) = setup_label(); + let mut conn = retain_definition(dir.path(), &relay, &original_keys, true); + label::set_label_in_dir( + dir.path(), + identity, + "After", + &[(relay, original_keys.public_key().to_hex())], + &original_keys, + |_| Err("offline".into()), + ) .unwrap(); - let old = read_journal(dir.path()).unwrap()[0].signed_events[0].clone(); - label::set_label_in_dir(dir.path(), identity, "B", &scopes, &keys, |op| { - enqueue_home_events(&mut conn, op) - }) + let op = read_journal(dir.path()).unwrap().pop().unwrap(); + let signed = op.signed_events[0].as_json(); + let mut raw = read_policy_records(&dir.path().join("agents/managed-agents.json")).unwrap(); + raw[0].source_team_persona_slug = Some("CodeReviewer".into()); + save(dir.path(), &raw).unwrap(); + let active_keys = Keys::generate(); + assert_ne!(active_keys.public_key().to_hex(), op.owner_pubkey); + recover_in_dir( + dir.path(), + |_| panic!("label proof"), + |op| enqueue_home_events(dir.path(), &mut conn, op), + ) .unwrap(); - let event = Event::from_json(&get_pending_sync(&conn).unwrap()[0].raw_event).unwrap(); - assert!( - event.created_at > old.created_at, - "newest label must dominate signed pending A" - ); + let pending = get_pending_sync(&conn).unwrap(); + assert_eq!(pending.len(), 1); + assert_eq!(pending[0].d_tag, "one"); + assert_eq!(pending[0].pubkey, original_keys.public_key().to_hex()); + assert_eq!(pending[0].raw_event, signed); + assert!(read_journal(dir.path()).unwrap().is_empty()); +} + +#[test] +fn unsigned_old_builtin_label_worklist_is_not_dropped_during_retained_only_resigning() { + let (dir, identity, keys, relay) = setup_label(); + let mut raw = read_policy_records(&dir.path().join("agents/managed-agents.json")).unwrap(); + raw[0].slug = Some("builtin:fizz".into()); + save(dir.path(), &raw).unwrap(); + let conn = retain_definition(dir.path(), &relay, &keys, true); + label::set_label_in_dir( + dir.path(), + identity, + "After", + &[(relay, keys.public_key().to_hex())], + &keys, + |_| Err("offline".into()), + ) + .unwrap(); + let mut ops = read_journal(dir.path()).unwrap(); + ops[0].signed_events.clear(); + save_journal(dir.path(), &ops).unwrap(); + label::prepare_label_retries(dir.path(), &keys).unwrap(); + let replay = read_journal(dir.path()).unwrap(); + assert_eq!(replay[0].signed_events.len(), 1); + assert!(replay[0].signed_events[0].content.contains("After")); + assert!(get_pending_sync(&conn).unwrap().is_empty()); } diff --git a/desktop/src-tauri/src/managed_agents/device_home_operations/tests.rs b/desktop/src-tauri/src/managed_agents/device_home_operations/tests.rs index 752e3664b4d..41568165e0e 100644 --- a/desktop/src-tauri/src/managed_agents/device_home_operations/tests.rs +++ b/desktop/src-tauri/src/managed_agents/device_home_operations/tests.rs @@ -95,7 +95,7 @@ fn claim_retention_failure_is_recoverable() { recover_in_dir( dir.path(), |binding| Ok(c.proof.matches(binding)), - |op| enqueue_home_events(&mut conn, op), + |op| enqueue_home_events(dir.path(), &mut conn, op), ) .unwrap(); assert_eq!(get_pending_sync(&conn).unwrap().len(), 2); @@ -156,7 +156,9 @@ fn claim_beats_future_release_preserves_catalog_sharing_and_replays_exact_ids() ); std::fs::create_dir_all(db.parent().unwrap()).unwrap(); let mut conn = open_retention_db(&db).unwrap(); - let rs = read_policy_records(&dir.path().join("agents/managed-agents.json")).unwrap(); + let mut rs = read_policy_records(&dir.path().join("agents/managed-agents.json")).unwrap(); + rs[0].source_team_persona_slug = Some("CodeReviewer".into()); + save(dir.path(), &rs).unwrap(); let mut d = rs[0].to_definition_view().unwrap(); d.shared = true; let future = nostr::Timestamp::from(nostr::Timestamp::now().as_secs() + 10000); @@ -170,7 +172,7 @@ fn claim_beats_future_release_preserves_catalog_sharing_and_replays_exact_ids() &RetainedEvent { kind: 30175, pubkey: c.scope.owner_pubkey.clone(), - d_tag: "one".into(), + d_tag: "codereviewer".into(), content: prior.content.clone(), created_at: future.as_secs() as i64, raw_event: prior.as_json(), @@ -196,10 +198,10 @@ fn claim_beats_future_release_preserves_catalog_sharing_and_replays_exact_ids() recover_in_dir( dir.path(), |binding| Ok(c.proof.matches(binding)), - |op| enqueue_home_events(&mut conn, op), + |op| enqueue_home_events(dir.path(), &mut conn, op), ) .unwrap(); - let head = get_retained_event(&conn, 30175, &c.scope.owner_pubkey, "one") + let head = get_retained_event(&conn, 30175, &c.scope.owner_pubkey, "codereviewer") .unwrap() .unwrap(); assert_eq!(Event::from_json(head.raw_event).unwrap().id, ids[0]); @@ -217,7 +219,7 @@ fn claim_transaction_failure_retains_intent_and_never_half_enqueues() { i, &owner, |rs| save(dir.path(), rs), - |op| enqueue_home_events(&mut conn, op) + |op| enqueue_home_events(dir.path(), &mut conn, op) ) .is_err()); assert!(get_pending_sync(&conn).unwrap().is_empty()); @@ -226,7 +228,7 @@ fn claim_transaction_failure_retains_intent_and_never_half_enqueues() { recover_in_dir( dir.path(), |binding| Ok(c.proof.matches(binding)), - |op| enqueue_home_events(&mut conn, op), + |op| enqueue_home_events(dir.path(), &mut conn, op), ) .unwrap(); assert_eq!(get_pending_sync(&conn).unwrap().len(), 2); @@ -372,3 +374,136 @@ fn new_import_pair_preserves_foreign_records_and_persists_only_new_keys() { 1 ); } + +#[test] +fn builtin_and_source_slug_claims_enqueue_canonical_persona_heads() { + for (id, source) in [ + ("builtin:fizz", None), + ("builtin:honey", None), + ("one", Some("CodeReviewer")), + ] { + let (dir, c, mut instance, keys) = setup(); + if let Some(source) = source { + let mut raw = + read_policy_records(&dir.path().join("agents/managed-agents.json")).unwrap(); + raw[0].source_team_persona_slug = Some(source.into()); + save(dir.path(), &raw).unwrap(); + } else { + save(dir.path(), &[]).unwrap(); + } + instance.persona_id = Some(id.into()); + let mut conn = open_retention_db(&dir.path().join("claim.db")).unwrap(); + commit_claim_in_dir( + dir.path(), + &c, + id, + instance, + &keys, + |raw| save(dir.path(), raw), + |op| enqueue_home_events(dir.path(), &mut conn, op), + ) + .unwrap(); + let pending = get_pending_sync(&conn).unwrap(); + let head = pending.iter().find(|row| row.kind == 30175).unwrap(); + let expected = source + .map(str::to_lowercase) + .unwrap_or_else(|| id.replace(':', "-")); + assert_eq!(head.d_tag, expected); + Event::from_json(&head.raw_event).unwrap().verify().unwrap(); + let raw = read_policy_records(&dir.path().join("agents/managed-agents.json")).unwrap(); + assert_eq!(raw[0].slug.as_deref(), Some(id)); + assert!(read_journal(dir.path()).unwrap().is_empty()); + } +} + +#[test] +fn builtin_journal_recovery_preserves_signed_bytes_and_rejects_wrong_scope_atomically() { + for id in ["builtin:fizz", "builtin:honey"] { + let (dir, c, mut instance, keys) = setup(); + save(dir.path(), &[]).unwrap(); + instance.persona_id = Some(id.into()); + assert!(commit_claim_in_dir( + dir.path(), + &c, + id, + instance, + &keys, + |raw| save(dir.path(), raw), + |_| Err("offline".into()), + ) + .is_err()); + let op = read_journal(dir.path()).unwrap().pop().unwrap(); + let signed: Vec<_> = op.signed_events.iter().map(Event::as_json).collect(); + let original = std::fs::read(journal(dir.path())).unwrap(); + let mut conn = open_retention_db(&dir.path().join("recovery.db")).unwrap(); + for mutation in [ + "persona_coordinate", + "coordinate", + "owner", + "kind", + "signature", + ] { + let mut invalid = op.clone(); + if mutation == "owner" { + invalid.owner_pubkey = Keys::generate().public_key().to_hex(); + } else { + let mut json = serde_json::to_value(&invalid.signed_events[1]).unwrap(); + if mutation == "signature" { + json["content"] = serde_json::json!("tampered"); + invalid.signed_events[1] = serde_json::from_value(json).unwrap(); + } else { + let index = if mutation == "persona_coordinate" { + 0 + } else { + 1 + }; + let original_event = &op.signed_events[index]; + let kind = if mutation == "kind" { + nostr::Kind::from(30175u16) + } else { + original_event.kind + }; + let tags = original_event.tags.iter().cloned().map(|tag| { + if mutation != "kind" + && tag.as_slice().first().map(String::as_str) == Some("d") + { + nostr::Tag::parse(["d", "wrong"]).unwrap() + } else { + tag + } + }); + invalid.signed_events[index] = + nostr::EventBuilder::new(kind, original_event.content.clone()) + .tags(tags) + .custom_created_at(original_event.created_at) + .sign_with_keys(&keys) + .unwrap(); + } + } + assert!(enqueue_home_events(dir.path(), &mut conn, &invalid).is_err()); + assert!(get_pending_sync(&conn).unwrap().is_empty()); + assert_eq!(std::fs::read(journal(dir.path())).unwrap(), original); + } + recover_in_dir( + dir.path(), + |b| Ok(c.proof.matches(b)), + |op| { + assert_eq!( + op.signed_events + .iter() + .map(Event::as_json) + .collect::>(), + signed + ); + enqueue_home_events(dir.path(), &mut conn, op) + }, + ) + .unwrap(); + let pending = get_pending_sync(&conn).unwrap(); + assert_eq!(pending.len(), 2); + for event in signed { + assert!(pending.iter().any(|row| row.raw_event == event)); + } + assert!(read_journal(dir.path()).unwrap().is_empty()); + } +} diff --git a/desktop/src-tauri/src/managed_agents/device_workflow_tests.rs b/desktop/src-tauri/src/managed_agents/device_workflow_tests.rs index be4fc50d277..4b322214323 100644 --- a/desktop/src-tauri/src/managed_agents/device_workflow_tests.rs +++ b/desktop/src-tauri/src/managed_agents/device_workflow_tests.rs @@ -132,7 +132,11 @@ impl Device { Ok(()) }, |operation| { - enqueue_home_events(&mut open_retention_db(&self.db())?, operation) + enqueue_home_events( + self.dir.path(), + &mut open_retention_db(&self.db())?, + operation, + ) }, )?; } else { @@ -340,7 +344,9 @@ fn release_then_reclaim() { a.save(rows); Ok(()) }, - |operation| enqueue_home_events(&mut open_retention_db(&a.db())?, operation), + |operation| { + enqueue_home_events(a.dir.path(), &mut open_retention_db(&a.db())?, operation) + }, ) .unwrap(); assert!(!a.rows().iter().any(|r| r.pubkey == first)); diff --git a/scripts/fork/patches.json b/scripts/fork/patches.json index 68c077e4407..503a11d0300 100644 --- a/scripts/fork/patches.json +++ b/scripts/fork/patches.json @@ -2367,7 +2367,7 @@ }, { "path": "desktop/src-tauri/src/managed_agents/device_home_operations.rs", - "responsibility": "Device creation policy, default-private sources, atomic claim and durable recovery regression coverage; Task8 durable original-scope home deletion/label publication and retry", + "responsibility": "Device creation policy, default-private sources, atomic claim and durable recovery regression coverage; Task8 durable original-scope home deletion/label publication and retry; Preserve canonical persona coordinates for builtin claims and durable journal recovery", "is_new": true, "required_symbols": [ "commit_home_claim_locked", @@ -2399,7 +2399,7 @@ { "caller_path": "desktop/src-tauri/src/managed_agents/device_home_operations.rs", "callee_symbol": "enqueue_home_events", - "invocation": "enqueue_home_events(&mut conn, operation)", + "invocation": "enqueue_home_events(dir, &mut conn, operation)", "behavior_test": "managed_agents::device_home_operations::tests::claim_transaction_failure_retains_intent_and_never_half_enqueues", "verification_command": "cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace managed_agents::device_home_operations::tests::claim_transaction_failure_retains_intent_and_never_half_enqueues -- --exact", "coverage": "production behavior with isolated injectable boundaries" @@ -2447,7 +2447,7 @@ { "caller_path": "desktop/src-tauri/src/managed_agents/device_home_operations.rs", "callee_symbol": "enqueue_home_events_in_transaction", - "invocation": "enqueue_home_events_in_transaction(&tx, operation)?;", + "invocation": "enqueue_home_events_in_transaction(&tx, operation, &definitions)?;", "behavior_test": "intent_save_and_each_sql_enqueue_failure_leave_no_partial_batch", "verification_command": "cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace intent_save_and_each_sql_enqueue_failure_leave_no_partial_batch", "coverage": "Production generic/native adapter runtime with MockRuntime, isolated app data, injected authority/process/key effects and real SQLite; Wry IPC wiring compile only, native/two-device acceptance outstanding" @@ -2475,6 +2475,14 @@ "behavior_test": "managed_agents::device_home_operations::label_tests", "verification_command": "cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace", "coverage": "R5 latest intent and scoped retry/timestamp regressions" + }, + { + "caller_path": "desktop/src-tauri/src/managed_agents/device_home_operations.rs", + "callee_symbol": "persona_d_tag", + "invocation": ".map(|d| super::persona_events::persona_d_tag(&d))", + "behavior_test": "builtin_and_source_slug_claims_enqueue_canonical_persona_heads", + "verification_command": "cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib builtin_and_source_slug_claims_enqueue_canonical_persona_heads", + "coverage": "Real signed events, local definitions and SQLite retention; builtin and source-slug coordinate regression" } ], "verification_commands": [ @@ -2484,7 +2492,7 @@ }, { "path": "desktop/src-tauri/src/managed_agents/device_home_operations/tests.rs", - "responsibility": "Device creation policy, default-private sources, atomic claim and durable recovery regression coverage", + "responsibility": "Device creation policy, default-private sources, atomic claim and durable recovery regression coverage; Canonical builtin/source-slug claims, exact signed-byte legacy recovery, and invalid event rejection", "is_new": true, "required_symbols": [], "invocation_seams": [], @@ -3198,7 +3206,7 @@ }, { "path": "desktop/src-tauri/src/managed_agents/device_home_operations/label.rs", - "responsibility": "Task8: durable secret-free device home deletion, label retries and owning production-adapter coverage; R5 coalesce all outstanding scope label worklists to latest intent with monotonic signed floors", + "responsibility": "Task8: durable secret-free device home deletion, label retries and owning production-adapter coverage; R5 coalesce all outstanding scope label worklists to latest intent with monotonic signed floors; canonical persona deduplication and superseded timestamp floors preserve builtin and source-slug label retries", "is_new": true, "required_symbols": [ "fn coalesce_label_worklists" @@ -3227,6 +3235,14 @@ "behavior_test": "managed_agents::device_home_operations::label_tests", "verification_command": "cargo test --manifest-path desktop/src-tauri/Cargo.toml --workspace", "coverage": "R5 latest intent and scoped retry/timestamp regressions" + }, + { + "caller_path": "desktop/src-tauri/src/managed_agents/device_home_operations/label.rs", + "callee_symbol": "persona_d_tag", + "invocation": ".get(&super::super::persona_events::persona_d_tag(&d))", + "behavior_test": "newer_label_is_signed_after_superseded_unenqueued_event", + "verification_command": "cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib newer_label_is_signed_after_superseded_unenqueued_event", + "coverage": "Real signed future heads, durable coalesced label worklists and SQLite; canonical and local timestamp floors" } ], "verification_commands": [