Skip to content

Commit 59178d2

Browse files
Add a behavioural conformance suite for MemoryProvider drivers
`audit_provider` checks that a driver's advertised capabilities match its reachable accessors — a structural check, proving the shape is honest, not the behaviour. Nothing checked that two drivers answer the same question the same way, which is the claim "swap the engine without the host learning anything new" actually rests on. `tinymemory-conformance` is that check. `assert_provider` takes any bound driver and drives the contract: the mandatory three families, upsert on `(namespace, key)`, namespace isolation, provenance preservation, recall limits and scoping, export pagination, import round-tripping, and unicode / empty / 64 KiB / control-character content. It depends on `tinymemory-api` and nothing else of substance. A suite that pulled in an engine could not prove interchangeability, having already chosen one; reaching `tinymemory-core` would drag in a bundled SQLite besides. Ships an in-memory reference driver, for two reasons. A suite that only ever ran against real engines cannot distinguish "the engine is wrong" from "the assertion is wrong", and a driver whose behaviour is obvious by inspection separates those. It also documents the contract by example — the upsert, the verbatim taint, the cursor that terminates on `None` rather than on an empty page. Writing it immediately found a distinction worth naming. The contract permits a driver that accepts writes and discards them; `NullMemoryProvider` is exactly that, and the storage assertions are vacuous for it. Rather than weakening each assertion to tolerate an empty read — which would let a driver that *intends* to retain and silently does not slip through — the suite probes retention once and reports which half it ran. The contract-shape assertions are never skipped. Provenance is the sharp assertion. A driver reading back `Internal` for content stored as `ExternalSync` has laundered external content into internal-trust content, and every downstream gate keyed on taint is then silently wrong. Refs #18 (§E1)
1 parent d742122 commit 59178d2

7 files changed

Lines changed: 1097 additions & 2 deletions

File tree

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[workspace]
2-
members = [".", "api", "core", "adapters/tinycortex", "adapters/remote"]
3-
default-members = [".", "api", "core", "adapters/tinycortex", "adapters/remote"]
2+
members = [".", "api", "core", "adapters/tinycortex", "adapters/remote", "conformance"]
3+
default-members = [".", "api", "core", "adapters/tinycortex", "adapters/remote", "conformance"]
44
# `vendor/` holds engine submodules (tinycortex, tinybus, tinyagents), each of
55
# which is its own workspace with its own lockfile. Same exclusion
66
# `vendor/tinycortex` uses for its own nested vendor directory.

conformance/Cargo.toml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
[package]
2+
name = "tinymemory-conformance"
3+
publish = false
4+
version = "0.1.0"
5+
edition = "2021"
6+
rust-version = "1.96"
7+
license = "MIT"
8+
description = "Behavioural conformance suite every MemoryProvider driver must pass"
9+
repository = "https://github.com/tinyhumansai/tinymemory"
10+
11+
[dependencies]
12+
# The contract under test. This crate deliberately depends on NOTHING else of
13+
# substance: a conformance suite that pulled in an engine would be unable to
14+
# prove that a driver is interchangeable, because it would already have chosen
15+
# one. In particular it must not reach `tinymemory-core`, which links a bundled
16+
# SQLite and the embedded engine unconditionally (issue #18 §D).
17+
tinymemory-api = { path = "../api" }
18+
# `MemoryProvider` and its families are object-safe async traits.
19+
async-trait = "0.1"
20+
# `ExportRecord::payload` is a `serde_json::Value`, so the portability
21+
# assertions have to construct and compare one.
22+
serde_json = "1"
23+
# The reference driver maps a poisoned lock onto `MemoryError::Other`, which is
24+
# `#[from] anyhow::Error`.
25+
anyhow = "1"
26+
27+
[dev-dependencies]
28+
# The suite's own tests drive it against the reference drivers.
29+
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
30+
31+
[lints.rust]
32+
unsafe_code = "forbid"
33+
missing_docs = "warn"
34+
unreachable_pub = "warn"
35+
36+
[lints.clippy]
37+
all = { level = "warn", priority = -1 }
38+
unwrap_used = "warn"
39+
expect_used = "warn"

conformance/src/lib.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//! Behavioural conformance for `MemoryProvider` drivers.
2+
//!
3+
//! TinyMemory's premise is that an engine can be swapped without the host
4+
//! learning anything new. [`audit_provider`](tinymemory_api::provider::audit_provider)
5+
//! checks that a driver's advertised capabilities match its reachable
6+
//! accessors, which proves the *shape* is honest. Nothing checked that two
7+
//! drivers answer the same question the same way — and that is the claim the
8+
//! premise actually rests on.
9+
//!
10+
//! This crate is that check. Hand [`assert_provider`] any bound driver and it
11+
//! drives the contract: the mandatory three families, upsert semantics on
12+
//! `(namespace, key)`, namespace isolation, provenance preservation, recall
13+
//! limits, export pagination, and import round-tripping.
14+
//!
15+
//! ```no_run
16+
//! use std::sync::Arc;
17+
//! use tinymemory_conformance::{assert_provider, InMemoryProvider};
18+
//!
19+
//! # async fn run() {
20+
//! assert_provider(Arc::new(InMemoryProvider::new())).await;
21+
//! # }
22+
//! ```
23+
//!
24+
//! # What it deliberately does not depend on
25+
//!
26+
//! Only `tinymemory-api`. A conformance suite that pulled in an engine could
27+
//! not prove interchangeability, because it would already have chosen one — and
28+
//! reaching `tinymemory-core` would drag in a bundled SQLite and the embedded
29+
//! engine besides (issue #18 §D).
30+
//!
31+
//! # Provenance is the sharp one
32+
//!
33+
//! [`assert_taint_is_preserved`] is not a formality. A driver that reads back
34+
//! `Internal` for content stored as `ExternalSync` has laundered external
35+
//! content into internal-trust content, and every policy gate keyed on taint is
36+
//! then silently wrong. That failure is invisible until something acts on it.
37+
38+
#![forbid(unsafe_code)]
39+
#![warn(missing_docs)]
40+
41+
pub mod reference;
42+
pub mod suite;
43+
44+
pub use reference::{InMemoryProvider, REFERENCE_DRIVER_ID};
45+
pub use suite::{
46+
assert_awkward_content_round_trips, assert_capability_audit, assert_export_cursor_terminates,
47+
assert_export_import_round_trip, assert_forget_is_idempotent, assert_list_filters_narrow,
48+
assert_namespaces_are_isolated, assert_provider, assert_recall_respects_limit_and_namespace,
49+
assert_store_get_round_trip, assert_taint_is_preserved,
50+
assert_upsert_replaces_rather_than_duplicates,
51+
};

0 commit comments

Comments
 (0)