velesdb_core/observer/context.rs
1//! Control-plane decision types for the read-path observer hook.
2//!
3//! These types form the *seam* handed to a [`DatabaseObserver`] implementation
4//! on the query/read path. Core defines the vocabulary (which operation is
5//! running, what an access decision looks like) but never the enforcing
6//! policy — that lives behind the port as a premium observer implementation.
7//!
8//! All public types are `#[non_exhaustive]` so future additions (new operation
9//! kinds, new decision variants, new scope fields) never break downstream
10//! implementers' `match` arms or struct literals (Requirement 3.3).
11//!
12//! Core references no premium crate, type, or symbol here (Requirement 3.4).
13
14use crate::velesql::Condition;
15
16/// The read operation being gated.
17///
18/// Additive-only (`#[non_exhaustive]`) so introducing new operation kinds never
19/// breaks a premium observer's `match` arms.
20#[non_exhaustive]
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum QueryOperationKind {
23 /// Dense vector similarity search.
24 VectorSearch,
25 /// Full-text / BM25 search.
26 TextSearch,
27 /// Hybrid (dense + sparse/text) fused search.
28 HybridSearch,
29 /// Graph traversal (`VelesQL` MATCH).
30 GraphTraversal,
31 /// Relational-style `VelesQL` SELECT (incl. JOIN / aggregation).
32 Select,
33}
34
35/// Read-time context handed to the read-path hook.
36///
37/// A borrowed view over the resolved query: it carries exactly what an
38/// access-control decision needs and nothing premium-specific
39/// (Requirement 1.7, 3.4). Borrowing means no allocation on the fast path.
40///
41/// `principal` and `tenant_hint` are opaque, caller-supplied strings; core
42/// never interprets their meaning — it only forwards them to the observer.
43#[non_exhaustive]
44#[derive(Debug, Clone)]
45pub struct QueryAccessContext<'a> {
46 /// Target collection name.
47 pub collection: &'a str,
48 /// Which read path is executing.
49 pub operation: QueryOperationKind,
50 /// Opaque caller-supplied principal hint (e.g. user id / api-key id),
51 /// passed through untouched for the observer to interpret.
52 pub principal: Option<&'a str>,
53 /// Opaque caller-supplied tenant hint, passed through untouched.
54 pub tenant_hint: Option<&'a str>,
55}
56
57/// Optional narrowing the observer asks core to apply to a read.
58///
59/// Reuses the existing [`velesql::Condition`](crate::velesql::Condition)
60/// AST language for row/collection narrowing — no parallel filter type is
61/// introduced. Using the `VelesQL` condition (rather than the lower-level
62/// [`filter::Condition`](crate::filter::Condition)) lets `apply_scope`
63/// AND-compose the constraint directly into a query's WHERE clause, which is
64/// itself a `velesql::Condition`.
65#[non_exhaustive]
66#[derive(Debug, Clone, Default)]
67pub struct AccessScope {
68 /// Opaque tenant scoping hint. Core records/forwards it for audit and
69 /// adapter-level routing; row/collection narrowing that core *enforces*
70 /// is expressed through `filter` below (kept policy-free in core).
71 pub tenant: Option<String>,
72 /// A `VelesQL` filter condition AND-composed into the query's WHERE
73 /// clause before execution. Reuses the existing `velesql::Condition`
74 /// language — no parallel filter type is introduced.
75 pub filter: Option<Condition>,
76}
77
78/// The control-plane decision returned by the read-path hook (Requirement 1.3).
79#[non_exhaustive]
80#[derive(Debug)]
81pub enum AccessDecision {
82 /// Execute the query unmodified (Requirement 1.6). Default decision.
83 Allow,
84 /// Abort the query and return this error without producing results
85 /// (Requirement 1.4).
86 Deny(crate::Error),
87 /// Execute with the given scope AND-composed into the filter pipeline
88 /// (Requirement 1.5).
89 AllowWithScope(AccessScope),
90}