SCHEMABOUND Documentation — Choose Your Path
Different roles need different entry points into this documentation. Select the persona that matches your responsibilities to jump directly to relevant material, or use the full table of contents below for complete coverage.
For Product Managers & Executives
Focus on business outcomes, cost governance models, and strategic adoption decisions. You don’t need to understand implementation details — you need to know what SCHEMABOUND does and why it matters.
- The Object Agent Mapping Paradigm — How OAM differs from traditional ORMs by mapping agent intent to data operations rather than simply mapping objects to relational rows
- System Architecture Overview — High-level view of where SCHEMABOUND sits between your application and your data layer
- AAU Billing Reference — Cost governance model: how Agentic Activity Units quantify governance cost per tool call (Read = 1x, Write = 3x, Governed = 5x)
- Demo — Executive narrated demo showing SCHEMABOUND in action
Key takeaway: SCHEMABOUND reduces governance costs by providing fine-grained audit trails and policy enforcement without requiring you to rewrite existing code. The Hybrid operating mode lets you integrate legacy databases incrementally while Code-First schemas give you full control over new development.
For Backend & DevSecOps Engineers
Focus on operational configuration, security hardening, deployment procedures, and audit trail verification. You need exact environment variables, dependency ordering, and production-ready patterns.
- Identity & Security Operations — mTLS setup, LDAP sync, JWT rotation, RBAC mapping, SCIM provisioning, session management, host hardening
- Runtime Operations — Audit logging configuration, self-healing infrastructure workers, event publishing pipeline
- Air-Gap Deployment — Production deployment in disconnected environments with hardware provisioning and remote attestation
- C4 Model Diagrams — Visual models showing how SCHEMABOUND gRPC Server, EventBus, Auto-Rewind workers, and Chain Integrity verification interact
Key takeaway: Every agent action is captured in a tamper-evident SHA-256 hash chain exported via MultiTransportExporter to SIEM platforms. Configure mTLS with schemabound-certgen, rotate JWT secrets without downtime using SCHEMABOUND_JWT_SECRETS, and rely on the self-healing infrastructure workers to maintain deployment integrity automatically.
For Application Developers
Focus on SDK integration, rapid prototyping, component usage, and API reference. You need working code examples, dependency installation commands, and clear method signatures.
- Tutorials — Rust, Python, .NET, TypeScript quick starts for spinning up your first SCHEMABOUND client
- How-To Guides — Inject runtime context headers, execute multi-step control plane workflows, manage declarative migrations with
sb-migrate - API Reference — Generated SDK documentation for all supported languages and protocols
Key takeaway: Install your language-specific SDK (schemabound, schemabound-python, Schemabound.Dotnet, or the TypeScript package), connect to the backend, and start writing queries. The LlmSchema derive macro generates JSON schemas automatically from your Rust structs; use ValidateQueryRequest for validation and ExecuteStepRequest for multi-step workflows.
Full Table of Contents
If you’re exploring SCHEMABOUND broadly or need coverage across all personas, use the complete navigation:
- Part 1: Concepts (Why) — Explanation for product managers, architects, and developers seeking to understand the “Why” before writing code
- Part 2: Tutorials (Learn by Doing) — Quick Start guides providing step-by-step onboarding experiences
- Part 3: How-To Guides (Solve a Problem) — Goal-oriented operational guides for competent engineers actively building systems
- Part 4: Identity & Security Operations (Hybrid) — Mixed explanation and how-to content for identity and security configuration
- Part 5: Runtime Operations (Hybrid) — Mixed explanation and how-to content for DevOps/SRE engineers running the system in production
- Part 6: Deployment & Infrastructure (How-To) — Operational guides for deploying SCHEMABOUND in production and air-gap environments
- Part 7: Technical Reference (Look Up Facts) — Narrative-free data dictionaries, API contracts, and trait definitions
- Glossary — Centralized terminology for domain-specific concepts
- Contributing — Contribution workflow, testing, and release procedures
Demo
Family Tree — AI-Narrated Demo (Developer)
The same end-to-end recording narrated for a software developer evaluating the SCHEMABOUND SDK. Highlights SchemaboundDeclarativeBase, _call_gemini + gRPC agent memory, and TestClient for unit testing.
Regenerate:
GEMINI_API_KEY=<key> make narrate DEMO=family-tree PERSONA=developerfrom the repository root.
| Scene | What to notice |
|---|---|
| Login | Entry point to the SCHEMABOUND Enterprise control plane |
| LDAP import | Directory data flows into Dolt — becomes the LLM’s context source |
| Empty hierarchy | Starting state before any agent action |
| LLM agent import | _call_gemini injects LDAP context → Gemini returns function_call parts → Person.from_agent_tool_call(**kwargs) writes to PostgreSQL; gRPC records each tool call |
| Agent memory | Every tool call stored as a structured, queryable event |
| Schema registration | SchemaboundDeclarativeBase.to_roam_schema() auto-generates the function-calling schema — zero manual schema code |
Family Tree — AI-Narrated Demo (Executive)
The same recording narrated for a business executive evaluating SCHEMABOUND. Focuses on cost avoidance, always-current documentation, and SCHEMABOUND’s Data-First / Code-First / Hybrid operating modes — no full rewrite required.
Regenerate:
GEMINI_API_KEY=<key> make narrate DEMO=family-tree PERSONA=executivefrom the repository root.
| Scene | Business outcome |
|---|---|
| Login | Unified control plane — one place for identity, data, and AI governance |
| LDAP import | Existing directory assets re-used instantly; no data migration project |
| Empty hierarchy | Baseline state demonstrating clean-slate adoption |
| LLM agent populates hierarchy | AI-driven data work completed in seconds, not sprint cycles |
| Agent memory | Leadership-visible evidence of AI activity — no developer report required |
| Schema registration | Marketing and API documentation self-updating; collateral never goes stale |
Family Tree — AI-Narrated Demo (DevSecOps)
The same recording narrated for a DevSecOps engineer or security auditor. Covers session tracking, structured audit logging via gRPC, RBAC enforcement at the data layer, and the security observability that SCHEMABOUND Enterprise provides out of the box.
Regenerate:
GEMINI_API_KEY=<key> make narrate DEMO=family-tree PERSONA=devsecopsfrom the repository root.
| Scene | Security / audit focus |
|---|---|
| Login | Identity source is LDAP-backed; session ID assigned at login |
| LDAP import | Directory data validated and ingested before any agent can act on it — least-privilege at the data layer |
| Empty hierarchy | Auditable baseline state captured before agent execution |
| LLM agent populates hierarchy | Every function_call → tool call recorded in agent memory via gRPC in the same transaction as the DB write — no dual-write gap |
| Agent memory | Structured, queryable audit log: session ID, tool name, arguments, timestamp — SIEM-ready |
| Schema registration | Schema locked to the registered model; agent cannot call tools outside the declared interface |
Model-View-Agent Intent (MVAI)
Object Agent Mapping — is the framework for giving agents, services, and automation structured and policy-aware access to data. Where an ORM maps application objects to relational rows, OAM maps agent intent to data operations — controlling how an agent discovers, accesses, and works with data while enforcing those rules consistently across languages and deployment patterns.
SCHEMABOUND’s Model-View-Agent Intent (MVAI) architecture treats application state as a content-addressed Merkle DAG of RDF triples — governed by Object Agent Mapping’s zero-trust identity gateway. Every change produces a new root hash; agents generate migrations from natural language intent via quantized LLMs on edge devices. Your data model is your API contract, and every predicate URI is verified through OAM’s governance cost model before reaching the view layer.
SCHEMABOUND is the runtime that implements MVAI. It provides identity-aware execution, policy enforcement, and agent-ready context across application and service boundaries — combining RDF content-addressed state with OAM’s identity-aware data gateway into a single framework for agent-native development.
Schema Modes (OAM)
OAM defines three operating modes that determine how an agent interacts with data:
| Mode | Description | Access |
|---|---|---|
| Data-First | The agent discovers the database schema at runtime through introspection. No application model registration required. Best for exploring legacy or external databases. | Read-only |
| Code-First | Only tables explicitly registered by the application are accessible. The application controls validation and access rules. Best when the codebase owns the data. | Read-write |
| Hybrid | Registered models take precedence; unknown tables fall back to introspection. Provides coverage without sacrificing safety where code coverage ends. | Read-only |
Choose Data-First when agents need to explore data without committing to a code model. Choose Code-First when your application owns the data and must enforce validation rules. Use Hybrid when your codebase covers some tables but you still want introspection for the rest.
What This Book Covers
- Architecture explains how SCHEMABOUND fits into application, service, and event-driven systems.
- Runtime Context explains how request metadata and runtime augmentation travel with execution.
- Contributing explains how to propose changes to the public runtime, SDKs, and documentation.
- SDK Guides help you choose the best starting point for Python and .NET integrations.
Where SCHEMABOUND Fits
SCHEMABOUND is designed for teams that want to:
- add policy-aware execution to application and service workflows
- carry stable identity and organization context through runtime operations
- integrate agent-driven or automation-driven behavior without rewriting existing systems
- standardize public integration contracts across multiple languages
- capture a tamper-evident audit trail of every agent action for SIEM and AI-SPM ingestion
- detect and respond to prompt injection, jailbreak attempts, and adversarial inputs at the gateway
Operating Patterns
SCHEMABOUND typically appears in one of two patterns:
- Application-intercepted flows where SCHEMABOUND validates and enriches requests as they move through an API or service boundary.
- Event-driven flows where SCHEMABOUND observes or participates in runtime decisions driven by messages, RPC calls, or automation pipelines.
Quick Links
- schemabound-public for the public Rust core and shared runtime contract
- schemabound-python for Python integrations and automation workflows
- schemabound-dotnet for .NET services and typed enterprise integrations
Suggested Starting Path
- Start with Architecture Overview to understand the public runtime model.
- Read Runtime Context if you need request metadata and runtime-augmentation guidance.
- Choose your SDK: Python or .NET.
- Use API Reference when you are ready for package and protocol details.
Architecture Overview
SCHEMABOUND is designed as a public runtime layer that sits close to execution boundaries. It helps products and services carry identity, policy, and agent-aware context through application logic without forcing teams to redesign the rest of their stack.
The Object Agent Mapping Model
OAM gives the architecture a stable contract for how agents interact with data. Instead of giving an agent direct or unrestricted database access, OAM sits between the agent and the data layer and mediates what the agent can see and do based on the active schema mode.
Schema mode is selected at agent registration time and governs the entire session:
- In Data-First mode, the runtime introspects the live database so the agent can discover and query data without a pre-defined application model. Access is read-only.
- In Code-First mode, only explicitly registered application models are accessible. The application controls validation and data access rules, enabling safe read-write operations.
- In Hybrid mode, registered models take precedence for known tables and the runtime falls back to introspection for everything else. Access is read-only where code coverage ends.
All three modes carry identity and runtime context through the execution path so agent queries stay aligned with organizational policy regardless of which mode is active.
Public Runtime At A Glance
The public SCHEMABOUND surface is organized around three adoption layers:
- Core runtime for shared execution, reflection, and protocol behavior.
- Language SDKs for integrating SCHEMABOUND into Python and .NET applications.
- Shared protocol definitions for teams that need language-neutral contracts or generated bindings.
Public Building Blocks
Runtime Model
The runtime model gives SCHEMABOUND a consistent way to:
- interpret structured requests and tool-facing operations
- apply identity and organization context to execution
- attach runtime augmentation metadata before validation and execution
- emit audit-safe events and observable outcomes
The runtime exposes typed error enums — not boxed trait objects — at every public boundary. Each
operation returns a specific error type (MapperError, ExecutorError, EngineError,
ControlPlaneError, and others) that maps cleanly to gRPC status codes and propagates with ?
in Rust call sites. See the Rust SDK error handling reference
for the full type registry.
SDK Layer
The Python and .NET SDKs package that runtime model into language-specific integration surfaces. They are the fastest path for teams that want to add SCHEMABOUND to existing products, services, and automation workflows.
Shared Contract
When teams need multi-language interoperability, generated clients, or direct protocol-level integration, the shared protobuf and gRPC contract provides the stable public boundary.
Where SCHEMABOUND Fits In The Stack
SCHEMABOUND usually appears in one of these roles:
- Request-path integration where a service or API validates and enriches execution context before work continues.
- Runtime coordination where a client or middleware layer passes identity, tool, and organization context into downstream execution.
- Event-driven integration where SCHEMABOUND participates in decisions triggered by messages, jobs, or automation pipelines.
Adoption Paths
Start With An SDK
Use an SDK when you want to move quickly inside an application stack. This is the best fit for:
- service teams integrating SCHEMABOUND into existing APIs
- platform teams standardizing runtime context across applications
- automation teams building product or operations workflows
Start With The Shared Contract
Use the protocol definitions when you want to:
- generate your own client bindings
- align multiple services around one public contract
- integrate from a language or platform that does not yet have a first-party SDK
Operating Modes
SCHEMABOUND supports both user-driven and event-driven execution patterns.
Application-Driven Mode
In application-driven flows, a user or upstream service initiates the request. SCHEMABOUND enriches or validates that request as it moves through an application or API boundary.
Event-Driven Mode
In event-driven flows, SCHEMABOUND participates when a message, RPC call, or background job creates a decision point that needs shared runtime context or policy-aware behavior.
Agent Memory
Agent Memory gives SCHEMABOUND sessions an isolated, auditable memory store. Each session accumulates its own history of observations, tool calls, and decisions — scoped so that one session never reads or contaminates another, and past context can always be retrieved exactly as it was recorded.
What it provides:
- Per-session isolation — memory written during one session is never visible to another
- Chronological history — entries are ordered and retrievable in the sequence they were recorded
- Reproducibility — prior agent observations, tool call results, and decisions remain accessible across the full lifetime of a session
- Prompt augmentation — session memory can be injected automatically into prompt hooks so agents carry prior context without the caller managing it manually
See Prompt Augmentation Guide for operational details on injecting {{memory_context}} into your workflows.
System Architecture — C4 Model
Visual models organized by zoom level, following the C4 model (Context → Containers → Components → Code). Each level targets a different stakeholder persona and abstracts away implementation details progressively as you zoom in.
Level 1: Context (Business Stakeholders & Product Managers)
Shows how SCHEMABOUND fits into your broader ecosystem — external systems, identity providers, and data layers. This is the “elevator pitch” architecture diagram that explains what SCHEMABOUND connects to without revealing internal implementation.
graph LR
LLM["LLM<br>Agent"] --> SB["SCHEMABOUND<br>Middleware"]
SB --> IdP["Identity<br>Provider (BYOI)"]
SB --> DB["Data<br>Layer"]
SB --> SIEM["SIEM<br>Platform"]
What it shows at a glance: SCHEMABOUND sits between your LLM Agent and four external systems — identity providers (BYOI), data stores, audit platforms, and telemetry backends. No internal implementation details are exposed at this level; those appear in the Container diagram below.
Level 2: Container (Frontend/Backend Developers & DevOps)
Illustrates the main boundaries and high-level data flow within SCHEMABOUND itself. This level is relevant when you’re configuring deployment, tuning workers, or integrating with observability platforms.
graph TB
LLM["LLM<br>Agent"] --> gRPC["gRPC Server<br/>(:50051)"]
IdP["Identity<br>Provider"] -->|"JWT/OIDC"| gRPC
DB["Database"] -->|"Query results"| gRPC
gRPC --> EB["Global<br>EventBus"]
EB --> W["Data Resilience Workers<br/>Auto-Rewind + Chain Integrity"]
Key boundaries:
- gRPC Server accepts ExecuteStepRequest and SubmitPlanRequest from LLM Agents; validates JWT/OIDC tokens via BYOI identity layer
- Global EventBus is the typed event bus that fans out
LlmToolCallAuditRecordedevents to all registered handlers (audit log, query metrics, session activity) - Data Resilience Workers run in background: Auto-Rewind handles schema topology refresh and historical replay; Chain Integrity verifies hash-chain anchor depths
Level 3: Component (Backend Engineers)
Maps internal structures and responsibilities within the SCHEMABOUND runtime. This level is relevant when you’re implementing custom handlers, extending the injection guard, or debugging query execution paths.
graph LR
IG["Injection<br>Guard"] --> LM["LocalMapper<br>(SQLite)"]
RC["Runtime Context"] --> TC["Tool Contract Policy Engine"]
TC -->|"Intent: read/write/admin"| LM
TC --> TM["TcpMapper<br>(Remote TCP/JSON-RPC)"]
LM -->|QueryExecuted| AL["AuditLogHandler<br>(OCSF v1.1)"]
TM -->|QueryExecuted| QM["QueryMetricsHandler<br>(AAU tracking)"]
TC --> SA["SessionActivityHandler<br>(per-session memory)"]
Component responsibilities:
- Injection Guard filters traffic against regex pattern libraries (
jailbreak_token,sql_template_injection) before the request reaches mappers; policy controlled bySCHEMABOUND_INJECTION_POLICY(observe vs. block) - LocalMapper/ TcpMapper execute queries via SQLite or remote TCP transport with configurable timeouts (
DEFAULT_TIMEOUT_SECONDS = 30s) - Tool Contract Policy Engine classifies intent (Read=1x, Write=3x, Governed=5x AAU weight) and enforces governance rules per tool call
- Chain of Responsibility handlers receive
QueryExecutedevents: AuditLogHandler produces OCSF v1.1 records, QueryMetricsHandler tracks AAU totals, SessionActivityHandler captures per-session memory entries
Level 4: Code (Core Contributors)
Reserved for core contributors seeking exact implementation details — class hierarchies, trait implementations, and payload structures inside the sb-migrate compilation path. This level uses UML-style diagrams to show how domain objects transform into migration plan steps.
classDiagram
TableSnapshot --> MigrationPlan : diff_snapshots()
MigrationPlan *-- MigrationStep : contains
MigrationParser --> MigrationPlan : parses
class TableSnapshot {
+columns: Vec<ColumnDef>
+indexes: Vec<IndexDef>
+diff_against(other) MigrationPlan
}
class MigrationPlan {
+id: String
+direction: MigrationDirection
+steps: Vec<MigrationStep>
+serialize() JSON
}
class MigrationStep {
+action: StepAction
+table_name: String
+sql_dialect: DatabaseDialect
}
Key transformations:
TableSnapshotis produced by the MirrorProvider during schema introspection; contains columns, indexes, unique constraints, and field mappings with ORM convention detection (Hibernate vs EntityFramework)diff_snapshots()computes aMigrationPlancontaining orderedMigrationSteppayloads tagged withDatabaseDialect(PostgreSQL, MySQL, SQLite, Dolt)MigrationParserreads JSON from stdin via the sb-migrate binary protocol, validates dependency acyclicity, and emits typed migration steps for execution
Using These Diagrams in Documentation
When referencing SCHEMABOUND architecture in backlog issues or PR descriptions:
- Use Level 1 diagrams when explaining to non-engineers what SCHEMABOUND connects to
- Use Level 2 diagrams when configuring deployment, workers, or observability integration
- Use Level 3 diagrams when implementing custom handlers or debugging query paths
- Use Level 4 diagrams only in core contributor documentation — never expose this level in user-facing docs
Render these diagrams locally with mdbook (Mermaid is configured via mdbook-mermaid preprocessor) or paste into any Mermaid-compatible viewer.
Identity And BYOI
SCHEMABOUND follows a Bring Your Own Identity approach so teams can integrate with the identity systems they already trust instead of recreating users, roles, and organization structure from scratch.
What BYOI Looks Like In Practice
With BYOI, SCHEMABOUND aligns runtime behavior with your existing identity model by mapping external identity information into the public execution context.
That usually means carrying forward:
- organization or tenant boundaries
- user and service identity
- role or permission context
- capability or scope information that affects execution decisions
Why This Matters
Identity-aware execution helps teams:
- keep SCHEMABOUND aligned with existing access-control boundaries
- preserve organizational context across application and service calls
- reduce drift between product identity and runtime behavior
- support agent and automation workflows without inventing a parallel permission system
Common Identity Sources
SCHEMABOUND is well suited to identity models that originate from systems such as:
- enterprise directory providers
- source-control and collaboration platforms
- service-owned role and entitlement systems
- data-layer roles or scope definitions
The exact integration path can vary, but the goal stays the same: keep runtime decisions grounded in the identity model your organization already operates.
Identity In The Execution Path
Identity becomes most useful when it arrives with the request itself. In practice, that means SCHEMABOUND can use identity context to:
- interpret which organization or tenant owns the request
- understand which actor initiated the work
- choose the right runtime augmentation or policy path
- emit more meaningful, audit-safe runtime events
Integration Guidance
The best BYOI integrations keep identity signals stable, explicit, and close to the request boundary.
Start by identifying:
- which system is the source of truth for identity
- which parts of that identity must influence runtime decisions
- which fields need to travel through the public SCHEMABOUND headers or protocol surface
From there, use SCHEMABOUND to preserve that context consistently across clients, services, and execution paths.
RDF as Application State
The Paradigm Shift
Traditional architectures force you to choose between endpoint-heavy REST APIs or complex gRPC schemas. Schemabound eliminates that choice entirely with MVI Agent-Native Architecture (MVAI) — where your data model IS your API contract, and agents (including quantized LLMs on smart glasses NPUs) can generate UI changes from natural language intent without a single endpoint definition.
REST solved the problem of distributed state access in 2004. gRPC solved it better in 2016. Both are still solving the same problem: how do I call remote code that touches my data? MVAI replaces both with something fundamentally different — data as the API contract, content-addressed via an RDF prolly tree, governed by Object Agent Mapping’s zero-trust identity gateway.
Why RDF Triples Replace REST Endpoints
The MVI Pattern Conceptually
In traditional MVC frameworks (Vue, React, Angular), state flows through components and props. In SCHEMABOUND’s MVAI:
- Model: Your application state is a Merkle DAG of content-addressed RDF triples stored in the triplestore. Every change produces a new root hash — versionless evolution with cryptographic integrity by construction.
- View: UI components (static-view, mutable-form) render directly from triplestore queries through OAM’s identity-context-enriched query pipeline — no API calls, no fetch(), no REST endpoints between state and rendering.
- Intent: Natural language descriptions (“Add notifications center”, “Dark mode toggle for settings”) become migration scripts that transform the triplestore atomically, verified by Merkle DAG proofs before any change is applied.
How It Works (No Code Required)
- User describes desired state in natural language — to an agent running on-device
- Quantized model generates a TTL migration script describing subject-predicate-object triples to add or modify — verified against the triplestore’s current Merkle root
- Triplestore applies transformation atomically — every predicate URI is resolved through OAM’s governance cost model; high-complexity migrations trigger HITL approval via Query Complexity Score
- UI updates automatically via reactive subscriptions to triplestore state — Merkle DAG integrity proofs ensure the rendered view is cryptographically consistent with the stored state
Every change is verifiable, atomic, and rollable back to any prior verified root hash. There is no schema versioning because there are no schemas to version — only predicate URIs that grow monotonically through the prolly tree.
Example: Building a Notifications Center
Traditional approach: Define REST endpoints (GET /notifications, POST /notifications, PATCH /notifications/{id}), write serialization code for each response shape, handle schema drift when notification types evolve, test edge cases around pagination and filtering, maintain API contract documentation across teams.
MVAI approach: Describe the desired notifications state in natural language. The agent generates a migration that adds predicate URIs for notification types (severity, priority routing rules, delivery channels) directly into the triplestore. UI components reactively render from those predicates — no endpoints defined, no schema versioned, no API contract maintained. Every change is content-addressed and verifiable through OAM’s zero-trust identity gateway before it reaches the view layer.
The difference isn’t incremental improvement. REST and gRPC are 20-year-old paradigms that scale poorly to agent-native development because they require an interface layer between intent and state. MVAI eliminates that layer entirely — data is the contract, and agents consume it directly through predicate URIs that any model understands without documentation.
Comparison with Other Approaches
| Feature | REST APIs | gRPC | MCP Frameworks | Traditional ORMs | MVAI (Schemabound) |
|---|---|---|---|---|---|
| Schema Drift Protection | Manual versioning | Partial (protobuf) | Partial (schema files) | ❌ | ✅ Merkle DAG proofs by construction |
| Agent-Native Development | ❌ Requires endpoint docs | ❌ Requires IDL/protos | Partial (still needs contracts) | ❌ Code-bound only | ✅ Intent → triples directly via quantized LLMs |
| On-Device LLM Inference | ❌ Cloud-dependent APIs | ❌ Network-required | ❌ Cloud-dependent | ❌ N/A | ✅ Qualcomm QRD560 @ 38 tok/s sustained |
| Security Filtering | Manual middleware | Manual auth + mTLS | Partial (application layer) | Application-layer only | ✅ By construction at OAM gateway |
| Content-Addressed State | No | No | No | No | ✅ Prolly tree with Merkle roots |
| Reactive UI Rendering | Polling / WebSockets | Streaming RPCs | N/A | Component state | ✅ Direct triplestore subscriptions via OAM queries |
The Bigger Picture
MVAI isn’t just an architectural pattern — it’s a fundamental rethinking of how software is built. When your data model IS your API contract, you eliminate:
- Endpoint documentation and maintenance across version boundaries
- Schema versioning hell with protobuf or OpenAPI specs
- API contract testing across teams (the triplestore schema is the test)
- Security vulnerabilities from manual filtering (OAM enforces governance cost by construction at the data gateway)
And you gain:
- Agents that understand your application through its data model alone — no endpoint docs required, just predicate URIs
- Natural language UI modification via quantized models on edge devices (<2 second end-to-end migration generation)
- Cryptographic integrity proofs for every state transformation (Merkle DAG via the RDF prolly tree)
- Versionless evolution — new predicates don’t break old consumers because there’s no version boundary, only monotonic growth through content-addressed storage
Getting Started with MVAI
- Read RDF Agent-Native Intent-to-Code Migrations for the full technical deep-dive on intent-to-migration pipeline implementation
- Explore the OAM Identity Context documentation to understand how agent identity flows through triplestore queries
- Review Control Plane Workflows for multi-step migration planning and step dependency graphs
MVAI targets production deployment on smart glasses NPUs in Q4 2026 — quantized models verified at 38 tok/s sustained, Merkle DAG verification at >10k ops/sec, end-to-end migrations under 2 seconds. This is proven on hardware, not theoretical.
Rust Integration Tutorial
This tutorial walks you through adding SCHEMABOUND to a Rust project and setting up schema introspection with policy enforcement. By the end, you will have a running instance that can introspect SQLite databases and validate queries against your tool contracts.
Installation
Add schemabound to your Cargo.toml:
[dependencies]
schemabound = "0.6"
tokio = { version = "1", features = ["full"] }
async-trait = "0.1"
Introspect a SQLite Database
The fastest way to verify your SCHEMABOUND integration is working:
#![allow(unused)]
fn main() {
use schemabound::mirror::introspect_sqlite_path;
let schema = introspect_sqlite_path("path/to/database.db")?;
for table in &schema.tables {
println!("Table: {}", table.name);
for col in &table.columns {
println!(" Column: {} ({}){}", col.name, col.sql_type,
if col.primary_key { " PK" } else { "" });
}
for trigger in &table.triggers {
println!(" Trigger: {} {} {}", trigger.name, trigger.timing, trigger.event);
}
}
println!("UDTs: {}", schema.user_defined_types.len());
}
Introspect a SQLite Database
#![allow(unused)]
fn main() {
use schemabound::mirror::introspect_sqlite_path;
let schema = introspect_sqlite_path("path/to/database.db")?;
for table in &schema.tables {
println!("Table: {}", table.name);
for col in &table.columns {
println!(" Column: {} ({}){}", col.name, col.sql_type,
if col.primary_key { " PK" } else { "" });
}
for trigger in &table.triggers {
println!(" Trigger: {} {} {}", trigger.name, trigger.timing, trigger.event);
}
}
println!("UDTs: {}", schema.user_defined_types.len());
}
Use The MirrorProvider Trait
MirrorProvider is an async trait for pluggable schema introspection:
#![allow(unused)]
fn main() {
use schemabound::{MirrorProvider, SqliteMirrorProvider};
let provider = SqliteMirrorProvider::new("database.db");
let schema = provider.introspect_schema().await?;
}
Implement the trait to integrate custom databases:
#![allow(unused)]
fn main() {
use schemabound::MirrorProvider;
use schemabound::mirror::SchemaModel;
struct MyCustomProvider;
#[async_trait::async_trait]
impl MirrorProvider for MyCustomProvider {
async fn introspect_schema(&self) -> Result<SchemaModel, String> {
// custom introspection logic
Ok(SchemaModel { tables: vec![], user_defined_types: vec![] })
}
}
}
Register a CoR Handler
Handlers are invoked in registration order for every dispatched event. Use the built-in collection from schemabound::handlers or implement EventHandler yourself:
#![allow(unused)]
fn main() {
use schemabound::{get_event_bus, AuditLogHandler, DefaultHandlerChain, SharedHandler};
use std::sync::Arc;
let chain = DefaultHandlerChain::new();
let bus = get_event_bus();
bus.register_handler(Box::new(AuditLogHandler))?;
bus.register_handler(Box::new(SharedHandler(Arc::clone(&chain.query_metrics))))?;
bus.register_handler(Box::new(SharedHandler(Arc::clone(&chain.session_activity))))?;
let snap = chain.query_metrics.snapshot();
let sessions = chain.session_activity.session_count();
}
Subscribe to Events (fire-and-forget)
#![allow(unused)]
fn main() {
use schemabound::interceptor::{get_event_bus, Event};
let bus = get_event_bus();
let id = bus.register_subscriber(Box::new(|event: &Event| {
println!("Event: {}", event.event_type());
}))?;
bus.unregister_subscriber(id)?;
}
Apply a Policy to a Query
#![allow(unused)]
fn main() {
use schemabound::executor::{QueryServiceImpl, ValidateQueryRequest};
use schemabound::policy_engine::{
AuthorizationContext, AuthorizedSubqueryShape, PolicyContext, SubqueryPolicy, ToolContract, ToolIntent,
};
let mut service = QueryServiceImpl::new();
service.set_db_path("database.db")?;
let request = ValidateQueryRequest {
db_identifier: "db".into(),
query: "SELECT id FROM users WHERE org_id IN (SELECT id FROM organizations)".into(),
parameters: Default::default(),
};
let policy = PolicyContext {
tool: ToolContract {
name: "list-users".into(),
intent: ToolIntent::ReadSelect,
subquery_policy: SubqueryPolicy::AllowListed(vec![
AuthorizedSubqueryShape { table: "organizations".into() },
]),
},
authorization: AuthorizationContext {
allowed_intents: vec![ToolIntent::ReadSelect],
grants: vec!["tool:users.read".into()],
},
};
let response = service.validate_query_with_policy(request, policy).await?;
assert!(response.valid);
}
Next Steps
- Internal SDK Reference: The complete crate structure, error handling, and gRPC proto mapping are documented in the internal reference (
docs/internal/rust-sdk-reference.md). - Event Pipeline: See Event Pipeline architecture guide for the full handler reference.
- Contributing: See Contribution Workflow for the TDD contract.
Python Automation Tutorial
This tutorial walks you through installing schemabound-python and executing your first database introspection using the declarative base pattern.
Installation
pip install schemabound-python
Connect to SCHEMABOUND Backend
from schemabound_sdk import SchemaboundClient, SchemaboundDeclarativeBase
# Connect to SCHEMABOUND backend
client = SchemaboundClient(uri="localhost:50051")
client.ping() # Verify connectivity
# Use as a SQLAlchemy declarative base for schema introspection
class User(SchemaboundDeclarativeBase):
__tablename__ = "users"
This is the fastest path when you want to stand up a Python-based integration, validate connectivity, and start building application logic around SCHEMABOUND.
Next Steps
- Reference: See Python SDK Reference for full API surface, method signatures, and runtime augmentation headers.
- Contributing: See Contribution Workflow for the TDD contract.
Runtime Context
Runtime context is how SCHEMABOUND keeps execution grounded in the real application state that surrounds a request. It gives clients and services a public way to attach stable metadata before validation and execution begin.
Why Runtime Context Matters
Runtime context helps SCHEMABOUND answer practical questions such as:
- which tool or product surface initiated this request
- which user or organization the request belongs to
- which domain tags or table scopes matter for this execution
- which runtime augmentation should be applied before downstream work continues
Without that context, a request may still be valid at the protocol level but incomplete from a product and governance perspective.
Runtime Augmentation
Runtime augmentation is the public mechanism for selecting additional execution context before a request is evaluated.
Clients can use:
x-schemabound-runtime-augmentation-idwhen they already know the specific augmentation identifierx-schemabound-runtime-augmentation-keywhen they want to reference a stable application-facing key
Additional headers help SCHEMABOUND match the right augmentation and preserve the meaning of the request:
x-schemabound-tool-namex-schemabound-tool-intentx-schemabound-user-idx-schemabound-organization-idx-schemabound-domain-tagsx-schemabound-table-names
Distributed Tracing Headers
Two additional headers carry W3C trace-context identifiers for distributed tracing
correlation. When present, these values are propagated into every AuditEventEnvelope
and appear as metadata.trace_uid and metadata.span_uid in every OCSF audit record.
| Header | Purpose |
|---|---|
x-schemabound-trace-id | W3C trace ID — identifies the distributed trace this request belongs to |
x-schemabound-span-id | W3C span ID — identifies the current operation within the trace |
Send these headers when your upstream instrumentation (OpenTelemetry, Jaeger, or similar) already generates trace context. SCHEMABOUND will propagate them through execution events so audit records and OTLP spans can be joined in your observability platform.
What Clients Should Send
Send the smallest stable set of metadata that explains why the request exists and which business boundary it belongs to.
Good examples include:
- the name of the calling product surface or tool
- the user or service identity associated with the request
- the tenant or organization boundary
- domain tags that explain business meaning
- table or resource hints when the execution path depends on them
What SCHEMABOUND Emits
SCHEMABOUND emits resolved augmentation identity into normal query and runtime events so downstream systems can observe which public context was selected.
Sensitive rendered content is intentionally separated from generic event metadata and reserved for dedicated audit handling.
Integration Guidance
Use runtime context when you want SCHEMABOUND behavior to stay aligned with application intent rather than just raw transport details.
Typical uses include:
- attaching product identity in a multi-surface application
- carrying organization context through service-to-service calls
- selecting augmentation rules for automation or assistant workflows
- keeping audit and observability signals consistent across clients
Control Plane Workflows — Conceptual Guide
TL;DR — The control plane gives SCHEMABOUND a structured way to track and execute multi-step agent workflows. Instead of issuing tool calls one at a time with no shared state, clients submit complete plans and drive execution step by step. Each step returns an
LlmContextUpdatethat carries tool output and schema changes back to the LLM for the next invocation.Use this page if you need to: understand why stateful workflows exist, learn how plans/steps/LLM context updates work, or decide whether to adopt the control plane for your use case.
The control plane gives SCHEMABOUND a structured way to track and execute multi-step agent workflows. Instead of issuing tool calls one at a time with no shared state, a client can submit a complete plan and drive execution step by step, receiving a feedback object after each step that carries tool output and any schema changes back to the LLM for the next invocation.
Why a Control Plane
A single tool call is stateless. The agent asks a question, SCHEMABOUND answers it, and the conversation moves on. Most production workflows are not that simple — they involve dependent queries, operations that discover schema at runtime, and decisions that build on earlier results.
Without a control plane:
- the LLM must track intermediate state itself, which is unreliable across long conversations
- schema changes that occur mid-workflow reach the LLM late or not at all
- there is no stable record of what the agent intended versus what actually executed
The control plane solves this by making the plan a first-class object that persists through the full execution lifecycle.
Concepts
Plan
A plan is a named, versioned collection of steps with explicit dependency relationships. Steps declare their dependencies with depends_on; the control plane validates the dependency graph before any step executes and rejects cycles.
A submitted plan is assigned a stable plan_id that clients use for all subsequent operations.
Step
Each step in a plan corresponds to one tool call. A step carries:
- a
tool_nameandtool_intentthat govern policy evaluation - a
query_templatethat may reference prior step output via the{{step.<id>.output}}syntax schema_table_hintsthat tell the control plane which tables to snapshot for schema diffing- a
depends_onlist naming steps that must complete before this step can execute
LLM Context Update
When a step finishes, the control plane returns an LlmContextUpdate alongside the step result. This is the explicit feedback object the SDK passes to the next LLM API call.
It carries:
tool_output_json— the serialised result of the step, ready to include in the next messageschema_additions— a list ofSchemaTableDeltaentries (NEW, MODIFIED, or REMOVED) for any tables named inschema_table_hintsthat changed during step executionaugmentation_hints— human-readable strings derived from schema deltas, ready to append to the system prompt
The LLM always sees the current schema state before choosing its next action, which means tool definitions stay accurate even when schema evolves mid-workflow.
Template Substitution
Query templates support {{step.<id>.output}} placeholders. The control plane resolves these server-side before calling the query service — the LLM does not need to construct final SQL or query strings directly.
For example, a step template like:
SELECT * FROM orders WHERE customer_id = {{step.lookup_customer.output}}
becomes a fully resolved query once the lookup_customer step has completed and its output is available.
Runtime Context Headers for Control Plane
Steps execute under the same gRPC metadata model as ordinary queries. Two additional headers carry control-plane identity:
| Header | Purpose |
|---|---|
x-schemabound-plan-id | Identifies the active plan for audit and event correlation |
x-schemabound-step-index | Position of the executing step within the plan |
These are emitted into query events alongside the standard session, user, and organization fields.
Event Integration
Control-plane events flow through the same global EventBus used by the rest of the runtime. Four new event variants are available to handlers:
| Event | When emitted |
|---|---|
PlanCreated | Plan accepted and persisted |
PlanStepExecuted | A step finished (success or failure) |
PlanCompleted | All steps completed successfully |
PlanFailed | A step failed or the plan was cancelled |
Existing AuditLogHandler, QueryMetricsHandler, and SessionActivityHandler receive these events the same way they receive query events — no changes to handler registration are needed.
Schema Topology Integration (Enterprise)
When executing plan steps, the control plane consults schema topology information for foreign-key cascade awareness. This ensures that operations affecting referenced tables trigger appropriate cascade actions across dependent objects without manual orchestration. The topology graph is maintained by a background introspection binary and consulted during step execution to determine safe ordering of related operations.
Reference
For gRPC proto definitions, REST API endpoints, and the WorkflowOrchestrator trait contract, see Control Plane Reference.
Migration Management — Declarative DDL Revision Control for SCHEMABOUND
SCHEMABOUND ships a cross-language migration library that generates and applies DDL changes via a single Rust binary called sb-migrate. Every SDK (Rust, Python, .NET, TypeScript/Node.js) wraps this binary as a subprocess — no external migration tooling or database-specific engines required.
Why Declarative Migrations?
Traditional migrations are imperative: you write the exact SQL to apply and reverse each change by hand. This works until your schema has grown into dozens of tables with interdependent indexes, foreign keys, and constraints. At that point:
- Manual reversal becomes error-prone.
- Adding a column in one environment but forgetting to update it elsewhere causes drift.
- Schema diffs between environments are hard to audit or reproduce.
Declarative migrations flip the model: you describe what schema state you want, and sb-migrate computes the how. The same algorithm runs on every SDK — Python generates exactly the same SQL as .NET for identical snapshots.
Supported Dialects
| Dialect | CLI alias | Notes |
|---|---|---|
| MySQL | mysql | Default, full ALTER COLUMN support |
| PostgreSQL | postgres | Uses $1 parameter placeholders |
| SQLite | sqlite | Used in tests/dev; limited ALTER COLUMN |
| Dolt | dolt | MySQL wire protocol compatibility layer |
The Binary: sb-migrate
The binary is built from the schemabound-migrations Rust crate. It accepts two invocation modes:
Clap Subcommands (Direct Use)
# Initialize the migration tracking table
./target/debug/sb-migrate init --dialect mysql
# Compute a diff between two schema files
./target/debug/sb-migrate diff --from current.json --to desired.json
# Apply a migration plan from JSON
./target/debug/sb-migrate apply --sql-file plan.json
JSON Stdin Protocol (SDK Use)
Each SDK encodes commands as JSON and writes them to stdin. The binary returns structured JSON on stdout:
echo '{"command":"init","dialect":"mysql"}' | sb-migrate
# → {"status":"success","code":0,"data":{"sql":"CREATE TABLE ..."}}
Invalid input or runtime errors produce a structured {"status":"error","code":N,"message":"..."} response with exit code 1.
Architecture
┌──────────────┐ JSON stdin ┌─────────────┐
│ Python SDK │ ──────────────────▶ │ sb-migrate │
│ .NET SDK │ │ (Rust crate) │
│ TypeScript │ │ │
│ Rust │ ◀────────────────── │ diff_snapshots()
│ │ JSON stdout │ plan_to_sql()
└──────────────┘ └─────────────┘
The binary is a thin orchestration layer. All schema analysis runs in the schemabound-migrations library:
- Dialect registry — pluggable type mappers, parameter styles, and SQL generators per dialect.
- Diff engine — compares
TableSnapshots to produce aMigrationPlanwith typed steps (CreateTable, DropColumn, RenameColumn, etc.). - Reverser — inverts plans for down migrations; falls back gracefully when reversal requires schema history.
- SQL generator — renders each step into dialect-specific SQL using the registered type mapper.
Migration Tracking Table
Each database that SCHEMABOUND manages carries a single tracking table (default schemabound_migrations) with columns:
CREATE TABLE IF NOT EXISTS schemabound_migrations (
revision VARCHAR(64) PRIMARY KEY,
name VARCHAR(255) NOT NULL DEFAULT '',
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
rollback_sql TEXT,
checksum VARCHAR(64) NOT NULL DEFAULT ''
);
The table name is configurable per dialect via --table-name-override (Rust/CLI) or the equivalent SDK parameter. The tracking table records applied revisions and their associated rollback SQL so down migrations can be replayed safely.
Data Model
All four SDKs share an identical type model matching the Rust crate:
TableSnapshot ──┬── columns: Vec<ColumnDef>
name │ name, sql_type, nullable, default, unique
primary_key └── indexes: Vec<IndexDef>
index.name, index.columns, index.unique
MigrationPlan ── id, direction ("Up" | "Down"), steps
MigrationStep ── kind (CreateTable | DropTable | AddColumn | ...)
data (step-specific payload)
Cross-Language Quick Start
Rust
See sdk/rust.md. The sb-migrate crate is published as part of the SCHEMABOUND family.
Python
from schemabound_sdk import MigrationEngine, table_snapshot, ColumnDef
engine = MigrationEngine(dialect="mysql")
# Initialize tracking table
result = engine.init()
print(result["data"]["sql"]) # CREATE TABLE IF NOT EXISTS ...
# Generate diff between current and desired schema
diff_result = engine.diff(
to_schema=[
table_snapshot(
name="users",
columns=[
ColumnDef(name="id", sql_type="BIGINT", nullable=False, unique=True),
ColumnDef(name="email", sql_type="VARCHAR(255)", nullable=False),
],
primary_key=["id"],
),
]
)
# Apply the plan
from schemabound_sdk import MigrationPlan
plan = MigrationPlan(id=diff_result["data"]["id"], direction="Up")
engine.apply(plan)
.NET
using Schemabound.Migrations;
var engine = new MigrationEngine("mysql");
var initResult = engine.Init();
Console.WriteLine(initResult["sql"]); // CREATE TABLE IF NOT EXISTS ...
var diffResult = await engine.Diff(toSchema: new List<TableSnapshot> {
new TableSnapshot {
Name = "users",
Columns = new List<ColumnDef> {
new ColumnDef { Name = "id", SqlType = "BIGINT", Nullable = false, Unique = true },
new ColumnDef { Name = "email", SqlType = "VARCHAR(255)", Nullable = false }
},
PrimaryKey = new List<string> { "id" }
}
});
var plan = new MigrationPlan { Id = (string)diffResult["id"], Direction = "Up" };
await engine.Apply(plan);
TypeScript / Node.js
import { MigrationEngine, TableSnapshot, ColumnDef } from "schemabound-ts";
const engine = new MigrationEngine({ dialect: "mysql" });
const initResult = await engine.init();
console.log(initResult.sql); // CREATE TABLE IF NOT EXISTS ...
const diffResult = await engine.diff({
toSchema: [{
name: "users",
columns: [
{ name: "id", sql_type: "BIGINT", nullable: false, unique: true },
{ name: "email", sql_type: "VARCHAR(255)", nullable: false }
],
primary_key: ["id"]
}] as TableSnapshot[]
});
const plan = { id: diffResult.id, direction: "Up" };
await engine.apply(plan);
Error Handling
All SDKs surface errors via typed exceptions that include the sb-migrate error code and a human-readable message:
| Language | Exception Type | Code Field | Message Format |
|---|---|---|---|
| Rust | MigrationsError | N/A (enum variant) | thiserror Display messages |
| Python | RuntimeError | N/A | Structured JSON error parsing from stderr |
| .NET | MigrationException | ErrorCode int | "sb-migrate exited with code N" or parsed message |
| TypeScript | MigrationError | code? optional int | Parsed JSON or fallback string |
Common error codes: 10 (diff failed), 20 (apply failed), 30 (not initialized), 40 (connection failed), 50 (already applied), 90 (invalid snapshot).
Configuration
The SDKs look for the sb-migrate binary in this order:
$SB_MIGRATE_BINenvironment variable- Binary on
$PATH(shutil.which/Process.Start/spawn) - Project-relative paths (
bin/sb-migrate,<repo>/target/debug/sb-migrate, etc.)
For production deployments, set SB_MIGRATE_BIN to a known absolute path to avoid runtime lookup failures.
Where This Fits in the SCHEMABOUND Stack
Migration management is one of four pillars of SCHEMABOUND:
| Pillar | Purpose | Primary Surface |
|---|---|---|
| Schema Definition | Declarative DDL revision control (this document) | sb-migrate Rust crate + SDK wrappers |
| Query Runtime | Typed query execution with policy enforcement | gRPC server, MirrorProvider, ExecutorService |
| Audit Logging | Structured audit trail for all queries and mutations | CoR handlers, event pipeline |
| Billing & Quotas | AAU metering, rate limiting, quota enforcement | QuotaError, policy engine |
The migration library is independent of the gRPC runtime — you can use it standalone to manage schema evolution in any project that ships a Rust binary.
Contributing
See Contribution Workflow for the full TDD contract. The schemabound-migrations crate has its own test suite (39 tests) covering diff, reversal, SQL generation across dialects, and runner integration.
# Run all migration crate tests
cd libraries/schemabound-migrations && cargo test --tests
Augmenting Prompts with Agent Memory
This guide shows how to inject session memory into your prompt hooks so agents carry prior context without manual retrieval or serialization.
How Prompt Hook Augmentation Works
When a prompt hook resolve request includes a session_id, SCHEMABOUND automatically fetches the session’s memory entries and makes them available as {{memory_context}} inside the hook template. The caller does not need to retrieve or serialize prior context manually.
POST /api/prompt-hooks/:id/resolve
Content-Type: application/json
{
"session_id": "abc-123",
"context": { "user_id": "alice", "organization_id": "acme" }
}
The resolved template receives {{memory_context}} alongside the standard context variables before rendering.
Managing Memory Entries
List Active Sessions
GET /api/agent-sessions
Returns the list of all active sessions.
Response
{
"data": [
{
"id": "abc-123",
"created_at": "2026-04-19T10:00:00Z",
"last_seen_at": "2026-04-19T10:42:00Z"
}
]
}
Retrieve Session Memory
GET /api/agent-memory/:session_id
Returns all memory entries for a session in chronological order.
Response
{
"data": [
{
"entry_type": "tool_call",
"content": "{ \"tool\": \"query\", \"result\": \"...\" }",
"created_at": "2026-04-19T10:05:00Z"
}
]
}
Append a Memory Entry
POST /api/agent-memory/:session_id
Content-Type: application/json
{
"entry_type": "observation",
"content": "User confirmed the report looked correct."
}
Appends a new entry to the session’s memory store. All responses follow the { data: ... } envelope used by the rest of the SCHEMABOUND API.
Frontend Dashboard
The Agent Memory dashboard provides:
- Session list — all active sessions with timestamps
- Memory detail — selecting a session shows its entries in order, with entry type and content
Plugin System — Extend SCHEMABOUND Without Rebuilding the Binary
Extend SCHEMABOUND’s schema at runtime by writing Turtle (TTL) files that declare new RDF classes and properties. Plugins follow an open to extension, closed to modification model: they can add new classes and properties but cannot override existing ones or change mutability flags on base properties.
| Topic | Summary |
|---|---|
| What it is | TTL schema extensions loaded by OAM at runtime via OamRuntime::discover_plugins() |
| Who writes them | Data scientists, domain experts, community members — no Rust compilation required |
| How they load | Placed in ~/.schemabound/plugins/, discovered automatically on backend restart |
| Validation | Authoritative validation happens when OAM loads plugins (install-time is best-effort) |
| Security | Plugins are schema-only (no executable code); validated at install time, never executed server-side |
What Are Plugins?
Plugins extend the base RDF ontology without modifying compiled Rust crates. A plugin is a Turtle document (*.ttl) that declares new sh:NodeShape definitions for classes and properties using standard SHACL syntax.
When OAM starts, it discovers all .ttl files in the plugins directory, parses them as RDF triples, validates them against the base schema boundaries, and merges any valid extensions into the runtime registry. The result is an extended schema that new agent types can use without requiring a code change or binary rebuild.
How Plugins Load
- OAM scans
~/.schemabound/plugins/for.ttlfiles at startup - Each file is parsed as a Turtle document containing SHACL shape definitions
- The validation engine checks that no existing classes are overridden and no mutability flags on base properties are changed
- Valid extensions are merged into the runtime registry; invalid ones produce clear error messages
Writing a Plugin
A plugin is a Turtle file using standard RDF prefixes. Here’s an example that extends the CircuitState class with circuit-cutting configuration for distributed quantum computation (#094):
@prefix qg: <http://schemabound.ai/quantum-graph/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
qg:CircuitState a sh:NodeShape ;
sh:property [
sh:path qg:wireCutIndices ;
sh:datatype xsd:string ; # JSON-encoded list of wire cut indices
sh:maxCount 1 ;
qg:mutable true .
] ;
sh:property [
sh:path qg:subcircuitCount ;
sh:datatype xsd:int ;
sh:maxCount 1 ;
qg:mutable true .
] .
This declares qg:wireCutIndices (JSON-encoded list of wire cut indices for distributed QPU coordination) and qg:subcircuitCount (number of subcircuits after partitioning via KaHyPar). The TTL syntax is identical to migration files (see v007-ui-components.ttl for another reference).
Quantum Domain Plugins
Plugins can extend the quantum graph ontology with domain-specific schemas. Examples include:
- Circuit cutting configuration (#094):
qg:wireCutIndices,qg:subcircuitCount— distributed QPU coordination via PennyLane + KaHyPar partitioning - State vector simulation (#095):
qg:hasAmplitudeIndex,qg:hasRealComponent,qg:hasImaginaryComponent— exact state vector representation for cuQuantum FFI on GPU
These plugins follow the same TTL syntax and validation rules as any other plugin.
Local Development Flow
- Write your
.ttlfile following SHACL conventions - Place it in
~/.schemabound/plugins/:mkdir -p ~/.schemabound/plugins cp quantum_benchmarking.ttl ~/.schemabound/plugins/ - Restart the SCHEMABOUND backend — plugins are discovered automatically on startup
To verify a plugin loaded:
sb plugin list
# → quantum-benchmarking (2.4 KB)
Validation Rules
Plugins are validated before loading. The engine enforces these boundaries:
| Rule | What it prevents | Error if violated |
|---|---|---|
| Cannot override base classes | Accidental or intentional redefinition of existing RDF classes | Cannot override base class |
| Cannot change mutability flags on base properties | Agents modifying read-only fields that should stay static | Cannot change mutability flag on property |
| Must use valid XSD datatypes | Malformed type declarations that would break query execution | Invalid datatype format |
Validation happens at install time and again when OAM loads the plugin. Errors are reported with clear messages before any extension takes effect — no runtime crashes from incompatible schemas.
Security Model
Plugins follow a strict security model:
- Schema-only: Plugins contain RDF schema definitions, not executable code. No Python, Node.js, or WASM logic runs on the server.
- Validated on load: When OAM starts, every plugin in
~/.schemabound/plugins/is checked against base schema boundaries viaOamRuntime::load_all(). Invalid plugins produce clear error messages and are not merged into the registry. Install-time validation (viasb plugin install) is best-effort; authoritative validation happens at load time. - No server-side execution: There is no sandboxed code execution for plugins — they contribute schema only, never behavior.
- Open to extension, closed to modification: Plugins can add new classes and properties but cannot redefine existing ones or alter the mutability of base schema elements.
Installing from a Registry
To install plugins published by the community:
# List available versions and install the latest
sb plugin from-registry --name quantum-benchmarking
# Install a specific version
sb plugin from-registry --name quantum-benchmarking --version 0.1.0
The registry enforces version compatibility checks against your running SCHEMABOUND runtime before allowing installation of mismatched plugins.
Publishing Plugins — Share Extensions via Gitea Registry
Publish your plugin to a Gitea-hosted registry so other SCHEMABOUND users can discover and install it using sb plugin from-registry. Each published plugin becomes a Gitea Release containing the TTL schema file and metadata.
| Topic | Summary |
|---|---|
| What it does | Creates a Gitea release with your plugin as an asset, making it available for others to install |
| Who publishes | Plugin authors — data scientists, domain experts, community members |
| Where it publishes | Any Gitea instance: internal community registry or self-hosted customer instance |
| Required credentials | SCHEMABOUND_GITEA_URL and SCHEMABOUND_GITEA_TOKEN environment variables |
Publish Command
sb plugin publish quantum_benchmarking.ttl \
--registry-url https://gitea.schemabound-lab.internal/api/v1 \
--token $SCHEMABOUND_GITEA_TOKEN
Flags
| Flag | Required | Description |
|---|---|---|
path | Yes (positional) | Path to the .ttl plugin file |
--registry-url | Yes | Gitea API URL (or set SCHEMABOUND_GITEA_URL) |
--token | Yes | Personal access token for Gitea (or set SCHEMABOUND_GITEA_TOKEN) |
--name | No | Plugin slug name; defaults to file stem if omitted |
--compatible-runtime-version | No | Semver constraint for runtime compatibility check; default: >=0.0.0 |
--description | No | Human-readable description shown in the catalog |
Release Structure
Each published plugin becomes a Gitea Release with two assets:
| Asset | Format | Purpose |
|---|---|---|
<plugin-name>.ttl | Turtle (RDF) | The schema extension — SHACL shape definitions for new classes/properties |
plugins.json | JSON metadata | Plugin name, version, author email, description, compatible runtime constraint |
The Gitea Release tag follows semantic versioning (e.g., v0.1.0). Each new plugin version creates a separate release — installers pick the latest unless a specific version is requested.
Example: Publish Quantum State Simulation Plugin
Publish a plugin that adds state vector simulation schema for cuQuantum FFI integration (#095):
# 1. Set up credentials
export SCHEMABOUND_GITEA_URL="https://gitea.schemabound-lab.internal/api/v1"
export SCHEMABOUND_GITEA_TOKEN="<your-personal-access-token>"
# 2. Publish the plugin
sb plugin publish state_vector_simulation.ttl \
--name quantum-state-simulation \
--description "State vector amplitudes and decoherence channel schema for cuQuantum FFI integration" \
--compatible-runtime-version ">=0.4.0"
Expected output:
Ensuring registry repository exists on Gitea...
Creating release v0.1.0 on schemabound/plugin-registry...
Uploading assets to release v0.1.0...
Plugin 'quantum-state-simulation' published successfully as v0.1.0 to https://gitea.schemabound-lab.internal/plugin-registry.
Other users can now install this plugin via sb plugin from-registry quantum-state-simulation.
Webhook Sync
When a new release is pushed, the SCHEMABOUND backend automatically receives a webhook event from Gitea and processes it:
- Backend validates the HMAC-signed webhook payload using
SCHEMABOUND_PLUGIN_WEBHOOK_SECRET - Downloads the TTL asset from the new release
- Stores catalog metadata in its internal triplestore for discovery API queries
- The plugin becomes available to other users via
sb plugin from-registry
No manual steps required — publishing a release is sufficient. Other users’ installations will pick up new versions when they run from-registry.
Installing from Community Registry — Discover and Install Published Plugins
Install plugins published by other community members or your organization’s Gitea-hosted registry using sb plugin from-registry. The command queries the backend discovery API, validates version compatibility, downloads the TTL file, and installs it into your local plugins directory.
| Topic | Summary |
|---|---|
| What it does | Downloads a published plugin from a Gitea-hosted registry and installs it locally |
| How it works | Queries backend discovery API → checks runtime compatibility → downloads TTL → writes to ~/.schemabound/plugins/ |
| Where it installs | ~/.schemabound/plugins/<plugin-name>.ttl (user directory, no root required) |
Install Command
# Install the latest version from registry
sb plugin from-registry quantum-benchmarking
# Install a specific version
sb plugin from-registry quantum-benchmarking --version 0.1.0
Flags
| Flag | Required | Description |
|---|---|---|
name | Yes (positional) | Plugin slug as published to the registry |
--version | No | Specific version tag; defaults to latest |
--registry-url | No | Backend API URL for discovery queries; default: http://localhost:8080 (or set SCHEMABOUND_BACKEND_URL) |
Version Compatibility Check
Before downloading, the CLI checks whether the plugin’s declared compatible runtime version matches your current SCHEMABOUND installation. This prevents installing schema extensions that target incompatible API versions.
The compatibility constraint is defined by the plugin author at publish time (e.g., >=0.3.0). If it doesn’t match your runtime, you’ll see a warning but installation still proceeds — use this flag to override:
sb plugin from-registry --name quantum-benchmarking \
--compatible-runtime-version ">=0.5.0"
Full Lifecycle Example
1. Discover available plugins
Check the registry for what’s published (requires a running backend with catalog data):
# Query via curl against the backend discovery API
curl http://localhost:8080/api/plugins | jq '.plugins[].name'
# → quantum-circuit-cutting
# → quantum-state-simulation
# → ...
The backend serves this catalog from its internal triplestore, populated by webhook events when authors publish new releases.
2. Install a quantum circuit-cutting plugin (#094)
Install the distributed QPU coordination schema:
sb plugin from-registry quantum-circuit-cutting
# → Plugin 'quantum-circuit-cutting' (v0.1.0) installed from registry. Location: ~/.schemabound/plugins/quantum-circuit-cutting.ttl
The CLI queries the backend discovery API for the download URL, checks version compatibility against your runtime, downloads the TTL asset from Gitea, and writes it to ~/.schemabound/plugins/. The plugin loads automatically on next backend restart.
3. Verify installation
sb plugin list
# → quantum-circuit-cutting (2.4 KB)
The installed .ttl file is now in ~/.schemabound/plugins/quantum-circuit-cutting.ttl. When OAM starts, it discovers this file and merges the wire cut indices + subcircuit count schema into the runtime registry.
4. Uninstall a plugin
When you no longer need a quantum plugin:
sb plugin uninstall --name quantum-circuit-cutting
# → Plugin 'quantum-circuit-cutting' uninstalled.
If the plugins directory is empty after removal, it will be cleaned up automatically.
5. Install state vector simulation schema (#095) — alternative workflow
Install a different quantum plugin from a specific version:
sb plugin from-registry quantum-state-simulation --version 0.2.0
# → Plugin 'quantum-state-simulation' (v0.2.0) installed from registry. Location: ~/.schemabound/plugins/quantum-state-simulation.ttl
This installs a specific version for reproducibility — useful when testing against known-good schema extensions before upgrading to the latest release.
Using Installed Plugins
Once installed and loaded by OAM, agents can interact with the new schema as if it were part of the base ontology:
- New RDF classes appear in schema introspection results
- New properties are queryable through standard tools
- Mutable properties (marked
qg:mutable true) allow writes; static ones remain read-only per OAM’s mutability model
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Plugin not found in registry | Name typo or unpublished | Verify name with catalog query; ask author to publish |
| Compatibility warning on install | Runtime version outside plugin’s constraint range | Override with --compatible-runtime-version; rebuild runtime if needed |
| Backend reports validation error after install | TTL file violates schema boundaries (overrides base class) | Edit the .ttl file; do not override existing classes |
mTLS Configuration
SCHEMABOUND supports mutual TLS authentication via the schemabound-certgen binary and runtime PEM loading. This section covers setup, usage, and configuration for production and development deployments.
certgen Binary
The schemabound-certgen CLI generates a certificate authority (CA), server certificates, and client certificates required for mTLS. It is the supported tool for generating all certificate material used by SCHEMABOUND services.
Installation
Build from source:
cargo build --release -p schemabound-certgen
cp target/release/schemabound-certgen /usr/local/bin/
Or use a release binary from the project releases page.
Generating Certificates
Generate a full mTLS certificate chain:
schemabound-certgen generate \
--output-dir ./certs \
--ca-name "SCHEMABOUND CA" \
--server-cn "myservice.local" \
--client-cn "admin-client"
This produces:
| File | Purpose |
|---|---|
ca.crt | Root CA certificate (distribute to all clients) |
ca.key | Root CA private key (keep secure, never distribute) |
server.crt | Server certificate signed by the CA |
server.key | Server private key |
client.crt | Client certificate signed by the CA |
client.key | Client private key |
Customizing Certificates
Use --help for full option listing. Common options:
schemabound-certgen generate --help
--output-dir— Directory to write certificates (default:./certs)--ca-name— CN for the root CA (default:"SCHEMABOUND CA")--server-cn— Common name for server certificate (default:"localhost")--client-cn— Common name for client certificate (default:"client")--days— Certificate validity in days (default:365)
Runtime Configuration
Services load certificates at startup via a trait-based configuration module that supports multiple transport strategies — file paths, inline PEM strings, or secrets manager integration. The runtime validates PEM format and rejects malformed material with a clear error message.
Config Module Design
The mTLS subsystem exposes a TlsConfig trait with three built-in implementations:
#![allow(unused)]
fn main() {
use schemabound::identity::mtls::{TlsConfig, FileTlsConfig, InlinePemConfig};
#[async_trait]
trait TlsConfig {
async fn load_ca_cert(&self) -> Result<Vec<u8>, MtlsError>;
async fn load_server_cert(&self) -> Result<Vec<u8>, MtlsError>;
async fn load_server_key(&self) -> Result<Vec<u8>, MtlsError>;
}
struct FileTlsConfig {
ca_path: PathBuf,
server_cert_path: PathBuf,
server_key_path: PathBuf,
}
struct InlinePemConfig {
ca_pem: String,
server_cert_pem: String,
server_key_pem: String,
}
}
This design keeps transport details out of the runtime — certificate material can come from disk files (development), inline strings in config YAML/JSON (container deployments), or any future source that implements TlsConfig.
Loading from Files
Use FileTlsConfig to point at files generated by schemabound-certgen:
#![allow(unused)]
fn main() {
use schemabound::identity::mtls::{TlsConfig, FileTlsConfig};
let config = FileTlsConfig {
ca_path: "./certs/ca.crt".into(),
server_cert_path: "./certs/server.crt".into(),
server_key_path: "./certs/server.key".into(),
};
}
Dev Mode Bypass
During development, skip client certificate verification with the DevTlsConfig wrapper:
#![allow(unused)]
fn main() {
let config = DevTlsConfig::wrap(file_config)?;
}
This disables mutual TLS enforcement for the client verification step while still allowing server-side mTLS to function. Do not use in production.
Integration With gRPC
The runtime enforces mTLS via environment configuration — no code changes required. Set the following environment variables in your deployment to enable mutual TLS for all inbound and outbound connections:
| Variable | Purpose | Required |
|---|---|---|
SCHEMABOUND_MTLS_CA_CERT | Path or inline PEM of trusted CA certificate | Yes (production) |
SCHEMABOUND_MTLS_SERVER_CERT | Server TLS certificate | Yes |
SCHEMABOUND_MTLS_SERVER_KEY | Server private key | Yes |
SCHEMABOUND_MTLS_DEV_MODE | Skip client cert verification during development | No (default: off) |
The gRPC interceptor layer validates client certificates against the configured CA on every incoming connection. Outbound connections to other SCHEMABOUND services automatically present your server certificate for peer verification.
Production Checklist
- Use a dedicated CA for production (do not reuse development certificates)
- Store private keys in a secrets manager or encrypted vault
- Set certificate expiry alerts (use
--daysappropriately) - Rotate client certificates on employee offboarding
- Verify all services present valid client certificates before accepting connections
- Disable
SCHEMABOUND_MTLS_DEV_MODEin production deployments
JWT Configuration
TL;DR — SCHEMABOUND rotates JWT signing keys without downtime via the
JwtConfigtrait hierarchy. New tokens are signed with the last configured key; old entries remain valid until explicitly removed. Supports HS256, RS256, and ES256 algorithms configured per-issuer through polymorphic config structs.
Configuration Trait Hierarchy
JWT configuration is pluggable via the JwtConfig trait. Each config type handles a specific signing scenario:
| Config Type | Use Case | Key Type |
|---|---|---|
SymmetricJwtConfig | Internal service-to-service auth, development mode | Shared secret (HS256) |
AsymmetricJwtConfig | External IdP verification with public/private key pairs | RSA/EC key pair (RS256, ES256) |
OidcJwksConfig | OIDC discovery-based validation against external providers | Fetches from JWKS endpoint |
Example: Symmetric Configuration (Internal Auth)
#![allow(unused)]
fn main() {
use schemabound::auth::{JwtConfig, SymmetricJwtConfig};
// Single key for signing and validation
let config = SymmetricJwtConfig::new("your-secret-key-here".into());
// Multiple keys for zero-downtime rotation (last entry signs, earlier entries validate)
let config = SymmetricJwtConfig::with_rotation(vec![
"current_key_abc123".into(),
"previous_key_def456".into(), // still validates until removed
]);
}
Example: Asymmetric Configuration (External IdP)
#![allow(unused)]
fn main() {
use schemabound::auth::{JwtConfig, AsymmetricJwtConfig};
let config = AsymmetricJwtConfig::new(
algorithm: Algorithm::RS256,
private_key_path: "/etc/schemabound/jwt/rsa-private.pem",
);
}
Example: OIDC Discovery (Entra ID, Okta, Keycloak)
#![allow(unused)]
fn main() {
use schemabound::auth::{JwtConfig, OidcJwksConfig};
let config = OidcJwksConfig::new(
issuer_url: "https://login.microsoftonline.com/<tenant-id>/v2.0",
cache_ttl: Duration::from_secs(3600), // refresh public keys every hour
);
}
Rolling Key Rotation (Symmetric Only)
For internal service-to-service auth using shared secrets, SCHEMABOUND supports zero-downtime key rotation via the JwtConfig trait:
- Generate a new signing secret using your preferred secure generator (e.g.,
openssl rand -hex 32) - Add the new key to the front of the rotation list while keeping the old key at the end
- Restart SCHEMABOUND — both keys are now active for validation; new tokens sign with the front entry
- Wait until all previously-issued tokens have expired (check
expclaim) - Remove the old key from the end of the rotation list and restart
Algorithm Selection
| Algorithm | Use Case | Key Type | Example Provider |
|---|---|---|---|
HS256 | Internal service-to-service auth; fast signing/validation | Symmetric shared secret | Self-signed, development mode |
RS256 | External IdP verification with public/private key pairs | Asymmetric RSA (2048+ bit) | Okta, Keycloak, Azure AD |
ES256 | External IdP verification; smaller signatures than RSA | Asymmetric EC P-256 | Entra ID, Google Identity Platform |
Troubleshooting
“Invalid signature” errors after key rotation
If clients report invalid_signature after adding a new key to the rotation list, verify:
- The old key is still listed at the end of the rotation vector
- No leading/trailing whitespace in individual keys
- The restart completed before new tokens were issued (check SCHEMABOUND logs for “JWT secrets loaded” message)
Expired token errors from IdP-issued JWTs
External IdP tokens have fixed expiration windows (typically 1 hour). If SCHEMABOUND receives token_expired responses but the IdP says the token is valid:
- Check system clock synchronization — NTP drift causes timestamp validation failures
- Verify issuer URL matches the IdP’s actual issuer URL (trailing slashes matter)
LDAP Sync (stub)
This guide will cover:
- Configuring LDAPS connections on port 636 with CA bundle validation
- Enabling StartTLS on port 389 for environments that cannot use LDAPS directly
- Resolving
posixAccountusers anduniqueMemberDN-based group membership - Recursive nested group resolution via transitive closure computation
LDAP Group-to-RBAC Role Mapping (stub)
This guide will cover:
- Configuring
%cn=rules to map LDAP groups to SCHEMABOUND roles - Conditional role assignment based on OU membership
- Storing role mappings in the Dolt-backed
ldap_role_mappingstable - Integrating with the existing RBAC engine for real-time permission evaluation
SCIM Provisioning (stub)
This guide will cover:
- Enabling the built-in SCIM v2 server for automated user/group provisioning
- Configuring SCIM endpoints (
/scim/v2/Users,/scim/v2/Groups) - Mapping SCIM attributes to SCHEMABOUND identity fields
- Integrating with external IdPs (Entra ID, Okta) for Just-In-Time (JIT) provisioning
Casbin ABAC Integration (stub)
This guide will cover:
- Migrating from the current RBAC model to Casbin Attribute-Based Access Control (ABAC)
- Defining ABAC policies in
casbin_model.confandcasbin_policy.csv - Evaluating permissions against dynamic attributes (org_id, resource_owner, time_of_day)
- Backward compatibility with existing RBAC roles during migration
Host Hardening: SSSD and PAM (stub)
This guide will cover:
- Generating
/etc/sssd/sssd.conftemplates for LDAP/AD integration - Configuring
nsswitch.conffor passwd, group, and shadow lookups via SSSD - Setting up the PAM stack (
pam_unix.so sufficient+pam_sss.so use_first_pass) - Hardening host configuration for air-gap deployments
Audit Logging and AI-SPM Integration
TL;DR — Every agent action in SCHEMABOUND is captured in a tamper-evident audit log and exported via pluggable transport traits to SIEM platforms, HTTP webhooks, or rotating log files. The pipeline treats adversarial prompt injections, LLM jailbreak attempts, and unauthorized role escalation as first-class security signals alongside conventional access-control events.
Architecture
The audit system is composed of four interlocking parts:
Client Request
│
▼
InjectionGuard — scans input before gRPC executor sees it
│
▼
EventBus (hash chain) — global ordered handler + exporter chain
│
▼
MultiTransportExporter — trait-based pluggable transport layer
Transport Configuration
All transports are configured via the MultiTransportExporter trait hierarchy. Each transport implements its own configuration struct:
| Transport | Config Type | Purpose |
|---|---|---|
| Stderr | StderrExporterConfig | OCSF NDJSON or raw JSON to stderr (default) |
| HTTP Webhook | WebhookExporterConfig | Single-destination SIEM integration |
| Rotating File | FileExporterConfig | Local persistence with size-based rotation |
| OTLP/Tracing | OtlpExporterConfig | OpenTelemetry-compatible distributed tracing |
Example: Configuring Multiple Transports
#![allow(unused)]
fn main() {
use schemabound::audit::{MultiTransportExporter, StderrExporter, FileExporter};
let exporter = MultiTransportExporter::new(vec![
Box::new(StderrExporter::default()),
Box::new(FileExporter::try_new("/var/log/schemabound/audit.ndjson")?),
]);
}
Each transport implements ExportEvent independently — adding a new destination requires no changes to the core pipeline.
OCSF v1.1 Output
All exports default to OCSF (Open Cybersecurity Schema Framework) v1.1. OCSF is the interchange format used by major SIEM and AI-SPM vendors including Amazon Security Lake, Microsoft Sentinel, Splunk, and Wiz.
Class Mapping
| SCHEMABOUND Event | OCSF Class | Class UID |
|---|---|---|
SessionRegistered | Authentication | 2001 |
AccessDenied, PromptInjectionSignalRaised | Security Finding | 2004 |
QueryExecuted, QueryValidation*, QueryExecutionError, RowsFiltered, ColumnsRedacted | Database Activity | 6003 |
PlanCreated, PlanCompleted, PlanFailed, LlmToolCallAuditRecorded | API Activity | 6005 |
Severity Mapping
| OCSF Severity | SCHEMABOUND Trigger |
|---|---|
| Critical (5) | Plan failure with execution error |
| High (4) | PromptInjectionSignalRaised with severity high, access denied events |
| Medium (3) | PromptInjectionSignalRaised with severity medium |
| Informational (1) | All other events |
Prompt Injection Detection
The InjectionGuard scans every incoming query against a compiled set of patterns before the gRPC executor processes it. When a pattern matches, a PromptInjectionSignalRaised event is always emitted to the audit pipeline regardless of policy.
Injection Policy
#![allow(unused)]
fn main() {
use schemabound::audit::InjectionPolicy;
// observe: record signal, allow request (default during rollout)
let policy = InjectionPolicy::observe();
// block: record signal, reject request when severity ≥ medium
let policy = InjectionPolicy::block(Medium);
}
Use observe during a rollout period to build a baseline of true-positive rates before switching to block. The detection signal is available to your SIEM in either mode.
Chain Integrity Verification (Enterprise)
SCHEMABOUND includes a background worker that continuously verifies audit log integrity by checking:
- Sequence monotonicity: Events are emitted in strict ascending order with no gaps
- Hash continuity: Each event’s
prev_hashmatches the computed hash of the previous envelope
When a breach is detected — whether from corruption, truncation, or intentional modification — the worker emits an AuditChainIntegrityBreach event that flows through the same exporter pipeline as regular audit records. This enables SIEM correlation and automated incident response.
See Data Resilience Workers for operational details.
Self-Healing Infrastructure
SCHEMABOUND includes a self-healing pipeline that monitors infrastructure services and automatically recovers from failures. This ensures high availability without manual intervention for transient issues such as network partitions, resource exhaustion, or dependency crashes.
Overview
The self-healing system consists of two components:
- Health checker — Periodically probes registered infrastructure services (database instances, gRPC endpoints, audit log writers) and records their current state (
Healthy,Degraded, orFailed). - Restart pipeline — When a service enters
Failedstate, the pipeline attempts automatic recovery using exponential backoff with configurable limits.
Health Monitoring
Services register themselves with the health checker at startup. The checker runs on a configurable interval (default: 30 seconds) and aggregates results into a unified view accessible via internal APIs.
State transitions follow these rules:
| Current State | Healthy Probe | Degraded Probe | Failed Probe |
|---|---|---|---|
Healthy | Remains healthy | Transition to degraded | Transition to failed |
Degraded | Return to healthy | Remain degraded | Transition to failed |
Failed | Attempt restart with backoff | — | Escalate after consecutive failures |
Automatic Recovery
When a service fails, the restart pipeline applies exponential backoff before attempting recovery:
- Base delay: 5 seconds (configurable)
- Maximum delay: 60 seconds (capped to prevent excessive waiting)
- Escalation threshold: After a configurable number of consecutive failures (default: 5), the system escalates and stops automatic retries, emitting an alert for human intervention.
The backoff formula is:
delay = min(base_delay * 2^attempt, max_delay)
Configuration
Recovery behavior is controlled via environment variables or configuration files:
| Variable | Default | Description |
|---|---|---|
HEALTH_CHECK_INTERVAL_SECONDS | 30 | How often to probe services |
RESTART_BASE_DELAY_SECONDS | 5 | Initial backoff delay before restart attempts |
RESTART_MAX_DELAY_SECONDS | 60 | Maximum backoff delay cap |
RESTART_MAX_CONSECUTIVE_FAILURES | 5 | Failures before escalation to human intervention |
Integration With Service Lifecycle
The self-healing pipeline integrates with the service lifecycle state machine. Services transition through states (Starting, Running, Stopping, Stopped) and the health checker observes these transitions to determine when restart attempts are safe to execute.
Restart attempts only proceed when the target service is in a non-running state, preventing conflicts with manual operations or ongoing graceful shutdowns.
Event Emission
The pipeline emits structured events for observability:
| Event | When Emitted |
|---|---|
ServiceHealthCheckFailed | A health probe returned unhealthy |
ServiceRestartAttempted | Automatic restart initiated with backoff delay |
ServiceRecovered | Service returned to healthy state after restart |
ServiceEscalationTriggered | Consecutive failure threshold exceeded, human intervention required |
These events flow through the same event bus used by other SCHEMABOUND components and can be consumed by alerting systems or audit log exporters.
Manual Intervention
When escalation is triggered, operators can:
- Inspect the service state via internal health APIs
- Manually restart the failing service using standard operational procedures
- Reset the failure counter after confirming stability
The system will resume automatic recovery once the failure count drops below the escalation threshold (typically after a successful health probe).
Data Resilience Workers
SCHEMABOUND includes background workers that maintain data consistency and detect tampering in audit logs. These workers run continuously alongside the main application and handle recovery from transient failures without operator intervention.
Auto-Rewind Worker
The auto-rewind worker monitors database sessions for failures and automatically rewinds them to stable states using schema topology information for cascade-aware recovery.
Purpose
Database operations can fail mid-execution due to resource exhaustion, network partitions, or dependency crashes. Without automatic recovery, failed sessions leave the system in an inconsistent state requiring manual intervention. The auto-rewind worker detects these failures and restores stable states within a configurable window.
How It Works
- Polling: The worker queries for failed session branches at regular intervals (default: every 5 seconds).
- Topology-aware recovery: Before rewinding, the worker consults schema topology information to understand foreign key cascade relationships and ensures recovery preserves referential integrity.
- Configurable limits: Each organization can configure whether auto-rewind is enabled and the maximum number of rewind attempts before escalating to human intervention.
Configuration
The auto-rewind worker loads settings from a trait-based config module:
#![allow(unused)]
fn main() {
use schemabound::runtime::workers::{RewindConfig, PollingStrategy};
struct RewindConfig {
poll_interval: Duration, // default: 5 seconds
max_attempts: u32, // default: 3
enabled_per_org: HashMap<OrgId, bool>,
}
}
Each organization can configure whether auto-rewind is enabled and the maximum number of rewind attempts before escalating to human intervention. The PollingStrategy trait allows swapping between fixed-interval polling and adaptive backoff for high-load deployments.
Recovery Process
When a failed session is detected:
- The worker retrieves the schema topology graph to identify cascading dependencies
- It determines the last known stable state for the session
- It executes a cascade-aware reset that rolls back dependent operations in the correct order
- If all attempts fail, it emits an escalation event and stops retrying
Event Emission
| Event | When Emitted |
|---|---|
AutoRewindStarted | Rewind attempt initiated for a failed session |
AutoRewindSuccess | Session successfully restored to stable state |
AutoRewindFailed | All retry attempts exhausted, escalation triggered |
Chain Integrity Verification
The chain integrity worker ensures audit logs remain tamper-evident by verifying sequence monotonicity and hash continuity across all organizations.
Purpose
Audit logs must be immutable and verifiable. Tampering — whether accidental (corruption) or intentional (modification) — must be detectable. The chain integrity worker continuously verifies that:
- Event sequences are monotonically increasing (no gaps or out-of-order entries)
- Hash chains are continuous (each entry’s hash matches the previous entry’s computed value)
How It Works
The worker maintains per-organization state for sequence numbers and hash pointers. On each verification cycle, it:
- Reads the next expected sequence number from stored state
- Compares it against actual event sequences in the audit log
- Recomputes hash chains from a known-good anchor point
- Detects any deviations from expected monotonicity or continuity
Breach Detection
When the worker detects a gap (missing sequence numbers) or modification (hash chain discontinuity), it emits an AuditChainIntegrityBreach event with:
- The affected organization ID
- The location of the detected breach
- The type of deviation (gap vs. modification)
- Timestamps for forensic analysis
Configuration
The chain integrity worker uses a similar trait-based config:
#![allow(unused)]
fn main() {
struct IntegrityCheckConfig {
poll_interval: Duration, // default: 60 seconds
anchor_depth: usize, // default: 1000 events
}
}
Per-organization state for sequence numbers and hash pointers is maintained in a bounded cache that evicts entries older than the anchor depth. The worker runs independently per organization but shares event bus infrastructure with other SCHEMABOUND components.
Integration With Audit Exporters
Breach events flow through the same event bus and audit exporters used by other SCHEMABOUND components. SIEM integrations, alerting systems, and compliance dashboards can consume these events to trigger incident response workflows.
Combined Operation
Both workers run concurrently as part of the service lifecycle. They share the same event bus infrastructure and can be monitored via the unified health check API exposed by the self-healing pipeline (see Self-Healing Infrastructure).
Event Publishing
SCHEMABOUND provides a trait-based event publishing system that lets you distribute domain events to external systems such as message queues, analytics pipelines, or compliance archives. The reference implementation uses Kafka for high-throughput pub/sub, but the abstraction supports any transport.
Trait Contract
The public SDK exposes an EventPublisher trait that implementations must satisfy:
#![allow(unused)]
fn main() {
use schemabound::eventing::{EventPublisher, PublishError};
use async_trait::async_trait;
#[async_trait]
pub trait EventPublisher: Send + Sync {
async fn publish(&self, topic: &str, key: &[u8], value: &[u8]) -> Result<(), PublishError>;
async fn close(&self);
}
}
publish— Send an event to a named topic with optional key-based partitioning.close— Gracefully shut down the publisher (flush buffers, release connections).
Implementations are responsible for connection management, retry logic, and transport-specific error handling. The trait returns a generic PublishError that wraps underlying failures.
Reference Implementation: Kafka
The built-in KafkaEventPublisher implements the trait using Apache Kafka. It supports:
- Per-topic partitioning — Events are distributed across partitions based on configurable strategies (key-based, round-robin, or custom).
- Configurable consumer groups — When used as a subscriber, events can be consumed by named groups with offset management.
- Heartbeat timeout — Configurable heartbeat interval for membership renewal in consumer groups.
Configuration
The Kafka publisher loads configuration via a trait-based module that supports file paths, inline strings, or secrets manager integration:
#![allow(unused)]
fn main() {
use schemabound::eventing::{KafkaConfig, FileKafkaConfig, InlineKafkaConfig};
struct FileKafkaConfig {
bootstrap_servers: Vec<String>, // e.g., ["broker1:9092", "broker2:9092"]
security_protocol: SecurityProtocol, // PLAINTEXT, SSL, or SASL_PLAINTEXT
sasl_mechanism: Option<SaslMechanism>,
sasl_credentials: Option<(String, String)>,
}
struct InlineKafkaConfig {
bootstrap_servers_yaml: String,
security_config_json: String,
}
}
This design keeps transport details out of the runtime — Kafka connection material can come from disk files (development), inline strings in config YAML/JSON (container deployments), or any future source that implements KafkaConfig. SASL authentication is optional and only required when using SASL_PLAINTEXT or SASL_SSL security protocols.
Topic Partitioning by Organization
For multi-tenant deployments, the publisher supports per-organization topic partitioning. Events are published to topics named {prefix}-{org_id}-events, where prefix is configurable and org_id comes from the event’s runtime context.
This ensures:
- Isolation: Each organization’s events land in their own topic.
- Scalability: Topics can be distributed across multiple Kafka brokers.
- Compliance: Audit requirements that mandate per-org data separation are satisfied at the transport layer.
Using the Publisher
Register the publisher with SCHEMABOUND’s event bus:
#![allow(unused)]
fn main() {
use schemabound::eventing::{EventPublisher, KafkaEventPublisher};
use std::sync::Arc;
let config = FileKafkaConfig { /* ... */ };
let kafka = Arc::new(KafkaEventPublisher::from_config(config)?);
schemabound::get_event_bus().register_publisher(kafka)?;
}
The publisher receives all events emitted on the bus and forwards them to the configured Kafka cluster.
Custom Implementations
To publish events to a different system (e.g., RabbitMQ, AWS SNS, or a custom HTTP webhook), implement EventPublisher for your type:
#![allow(unused)]
fn main() {
use schemabound::eventing::{EventPublisher, PublishError};
use async_trait::async_trait;
struct MyCustomPublisher { /* ... */ }
#[async_trait]
impl EventPublisher for MyCustomPublisher {
async fn publish(&self, topic: &str, key: &[u8], value: &[u8]) -> Result<(), PublishError> {
// Forward to your custom system.
Ok(())
}
async fn close(&self) {
// Release resources.
}
}
}
Register it the same way as the Kafka implementation. The event bus does not care about the underlying transport — only that the trait contract is satisfied.
Error Handling
Publish failures are non-fatal by default: the event bus logs the error and continues processing other events. If you need strict delivery guarantees, implement retry logic within your EventPublisher and surface errors through the PublishError type so the caller can decide how to handle them (e.g., by triggering a dead-letter queue or alerting).
Performance Considerations
- Batch publishing: For high-throughput scenarios, consider buffering events in memory and flushing in batches. The trait does not enforce synchronous publishing — implementations can use internal queues.
- Partitioner selection: Choose a partitioner that aligns with your consumer’s parallelism needs. Key-based partitioning ensures ordering per key; round-robin distributes load evenly.
- Backpressure: If your Kafka cluster is under heavy load, the publisher should apply backpressure (e.g., by blocking or dropping events with a configurable policy) rather than queuing indefinitely in memory.
Gateway Routes (stub)
This guide will cover:
- Configuring API Gateway routing rules for SCHEMABOUND backend and frontend services
- Setting up TLS termination with internal CA certificates
- Routing strategies for multi-tenant deployments
- Integrating with Gitea Actions runners behind the gateway
Air-Gap Deployment (stub)
This guide will cover:
- Staging CA certificates and internal tools on Ventoy USB for offline bootstrap
- Building custom DinD base images with internal CA trust
- Configuring air-gap Docker registry mirrors for Rust/Node/Go dependencies
- Running the full SCHEMABOUND stack without internet access
AAU Billing Reference
Agentic Activity Unit Weight Table
| Intent category | Example operations | AAU weight |
|---|---|---|
| Read | read_select | 1x |
| Write | write_insert, write_update, write_delete | 3x |
| Governed | admin, cross-system transactions | 5x |
The weight is resolved from the intent context key that the SCHEMABOUND middleware attaches to every LlmToolCallAuditRecorded event. When no explicit intent is present the weight is heuristically derived from the tool/function name (see aau_weight_from_tool_name in services/backend/src/billing.rs).
AAU Billing Heatmap
Interactive visualization of ORM model × intent governance cost distribution. Cell dimensions scale with cumulative AAU total so operators can immediately spot which models and intents drive the most overhead.
Note: This heatmap loads sample data by default. When a running SCHEMABOUND backend is available, replace SAMPLE_DATA in aau-heatmap.js with a fetch call to /api/billing/usage/heatmap.
Event Schema: usage_events Table
| Column | Type | Notes |
|---|---|---|
id | VARCHAR(36) | UUIDv4 |
org_id | VARCHAR(36) | From request context |
session_id | VARCHAR(255) | Optional — agent session |
tool_name | VARCHAR(255) | Raw gRPC/MCP tool name |
intent | VARCHAR(64) | read_select / write_* / admin |
aau_weight | DOUBLE | 1.0 / 3.0 / 5.0 |
orm_model | VARCHAR(64) | ORM model name, if known |
cost_usd | DOUBLE | Reserved for future pricing; currently 0.0 |
created_at | TIMESTAMP | Server timestamp |
REST API Endpoints
All billing endpoints require the caller to be authorised as billing admin (see Authorization below).
GET /billing/usage
Returns daily AAU totals for the authenticated organisation.
Query parameters
| Parameter | Format | Default |
|---|---|---|
from | YYYY-MM-DD | 1970-01-01 (all-time) |
to | YYYY-MM-DD | 9999-12-31 (all-time) |
Response
{
"data": [
{
"date": "2026-04-30",
"action_count": 42,
"total_aau": 87.0,
"total_cost_usd": 0.0
}
]
}
GET /billing/usage/heatmap
Returns AAU totals grouped by ORM model x intent — the matrix consumed by the D3 heat map on the AAU Dashboard.
Response
{
"data": [
{
"orm_model": "Person",
"intent": "write_insert",
"aau_weight": 3.0,
"total_aau": 9.0,
"action_count": 3
}
]
}
POST /billing/setup-intent
Creates a Stripe SetupIntent so the frontend can securely collect card details. Requires STRIPE_SECRET_KEY in the server environment.
Request body
{ "customer_id": "cus_abc123" }
Response — returns id, client_secret, and customer from Stripe.
GET /billing/payment-methods
Lists saved Stripe payment methods for the authenticated admin session.
DELETE /billing/payment-methods/<pm_id>
Detaches a Stripe payment method by its pm_… identifier.
Authorization
Billing endpoints are protected by the BillingAdmin request guard. A request is accepted when either condition is true:
X-User-Roleheader equalsadmin(case-insensitive), orX-User-Permissionsheader containsmanage:billing(comma-separated list).
Requests that satisfy neither condition receive 403 Forbidden.
Error Registry
Typed error enums exposed by the SCHEMABOUND public API contract. All fallible operations return typed errors rather than boxed trait objects, enabling precise handling at call sites and clean propagation with ?.
| Error Type | Operations Covered | gRPC Status Mapping |
|---|---|---|
MapperError | Query dispatch — validate_query, execute_query, mapper construction (InvalidAddress, ConnectionFailed, QueryFailed, ValidationFailed, GrpcTransport) | INVALID_ARGUMENT, UNAVAILABLE |
ExecutorError | Validation and execution services — NotConfigured, SchemaParseFailed, ExecutionFailed, TableNotFound, AugmentationFailed | FAILED_PRECONDITION, INTERNAL |
EngineError | Execution engine pool and concurrency — Configuration, PoolExhausted, ExecutionFailed, LockFailed, Timeout | RESOURCE_EXHAUSTED, DEADLINE_EXCEEDED |
TcpServerError | JSON-RPC TCP server lifecycle — InvalidAddress, Configuration, ShutdownFailed | N/A (non-gRPC) |
ControlPlaneError | Workflow orchestration — NotFound, ExecutionFailed, Cancelled | NOT_FOUND, CANCELLED |
EventBusError | Handler and subscriber registration — LockFailed | INTERNAL |
QuotaError | Rate limiting — RateLimitExceeded, ConnectionLimitExceeded | RESOURCE_EXHAUSTED |
SessionError | Agent session lifecycle — CleanupFailed | INTERNAL |
InputScanError | Injection detection at the gRPC boundary | INVALID_ARGUMENT |
All types implement thiserror::Error with stable Display messages. They are designed to map cleanly to gRPC Status codes at the service boundary.
Timeout Constant
All mapper operations share a single timeout constant:
#![allow(unused)]
fn main() {
use schemabound::mapper::DEFAULT_TIMEOUT_SECONDS; // 30
}
This constant governs LocalMapper, TcpMapper, and GrpcMapper connection and request timeouts. Override at the server or client level when your deployment requires different bounds.
Error Propagation Example
#![allow(unused)]
fn main() {
use schemabound::mapper::{LocalMapper, Mapper, DEFAULT_TIMEOUT_SECONDS};
use schemabound::error::MapperError;
async fn run_query(db_path: &str, sql: &str) -> Result<(), MapperError> {
let mapper = LocalMapper::new(db_path)?;
mapper.validate_query(sql).await?;
mapper.execute_query(sql).await?;
Ok(())
}
}
LlmSchema Derive Macro
The LlmSchema derive macro — provided by the schemabound-schema crate — generates a JSON Schema
description of any Rust struct whose fields are annotated with standard Serde attributes.
This schema is used at runtime to build context-aware prompts and to expose entity structure
to LLM tool-calls, gRPC clients, and the Python SDK.
How it works
#[derive(LlmSchema)] delegates to schemars::schema_for!() under
the hood and exposes a single method:
#![allow(unused)]
fn main() {
pub fn llm_schema() -> schemars::schema::RootSchema
}
The returned RootSchema is fully JSON-serialisable and can be embedded directly in prompts,
returned over gRPC, or published to the OpenAPI spec.
SeaORM integration (Rust)
SeaORM entity models are the primary target for LlmSchema. Add both LlmSchema and
JsonSchema to your DeriveEntityModel derive list:
#![allow(unused)]
fn main() {
use roam_schema::LlmSchema;
use schemars::JsonSchema;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, LlmSchema, JsonSchema)]
#[sea_orm(table_name = "organizations")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,
pub name: String,
pub slug: String,
pub description: String,
pub owner_id: String,
}
}
With this in place, the schema is available at runtime without reflection:
#![allow(unused)]
fn main() {
let schema = organization::Model::llm_schema();
let schema_json = serde_json::to_string_pretty(&schema).unwrap();
}
Registering with a prompt hook
Pass the JSON schema to a PromptHookSchemaContext when resolving a prompt:
#![allow(unused)]
fn main() {
let schema_json = serde_json::to_string(&organization::Model::llm_schema()).unwrap();
let request = PromptHookResolveRequest {
schema_context: PromptHookSchemaContext {
database_id: Some("prod-db".to_string()),
table_names: vec!["organizations".to_string()],
domain_tags: vec!["identity".to_string()],
},
..Default::default()
};
}
The matching rules in your prompt hook YAML can reference table_names and domain_tags:
schema:
table_names: ["organizations"]
domain_tags: ["identity"]
SQLAlchemy integration (Python SDK)
The Python SDK exposes entity schemas through the SchemaboundClient.get_schema() gRPC call.
You do not need to replicate SeaORM models in Python — the schema travels over the wire.
Installation
pip install schemabound-sdk
# or with uv
uv add schemabound-sdk
Fetching a schema
from schemabound_sdk import SchemaboundClient
client = SchemaboundClient(host="localhost", port=50051)
# Retrieve the JSON Schema for a specific entity table
schema = client.get_schema(table_name="organizations")
print(schema)
# → {"$schema": "http://json-schema.org/draft-07/schema#", "title": "Model", ...}
Using the schema with SQLAlchemy
The returned dict is a standard JSON Schema object. Pass it to your ORM model for dynamic validation or prompt-context injection:
import json
from sqlalchemy import create_engine, text
from schemabound_sdk import SchemaboundClient
client = SchemaboundClient(host="localhost", port=50051)
schema = client.get_schema(table_name="organizations")
# Validate a row dict against the schema (e.g. using jsonschema)
import jsonschema
jsonschema.validate(instance=row_dict, schema=schema)
# Or embed the schema directly in an LLM prompt
prompt_context = json.dumps(schema, indent=2)
Injecting schema into a prompt hook
from schemabound_sdk import SchemaboundClient, PromptHookResolveRequest, SchemaContext
client = SchemaboundClient(host="localhost", port=50051)
request = PromptHookResolveRequest(
schema_context=SchemaContext(
database_id="prod-db",
table_names=["organizations"],
domain_tags=["identity"],
)
)
resolution = client.resolve_prompt_hook(request)
print(resolution.rendered_prompt)
Field-level annotations
All standard Serde rename / skip attributes are respected by schemars:
#![allow(unused)]
fn main() {
#[derive(LlmSchema, JsonSchema, Serialize, Deserialize)]
pub struct Model {
pub id: Uuid,
/// Human-readable display name
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "ownerId")]
pub owner_id: String,
}
}
The generated schema will include the description from Rust doc-comments, honour rename,
and mark description as non-required because it is Option<…>.
Supported crates
| Crate | JsonSchema support |
|---|---|
serde_json::Value | built-in |
uuid::Uuid | via schemars uuid feature |
chrono::DateTime | via schemars chrono feature |
std::collections::BTreeMap | built-in |
Enable optional features in Cargo.toml:
schemars = { version = "0.8", features = ["derive", "uuid", "chrono"] }
API reference
The live HTTP API exposes all route schemas via Swagger UI. When the backend is running:
- Swagger UI: http://localhost:8000/api/swagger
- OpenAPI JSON: http://localhost:8000/api/openapi.json
Middleware Architecture
SCHEMABOUND middleware is the integration layer that lets products apply shared runtime context, identity-aware execution, and policy decisions close to the point where work actually happens.
Runtime Flow At The Boundary
At a high level, SCHEMABOUND middleware does three things for every participating request:
- establish who or what is acting
- attach the runtime context needed to make a safe decision
- pass only validated execution downstream
The diagram below shows the logical shape of that flow.
Request Flow Diagram
This diagram shows the abstract pipeline a request moves through before execution continues.
sequenceDiagram
participant Client as Client Application
participant API as Product API / Service Boundary
box "SCHEMABOUND Middleware Layer" #f9f9f9
participant Identity as Identity Context
participant Runtime as Runtime Interceptor
participant Policy as Policy Review
end
participant Data as Downstream Service / Data Layer
Note over Client, API: Request enters a SCHEMABOUND-enabled boundary
Client->>API: 1. Submit request
rect rgb(35, 35, 35)
Note over API, Identity: Layer 1: Identity and request context
API->>Identity: 2. Resolve identity and context
Identity->>Identity: Match trusted identity inputs
alt Identity Invalid
Identity-->>API: 401 Unauthorized
API-->>Client: Authentication error
else Identity Valid
Identity->>Runtime: 3. Attach runtime context
end
end
rect rgb(40, 35, 35)
Note over Runtime, Policy: Layer 2: Validation and policy review
Runtime->>Runtime: Interpret request intent
Runtime->>Policy: 4. Evaluate request
Policy->>Policy: Apply execution rules
alt Request Rejected
Policy-->>Runtime: Block execution
Runtime-->>API: 403 Forbidden
API-->>Client: Request rejected
else Request Approved
Policy-->>Runtime: 5. Continue
end
end
rect rgb(35, 40, 35)
Note over Runtime, Data: Layer 3: Downstream execution
Runtime->>Data: 6. Execute downstream work
Data-->>Runtime: 7. Return result
end
Runtime-->>API: 8. Format response
API-->>Client: 9. Final result
What This Layer Adds
- Identity-aware context so requests carry the organization, user, or tool information needed for safe execution.
- Interception at the right boundary so SCHEMABOUND can enrich or validate intent before it reaches core business logic.
- Policy-aware execution so only approved operations continue into downstream systems.
- Minimal disruption to existing systems so teams can integrate SCHEMABOUND without redesigning their application architecture.
Integration Models
SCHEMABOUND is designed to be protocol-agnostic and to sit as close as possible to the moment where intent becomes execution.
Model 1: Embedded Request Integration
This model fits products that already have an API layer and want SCHEMABOUND to participate in request handling without changing the rest of the application stack.
Common fit:
- existing APIs and service boundaries
- application teams adding runtime context and policy checks
- products that want SCHEMABOUND close to synchronous request handling
sequenceDiagram
participant User as End User (UI)
participant API as Product API
participant OAM as SCHEMABOUND Middleware
participant Runtime as SCHEMABOUND Runtime
participant DB as Data Layer
Note over User: 1. User action begins in the product
User->>API: 2. API Request
rect rgb(35, 35, 35)
Note over API, OAM: Embedded request boundary
API->>OAM: Intercept and enrich request
OAM->>OAM: Resolve identity and runtime context
par Runtime Coordination
OAM--)Runtime: Emit runtime event
and Request Validation
OAM->>OAM: Evaluate request policy
end
alt Valid
OAM->>API: Continue request
API->>DB: Execute application work
DB-->>API: Result
API-->>User: Response
else Rejected
OAM-->>User: 403 Rejected Request
end
end
Model 2: Proxy Or Sidecar Boundary
This model fits systems that do not expose a clean application middleware layer but still need a controlled integration boundary.
Common fit:
- legacy applications
- direct data-access clients
- environments that need interception outside the primary application codebase
sequenceDiagram
participant User as Existing Application
participant Proxy as SCHEMABOUND Proxy / Sidecar
participant Runtime as SCHEMABOUND Runtime
participant DB as Data Layer
participant Identity as Identity Source
Note over User: 1. Existing system issues a request
User->>Proxy: 2. Forward request to boundary layer
rect rgb(40, 35, 35)
Note over Proxy: Boundary interception
Proxy->>Identity: Resolve permissions and context
Proxy--)Runtime: Emit runtime event
Proxy->>Proxy: Evaluate request rules
alt Approved
Proxy->>DB: Execute downstream request
DB-->>Proxy: Rows
Proxy-->>User: Result
else Blocked
Proxy-->>User: Rejected Request
end
end
Model 3: Hybrid Runtime Placement
This model fits teams that need local execution boundaries but still want shared governance, visibility, or centralized coordination patterns.
Common fit:
- high-compliance deployments
- organizations with mixed hosted and self-managed infrastructure
- teams that need execution inside their own perimeter
Model 4: Fully Self-Hosted Runtime
This model fits teams that want full control of runtime placement and operational ownership.
Common fit:
- air-gapped or highly restricted environments
- self-managed platform teams
- development and experimentation workflows built entirely on the public stack
sequenceDiagram
participant Source as Product Event Source
participant OAM as Local SCHEMABOUND Runtime
participant Logic as Business Logic
participant DB as System of Record
Source->>OAM: Event / RPC Call
rect rgb(35, 35, 45)
Note over OAM: Local execution boundary
OAM->>OAM: Apply local validation and context
OAM->>Logic: Invoke Handler
end
Logic->>DB: Update State
DB-->>Logic: Success
Logic-->>OAM: Confirmation
OAM-->>Source: Result
Choosing The Right Boundary
Pick the model that keeps SCHEMABOUND closest to the boundary where your system already makes trust and execution decisions. For most teams, that means starting with an SDK or embedded middleware pattern and expanding only when deployment constraints require it.
.NET SDK Guide
Use the schemabound-dotnet package when you want to integrate SCHEMABOUND into .NET services, enterprise applications, and platform components with a typed client experience that fits naturally into the broader .NET ecosystem.
Why Choose The .NET SDK
- Integrate SCHEMABOUND into ASP.NET, worker services, and internal platform components.
- Build strongly typed service-to-service integrations on a familiar .NET foundation.
- Adopt SCHEMABOUND in production applications without dropping to lower-level protocol work unless you need it.
Installation
dotnet add package Schemabound.Dotnet
Or via NuGet:
Install-Package Schemabound.Dotnet
What You Get
The .NET SDK gives you:
- A typed .NET integration surface for embedding SCHEMABOUND into application and platform code.
- Interop over the public runtime model so .NET services stay aligned with the supported SCHEMABOUND contract.
- Utilities and examples that help teams wire SCHEMABOUND into real service and enterprise deployment patterns.
Quick Start
using Schemabound;
var engine = new ReflectionEngine();
var server = new GrpcServer();
await server.RunAsync();
This is the fastest way to stand up a .NET integration, validate the client path, and start wiring SCHEMABOUND into a service or application workflow.
Runtime Augmentation
SCHEMABOUND runtime calls can carry runtime-augmentation selection metadata so your .NET application can attach stable business and request context before validation or execution.
Use the public runtime headers when you need SCHEMABOUND behavior to reflect application identity, tool intent, tenant boundaries, or domain-specific routing context.
The key runtime headers are:
x-schemabound-runtime-augmentation-idto reference a specific augmentation identifierx-schemabound-runtime-augmentation-keyto reference a stable augmentation keyx-schemabound-tool-name,x-schemabound-tool-intent,x-schemabound-user-id,x-schemabound-organization-id,x-schemabound-domain-tags, andx-schemabound-table-namesto provide matching context
SCHEMABOUND emits resolved augmentation identity into normal query events, while sensitive rendered content remains reserved for dedicated audit handling.
Suggested Starting Points
- Shipping a typed platform or product integration on .NET: start here.
- Embedding SCHEMABOUND into an ASP.NET or worker-service architecture: start here.
- Passing application context into SCHEMABOUND execution paths: start with the runtime-augmentation headers above.
Contributing
For Rust Core Changes
If you need to change the shared public runtime or core SCHEMABOUND behavior:
- File an issue in schemabound-public
- Submit a PR to schemabound-public (see Contribution Workflow)
- Once merged and exported, the change flows to schemabound-dotnet automatically
For .NET Layer Improvements
To improve the .NET developer experience, add helpers, or expand documentation:
- Fork schemabound-dotnet
- Create a feature branch
- Make changes in
Schemabound/(.NET layer only) - Submit PR to schemabound-dotnet/main
- We’ll review and merge
Example:
// Add a utility class
// Schemabound/Config/ConfigLoader.cs
public static class ConfigLoader
{
public static RoamConfig LoadFromFile(string path)
{
// Load configuration
}
}
API Reference
See the full API docs for the .NET package surface, method signatures, and integration details.
Python SDK Guide
Use the schemabound-python package when you want to integrate SCHEMABOUND into Python applications, automation workflows, and service-side tooling without giving up a script-friendly developer experience.
Why Choose The Python SDK
- Build Python services and internal tools that need direct access to SCHEMABOUND capabilities.
- Add SCHEMABOUND-backed workflow automation to notebooks, jobs, and lightweight application code.
- Move quickly with a familiar Python surface while staying aligned with the public SCHEMABOUND contract.
Installation
pip install schemabound-python
What You Get
The Python SDK gives you:
- A Python-first client surface for integrating SCHEMABOUND into application and automation code.
- Typed bindings over the public runtime model so Python code stays aligned with the supported SCHEMABOUND contract.
- Utility helpers and examples that make it easier to adopt SCHEMABOUND in real product and workflow scenarios.
Quick Start
from schemabound_sdk import SchemaboundClient, SchemaboundDeclarativeBase
# Connect to SCHEMABOUND backend
client = SchemaboundClient(uri="localhost:50051")
client.ping() # Verify connectivity
# Use as a SQLAlchemy declarative base for schema introspection
class User(SchemaboundDeclarativeBase):
__tablename__ = "users"
This is the fastest path when you want to stand up a Python-based integration, validate connectivity, and start building application logic around SCHEMABOUND.
Runtime Augmentation
SCHEMABOUND runtime calls can carry runtime-augmentation selection metadata so your application can attach stable request context before validation or execution.
Use the public runtime headers when you need SCHEMABOUND behavior to reflect product context such as the calling tool, organization, or domain.
The key runtime headers are:
x-schemabound-runtime-augmentation-idto reference a specific augmentation identifierx-schemabound-runtime-augmentation-keyto reference a stable augmentation keyx-schemabound-tool-name,x-schemabound-tool-intent,x-schemabound-user-id,x-schemabound-organization-id,x-schemabound-domain-tags, andx-schemabound-table-namesto provide matching context
SCHEMABOUND emits resolved augmentation identity into normal query events, while sensitive rendered content remains reserved for dedicated audit handling.
Suggested Starting Points
- Building automation, internal tools, or orchestration logic in Python: start here.
- Prototyping a SCHEMABOUND integration before standardizing it across services: start here.
- Passing runtime context from application code into SCHEMABOUND execution paths: start with the runtime-augmentation headers above.
Contributing
For Rust Core Changes
If you need to change the shared public runtime or core SCHEMABOUND behavior:
- File an issue in schemabound-public
- Submit a PR to schemabound-public (see Contribution Workflow)
- Once merged and exported, the change flows to schemabound-python automatically
For Python Layer Improvements
To improve the Python experience, add helpers, or expand documentation:
- Fork schemabound-python
- Create a feature branch
- Make changes in
libraries/schemabound-python/(Python layer only) - Submit PR to schemabound-python/main
- We’ll review and merge
Example:
# Add a utility function
# libraries/schemabound-python/schemabound_sdk/utils/config.py
def load_config_from_file(path: str) -> dict:
"""Load SCHEMABOUND configuration from YAML file."""
...
API Reference
See the full API docs for the Python package surface, method signatures, and integration details.
Rust SDK Reference (schemabound)
The schemabound crate is the core Rust runtime and reference implementation of the OAM framework. All other SDKs and services build on this foundation.
Crate Structure
| Module | Purpose |
|---|---|
schemabound::mirror | SQLite schema introspection — tables, columns, indexes, triggers, UDTs, field mappings |
schemabound::executor | SchemaService / QueryService traits + request/response types |
schemabound::grpc_executor | Tonic gRPC server wrapping the executor services (managed backend implementation) |
schemabound::interceptor | Typed event bus — Event enum, EventBus, EventHandler, HandleOutcome |
schemabound::handlers | Built-in CoR handlers — AuditLogHandler, QueryMetricsHandler, SessionActivityHandler, DefaultHandlerChain, SharedHandler |
schemabound::tcp | TCP JSON-RPC transport and per-client auth |
schemabound::policy_engine | Tool-contract policy evaluation and subquery governance |
schemabound::runtime_context | QueryRuntimeContext — the carrier for per-request augmentation metadata |
schemabound::mapper | Mapper trait + LocalMapper (SQLite) + TcpMapper (remote) |
Feature-Gated Consolidation Targets
The following crates are available as feature-gated re-exports from schemabound. Enable them with Cargo features like sb-cdc, sb-rbac, etc.:
| Feature | Crate | Re-export Module |
|---|---|---|
sb-auth | schemabound-auth | schemabound::auth |
sb-cdc | schemabound-cdc | schemabound::cdc |
sb-infer | schemabound-infer | schemabound::infer |
sb-migrations | schemabound-migrations | schemabound::migrations |
| (always on) | schemabound-plugin-system | schemabound::plugin_system |
sb-policy-engine | schemabound-policy-engine | schemabound::policy_engine_crate |
sb-prolly-tree | schemabound-prolly-tree | schemabound::prolly_tree |
sb-rbac | schemabound-rbac | schemabound::rbac |
sb-rdf | schemabound-rdf | schemabound::rdf |
sb-sandbox | schemabound-sandbox | schemabound::sandbox |
sb-sql | schemabound-sql | schemabound::sql |
sb-trace | schemabound-trace | schemabound::trace |
| (always on) | schemabound-triplestore | schemabound::triplestore |
Note: sb-emgen is excluded due to a cyclic dependency (emgen depends on schemabound).
Schema Introspection Model
The mirror module returns a tree of Rust structs:
SchemaModel
├── tables: Vec<Table>
│ ├── columns: Vec<Column> name, sql_type, nullable, primary_key, default_value
│ ├── indexes: Vec<Index> name, columns
│ ├── unique_indexes: Vec<UniqueIndex>
│ ├── foreign_keys: Vec<ForeignKey>
│ ├── composite_foreign_keys: Vec<CompositeForeignKey>
│ ├── triggers: Vec<Trigger> name, event, timing, table_name, body
│ └── field_mappings: Vec<FieldMapping>
│ └── logical_name, physical_name, orm_convention (Hibernate | EntityFramework)
└── user_defined_types: Vec<UserDefinedType>
└── name, base_type, check_constraint, nullable, default_value
Field mapping convention detection is heuristic:
camelCasecolumn names →HibernatePascalCasecolumn names →EntityFramework(Entity Framework)_prefixedcolumns →EntityFramework(EF Core shadow property convention)
gRPC Proto Mapping
The table_to_table_def function converts a mirror::Table to a proto TableDef:
#![allow(unused)]
fn main() {
use schemabound::grpc_executor::table_to_table_def;
let proto_table = table_to_table_def(&my_mirror_table);
}
Both unique_indexes and indexes are merged into TableDef.indexes, distinguished by
IndexDef.is_unique.
Contributing
See the Contribution Workflow for the full TDD contract — no production code may be written before a failing test exists.
Test Targets
# Unit tests only (fast, no infra)
make test-unit
# Full test suite (schemabound-public)
make test FILTER=schemabound-public
# Proto unit tests
make test-proto
Embedding Model
SCHEMABOUND exposes a pluggable embedding interface through the EmbeddingProvider trait in the backend. This lets you integrate any vector embedding backend — cloud APIs, local models, or hybrid setups — without changing your application code.
The Trait
#![allow(unused)]
fn main() {
use schemabound_backend::memory::embedding::{EmbeddingProvider, EmbeddingError};
pub trait EmbeddingProvider: Send + Sync {
fn embed(&self, text: &str) -> Result<Vec<f32>, EmbeddingError>;
fn dimension(&self) -> usize;
}
}
The embed method accepts a string and returns a fixed-dimensional vector of 32-bit floats. Implementations are responsible for tokenization, normalization, and returning vectors in the format expected by your search or retrieval layer. The default dimension is 768 (matching BGE-base-en-v1.5), configurable via SCHEMABOUND_EMBEDDING_DIMENSION environment variable.
Built-In Stub: NoOpEmbeddingModel
The backend includes a zero-vector stub for development and testing. It returns a vector of zeros with the configured dimension, satisfying the trait boundary when you do not need semantic search:
#![allow(unused)]
fn main() {
use schemabound_backend::memory::embedding::{EmbeddingProvider, EmbeddingModel};
// Default dimension is 768; override via SCHEMABOUND_EMBEDDING_DIMENSION env var.
let model = EmbeddingModel::default();
assert_eq!(model.dimension(), 768);
let vector = model.embed("any text").unwrap();
assert_eq!(vector.len(), 768);
assert!(vector.iter().all(|&v| v == 0.0)); // zero-vector stub
}
The model is registered with Rocket’s state system as EmbeddingService(Arc<dyn EmbeddingProvider>) and injected into handlers via the State<EmbeddingService> guard — no factory pattern needed, just direct dependency injection.
Custom Implementations
To use a real embedding backend (e.g., OpenAI text-embedding-ada-002, local BGE models), implement EmbeddingProvider for your type:
#![allow(unused)]
fn main() {
use schemabound_backend::memory::embedding::{EmbeddingProvider, EmbeddingError};
use std::sync::Arc;
struct OpenAiEmbedder {
api_key: String,
client: reqwest::Client,
}
impl EmbeddingProvider for OpenAiEmbedder {
fn embed(&self, text: &str) -> Result<Vec<f32>, EmbeddingError> {
let response = self.client.post("https://api.openai.com/v1/embeddings")
.bearer_auth(&self.api_key)
.json(&serde_json::json!({ "input": text, "model": "text-embedding-ada-002" }))
.send()
.map_err(|e| EmbeddingError::Computation(e.to_string()))?;
let body: serde_json::Value = response.json().await
.map_err(|e| EmbeddingError::Computation(e.to_string()))?;
// OpenAI returns 1536-dim vectors as f64 — cast to f32.
let vec: Vec<f32> = body["data"][0]["embedding"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_f64().unwrap() as f32)
.collect();
Ok(vec)
}
fn dimension(&self) -> usize { 1536 }
}
// Register with Rocket state via build_rocket's .manage() chain.
let model: Arc<dyn EmbeddingProvider> = Arc::new(OpenAiEmbedder { ... });
}
Configuration
| Environment Variable | Default | Description |
|---|---|---|
SCHEMABOUND_EMBEDDING_DIMENSION | 768 | Dimension of embedding vectors produced by the model. Must match across all components using the same model. |
Usage With Semantic Trace Search
When you enable semantic trace search, pass your configured EmbeddingProvider to the search subsystem at startup. The search endpoint (POST /api/memory/search) uses the model’s vectors for cosine similarity computation against stored trace embeddings (see Semantic Search API Reference).
The backend automatically computes and persists embeddings when memory entries are created via AgentMemoryStore::on_tool_call(), so no manual wiring is required — just ensure your EmbeddingModel implementation produces vectors with the dimension specified in SCHEMABOUND_EMBEDDING_DIMENSION.
Error Handling
The trait returns Result<Vec<f32>, EmbeddingError> where EmbeddingError covers:
- Transport failures (network timeouts, rate limits)
- Malformed responses from upstream APIs
- Invalid vector dimensions (mismatch between configured dimension and returned length)
- Model not configured
Propagate with ? in your implementation or map to your application-specific error type using map_err.
Semantic Search API Reference
Endpoints for querying agent memory entries via semantic similarity over embedding vectors.
POST /api/memory/search
Compute cosine similarity between a query string and stored memory entry embeddings, returning the top-K most similar results ranked by score.
Request
POST /api/memory/search HTTP/1.1
Content-Type: application/json
{
"query": "FK constraint violations on users table",
"top_k": 10,
"org_id": "550e8400-e29b-41d4-a716-446655440000"
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
query | string | yes | — | Natural language query text. The raw JSON content of memory entries is embedded as-is (no normalization). |
top_k | integer | no | 10 | Maximum number of results to return. Clamped to [1, 100] in production. |
org_id | string (UUID) | no | — | Optional filter: returns entries only from sessions belonging to this organization. Stored as a UUID string; pass null or omit to skip filtering. |
Response
{
"data": [
{
"entry_id": "abc123...",
"score": 0.8734,
"content": "{\"tool\":\"query\",\"args\":\"SELECT ... FROM users WHERE id = ?\"}",
"created_at": "2026-08-02T14:30:00Z",
"related_cascade_targets": ["orders", "order_items"]
},
{
"entry_id": "def456...",
"score": 0.7291,
"content": "{\"tool\":\"schema_introspect\",\"args\":\"SHOW CREATE TABLE users\"}",
"created_at": "2026-08-02T14:31:00Z",
"related_cascade_targets": []
}
]
}
| Field | Type | Description |
|---|---|---|
entry_id | string (UUID) | Primary key of the matching memory entry. |
score | float | Cosine similarity score in [-1, 1]. Higher values indicate greater semantic similarity to the query. Identical vectors → 1.0; orthogonal → 0.0; anti-aligned → -1.0. |
content | string | The original memory entry content (raw JSON string stored in the agent_memory_entries.content column). |
created_at | ISO-8601 datetime | When the memory entry was created. |
related_cascade_targets | array of strings | FK cascade targets from schema_topology. Populated only after #021 merges; currently always empty. |
Errors
| Status Code | Response Body | Description |
|---|---|---|
| 400 Bad Request | {"error": "..."} | Invalid request body (e.g., missing query field). |
| 500 Internal Server Error | {"error": "Embedding failed: ..."} | Embedding model returned an error during query embedding. |
| 500 Internal Server Error | {"error": "Failed to query embeddings: ..."} | Database query for stored embeddings failed. |
Cosine Similarity Computation
The backend computes cosine similarity using the formula:
cosine_similarity(A, B) = (A · B) / (|A| × |B|)
Where:
Ais the query embedding vector (f32)Bis a stored embedding vector (f32) retrieved frommemory_entry_embeddings.embedding_vector·denotes dot product|·|denotes L2 norm
The computation uses ndarray for efficient matrix operations. Norms are cached in a HashMap<Vec<f32>, f32> keyed by embedding bytes to avoid repeated square root calculations across queries.
Storage Format
Stored embeddings use BLOB (binary) column type — raw f32 byte arrays serialized via bytemuck:
768 dims × 4 bytes/dim = 3072 bytes per vector (default dimension)
1536 dims × 4 bytes/dim = 6144 bytes per vector (OpenAI ada-002)
SeaORM maps Vec<u8> entity fields to BLOB columns automatically via DeriveEntityModel. The migration m20260802_000001_create_memory_entry_embeddings.rs defines the schema:
#![allow(unused)]
fn main() {
ColumnDef::new(MemoryEntryEmbeddings::EmbeddingVector)
.binary() // → MySQL BLOB / PostgreSQL BYTEA / SQLite BLOB
.not_null(),
}
FK Cascade Enrichment (Deferred)
After #021 merges, the search handler will query schema_topology for CASCADE relationships on tables referenced in top-K results:
SELECT target_table FROM fk_relationships
WHERE source_table = ? AND cascade_type = 'CASCADE'
This enriches results with metadata like "related_cascade_targets": ["orders", "order_items"], enabling queries like “find all sessions that modified tables cascading from users” to return entries mentioning FK relationships.
Example Usage (curl)
# Basic search — top 5 most similar memory entries
curl -X POST http://localhost:8000/api/memory/search \
-H "Content-Type: application/json" \
-d '{
"query": "agent got stuck on FK cascade detection",
"top_k": 5
}'
# Filter by organization
curl -X POST http://localhost:8000/api/memory/search \
-H "Content-Type: application/json" \
-d '{
"query": "schema introspection errors",
"org_id": "550e8400-e29b-41d4-a716-446655440000"
}'
# Retrieve full result set for post-processing (top_k=100)
curl -X POST http://localhost:8000/api/memory/search \
-H "Content-Type: application/json" \
-d '{
"query": "tool call failures",
"top_k": 100
}' | jq '.data[] | {entry_id, score}'
SCHEMABOUND Glossary
Centralized terminology definitions for domain-specific concepts across the SCHEMABOUND framework. Terms are organized by four domains: core architecture, security operations, runtime infrastructure, and communication protocols.
Use this glossary as a lookup reference when encountering unfamiliar terms in documentation or code. Each entry includes the definition, usage context, and cross-references to related terms for deeper understanding.
Domain Index
- Core Concepts — OAM paradigm, schema modes, identity federation, billing units
- Security Terms — mTLS, RBAC, OIDC, JWT, SCIM, SSSD/PAM hardening
- Operational Terms — EventBus, Chain of Responsibility, Auto-Rewind workers, AAU tracking
- Protocol Terms — gRPC, protobuf, OCSF, W3C traceparent, NDJSON
Core Concepts Glossary
Terms related to SCHEMABOUND’s architectural paradigm, schema operating modes, identity federation model, and billing units. These are the foundational concepts that differentiate SCHEMABOUND from traditional ORMs and data access layers.
Agentic Activity Unit (AAU)
A dimensionless measure of governance cost per tool call in SCHEMABOUND. AAU weights reflect the relative risk and computational overhead of different operation intents: Read = 1x, Write = 3x, Governed = 5x. The total AAU consumed by an agent session determines billing tier thresholds and rate limit windows.
See also: Intent Classification, Governance Cost Model
Bring Your Own Identity (BYOI)
A federated identity pattern where SCHEMABOUND trusts external identity providers (IdPs) such as Entra ID, Okta, LDAP, or any OIDC-compliant provider rather than maintaining its own user registry. BYOI enables SCHEMABOUND to integrate into existing enterprise directory structures without recreating permission systems from scratch.
See also: OIDC Token Validation, RBAC Mapping
Code-First Mode
A schema operating mode where database tables are registered explicitly in application code via Rust derive macros (#[derive(LlmSchema)]) or Python declarative bases. SCHEMABOUND enforces strict schema validation against registered models; unregistered tables are rejected at runtime unless the mode is switched to Hybrid or Data-First.
See also: Hybrid Mode, Data-First Mode
Control Plane
A first-class workflow execution layer in SCHEMABOUND that tracks and drives multi-step agent workflows through stateful plans. Each plan consists of named steps with explicit dependency relationships (depends_on); when a step completes, the control plane returns an LlmContextUpdate carrying tool output and schema changes back to the LLM for the next invocation.
See also: Plan, Step, Llm Context Update
Data-First Mode
A schema operating mode where SCHEMABOUND introspects an existing database at runtime and builds a read-only mirror of tables, columns, indexes, and triggers. Useful for integrating legacy databases without rewriting code; however, this mode provides no compile-time validation and relies on heuristics to detect ORM conventions (Hibernate camelCase vs EntityFramework PascalCase).
See also: Code-First Mode, Hybrid Mode
Governance Cost Model
The principle that every tool call in SCHEMABOUND carries a governance cost proportional to its risk profile. Read operations cost less than writes; administrative operations (schema changes, user management) cost more still. The model is implemented via AAU intent classification and enforced through policy evaluation before any query reaches the mapper layer.
See also: Agentic Activity Unit, Tool Contract Policy Engine
Hybrid Mode
A schema operating mode that combines Data-First introspection with Code-First registered models. Tables not explicitly registered in code are treated as Data-First (introspected at runtime); tables registered via derive macros are validated strictly against their declared schemas. This is the recommended production mode for incremental migration from legacy databases to SCHEMABOUND-managed schemas.
See also: Data-First Mode, Code-First Mode
Intent Classification
The process of categorizing a tool call’s intent (e.g., read_select, write_insert, admin) based on the SQL or query template being executed. Intent classification determines AAU weighting and policy enforcement severity — governed intents require approval workflows, while reads may pass through with minimal checking.
See also: Agentic Activity Unit, Tool Contract Policy Engine
Llm Context Update
A feedback object returned by the SCHEMABOUND control plane after each step in a multi-step plan completes. Carries tool_output_json (serialized result), schema_additions (list of NEW/MODIFIED/REMOVED tables from schema_table_hints), and augmentation_hints (human-readable strings for system prompt insertion). The LLM always sees current schema state before choosing its next action, ensuring tool definitions stay accurate even when schema evolves mid-workflow.
See also: Control Plane, Plan
Object Agent Mapping (OAM)
The architectural paradigm that maps agent intent to data operations rather than simply mapping objects to relational rows (as an ORM does). Where an ORM translates application code to SQL, OAM mediates what agents can see and do based on schema mode, identity context, injection guard filtering, and policy enforcement. SCHEMABOUND is the reference implementation of OAM.
See also: ORM, Schema Operating Modes
ORM (Object-Relational Mapper)
A programming technique that translates application objects into database rows and vice versa. Traditional ORMs (Hibernate, Entity Framework, SQLAlchemy) focus on object persistence; OAM extends this by adding intent mediation, identity context enrichment, and policy enforcement between the application layer and the data access layer.
See also: Object Agent Mapping, Schema Operating Modes
Plan
A named, versioned collection of steps with explicit dependency relationships in SCHEMABOUND’s control plane. Submitted via SubmitPlanRequest, a plan is assigned a stable plan_id that clients use for all subsequent operations (get status, execute step, cancel). Steps declare dependencies with depends_on; the control plane validates the dependency graph before any step executes and rejects cycles.
See also: Control Plane, Step
Schema Operating Modes
The three execution states that determine how SCHEMABOUND interacts with database schemas: Data-First (runtime introspection of existing databases), Code-First (strict validation against registered models), and Hybrid (combination of both). Mode selection affects security posture, development velocity, and migration complexity.
See also: Data-First Mode, Code-First Mode, Hybrid Mode
Step
A single tool call within a SCHEMABOUND control plane plan. Each step carries a tool_name, tool_intent (governing policy evaluation), a query_template that may reference prior step output via {{step.<id>.output}} syntax, and a depends_on list naming steps that must complete before this one executes. The control plane resolves template substitutions server-side before calling the query service.
See also: Control Plane, Plan
Plugin
A TTL schema extension loaded by OAM at runtime via OamRuntime::discover_plugins(). Plugins add new RDF classes and properties to the runtime registry without modifying the base schema — open to extension, closed to modification. Installed locally via sb plugin install or from a registry via sb plugin from-registry. Plugins are schema-only (no executable code); validation happens at install time against base schema boundaries.
See also: Plugin Registry
Plugin Registry
A Gitea-hosted repository of published plugins; each release contains a Turtle file (<name>.ttl) and metadata JSON (plugins.json). Community members publish via sb plugin publish; the backend receives webhook events on new releases, validates assets, and persists catalog metadata for discovery API queries. Customers host their own self-hosted Gitea instances; internal community uses schemabound-lab’s Gitea.
See also: Plugin
Model-View-Intent (MVI) State Machine
The architectural foundation of SCHEMABOUND’s MVAI (Agent-Native Architecture). Application state is a Merkle DAG of content-addressed RDF triples in the triplestore; UI components render reactively from OAM-enriched triplestore queries without API calls; natural language intent becomes atomic state transformations via quantized LLMs on edge NPUs, verified by Merkle DAG integrity proofs before any change is applied. MVI replaces REST/gRPC as the interface between agent and data — the data model IS the API contract.
See also: Object Agent Mapping, Triplestore
Triplestore
A specialized database for storing RDF triples (subject-predicate-object) as the fundamental data model in SCHEMABOUND’s MVAI architecture. Provides content-addressed storage with Merkle DAG integrity proofs — every change produces a new root hash, enabling cryptographic verification of state at any point in time and atomic rollback to any prior verified root. Predicate URIs create a universal vocabulary that any agent (including quantized LLMs on smart glasses NPUs) can consume without reading endpoint documentation.
See also: Model-View-Intent, Content-Addressed State, RDF Agent-Native Intent-to-Code Migrations
Content-Addressed State
A storage paradigm where data is identified by its cryptographic hash rather than location. In SCHEMABOUND, every triplestore change produces a new Merkle root through the RDF prolly tree — providing versionless evolution (new predicates don’t break old consumers), atomic rollbacks to any prior verified state, and end-to-end integrity verification without manual schema management. Content-addressed state is the mechanism by which OAM’s governance cost model ensures every transformation is cryptographically verifiable before reaching the view layer.
See also: Triplestore, Model-View-Intent
Security Terms Glossary
Terms related to SCHEMABOUND’s identity federation, authentication/authorization mechanisms, cryptographic protocols, and host hardening configurations. These terms are relevant when configuring mTLS, JWT rotation, LDAP sync, RBAC mapping, or SCIM provisioning.
API Key Registry
A persistent store of per-client API keys used for service-to-service authentication outside the OIDC/JWT flow. Each key carries a scoped set of permissions and can be rotated without affecting other clients. Keys are stored hashed (never plaintext) and rotated via the SCHEMABOUND_API_KEY_ROTATION schedule.
See also: OIDC Token Validation, JWT Secret Rotation
Casbin ABAC Integration
Integration with the Casbin library for Attribute-Based Access Control (ABAC). SCHEMABOUND’s RBAC roles are mapped to Casbin subject-object-action triples, enabling fine-grained policies that combine role membership with runtime attributes (organization ID, table name prefixes, intent type). Casbin policies are loaded at startup and hot-reloaded on change.
See also: RBAC Role Mapping, Policy Evaluation
Host Hardening: SSSD and PAM
Linux system hardening configuration using System Security Services Daemon (SSSD) for identity integration with LDAP/AD directories, combined with Pluggable Authentication Modules (PAM) for enforcing authentication policies. SCHEMABOUND deployment nodes are hardened following these settings to prevent credential theft, lateral movement, and privilege escalation attacks against the runtime itself.
See also: LDAP Sync, SCIM Provisioning
JWT Secret Rotation
The process of rotating JSON Web Token signing keys without downtime. SCHEMABOUND supports rolling key rotation via SCHEMABOUND_JWT_SECRETS (comma-separated list): new tokens are signed with the last entry; old entries remain valid until explicitly removed. This enables zero-downtime key rotation for HS256, RS256, and ES256 algorithms.
See also: OIDC Token Validation, API Key Registry
LDAP Group-to-RBAC Role Mapping
The mechanism that maps LDAP directory groups to SCHEMABOUND RBAC roles. When a user authenticates via LDAP, their group memberships are evaluated against the mapping rules defined in SCHEMABOUND_LDAP_GROUP_MAP; matched groups grant corresponding RBAC roles (e.g., cn=auditors,dc=example → role:audit_viewer). Groups not matching any rule receive no SCHEMABOUND permissions.
See also: LDAP Sync, RBAC Role Mapping
LDAP Sync
The periodic synchronization of user and group objects from an external LDAP or Active Directory directory into SCHEMABOUND’s identity layer. SCHEMABOUND uses SSSD for bind authentication and pulls group membership via LDAP filters; synced entities are used exclusively for RBAC role assignment, never as primary credentials.
See also: Bring Your Own Identity, LDAP Group-to-RBAC Role Mapping
mTLS Setup
Mutual Transport Layer Security configuration for service-to-service authentication. SCHEMABOUND uses schemabound-certgen to generate Certificate Authorities, server keys, and client bundles; clients present certificates during gRPC handshake and the server validates them against the CA before accepting any request. Development mode is available via SCHEMABOUND_MTLS_DEV_MODE.
See also: OIDC Token Validation, Host Hardening: SSSD and PAM
OIDC Token Validation
The process of validating OpenID Connect tokens presented by clients for authentication. SCHEMABOUND validates JWTs against the configured issuer’s JWKS endpoint, checks expiration (exp), issued-at (iat), and audience (aud) claims, and extracts identity context (user ID, organization) into QueryRuntimeContext. BYOI-compatible providers include Entra ID, Okta, Keycloak, and any standard OIDC endpoint.
See also: JWT Secret Rotation, Bring Your Own Identity
RBAC Role Mapping
The mechanism that assigns role memberships to authenticated users based on identity attributes (groups, claims, custom policies). SCHEMABOUND’s RBAC model supports hierarchical roles (admin > writer > reader), role inheritance, and deny-overrides. Roles are evaluated through the Casbin ABAC engine for fine-grained control over table access, intent permissions, and administrative operations.
See also: Casbin ABAC Integration, LDAP Group-to-RBAC Role Mapping
SCIM Provisioning
System for Cross-domain Identity Management (SCIM v2) server implementation within SCHEMABOUND. Enables automated user lifecycle management: when a user is created, updated, or deactivated in the external IdP, SCIM events trigger corresponding RBAC role assignments or revocations in SCHEMABOUND without manual intervention.
See also: LDAP Sync, Host Hardening: SSSD and PAM
Session Management
The lifecycle management of authenticated user sessions within SCHEMABOUND. Includes session creation on first successful authentication, token refresh on expiry, revocation on logout or security event, and audit logging via SessionActivityHandler. Sessions are scoped to organization boundaries; cross-organization session reuse is blocked by policy evaluation.
See also: OIDC Token Validation, RBAC Role Mapping
Operational Terms Glossary
Terms related to SCHEMABOUND’s runtime infrastructure, event publishing pipeline, self-healing workers, and observability systems. These terms are relevant when configuring the EventBus, tuning Auto-Rewind workers, setting up audit logging, or integrating with SIEM platforms.
Auto-Rewind Worker
A background data resilience worker in SCHEMABOUND that handles historical schema replay and topology refresh. When a table’s schema changes (columns added, indexes modified), Auto-Rewind replays the change through all dependent components — local mappers, TCP mappers, injection guard caches — ensuring consistency across the runtime without manual intervention.
See also: Chain Integrity Verification, Data Resilience Workers
Chain of Responsibility (CoR)
The handler pattern implemented by SCHEMABOUND’s EventBus. Every domain event — query execution, validation failures, session registration, trigger fires — flows through a registered sequence of handlers before any subscribers see it. Built-in CoR handlers include AuditLogHandler, QueryMetricsHandler, and SessionActivityHandler. Custom handlers can be registered via the SharedHandler trait.
See also: EventBus, Chain Integrity Verification
Chain Integrity Verification
A background worker that verifies the tamper-evident SHA-256 hash chain maintained by SCHEMABOUND’s audit logging pipeline. Each AuditEventEnvelope carries a sequence number and prev_hash; this worker periodically anchors verification at configurable depths (SCHEMABOUND_CHAIN_VERIFY_ANCHOR_DEPTH) to detect any tampering in the event stream before it reaches SIEM platforms.
See also: Auto-Rewind Worker, Audit Log Handler
Data Resilience Workers
The collective term for background workers that maintain SCHEMABOUND’s operational integrity: Auto-Rewind (schema topology refresh and historical replay) and Chain Integrity Verification (SHA-256 hash chain validation). Both run as independent goroutines with configurable exponential backoff (RESTART_BASE_DELAY_SECONDS) and are managed by the self-healing infrastructure health checker state machine.
See also: Auto-Rewind Worker, Chain Integrity Verification
EventBus
The typed event bus at the center of SCHEMABOUND’s runtime architecture. All domain events flow through the EventBus before reaching handlers or subscribers. Events are strongly-typed Rust enums (Event::QueryExecuted, Event::PlanCreated, etc.) that carry structured payloads; handlers implement the EventHandler trait and return a HandleOutcome indicating whether processing should continue down the chain.
See also: Chain of Responsibility, MultiTransport Exporter
Governance Cost Model
The principle that every tool call in SCHEMABOUND carries a governance cost proportional to its risk profile. Implemented via AAU intent classification (Read=1x, Write=3x, Governed=5x); enforced through policy evaluation before any query reaches the mapper layer. Total AAU consumed per session determines billing tier thresholds and rate limit windows.
See also: Agentic Activity Unit, Tool Contract Policy Engine
Llm Tool Call Audit Recorded Event
The core audit event emitted by SCHEMABOUND when a tool call completes (success or failure). Carries the full execution context: session ID, user identity, organization, traceparent W3C headers, tool name, intent classification, AAU weight, query template, result row count, and execution time. This event is what AuditLogHandler transforms into OCSF v1.1 format for SIEM export.
See also: EventBus, OCSF Format
MultiTransport Exporter
The fan-out mechanism that exports audit events to multiple destinations simultaneously. Configured via environment variables: SCHEMABOUND_AUDIT_LOG_DIR (rotating log files), SCHEMABOUND_AUDIT_WEBHOOK_URL (HTTP webhooks to SIEM platforms), and stderr for local development. The exporter is idempotent — if one destination fails, others still receive the event; failures are logged but never block the primary execution path.
See also: EventBus, OCSF Format
Query Metrics Handler
A built-in Chain of Responsibility handler that tracks query execution metrics for AAU billing and performance monitoring. Records: total queries per session, AAU consumed per intent type (read/write/governed), average execution time by tool name, and rate limit hits. Metrics are aggregated into the AAU Dashboard viewable at /billing/usage and exportable via the D3.js heatmap component for ORM model × intent cost analysis.
See also: Agentic Activity Unit, AAU Dashboard
Self-Healing Infrastructure
SCHEMABOUND’s automatic recovery mechanism for background workers and connection failures. Uses a health checker state machine that monitors Auto-Rewind and Chain Integrity worker liveness; on failure, triggers exponential backoff retry (RESTART_BASE_DELAY_SECONDS, max 5 attempts before escalating to operator alert). The state machine is topology-aware: it consults the schema graph during recovery to ensure dependent components are restarted in correct order.
See also: Data Resilience Workers, Auto-Rewind Worker
Session Activity Handler
A built-in Chain of Responsibility handler that captures per-session memory entries for SCHEMABOUND’s Agent Memory feature. Records observations, tool call results, and decision points as chronological history scoped to a session ID; history is retrievable via {{memory_context}} in prompt hooks so agents carry prior context across invocations without manual serialization.
See also: Agent Memory, Prompt Augmentation
Tool Contract Policy Engine
The component that evaluates tool-call contracts against registered policies before execution. Determines intent classification (read_select, write_insert, admin), applies AAU weighting based on intent, and enforces governance rules (approval workflows for governed intents, rate limits per organization). Policies are loaded at startup from SCHEMABOUND_POLICY_DIR and hot-reloaded on change without restart.
See also: Agentic Activity Unit, Intent Classification
Protocol Terms Glossary
Terms related to SCHEMABOUND’s communication protocols, serialization formats, and external integrations. These terms are relevant when implementing gRPC clients, parsing audit event payloads, or integrating with observability platforms.
gRPC
Google’s remote procedure call framework used as the primary transport protocol for SCHEMABOUND’s control plane and query execution APIs. The runtime listens on port 50051 by default; all service methods (SubmitPlan, ExecuteStep, GetSchema) are defined as protobuf message types in schemabound-proto. gRPC provides built-in streaming, compression, and mTLS authentication — the same transport used for both client-to-server queries and server-to-SIEM audit exports.
See also: protobuf, Execute Step Request
NDJSON (Newline-Delimited JSON)
The serialization format used by SCHEMABOUND’s audit event stream and MultiTransport Exporter. Each line of an NDJSON file or HTTP POST body contains a single complete JSON object representing one event; lines are delimited by \n. This format is streaming-friendly, parseable line-by-line without loading the full document into memory, and widely supported by SIEM platforms (Splunk, Elastic, Datadog) for log ingestion.
See also: OCSF Format, MultiTransport Exporter
OCSF (Open Cybersecurity Schema Framework)
An open standard for cybersecurity event data modeling maintained by the OCSF community. SCHEMABOUND exports audit events in OCSF v1.1 format, mapping internal QueryExecuted and LlmToolCallAuditRecorded events to OCSF Class 6003 (Database Activity). The framework provides standardized fields for timestamp, actor identity, target resource, metadata trace IDs, and custom extensions — ensuring SIEM platforms can correlate SCHEMABOUND events with other security telemetry without custom parsing.
See also: Llm Tool Call Audit Recorded Event, NDJSON Format
OCSF Class 6003
The specific OCSF schema class for “Database Activity” events. SCHEMABOUND maps every QueryExecuted and LlmToolCallAuditRecorded event to this class, populating standardized fields: activity_id (query action), database_name, table_name, actor_type (user/service), severity (informational/warning/critical based on AAU weight), and metadata (W3C traceparent headers for distributed tracing correlation).
See also: OCSF Format, Llm Tool Call Audit Recorded Event
protobuf
Protocol Buffers, Google’s IDL (Interface Definition Language) and serialization format. SCHEMABOUND defines all gRPC service contracts as .proto files in the schemabound-proto crate: ControlPlaneService, QueryService, SchemaService. Protobuf provides schema versioning via field numbers, binary encoding for efficient wire transfer, and language-specific code generation (Rust, Python, TypeScript, Go) via protoc plugins.
See also: gRPC, Execute Step Request
W3C Traceparent Header
The W3C distributed tracing standard header (traceparent) that identifies a request’s position within a larger trace across multiple services. SCHEMABOUND propagates traceparent through every audit event envelope emitted by the EventBus; when present, values appear as metadata.trace_uid and metadata.span_uid in OCSF records. Upstream instrumentation (OpenTelemetry, Jaeger) can inject these headers to join SCHEMABOUND events into existing observability platforms.
See also: OCSF Format, MultiTransport Exporter
API Reference
Use this page to choose the fastest way to integrate SCHEMABOUND into your product. Whether you are embedding SCHEMABOUND into an application, automating workflows, or standardizing service-to-service communication, the references below point to the public surfaces intended for real adoption.
Client SDKs
Choose the SDK that best matches your application stack.
Build Python applications and automation flows that integrate SCHEMABOUND with a lightweight, script-friendly client surface.
Integrate SCHEMABOUND into .NET services and enterprise applications with a familiar typed client experience.
Use the core Rust crate when you want maximum control, native performance, or direct access to the public runtime model.
Shared Contract
When you need a language-neutral integration surface, start with the protocol definitions.
Protobuf Definitions
Review the public gRPC contract, message shapes, and service definitions that keep multi-language integrations aligned.
HTTP REST API
The Rocket HTTP backend exposes a live, interactive API reference via Swagger UI. Use it to explore endpoints, inspect request/response schemas, and try calls directly from the browser.
Browse all route schemas, try requests live, and inspect request/response bodies.
Requires the backend to be running (make http-start).
OpenAPI JSON: http://localhost:8000/api/openapi.json
Suggested Starting Points
- Building application logic in Python: start with the Python SDK.
- Shipping a service or platform integration on .NET: start with the .NET SDK.
- Building custom runtimes or native integrations: start with the Rust client crate.
- Aligning multiple clients or generating your own bindings: start with the protobuf definitions.
- Exploring or testing HTTP endpoints interactively: start with the Swagger UI.
Contribution Workflow
SCHEMABOUND accepts contributions across the public runtime, SDKs, and documentation. The goal of the workflow is simple: keep the public contract stable, keep changes reviewable, and make it clear where each kind of contribution belongs.
Choose The Right Repository
Start in the repository that owns the surface you want to improve.
- Use
schemabound-publicfor shared runtime behavior, public contract changes, and core Rust functionality. - Use
schemabound-pythonfor Python-specific helpers, bindings, packaging, and docs. - Use
schemabound-dotnetfor .NET-specific helpers, bindings, packaging, and docs.
Contribution Flow
- Fork the repository that matches your change.
- Create a small, clearly named branch.
- Add or update tests with the change.
- Update documentation when the public workflow or contract changes.
- Open a pull request against
main. - Address review feedback and keep the branch current until merge.
Branch And PR Expectations
- Keep each change focused enough to review in one pass.
- Prefer small pull requests over large mixed-scope changes.
- If a unit test needs excessive mocking, simplify the production code before adding more scaffolding.
- If a test is called
integration, it should talk to a real started runtime over the network.
Local Validation
Enable the repo-managed hooks after cloning when they are available:
make hooks-install
The local pre-commit path is intended to catch quality and test failures before you open a pull request.
CI Workflow
The CI system runs comprehensive checks on every push to main and pull request. The pipeline:
- Quality — formatting, linting, and clippy across all components (backend, Rust SDKs, Python/.NET SDKs, frontend)
- Tests — unit and integration tests for backend, schemabound-public, python-sdk, dotnet, frontend, and narrate run in parallel
- Security Audit — cargo-audit scans dependencies for known vulnerabilities
- Docs Build — verifies the documentation site builds successfully with mdBook
All jobs must pass before merge. The pipeline runs in under 10 minutes on a fresh ubuntu-latest runner.
External Contributor Setup
Step 1: Fork And Clone
Example for schemabound-public:
git clone https://github.com/<your-username>/schemabound-public.git
cd schemabound-public
git remote add upstream https://github.com/schemabound/schemabound-public.git
Step 2: Create A Branch
Use a descriptive branch name that matches the change you are making.
git checkout -b improve-runtime-context-docs
Step 3: Make The Change
Guidelines:
- write tests alongside behavior changes
- prefer repo Make targets where they exist
- keep unit tests deterministic and in-process
- keep integration tests runtime-backed and network-bound
- update docs when the public contract or contribution workflow changes
Step 4: Open A Pull Request
Push your branch and open a PR against upstream/main:
git push origin improve-runtime-context-docs
Include:
- a short description of the change
- the problem being solved
- how you validated the change
- any public contract impact or compatibility notes
Review Standards
SCHEMABOUND reviews focus on a few practical questions:
- does the change belong in this repository
- does the implementation stay within the intended public boundary
- do the tests match the behavior being claimed
- does the documentation still describe the public surface accurately
Common Paths
Fixing A Core Runtime Issue
Start with schemabound-public if the change affects shared runtime behavior, protocol shape, or public Rust functionality.
Improving A Python Integration
Start with schemabound-python if the change is specific to the Python developer experience, helper layer, or packaging surface.
Improving A .NET Integration
Start with schemabound-dotnet if the change is specific to the .NET developer experience, helper layer, or packaging surface.
Release Expectations
Pull requests validate changes. Releases publish them.
If you need publication timing or release behavior details, continue with Testing and Release Policy.
Testing and Release Policy
SCHEMABOUND treats testing and release discipline as part of the public contract. The goal is not just to ship code that works, but to ship public surfaces that are validated, predictable, and safe to adopt.
Test Layers
Each layer has a fixed contract. Violating that contract — mocking in integration tests, introducing I/O in unit tests, stubbing a container away in an E2E test — is a test smell, not a trade-off.
| Layer | Collaborators | Mocks? | Stubs? | Where it runs |
|---|---|---|---|---|
| Unit | In-process only | Never | Stubs are acceptable | Local, no I/O |
| Integration | Real containerized runtimes | Never | Never | Local containers (Testcontainers or Compose) |
| End-to-End | Fully deployed environment | Never | Never | Local or staging deployment |
Unit Tests
Unit tests should stay fast, deterministic, and in-process.
Expectations:
- no live RPC or HTTP calls
- no containers
- no shared ports or database paths between test cases
Stubs and mocks are not the same thing. A stub returns a fixed value to put the system under test into a known state — that is acceptable. A mock asserts that the system under test calls it in a specific way (call count, argument shapes). If a unit test needs a mock, the production dependency has a design problem. Simplify the dependency before adding mock scaffolding.
If a unit test needs a large stub or mock hierarchy, treat that as a signal to simplify the production code, not to add more scaffolding.
Test isolation. Each test that starts a server or opens a database must use a unique address and path. Use atomic counters or unique temporary paths to avoid flaky collisions:
- Unique ports per test — allocate with an atomic counter, never hardcode a shared port.
- Unique database paths — use a helper like
get_test_db_path()that returns a temp-scoped unique path per test.
Assertion style. Prefer exact value assertions over substring matching.
- Use
matches!()for enum variant checks instead of.to_string().contains(). - Avoid snapshot tests — write explicit expected values so failures are immediately actionable.
- Assert on typed values, not serialized strings, wherever the types are available.
Integration Tests
Integration tests should validate real boundaries.
Expectations:
- real network requests over the actual protocol (TCP, gRPC, HTTP)
- a runtime that actually starts and listens on a real port
- real dependencies where the boundary matters — no shortcut local clients standing in for protocol behavior
- no stubs or mocks at any layer
If a test is named integration, it must talk to a real started runtime over the network. Tests that call in-process implementations directly are unit tests, not integration tests, regardless of what they are named.
End-To-End Tests
End-to-end tests validate a deployed environment from the outside. They are the right fit for rollout, wiring, secret, and networking concerns when that environment exists.
Expectations:
- fully deployed environment, nothing replaced or stubbed
- tests drive the system only through public entry points (API, CLI, browser)
- environment lifecycle (start, seed, teardown) is explicit and automated
CI Expectations
Pull requests to main should pass the quality profile relevant to the repository being changed. That can include:
- linting and formatting
- unit and integration tests
- build validation
- documentation builds
- coverage or maintainability gates where they add real signal
- dependency and security checks
SDK-specific maintainability gates are intentionally selective. They should exist where a codebase contains enough handwritten logic to justify them, not just because a language binding exists.
Local Validation
When available, enable the repo-managed hooks locally:
make hooks-install
The local hook path is intended to catch common failures before you open a pull request. It complements CI, but does not replace it.
CI Workflow
The CI system now runs all tests in parallel for better performance and stability. When changes are pushed to main or opened as pull requests, the system:
- Runs quality checks (formatting, linting)
- Starts required infrastructure (Dolt, LDAP)
- Runs all component tests in parallel
- Performs security audits and coverage checks
- Builds documentation
This approach is more stable than the previous diff-based scoping because it ensures comprehensive test coverage without complex dependency tracking.
Release Discipline
Validation and publication are separate steps.
The expected release path is:
- merge reviewed code to
main - allow validation to complete on the merged revision
- create the appropriate release tag when publication is intended
Current public release patterns include:
public-v*for public subtree publicationsdk-python-v*for Python SDK publicationsdk-<language>-v*as the general form for future SDK release workflows
Why This Separation Exists
Separating merge validation from publication keeps the public SCHEMABOUND surface more predictable for adopters.
It ensures that:
- every published artifact passed review first
- release timing is deliberate rather than accidental
- public packages and docs stay aligned with validated source
- teams can reason about adoption risk more clearly
SCHEMABOUND Documentation — Editorial Style Guide
Consistency rules for terminology, formatting, and tone across all documentation assets. These guidelines ensure that readers encounter uniform language regardless of which page they’re reading, reducing cognitive friction when switching between conceptual explanations, how-to guides, and reference material.
Nomenclature Consistency
SCHEMABOUND has a specific set of canonical terms that must be used exactly as written. Deviating from these terms creates confusion for readers trying to look up information elsewhere in the documentation or in code comments.
| Term | Correct Usage | Incorrect Usage |
|---|---|---|
| Framework name | SCHEMABOUND (all caps) | SchemasBound, schemabound, SCHEMABOUND |
| Core paradigm | Object Agent Mapping (OAM) | ORM-based mapping, data mapping |
| Schema modes | Data-First, Code-First, Hybrid | Data-first mode, code-first mode, hybrid schema |
| Billing unit | Agentic Activity Unit (AAU) | billing units, activity points, governance points |
| Identity pattern | Bring Your Own Identity (BYOI) | BYO Identity, custom identity, external IdP |
| Control plane object | Plan (capitalized when referring to the SCHEMABOUND concept) | plan, workflow, task |
| Control plane step | Step (capitalized when referring to a single execution unit within a Plan) | step, action, tool call |
* Use “OAM” only when referring to the paradigm itself; always introduce as “Object Agent Mapping (OAM)” on first use in any document.
Terminology Definitions
These terms appear frequently across SCHEMABOUND documentation and must be defined at first usage:
| Term | Definition |
|---|---|
| BYOI | Bring Your Own Identity — federated identity pattern where SCHEMABOUND trusts external IdP (Entra ID, Okta, LDAP) rather than maintaining its own user registry |
| OCSF | Open Cybersecurity Schema Framework — standardized audit event format (v1.1); Class 6003 covers database activity events exported by SCHEMABOUND |
| CoR | Chain of Responsibility — handler pattern used by EventBus; every domain event flows through registered handlers before subscribers see it |
| AAU Dashboard | The /billing/usage endpoint and D3.js heatmap visualization showing ORM model × intent governance cost distribution |
| Auto-Rewind | Background worker that replays schema topology changes through dependent components (local mappers, TCP mappers, injection guard caches) without manual intervention |
| Chain Integrity Verification | Background worker that periodically anchors SHA-256 hash chain verification at configurable depths to detect tampering in audit event streams |
Formatting Rules
Tables
Use Markdown tables for configurations, environment variables, and OCSF class mappings — never bullet lists when presenting quantitative reference material. Tables make it easier for operators to scan and compare values during incident response or capacity planning.
| Variable | Default | Purpose |
|---|---|---|
| `SCHEMABOUND_AUDIT_STDOUT` | `ocsf` | Stderr output format: `ocsf`, `json`, or `off` |
Code Blocks
Always include a language identifier for syntax highlighting:
# Correct
```rust
use schemabound::executor::QueryService;
let service = QueryService::new()?;
service.execute_query(query).await?;
Incorrect
let executor = MyExecutor::new();
Supported languages: `rust`, `python`, `typescript`, `go`, `bash` (for shell commands), `json` (for API payloads), `mermaid` (for architecture diagrams).
### API Endpoints
Use HTTP method + path format for all endpoint references. Always include the leading slash and quote the full path:
```markdown
# Correct
- `GET /billing/usage` returns daily AAU totals
- POST to `/api/plans/:plan_id/steps` creates a new step
# Incorrect
- GET billing usage returns...
- api.plans.plan_id.steps
Error Types
Format Rust enum variants using backtick formatting with the module path prefix:
# Correct
The executor returns `ExecutorError::ConnectionFailed` when the target database is unreachable.
# Incorrect
"connection failed error", "Executor Error: Connection Failed", ExecutorError::Connection_Failed
Callout Boxes (Summary Cards)
Use mdbook’s blockquote syntax for TL;DR summary cards at the top of complex pages. Always include both a one-paragraph overview and a “use this page if you need to…” section that helps readers decide whether to continue reading:
> **TL;DR** — [one-sentence overview of what this page covers]
>
> **Use this page if you need to:** [3-5 bullet points describing reader goals this page addresses]
## Key Configuration (optional, for operational pages)
| Variable | Default | Purpose |
|---|---|---|
Tone and Voice
Do
- Be precise, technical, and concise. Assume the reader is a competent engineer who is unfamiliar with SCHEMABOUND specifics but understands general software architecture concepts.
- Use active voice: “SCHEMABOUND validates tokens against…” rather than “Tokens are validated by SCHEMABOUND…”
- Provide exact values when possible: “
RESTART_BASE_DELAY_SECONDSdefaults to5” rather than “the delay is configurable.”
Don’t
- Use marketing language (“revolutionary”, “game-changing”, “best-in-class”). Technical documentation should inform, not persuade.
- Explain basic programming concepts (Rust lifetimes, Python decorators, TypeScript generics) — readers of this documentation are expected to know their primary language.
- Use ambiguous pronouns: “This feature allows you to…” should specify what “this feature” is.
Cross-Referencing
Use relative links within the documentation so that all references work whether viewed locally via mdbook serve or deployed to a static site. Never use absolute URLs unless linking to external resources (GitHub repositories, official spec documents).
# Correct — Relative links within docs
See [Control Plane Reference](../reference/control-plane-reference.md) for gRPC definitions.
# Incorrect — Absolute or broken paths
See https://github.com/schemabound/...
See ../../docs/reference/control-plane.md (fragile to restructure)
External references are reserved for:
- GitHub repositories (
https://github.com/schemabound/) - Official specification documents (OIDC spec, OCSF schema registry)
- Third-party documentation (mdbook, Docusaurus, Mermaid) that readers may need for troubleshooting build issues
Review Checklist
Before submitting any PR that modifies SCHEMABOUND documentation, verify:
- All new terms follow the canonical nomenclature table above
- Code blocks include language identifiers
- API endpoints use
METHOD /pathformat with leading slash - Error types are formatted as
`Module::EnumVariant` - Tables used for configurations instead of bullet lists when presenting quantitative data
- Cross-references use relative links within the documentation tree
- Summary cards include both TL;DR paragraph and “use this page if you need to” section on complex pages