From f3eb6cf2bc1f685a4a0ac42a27c4b3b684b81fc6 Mon Sep 17 00:00:00 2001 From: Shanu Date: Wed, 19 Aug 2026 13:00:06 +0530 Subject: [PATCH 1/2] Close the audit's minor findings: hermetic git tests, hardened containment check, full-provider conformance, true docs Four items from the #18 end-to-end audit, each small, none deferrable without cost: 1. The sources git tests inherited the developer's global git config, so a machine with `commit.gpgsign = true` parked the whole test binary on a pinentry prompt -- the exact hang that ate hours of this arc's own build time. `git_ok` now nulls GIT_CONFIG_GLOBAL/SYSTEM and disables signing per invocation. 2. The audit probed `engine-containment.sh` and found three regex bypasses: `extern crate tinycortex`, whitespace before `::`, and a `//` inside a string literal eating the rest of a line that also held a real use. String literals are now stripped before comments, and the pattern covers `extern crate` and spaced `::`. All three probes now fail the script; prose still passes. 3. Criterion 5 had an honest gap its own docs admitted: the eighteen- family `TinycortexProvider` was never conformance-tested, only the mandatory-three composition. It needs the host's process-global embedding seam, which makes in-lib tests order-dependent -- so it gets the integration target the doc promised, owning the global for its whole binary: `tests/full_provider_conformance.rs`, running `assert_provider` plus the retains-writes pin over a real workspace store. The in-lib doc now points at it instead of apologising. 4. The adapter's crate docs still claimed the engine and contract Memory traits were "separate traits over the same values" -- false since the upstream contract re-export: `tinycortex::memory::Memory` is `tinymemory_api::traits::Memory` (verified through tinycortex_api's re-export chain). Docs now say what is true. cargo test -p tinymemory-tinycortex --test full_provider_conformance: 2 passed cargo test -p tinymemory-sources (default): 58 passed; github git tests green under a signing-enabled global config scripts/ci/engine-containment.sh: holds; three bypass probes caught --- Cargo.lock | 1 + adapters/tinycortex/Cargo.toml | 2 + adapters/tinycortex/src/conformance_test.rs | 5 +- adapters/tinycortex/src/lib.rs | 7 +- .../tests/full_provider_conformance.rs | 120 ++++++++++++++++++ scripts/ci/engine-containment.sh | 16 ++- sources/src/readers/github/git_tests.rs | 10 ++ 7 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 adapters/tinycortex/tests/full_provider_conformance.rs diff --git a/Cargo.lock b/Cargo.lock index 8fb348a..4aef392 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2009,6 +2009,7 @@ dependencies = [ "log", "serde", "serde_json", + "tempfile", "tinycortex", "tinymemory-api", "tinymemory-conformance", diff --git a/adapters/tinycortex/Cargo.toml b/adapters/tinycortex/Cargo.toml index ce0b7c3..df663d8 100644 --- a/adapters/tinycortex/Cargo.toml +++ b/adapters/tinycortex/Cargo.toml @@ -53,6 +53,8 @@ anyhow = "1" # The behavioural contract suite, run against this crate's drivers # (issue #18 §E1, acceptance criterion 5). tinymemory-conformance = { path = "../../conformance" } +# The full-provider conformance target opens a real workspace store. +tempfile = "3" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [lints.rust] diff --git a/adapters/tinycortex/src/conformance_test.rs b/adapters/tinycortex/src/conformance_test.rs index 88f0ada..51483a5 100644 --- a/adapters/tinycortex/src/conformance_test.rs +++ b/adapters/tinycortex/src/conformance_test.rs @@ -15,9 +15,8 @@ //! needs a `MemoryClient` — which needs the host's process-global seams //! (`set_embedding_host` and friends) installed before it will open. A test //! that installs a process global is order-dependent, which `AGENTS.md` rules -//! out, so covering it needs its own integration target that owns the global -//! for the whole binary. That is not written yet, and criterion 5 is not -//! complete until it is. +//! out, so it is covered in `tests/full_provider_conformance.rs`: an +//! integration target that owns the global for its whole binary. //! //! Running against `InMemoryMemoryStore` rather than a SQLite workspace is //! deliberate and is also the sharper test: it is the engine's simplest diff --git a/adapters/tinycortex/src/lib.rs b/adapters/tinycortex/src/lib.rs index 57ae0bf..0d82bc9 100644 --- a/adapters/tinycortex/src/lib.rs +++ b/adapters/tinycortex/src/lib.rs @@ -8,9 +8,10 @@ //! field added to one contract had to be added to the other and to the //! conversion — three places, or the value was silently dropped. Since //! issue #18 §A1 `tinycortex-api` re-exports `tinymemory-api` rather than -//! redefining it, so both sides name one type and `convert` is gone. What -//! remains is the trait shape: the engine's storage trait and the contract's -//! are still separate traits over the same values. +//! redefining it, so both sides name one type and `convert` is gone — and +//! since the contract re-export landed upstream, `tinycortex::memory::Memory` +//! *is* `tinymemory_api::traits::Memory`: one trait, one set of values. What +//! this crate adds on top is composition, not translation. //! //! ## What is here //! - [`TinycortexMemory`] — wraps any TinyCortex [`tinycortex::memory::Memory`] diff --git a/adapters/tinycortex/tests/full_provider_conformance.rs b/adapters/tinycortex/tests/full_provider_conformance.rs new file mode 100644 index 0000000..e871856 --- /dev/null +++ b/adapters/tinycortex/tests/full_provider_conformance.rs @@ -0,0 +1,120 @@ +//! The conformance suite over the FULL eighteen-family driver (#18 §E1/§E3). +//! +//! `conformance_test.rs` (in-lib) covers `crate::provider` — the mandatory +//! three families over any engine backend. This target covers +//! [`tinymemory_tinycortex::engine::TinycortexProvider`], which the in-lib +//! test cannot: the provider needs a `MemoryClient`, and a `MemoryClient` +//! needs the host's process-global embedding seam installed. A process global +//! makes tests order-dependent inside a shared binary, so this lives in its +//! own integration target that owns the global for its whole lifetime — the +//! arrangement the in-lib test's module doc promised. +//! +//! The seam is the same noop shape the §B5 acceptance test uses: recall +//! quality is not under test here, contract shape is. + +// A panic in a test IS the failure report — same allowance the in-lib +// conformance test carries. +#![allow(clippy::expect_used)] + +use std::sync::Arc; + +use tinymemory_tinycortex::engine::{EngineRuntimeConfig, TinycortexProvider}; + +/// The one piece of host wiring `MemoryClient` requires. +#[derive(Debug)] +struct NoopEmbeddingHost; + +impl tinymemory_api::host::EmbeddingHost for NoopEmbeddingHost { + fn resolve_api_key(&self, _provider: &str) -> Option { + None + } + + fn ollama_base_url(&self) -> String { + "http://127.0.0.1:1".into() + } + + fn default_embedding_provider(&self) -> Arc { + Arc::new(tinymemory_api::host::NoopEmbedding) + } + + fn create_embedding_provider_with_credentials( + &self, + _provider: &str, + _model: &str, + _dims: usize, + _api_key: &str, + _custom_endpoint: Option<&str>, + ) -> Result, String> { + Ok(Box::new(tinymemory_api::host::NoopEmbedding)) + } + + fn model_supports_dimensions(&self, _model: &str) -> bool { + false + } + + fn cloud_embedding_provider( + &self, + _model: &str, + _dims: usize, + ) -> Result, String> { + Ok(Box::new(tinymemory_api::host::NoopEmbedding)) + } + + fn default_cloud_embedding_model(&self) -> &str { + "noop" + } + + fn default_cloud_embedding_dimensions(&self) -> usize { + 8 + } + + fn ollama_embedding_provider( + &self, + _base_url: &str, + _model: &str, + _dims: usize, + ) -> Result, String> { + Ok(Box::new(tinymemory_api::host::NoopEmbedding)) + } +} + +fn provider_over(workspace: &std::path::Path) -> TinycortexProvider { + tinymemory_core::embedding_host::set_embedding_host(Arc::new(NoopEmbeddingHost)); + let client = Arc::new( + tinymemory_core::store::MemoryClient::from_workspace_dir(workspace.to_path_buf()) + .expect("open the workspace store"), + ); + let config = EngineRuntimeConfig { + workspace_dir: workspace.to_path_buf(), + config_path: workspace.join("config.toml"), + memory: Default::default(), + memory_tree: Default::default(), + scheduler_gate: Default::default(), + local_ai: Default::default(), + embeddings_provider: None, + memory_provider: None, + default_model: None, + default_temperature: 0.2, + output_language: None, + memory_sources: serde_json::Value::Null, + }; + TinycortexProvider::new("tinycortex".into(), config, client) +} + +#[tokio::test(flavor = "multi_thread")] +async fn the_full_tinycortex_provider_upholds_the_contract() { + let workspace = tempfile::tempdir().expect("workspace"); + let provider = provider_over(workspace.path()); + tinymemory_conformance::assert_provider(Arc::new(provider)).await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn the_full_provider_actually_retains() { + let workspace = tempfile::tempdir().expect("workspace"); + let provider = provider_over(workspace.path()); + assert!( + tinymemory_conformance::retains_writes(&provider).await, + "the workspace store must retain writes, or the suite above asserts \ + almost nothing" + ); +} diff --git a/scripts/ci/engine-containment.sh b/scripts/ci/engine-containment.sh index 6ead0e6..1290a0d 100755 --- a/scripts/ci/engine-containment.sh +++ b/scripts/ci/engine-containment.sh @@ -13,13 +13,23 @@ set -euo pipefail cd "$(dirname "$0")/../.." # Strip comment lines (`//`, `///`, `//!`) before matching so prose cannot -# trip it; then require the crate name in path position. +# trip it; then require the crate name in path position. The audit probed the +# first version of this regex and found three bypasses, each closed below: +# `extern crate tinycortex;` (no `::`), whitespace between the crate name and +# the path separator (`tinycortex ::memory`), and a `//` inside a string +# literal on the same line eating a real use (`let u="//x"; use tinycortex::A;` +# — comment-stripping must not fire inside quotes). Block comments can still +# yield false POSITIVES (prose inside `/* */` is not stripped), which fails +# safe: a human looks, nothing slips through. offenders="$( grep -rln --include='*.rs' 'tinycortex' core/src \ | grep -v '^core/src/engine/' \ | while read -r f; do - if sed -E 's://.*$::' "$f" \ - | grep -Eq '(^|[^A-Za-z0-9_])(use[[:space:]]+tinycortex\b|tinycortex::)'; then + # Strip string literals first (so a `//` inside one cannot hide the + # rest of the line), then line comments; then match path positions. + if sed -E 's:"([^"\\]|\\.)*"::g' "$f" \ + | sed -E 's://.*$::' \ + | grep -Eq '(^|[^A-Za-z0-9_])(use[[:space:]]+tinycortex\b|extern[[:space:]]+crate[[:space:]]+tinycortex\b|tinycortex[[:space:]]*::)'; then echo "$f" fi done diff --git a/sources/src/readers/github/git_tests.rs b/sources/src/readers/github/git_tests.rs index 9c1011a..6c98036 100644 --- a/sources/src/readers/github/git_tests.rs +++ b/sources/src/readers/github/git_tests.rs @@ -4,8 +4,18 @@ use std::process::Command; /// Run `git` with the given args in `cwd`, asserting success and returning /// stdout as a string. +/// +/// The developer's own git configuration is neutralised: a global +/// `commit.gpgsign = true` would otherwise park `git commit` on a pinentry +/// prompt and hang the whole test binary — on exactly the machines most +/// likely to run these tests. `GIT_CONFIG_GLOBAL`/`GIT_CONFIG_SYSTEM` point +/// at nothing, and signing is off explicitly for good measure. fn git_ok(cwd: &Path, args: &[&str]) -> String { let out = Command::new("git") + .env("GIT_CONFIG_GLOBAL", "/dev/null") + .env("GIT_CONFIG_SYSTEM", "/dev/null") + .env("GIT_CONFIG_NOSYSTEM", "1") + .args(["-c", "commit.gpgsign=false", "-c", "tag.gpgsign=false"]) .args(args) .current_dir(cwd) .output() From 6c51d60a867aedb325793dc470ff1007d1aca557 Mon Sep 17 00:00:00 2001 From: Shanu Date: Wed, 19 Aug 2026 13:14:09 +0530 Subject: [PATCH 2/2] ci: pin every action to a commit SHA, bumped by Dependabot The promise made on #61's two tinysweeper threads, kept after that PR merged so its new matrix jobs are covered too: a tag or branch is mutable, and whoever owns the action's repo can repoint it and run new code with this workflow's secrets. All 21 `uses:` refs now name a full commit SHA with the tag kept as a trailing comment for readability, and Dependabot gains the `github-actions` ecosystem so the pins move by reviewable PR rather than by rot. --- .github/workflows/ci.yml | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f801a97..346fdca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,18 +21,18 @@ jobs: name: Rust runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: # This job executes repository code (cargo build/test); don't persist # the token in git config. persist-credentials: false submodules: recursive - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: components: rustfmt, clippy - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Check formatting run: cargo fmt --all -- --check @@ -132,18 +132,18 @@ jobs: name: Feature powerset and coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: submodules: recursive persist-credentials: false - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: components: llvm-tools-preview - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - - uses: taiki-e/install-action@v2 + - uses: taiki-e/install-action@5b4d68e2e660441203ab128a23676f1e4faf1532 # v2 with: tool: cargo-hack,cargo-llvm-cov @@ -217,14 +217,14 @@ jobs: - name: all features features: --all-features steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false submodules: recursive - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 # Scoped to the facade: these are *its* features, and it is what a host # compiles against. An engine's own suite runs in the main job. @@ -243,16 +243,16 @@ jobs: name: Module (own workspace) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false submodules: recursive - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: components: rustfmt, clippy - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Check formatting run: cargo fmt --manifest-path crates/tinymemory-module/Cargo.toml --all -- --check @@ -305,14 +305,14 @@ jobs: name: Docs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false submodules: recursive - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Build documentation env: @@ -323,7 +323,7 @@ jobs: name: Minimum supported Rust version runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false submodules: recursive @@ -340,11 +340,11 @@ jobs: fi echo "version=$msrv" >> "$GITHUB_OUTPUT" - - uses: dtolnay/rust-toolchain@master + - uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # master with: toolchain: ${{ steps.msrv.outputs.version }} - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Build with the declared MSRV run: cargo build --all-targets --all-features @@ -353,12 +353,12 @@ jobs: name: Supply chain runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false submodules: recursive - name: Check advisories, licenses, bans, and sources - uses: EmbarkStudios/cargo-deny-action@v2 + uses: EmbarkStudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25 # v2 with: command: check all