@@ -312,6 +312,137 @@ impl std::fmt::Display for KnowableFromError {
312312
313313impl std:: error:: Error for KnowableFromError { }
314314
315+ // ─────────────────────────────────────────────────────────────────────
316+ // VART backend (feature `vart-backend`)
317+ // ─────────────────────────────────────────────────────────────────────
318+ //
319+ // Reference backend impl named in PR #25's crate docs and ADR-024's
320+ // "VART pinned to the AdaWorldAPI mirror" cross-reference. Each
321+ // `register` call advances the trie's global version monotonically
322+ // and returns the new version as the `knowable_from` stamp.
323+ //
324+ // Architecture alignment:
325+ // - The NiblePath-shaped `class_identity` (e.g. `ogit-erp/sale.order`)
326+ // is prefix-radix-indexed natively by VART — same routing primitive
327+ // the runtime side uses (bardioc PR #18 / lance-graph PR #470
328+ // described the same trie-append pattern for `inv.object_instance`).
329+ // - VART is immutable / copy-on-write with snapshot isolation — every
330+ // register produces a new logical version; readers at any prior
331+ // version see the world-as-of-that-version. Suits the "audit-as-
332+ // version" discipline ADR-008 / ADR-013 already pin.
333+ // - The `schema_ddl_hint` parameter is intentionally discarded in v1
334+ // (VART's value type is `u64` to keep the trie homogeneous). A
335+ // follow-up could wire a parallel `Tree<VariableSizeKey, String>`
336+ // for hints if a real consumer needs them; today's `surrealql-hint`
337+ // feature already renders DDL at the helper layer, so the hint
338+ // lives upstream of the backend.
339+ /// VART-backed `KnowableFromStore` implementation — feature-gated
340+ /// (`vart-backend`). See [`vart_backend::VartKnowableFromStore`] for
341+ /// the full impl + design notes.
342+ #[ cfg( feature = "vart-backend" ) ]
343+ pub mod vart_backend {
344+ use super :: { KnowableFromError , KnowableFromStore } ;
345+ use std:: sync:: Mutex ;
346+ use vart:: art:: Tree ;
347+ use vart:: VariableSizeKey ;
348+
349+ /// `KnowableFromStore` impl backed by an in-memory versioned
350+ /// adaptive radix trie. Each [`register`] call advances the trie's
351+ /// global version; the new version IS the `knowable_from` stamp
352+ /// (value stored = version, so lookup returns it directly).
353+ ///
354+ /// The `class_identity` is encoded as a NULL-terminated byte
355+ /// sequence before being keyed into VART. NULL termination prevents
356+ /// prefix collisions per the variable-length-key discipline noted
357+ /// in VART's `src/lib.rs` documentation — without it, `ogit-op/Work`
358+ /// and `ogit-op/WorkPackage` would address overlapping subtrees.
359+ ///
360+ /// Thread-safe via internal [`Mutex`]; the trait bound
361+ /// `Send + Sync` is satisfied.
362+ ///
363+ /// # Persistence (v1: in-memory only)
364+ ///
365+ /// v1 keeps the trie in memory. VART itself is *structurally*
366+ /// persistable (immutable copy-on-write); wiring it to a Lance /
367+ /// surrealkv / disk store is the natural next step but lives
368+ /// behind a separate feature gate once a real consumer needs it.
369+ ///
370+ /// [`register`]: KnowableFromStore::register
371+ pub struct VartKnowableFromStore {
372+ tree : Mutex < Tree < VariableSizeKey , u64 > > ,
373+ }
374+
375+ impl VartKnowableFromStore {
376+ /// Build a new in-memory VART-backed store with an empty trie.
377+ /// The first `register` call returns version `1` (VART's
378+ /// `version()` starts at `0` for an empty trie).
379+ #[ must_use]
380+ pub fn new ( ) -> Self {
381+ Self { tree : Mutex :: new ( Tree :: new ( ) ) }
382+ }
383+
384+ /// Current max version across the trie. `0` if no `register`
385+ /// has been called yet.
386+ #[ must_use]
387+ pub fn current_version ( & self ) -> u64 {
388+ self . tree . lock ( ) . map ( |t| t. version ( ) ) . unwrap_or ( 0 )
389+ }
390+
391+ /// Build a VART `VariableSizeKey` from a class identity string,
392+ /// appending the NULL byte per the variable-length-key
393+ /// discipline (see struct-level doc).
394+ fn make_key ( class_identity : & str ) -> VariableSizeKey {
395+ let mut bytes = class_identity. as_bytes ( ) . to_vec ( ) ;
396+ bytes. push ( 0 ) ;
397+ // VART's inherent `from(Vec<u8>)` constructor (the trait
398+ // `From<&[u8]>` impl exists but is shadowed by the
399+ // inherent method).
400+ VariableSizeKey :: from ( bytes)
401+ }
402+ }
403+
404+ impl Default for VartKnowableFromStore {
405+ fn default ( ) -> Self {
406+ Self :: new ( )
407+ }
408+ }
409+
410+ impl KnowableFromStore for VartKnowableFromStore {
411+ fn register (
412+ & self ,
413+ class_identity : & str ,
414+ _schema_ddl_hint : Option < & str > ,
415+ ) -> Result < u64 , KnowableFromError > {
416+ let mut tree = self . tree . lock ( ) . map_err ( |e| {
417+ KnowableFromError :: Backend ( format ! ( "vart mutex poisoned: {e}" ) )
418+ } ) ?;
419+ // Advance the trie's logical version monotonically — the new
420+ // version IS the knowable_from stamp we return. saturating_add
421+ // guards the theoretical wrap (registering 2^64 times).
422+ let new_version = tree. version ( ) . saturating_add ( 1 ) ;
423+ let key = Self :: make_key ( class_identity) ;
424+ // insert_or_replace is the upsert path: re-registering the same
425+ // class identity advances its version (the registry behaviour
426+ // the runtime side's `inv.object_instance` trie-append also uses
427+ // — every commit is a new version of the entry).
428+ tree. insert_or_replace ( & key, new_version, new_version, 0 )
429+ . map_err ( |e| {
430+ KnowableFromError :: Backend ( format ! ( "vart insert: {e:?}" ) )
431+ } ) ?;
432+ Ok ( new_version)
433+ }
434+
435+ fn knowable_from ( & self , class_identity : & str ) -> Option < u64 > {
436+ let tree = self . tree . lock ( ) . ok ( ) ?;
437+ let key = Self :: make_key ( class_identity) ;
438+ // Latest snapshot — pass the trie's current version so we get
439+ // the freshest stamp for the key.
440+ let latest = tree. version ( ) ;
441+ tree. get ( & key, latest) . map ( |( v, _ts, _vsn) | v)
442+ }
443+ }
444+ }
445+
315446#[ cfg( test) ]
316447mod tests {
317448 use super :: * ;
@@ -548,4 +679,95 @@ mod tests {
548679 "expected DEFINE FIELD in the hint, got: {hint}"
549680 ) ;
550681 }
682+
683+ // ── VART backend tests (feature `vart-backend`) ─────────────────────
684+ // Exercise the reference-backend impl named in PR #25's crate docs.
685+ // Verifies: monotonic version advance, lookup after register,
686+ // prefix-collision safety (the NULL-byte termination discipline),
687+ // and the composition with `register_class_knowable_from` (the
688+ // PR #31 canonical-identity helper).
689+ // ────────────────────────────────────────────────────────────────────
690+
691+ #[ cfg( feature = "vart-backend" ) ]
692+ #[ test]
693+ fn vart_empty_returns_none_and_version_zero ( ) {
694+ let store = crate :: vart_backend:: VartKnowableFromStore :: new ( ) ;
695+ assert_eq ! ( store. current_version( ) , 0 ) ;
696+ assert ! ( store. knowable_from( "ogit-erp/Account" ) . is_none( ) ) ;
697+ }
698+
699+ #[ cfg( feature = "vart-backend" ) ]
700+ #[ test]
701+ fn vart_register_returns_monotonic_versions ( ) {
702+ let store = crate :: vart_backend:: VartKnowableFromStore :: new ( ) ;
703+ let v1 = store. register ( "ogit-erp/A" , None ) . unwrap ( ) ;
704+ let v2 = store. register ( "ogit-erp/B" , None ) . unwrap ( ) ;
705+ let v3 = store. register ( "ogit-erp/C" , None ) . unwrap ( ) ;
706+ assert ! ( v1 < v2 && v2 < v3, "versions not monotonic: {v1} {v2} {v3}" ) ;
707+ // Empty trie's version() starts at 0, so first register lands at 1.
708+ assert_eq ! ( v1, 1 ) ;
709+ }
710+
711+ #[ cfg( feature = "vart-backend" ) ]
712+ #[ test]
713+ fn vart_knowable_from_returns_latest_for_key ( ) {
714+ let store = crate :: vart_backend:: VartKnowableFromStore :: new ( ) ;
715+ let v = store. register ( "ogit-op/WorkPackage" , None ) . unwrap ( ) ;
716+ assert_eq ! ( store. knowable_from( "ogit-op/WorkPackage" ) , Some ( v) ) ;
717+ // Unrelated class returns None.
718+ assert ! ( store. knowable_from( "ogit-op/Issue" ) . is_none( ) ) ;
719+ }
720+
721+ #[ cfg( feature = "vart-backend" ) ]
722+ #[ test]
723+ fn vart_re_register_same_class_advances_version ( ) {
724+ // Upsert semantics: re-registering bumps the version (the trie's
725+ // immutable-versioned shape — each register is a new logical
726+ // moment in the registry).
727+ let store = crate :: vart_backend:: VartKnowableFromStore :: new ( ) ;
728+ let v_first = store. register ( "ogit-erp/Account" , None ) . unwrap ( ) ;
729+ let v_second = store. register ( "ogit-erp/Account" , None ) . unwrap ( ) ;
730+ assert ! ( v_second > v_first) ;
731+ // Latest snapshot returns the most recent version.
732+ assert_eq ! ( store. knowable_from( "ogit-erp/Account" ) , Some ( v_second) ) ;
733+ }
734+
735+ #[ cfg( feature = "vart-backend" ) ]
736+ #[ test]
737+ fn vart_prefix_keys_do_not_collide ( ) {
738+ // The NULL-byte-termination discipline: `ogit-op/Work` and
739+ // `ogit-op/WorkPackage` differ only in suffix; without
740+ // termination, the trie could conflate them at the radix
741+ // boundary. With termination, distinct stamps.
742+ let store = crate :: vart_backend:: VartKnowableFromStore :: new ( ) ;
743+ let v_work = store. register ( "ogit-op/Work" , None ) . unwrap ( ) ;
744+ let v_pkg = store. register ( "ogit-op/WorkPackage" , None ) . unwrap ( ) ;
745+ assert_ne ! ( v_work, v_pkg) ;
746+ assert_eq ! ( store. knowable_from( "ogit-op/Work" ) , Some ( v_work) ) ;
747+ assert_eq ! ( store. knowable_from( "ogit-op/WorkPackage" ) , Some ( v_pkg) ) ;
748+ }
749+
750+ #[ cfg( feature = "vart-backend" ) ]
751+ #[ test]
752+ fn vart_same_name_different_prefixes_do_not_collide ( ) {
753+ // The Codex P2 motivating case from PR #31 — same class name
754+ // (`WorkPackage`) under different OGIT prefixes must register
755+ // as distinct entries when the canonical identity differs.
756+ // VART-backed end-to-end through `register_class_knowable_from`.
757+ use ogar_vocab:: Class ;
758+ let store = crate :: vart_backend:: VartKnowableFromStore :: new ( ) ;
759+ let v_op = register_class_knowable_from (
760+ & Class :: new ( "WorkPackage" ) ,
761+ "ogit-op/WorkPackage" ,
762+ & store,
763+ ) . unwrap ( ) ;
764+ let v_erp = register_class_knowable_from (
765+ & Class :: new ( "WorkPackage" ) ,
766+ "ogit-erp/WorkPackage" ,
767+ & store,
768+ ) . unwrap ( ) ;
769+ assert_ne ! ( v_op, v_erp) ;
770+ assert_eq ! ( store. knowable_from( "ogit-op/WorkPackage" ) , Some ( v_op) ) ;
771+ assert_eq ! ( store. knowable_from( "ogit-erp/WorkPackage" ) , Some ( v_erp) ) ;
772+ }
551773}
0 commit comments