diff --git a/GEMINI.md b/GEMINI.md index 234ae72..a964871 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -92,13 +92,21 @@ If a schema originates in the `user` bucket, the validator does *not* rigidly re To support polymorphic API contracts (e.g., heterogeneous arrays of generic widgets) without manually writing massive `oneOf` blocks, JSPG provides the `$family` macro. While `$ref` defines rigid structure, `$family` relies on an abstract **Descendants Graph**. -During compilation, `jspg` temporarily tracks every `$ref` pointer globally to build a reverse-lookup graph of "Descendants". +During compilation, `jspg` temporarily tracks every `$ref` pointer globally to build a reverse-lookup graph of "Descendants". It also calculates the **Inheritance Depth** of every schema (how far removed it is from the root entity). When `{"$family": "widget"}` is encountered, JSPG: 1. Locates the `widget` schema in the Descendants graph. 2. Expands the macro by finding *every* schema in the entire database that structurally `$ref`s `widget`, directly or indirectly (e.g., `stock.widget`, an anonymous object, etc.). -3. Replaces the `$family` keyword with a standard `one_of` array containing `$ref`s to those discovered descendants. +3. Evaluates the incoming JSON against **every** descendant schema in that family *strictly*. + +If you request `{"$family": "light.widget"}`, it simply evaluates all schemas that `$ref` the generic abstract `light.widget` interface. + +#### E. Strict Matches & The Depth Heuristic +JSPG strictly enforces that polymorphic structures (`oneOf`, or a `$family` expansion) match **exactly one** valid schema permutation. It does not support fuzzy matching (`anyOf`). +If a JSON payload matches more than one schema in a union (which happens frequently due to implicit inheritance where an object might technically satisfy the requirements of both `entity` and `user`), JSPG automatically applies the **Depth Heuristic Tie-Breaker**: +* It looks up the pre-calculated Inheritance Depth for all valid passing candidates. +* It selects the candidate that is **deepest** in the inheritance tree (the most explicitly defined descendant). +* If multiple passing candidates tie at the exact same depth level, an `AMBIGUOUS` error is thrown, forcing the developer to supply a more precise type discriminator or payload. -If you request `{"$family": "light.widget"}`, it simply expands to all schemas that `$ref` the generic abstract `light.widget` interface. This cleanly separates **Database Physics** (derived from the Postgres `Types` bucket and viral `$ref` inheritance) from **Structural Polymorphism** (derived purely from the abstract `$ref` tree). ### 2. Strict by Default & Extensibility @@ -129,14 +137,19 @@ The extension is written in Rust using `pgrx` and structures its schema parser t * **Compiler Phase**: schema JSONs are parsed into this struct, linked (references resolved), and then compiled into an efficient validation tree. * **Validation Phase**: The compiled validators traverse the JSON instance using `serde_json::Value`. -### Concurrency & Threading ("Atomic Swap") +### Concurrency & Threading ("Immutable Graphs") -To support high-throughput validation while allowing for runtime schema updates (e.g., during development or hot-reloading), JSPG uses an **Atomic Swap** pattern. +To support high-throughput validation while allowing for runtime schema updates (e.g., during development or hot-reloading), JSPG uses an **Atomic Swap** pattern based on 100% immutable schemas. -1. **Immutable Validator**: The `Validator` struct immutably owns the `Registry`. Once created, a validator instance (and its registry) never changes. -2. **Global Pointer**: A global `RwLock>>` holds the current active validator. -3. **Lock-Free Reads**: Validation requests acquire a read lock just long enough to clone the `Arc` (incrementing a reference count), then release the lock immediately. Validation proceeds on the snapshot, ensuring no blocking during schema updates. -4. **Atomic Updates**: When schemas are reloaded (`cache_json_schemas`), a new `Registry` and `Validator` are built entirely on the stack. The global pointer is then atomically swapped to the new instance under a write lock. +1. **Parser Phase**: Schema JSONs are parsed into ordered `Schema` structs. +2. **Compiler Phase**: The database iterates all parsed schemas and pre-computes native optimization maps: + * **Descendants Map**: A reverse `$ref` lookup graph for instant `$family` resolution. + * **Depths Map**: The `$ref` lineage distance of every schema for heuristic tie-breaking. + * **Variations Map**: The Native Type inheritance hierarchy. +3. **Immutable Validator**: The `Validator` struct immutably owns the `Database` registry and all its global maps. Once created, a validator instance (and its registry) never changes. Schemas themselves are completely frozen; `$ref` strings are resolved dynamically at runtime using the pre-computed O(1) maps, eliminating the need to physically mutate or link pointers across structures. +4. **Global Pointer**: A global `RwLock>>` holds the current active validator. +5. **Lock-Free Reads**: Validation requests acquire a read lock just long enough to clone the `Arc` (incrementing a reference count), then release the lock immediately. Validation proceeds on the snapshot, ensuring no blocking during schema updates. +6. **Atomic Updates**: When schemas are reloaded (`cache_json_schemas`), a new `Registry` and `Validator` are built entirely on the stack. The global pointer is then atomically swapped to the new instance under a write lock. ## 🧪 Testing