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    /// # Errors
17    ///
18    /// Returns an error when the requested operation cannot be completed.
19    pub async fn ensure_active_ownership(
20        valence_model: &str,
21        record_id: &str,
22        owner: OwnerRef,
23        v: &Valence,
24    ) -> Result<()> {
25        if skip_ownership_for_table(valence_model) {
26            return Ok(());
27        }
28
29        let id = ownership_row_id(valence_model, record_id);
30        let sys = system_valence(v);
31        let backend = Self::ownership_backend(valence_model, &sys)?;
32        let row = json!({
33            "id": id,
34            "valence_model": valence_model,
35            "record_id": record_id,
36            "owner_id": owner.owner_id,
37            "owner_type": owner.owner_kind.as_str(),
38            "status": "active",
39        });
40        backend
41            .upsert_record("valence_data_ownership", id.as_str(), row)
42            .await
43            .map_err(|e| Error::database(e.to_string()))?;
44        crate::read_cache::invalidate("valence_data_ownership", &id);
45        crate::read_cache::invalidate(valence_model, record_id);
46        Ok(())
47    }
48
49    /// Set ownership status to `deleted` after the physical row has been removed (audit trail).
50    /// # Errors
51    ///
52    /// Returns an error when the requested operation cannot be completed.
53    pub async fn mark_deleted_ownership(
54        valence_model: &str,
55        record_id: &str,
56        v: &Valence,
57    ) -> Result<()> {
58        if skip_ownership_for_table(valence_model) {
59            return Ok(());
60        }
61        let id = ownership_row_id(valence_model, record_id);
62        let sys = system_valence(v);
63        let backend = Self::ownership_backend(valence_model, &sys)?;
64        let patch = json!({ "status": "deleted" });
65        backend
66            .merge_record("valence_data_ownership", &id, patch)
67            .await
68            .map_err(|e| Error::database(e.to_string()))
69            .map(|_| ())?;
70        crate::read_cache::invalidate("valence_data_ownership", &id);
71        crate::read_cache::invalidate(valence_model, record_id);
72        Ok(())
73    }
74
75    /// Mark ownership as pending deletion (called before the row delete side-effects).
76    /// # Errors
77    ///
78    /// Returns an error when the requested operation cannot be completed.
79    pub async fn mark_pending_deletion(
80        valence_model: &str,
81        record_id: &str,
82        v: &Valence,
83    ) -> Result<()> {
84        if skip_ownership_for_table(valence_model) {
85            return Ok(());
86        }
87        let id = ownership_row_id(valence_model, record_id);
88        let sys = system_valence(v);
89        let backend = Self::ownership_backend(valence_model, &sys)?;
90        let row = json!({
91            "id": id,
92            "valence_model": valence_model,
93            "record_id": record_id,
94            "status": "pending_deletion",
95        });
96        backend
97            .upsert_record("valence_data_ownership", id.as_str(), row)
98            .await
99            .map_err(|e| Error::database(e.to_string()))
100            .map(|_| ())?;
101        crate::read_cache::invalidate("valence_data_ownership", &id);
102        crate::read_cache::invalidate(valence_model, record_id);
103        Ok(())
104    }
105
106    /// Transfer ownership and append a history row.
107    ///
108    /// # Security
109    ///
110    /// Privileged: runs under a system-scoped valence. Hosts must authorize the caller before
111    /// invoking; do not expose as an unauthenticated client API.
112    ///
113    /// # Errors
114    ///
115    /// Returns an error when the requested operation cannot be completed.
116    pub async fn transfer_ownership(
117        valence_model: &str,
118        record_id: &str,
119        new_owner: OwnerRef,
120        reason: Option<String>,
121        v: &Valence,
122    ) -> Result<()> {
123        let existing = Self::get_ownership_json(valence_model, record_id, v)
124            .await?
125            .ok_or_else(|| {
126                Error::NotFound(format!("ownership missing for {valence_model}:{record_id}"))
127            })?;
128
129        let from_owner_id = existing
130            .get("owner_id")
131            .and_then(|v| v.as_str())
132            .unwrap_or("")
133            .to_string();
134        let from_owner_type = existing
135            .get("owner_type")
136            .and_then(|v| v.as_str())
137            .unwrap_or("system")
138            .to_string();
139
140        let oid = ownership_row_id(valence_model, record_id);
141        let sys = system_valence(v);
142        let backend = Self::ownership_backend(valence_model, &sys)?;
143        let merged = json!({
144            "owner_id": new_owner.owner_id,
145            "owner_type": new_owner.owner_kind.as_str(),
146            "status": "active",
147        });
148        backend
149            .merge_record("valence_data_ownership", &oid, merged)
150            .await
151            .map_err(|e| Error::database(e.to_string()))
152            .map(|_| ())?;
153        crate::read_cache::invalidate("valence_data_ownership", &oid);
154        crate::read_cache::invalidate(valence_model, record_id);
155
156        append_transfer_history_row(
157            valence_model,
158            record_id,
159            &oid,
160            &from_owner_id,
161            &from_owner_type,
162            &new_owner,
163            reason,
164            v,
165        )
166        .await
167    }
168}