Skip to main content

khive_runtime/secret_gate_finalizer/
entity_direct.rs

1//! Direct-ingest facade retaining preflight snapshot and conditional writes.
2
3use khive_storage::Entity;
4
5use super::entity_admission::{EntityAdmission, EntityCandidatePrepared};
6use super::entity_transaction::EntityCandidateMutation;
7use crate::{
8    AtomicOpFailure, AtomicOpPlan, AtomicRunOutcome, KhiveRuntime, NamespaceToken,
9    PostCommitEffect, RuntimeError, RuntimeResult,
10};
11
12#[derive(Debug)]
13pub enum EntityCandidateAdmission {
14    /// No write occurred. Continue the existing clean-candidate storage route.
15    Legacy(Entity),
16    /// Record, runtime stamp, required FTS/map, and audit committed together.
17    Committed(Entity),
18    /// Conditional insert/CAS lost; the ingest owner may reread and rebase.
19    Conflict,
20}
21
22impl KhiveRuntime {
23    /// Admit a structural direct-ingest candidate. Matched candidates cannot
24    /// carry content_ref, because this facade does not own attachment writes.
25    /// The ordinary attachment-aware constructor remains the supported path
26    /// for those records; clean Legacy candidates keep their original shape.
27    pub async fn try_commit_manifest_entity_candidate(
28        &self,
29        token: &NamespaceToken,
30        prepared: EntityCandidatePrepared,
31        mutation: EntityCandidateMutation,
32    ) -> RuntimeResult<EntityCandidateAdmission> {
33        let admitted = match prepared.admit(token)? {
34            EntityAdmission::Legacy(entity) => return Ok(EntityCandidateAdmission::Legacy(entity)),
35            EntityAdmission::Exempt(admitted) => admitted,
36        };
37        self.validate_entity_kind(&admitted.entity().kind)?;
38        self.validate_entity_type_for_kind(
39            &admitted.entity().kind,
40            admitted.entity().entity_type.as_deref(),
41        )?;
42        let (required, _) = self
43            .prepare_admitted_entity_indexes(token, admitted.entity(), &[], false)
44            .await?;
45        let plan = admitted.into_plan(mutation, required, PostCommitEffect::None)?;
46        let committed = plan.committed_entity();
47        match crate::run_atomic_unit(
48            self.sql().as_ref(),
49            vec![AtomicOpPlan::FinalizeEntity(Box::new(plan))],
50        )
51        .await
52        {
53            Ok(AtomicRunOutcome::Committed { .. }) => {
54                Ok(EntityCandidateAdmission::Committed(committed))
55            }
56            Ok(AtomicRunOutcome::RolledBack {
57                failure: AtomicOpFailure::EntityConflict(conflict),
58                ..
59            }) => Err(conflict.into_error().into()),
60            Ok(AtomicRunOutcome::RolledBack {
61                failure: AtomicOpFailure::GuardFailed { .. },
62                ..
63            }) => Ok(EntityCandidateAdmission::Conflict),
64            Ok(AtomicRunOutcome::RolledBack { failure, .. }) => Err(RuntimeError::Internal(
65                format!("entity manifest finalization rolled back: {failure:?}"),
66            )),
67            Err(error) => Err(RuntimeError::Storage(error.0)),
68        }
69    }
70}