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