Architecture¶
Status: active design baseline
Current implementation: Cargo workspace — library crate oxiland,
crates/oxiland-cli (0.6+), crates/oxiland-capi (0.8+ C ABI with 0.11
demonstrated packaging retained at tip 0.13), and python/ PyPI package
(0.7+, not a workspace member). Oxigraph 0.5.9 remains the RDF engine.
fuzz/ holds parser/FFI/lifecycle targets.
Next review gate: before 1.0 readiness (final contract freeze; suite-wide
performance claim closed under ADR-029)
This document specifies the implemented dependency direction, ownership model, and safety boundaries. It is a design contract rather than an item-level API reference.
Design principles¶
These principles refine the project charter; the charter wins if the documents ever conflict.
- Safe Rust is the primary implementation surface.
- Oxigraph owns RDF representation, parsing, serialization, storage, and SPARQL execution wherever its semantics satisfy the compatibility contract.
- Redland compatibility belongs in adapters, not in Oxigraph forks.
- Legacy C ownership rules are isolated from the safe API.
- Unsupported behavior fails explicitly; it is never silently approximated.
- Observable compatibility wins over matching Redland's internal design.
- Expensive behavior must be visible in API names, types, or documentation.
- Public facades should be mock-free and testable against both Oxigraph and native Redland fixtures.
System boundaries¶
Rust callers ──> oxiland safe facade ──> Oxigraph
│
Python ──> oxiland (PyPI, 0.7+) ───────┤
│
C callers ──> oxiland-capi (0.8+) ─────┘
│
allocation/callback shim
Conformance manifests ─┐
Redland oracle runner ─┼─> shared normalized fixtures ─> parity evidence
Downstream consumers ──┘
Dependency arrows point inward toward the safe facade and Oxigraph. The safe
crate never depends on the C ABI crate, the Python package, native Redland,
test harnesses, or the CLI. The Python package binds the safe Rust crate
directly; it is not layered on oxiland-capi.
Repository and planned workspace¶
oxiland/
├── src/ Safe Rust facade
├── tests/ Rust integration tests
├── docs/ User, evaluator, contributor, and archive documentation
├── python/ Pythonic PyPI package (`oxiland`, 0.7+)
├── crates/
│ ├── oxiland-capi/ C ABI and opaque handle management (0.8+)
│ └── oxiland-cli/ rdfproc-shaped workflows (0.6+)
├── compatibility/
│ ├── inventory/ Generated Redland API manifests
│ ├── fixtures/ Shared behavioral fixtures
│ └── harness/ Native Redland differential runner
└── fuzz/ Parser, FFI, and lifecycle fuzz targets
Implemented through tip 0.12: the root Rust library, crates/oxiland-cli,
crates/oxiland-capi (0.11 demonstrated packaging retained), python/,
fuzz/, tests, documentation, compatibility inventory/fixtures/harness,
sealed durable storage adapters, and release automation. Remaining C behavioral
gaps are documented in user limitations—not silent expansions of the allowlist.
Component responsibilities¶
| Component | Owns | Must not own |
|---|---|---|
terms |
RDF type exports and compatibility constructors | storage or parsing |
model |
datasets, contexts, transactions, streams, and backend-independent persistence policy | syntax detection or native engine calls |
storage |
backend identities, open options, capabilities, and the sealed durable adapter boundary | RDF/SPARQL semantics or silent backend substitution |
io |
parser/serializer configuration and byte I/O | persistent-store policy |
query |
query/update configuration and result adapters | C allocation |
world |
factories, shared features, and logging hooks | global mutable runtime |
utility |
URI, digest, file, vocabulary, and Unicode helpers | RDF engine logic |
oxiland-capi |
opaque handles and C ownership translation | RDF semantics |
python/ (PyPI) |
idiomatic Python API over the safe facade | C ABI ownership; 1:1 Rust mirroring |
oxiland-cli |
command parsing and human-facing output | reusable domain behavior |
The facade should use owned and borrowed variants consistently. Fallible
construction returns Result; iteration exposes fallible iterators instead of
sentinel pointers.
Data and lifetime model¶
- Oxigraph RDF terms are the canonical in-process representation unless an accepted decision record establishes a wrapper requirement.
- Models own cloneable Oxigraph store handles; cloning a model does not clone its dataset.
- Borrowed results may not outlive their model or query execution context.
- Iterator APIs should stream.
Model::findreturnsStatementMatches(ADR-005). Parser (0.2) and query (0.3) streams follow the same fallible iterator policy documented in 0.5 (ADR-013). - Blank-node identity is scoped by the parser or dataset operation that creates it; adapters must not derive identity from labels alone.
- User-provided callbacks are invoked outside internal locks whenever possible.
Error and capability model¶
Errors should retain the subsystem and underlying cause while presenting stable public categories: invalid RDF, parsing, serialization, query parse, query evaluation, storage, I/O, unsupported capability, and C-boundary failure.
Backend- or version-specific behavior is queried through typed capabilities. Feature URI support may adapt to those capabilities, but unknown feature URIs must not be reported as successful no-ops.
Concurrency and cancellation¶
Public types should be Send and Sync only when their Oxigraph-backed
behavior and callback contents make that truthful. Thread-safety is asserted in
tests and, for the C API, in a published per-handle matrix.
Long-running parse, query, update, and bulk-load operations need an explicit
cancellation policy. As of 0.3, Query and Update accept an Oxigraph
CancellationToken (ADR-012). Wall-clock timeouts are caller-driven by
cancelling the token from another thread. The API must not imply reliable
interruption where none exists.
C ABI boundary¶
The C layer is a separate crate (oxiland-capi) allowed to contain narrowly
reviewed unsafe code. Its handles own or reference safe Oxiland objects. Each
handle type must define:
- allocation and destruction functions;
- null handling;
- clone/reference behavior;
- callback lifetime and thread rules;
- panic containment;
- error and logging translation.
No Rust panic may cross the C boundary. C strings and buffers must have one documented allocator and matching free operation.
Additional invariants:
- every entry point validates nullability before dereferencing;
- opaque handles carry a type discriminator or equivalent misuse defense;
- destruction is idempotent only where Redland promises it;
- callbacks document re-entry, concurrency, and borrowed-pointer duration;
- thread-local “last error” state is used only if required by the mapped API;
catch_unwindis a containment boundary, not a substitute for invariants.
Compatibility mapping strategy¶
Redland concepts fall into three groups:
- Direct mappings: nodes, triples, models, contexts, parsers, serializers, and SPARQL.
- Semantic adapters: world factories, feature URIs, streams, storage options, logging, and result formatting.
- Rust replacements: manual allocation, generic lists, generic hashes, and other mechanisms already supplied more safely by Rust.
Rust replacements still require inventory entries and migration examples; they are not omitted from parity accounting.
Extension points¶
Storage, parser, serializer, query-language, and digest factories are compatibility-sensitive extension points. Before exposing a public registration API, decide whether it can be soundly represented as:
- a Rust trait with owned registrations;
- a finite Oxigraph-backed capability table; or
- a C-only callback adapter.
Factory names are data in Redland programs. Unknown names must produce an explicit error and preserved diagnostic context.
Durable storage follows the staged backend expansion plan: extract a sealed adapter and conformance harness in 0.8, add optional first-party engines in 0.9, then decide whether a public user-supplied backend trait is supportable before 0.10. This revisits ADR-006 without weakening its Fjall format-v1 promise or ADR-018's rejection of arbitrary native plug-in registration.
The implemented internal dependency direction and the responsibility split in the C model adapter are recorded in the SOLID boundaries note.
Dependency policy¶
Oxigraph is pinned intentionally within each Oxiland release. Upgrades require the full conformance suite because RDF syntax, storage, and SPARQL changes can affect observable compatibility. Additional dependencies should be small, maintained, and justified by a Redland parity requirement.
Dependency review records:
- license and minimum supported Rust version;
- default features and native build requirements;
- maintenance and security posture;
- effect on WebAssembly and pure-Rust fjall builds;
- whether it crosses the safe/C boundary.
Architecture decision records¶
Decisions that constrain later compatibility work are recorded in
DECISIONS.md. A decision record includes context, choice,
alternatives, compatibility impact, and a revisit trigger. Pull requests should
not reverse an accepted decision only through code changes.
Open architecture questions¶
- Which C handles need reference counting to reproduce observed aliasing?
- Can the sealed durable-store adapter become a public custom-backend trait without freezing engine-specific lifetimes, transactions, or unsafe open requirements into the 1.0 facade (proposed ADR-022)?
Resolved recently: Redland factory registration disposition for safe Rust
(ADR-018); oxiland-cli rdfproc workflows (ADR-019); naming freeze intent
(ADR-020); header-derived inventory generation (ADR-021); sealed durable
adapter and backend matrix (ADR-022); C ABI ownership/panic/allocator
(ADR-023); 0.5 stream/utility surface (ADR-013–ADR-016); query cancellation
via Oxigraph CancellationToken (ADR-012).
Term re-exports are governed by ADR-004 and revisited only on its evidence trigger. The remaining questions are decision candidates, not implicit TODOs.
Reading this architecture¶
- Public Rust signatures and item semantics: docs.rs
- Python contract: Python API
- Storage deployment: Rust and Python production guides
- Current verified claims: parity ledger
- Durable design rationale: decision log
- Future sequencing: roadmap