Skip to main content

valence_core/ownership/service/
write.rs

1//! Ownership write paths: ensure, status transitions, and transfers.
2
3use serde_json::json;
4
5use crate::error::{Error, Result};
6use crate::owner_ref::OwnerRef;
7use crate::runtime::Valence;
8
9use super::helpers::{
10    append_transfer_history_row, ownership_row_id, skip_ownership_for_table, system_valence,
11};
12use super::OwnershipService;
13
14impl OwnershipService {
15    /// Insert or replace the active ownership row for `valence_model` / `record_id`.
16    pub async fn ensure_active_ownership(
17        valence_model: &str,
18        record_id: &str,
19        owner: OwnerRef,
20        v: &Valence,
21    ) -> Result<()> {
22        if skip_ownership_for_table(valence_model) {
23            return Ok(());
24        }
25
26        let id = ownership_row_id(valence_model, record_id);
27        let sys = system_valence(v);
28        let backend = Self::ownership_backend(valence_model, &sys)?;
29        let row = json!({
30            "id": id,
31            "valence_model": valence_model,
32            "record_id": record_id,
33            "owner_id": owner.owner_id,
34            "owner_type": owner.owner_kind.as_str(),
35            "status": "active",
36        });
37        backend
38            .upsert_record("valence_data_ownership", id.as_str(), row)
39            .await
40            .map_err(|e| Error::Database(e.to_string()))?;
41        crate::read_cache::invalidate("valence_data_ownership", &id);
42        crate::read_cache::invalidate(valence_model, record_id);
43        Ok(())
44    }
45
46    /// Set ownership status to `deleted` after the physical row has been removed (audit trail).
47    pub async fn mark_deleted_ownership(
48        valence_model: &str,
49        record_id: &str,
50        v: &Valence,
51    ) -> Result<()> {
52        if skip_ownership_for_table(valence_model) {
53            return Ok(());
54        }
55        let id = ownership_row_id(valence_model, record_id);
56        let sys = system_valence(v);
57        let backend = Self::ownership_backend(valence_model, &sys)?;
58        let patch = json!({ "status": "deleted" });
59        backend
60            .merge_record("valence_data_ownership", &id, patch)
61            .await
62            .map_err(|e| Error::Database(e.to_string()))
63            .map(|_| ())?;
64        crate::read_cache::invalidate("valence_data_ownership", &id);
65        crate::read_cache::invalidate(valence_model, record_id);
66        Ok(())
67    }
68
69    /// Mark ownership as pending deletion (called before the row delete side-effects).
70    pub async fn mark_pending_deletion(
71        valence_model: &str,
72        record_id: &str,
73        v: &Valence,
74    ) -> Result<()> {
75        if skip_ownership_for_table(valence_model) {
76            return Ok(());
77        }
78        let id = ownership_row_id(valence_model, record_id);
79        let sys = system_valence(v);
80        let backend = Self::ownership_backend(valence_model, &sys)?;
81        let row = json!({
82            "id": id,
83            "valence_model": valence_model,
84            "record_id": record_id,
85            "status": "pending_deletion",
86        });
87        backend
88            .upsert_record("valence_data_ownership", id.as_str(), row)
89            .await
90            .map_err(|e| Error::Database(e.to_string()))
91            .map(|_| ())?;
92        crate::read_cache::invalidate("valence_data_ownership", &id);
93        crate::read_cache::invalidate(valence_model, record_id);
94        Ok(())
95    }
96
97    /// Transfer ownership and append a history row.
98    pub async fn transfer_ownership(
99        valence_model: &str,
100        record_id: &str,
101        new_owner: OwnerRef,
102        reason: Option<String>,
103        v: &Valence,
104    ) -> Result<()> {
105        let existing = Self::get_ownership_json(valence_model, record_id, v)
106            .await?
107            .ok_or_else(|| {
108                Error::NotFound(format!("ownership missing for {valence_model}:{record_id}"))
109            })?;
110
111        let from_owner_id = existing
112            .get("owner_id")
113            .and_then(|v| v.as_str())
114            .unwrap_or("")
115            .to_string();
116        let from_owner_type = existing
117            .get("owner_type")
118            .and_then(|v| v.as_str())
119            .unwrap_or("system")
120            .to_string();
121
122        let oid = ownership_row_id(valence_model, record_id);
123        let sys = system_valence(v);
124        let backend = Self::ownership_backend(valence_model, &sys)?;
125        let merged = json!({
126            "owner_id": new_owner.owner_id,
127            "owner_type": new_owner.owner_kind.as_str(),
128            "status": "active",
129        });
130        backend
131            .merge_record("valence_data_ownership", &oid, merged)
132            .await
133            .map_err(|e| Error::Database(e.to_string()))
134            .map(|_| ())?;
135        crate::read_cache::invalidate("valence_data_ownership", &oid);
136        crate::read_cache::invalidate(valence_model, record_id);
137
138        append_transfer_history_row(
139            valence_model,
140            record_id,
141            &oid,
142            &from_owner_id,
143            &from_owner_type,
144            &new_owner,
145            reason,
146            v,
147        )
148        .await
149    }
150}