Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 67 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,16 @@ jobs:
# deliberately can still be built by someone else's dependency. `--depth 2`
# covers every pair without the combinatorial blow-up of the full set.
#
# Check-only: this is about whether the combinations *compile*, and the
# behaviour of each is the main job's business.
# Check-only: this is about whether the combinations *compile*, and
# whether they link and run is the `feature-configs` job's business.
#
# This is also where §E2's `contacts` row is covered, without a step of
# its own: `contacts` is a feature of `tinymemory-core`, and the powerset
# enumerates it as a subset of size one on this same runner. What it does
# not cover is *executing* it, which needs `macos-latest` — the feature's
# only behaviour is a CNContactStore reader whose dependencies sit behind
# a `cfg(target_os = "macos")` table, so on ubuntu there is nothing to run
# but the empty stub. That runner is deliberately not spent.
- name: Feature powerset compiles
run: cargo hack --feature-powerset --depth 2 --workspace check --all-targets

Expand All @@ -166,6 +174,63 @@ jobs:
cargo llvm-cov --all-features --workspace --summary-only \
| tee "$GITHUB_STEP_SUMMARY"

# §E2's first half: build **and test** each engine configuration on its own.
#
# What this adds over the powerset pass, precisely: `cargo check` never
# links, and it never runs a test binary. A feature set that type-checks can
# still fail to link — the root `Cargo.toml` documents one such hazard, where
# a second crate claiming `links = "git2"` becomes a hard cargo error — and
# that failure is invisible to a check. So these rows are worth their minutes
# for linking and running, not for behaviour that varies by feature: the
# facade's own suite is the same set of tests in every configuration, because
# `DriverRegistry` admission is a static policy table rather than a function
# of which adapters were compiled in.
#
# Two of §E2's nine configurations name features the facade does not have.
# `--features contacts` belongs to `tinymemory-core` and is covered by the
# powerset job above. `--features sync-composio` names a feature that exists
# nowhere in the workspace: the Composio sync is unconditional in
# `tinymemory-core`, so there is nothing to select and nothing to isolate.
# Recorded here rather than quietly dropped, because a missing row in a
# matrix reads as covered.
feature-configs:
name: Test ${{ matrix.name }}
runs-on: ubuntu-latest
strategy:
# Every configuration is independent, and knowing that three of them
# broke is worth more than stopping at the first.
fail-fast: false
matrix:
include:
- name: no default features
features: --no-default-features
- name: tinycortex
features: --features tinycortex
- name: tinycortex and memory-git
features: --features tinycortex,memory-git
- name: mem0
features: --features mem0
- name: supermemory
features: --features supermemory
- name: cognee
features: --features cognee
- name: all features
features: --all-features
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: recursive

- uses: dtolnay/rust-toolchain@stable
Comment thread
YellowSnnowmann marked this conversation as resolved.

- uses: Swatinem/rust-cache@v2
Comment thread
YellowSnnowmann marked this conversation as resolved.

# 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.
- name: Test
run: cargo test -p tinymemory ${{ matrix.features }}

# The module crate is its own workspace root (see the `exclude` note in the
# root Cargo.toml), so NONE of the steps above touch it: `--all-targets`,
# `--all-features` and `--workspace` all stop at the workspace boundary and
Expand Down
58 changes: 21 additions & 37 deletions core/src/diff/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@
//!
//! The crate calls `items_for_source(source_id)` with the *logical* source id,
//! but the host chunk `source_id LIKE` prefix is kind-dependent — Composio
//! sources key their chunks by `<toolkit>:%`, not `mem_src:<id>:%`, and the
//! toolkit is not derivable from the logical id alone. The adapter is therefore
//! built from the full [`MemorySourceEntry`] list (which carries `toolkit`) and
//! resolves each id → prefix up front.
//! sources key their chunks by `<toolkit>:<connection_id>:%`, not
//! `mem_src:<id>:%`, and neither is derivable from the logical id alone. The
//! adapter is therefore built from the full [`MemorySourceEntry`] list (which
//! carries both) and resolves each id → prefix up front, through
//! `sources::status::source_id_prefix` — the one definition of the scheme,
//! shared so a snapshot and a status can never disagree about which chunks
//! belong to a source. Named rather than linked: it is `pub(crate)`, and a
//! link to it from this module's public documentation is a rustdoc error.

use std::collections::HashMap;
use std::sync::Arc;
Expand All @@ -27,7 +31,8 @@ use crate::engine::backend::diff::{extract_item_id, SnapshotItem, SnapshotItemSo
#[cfg(test)]
use tinymemory_api::host::test_support::TestHostConfig;

use crate::sources::types::{MemorySourceEntry, SourceKind};
use crate::sources::status::source_id_prefix;
use crate::sources::types::MemorySourceEntry;
use crate::Config;

/// Host [`SnapshotItemSource`] backed by `mem_tree_chunks`.
Expand Down Expand Up @@ -126,22 +131,10 @@ impl SnapshotItemSource for ChunkStoreItemSource {
}
}

/// Build the `source_id LIKE` prefix that matches chunks belonging to a source.
/// Mirrors `memory_sources::status::source_id_prefix`.
pub(crate) fn source_id_prefix(source: &MemorySourceEntry) -> String {
match source.kind {
SourceKind::Composio => source
.toolkit
.as_deref()
.map(|t| format!("{t}:%"))
.unwrap_or_else(|| "__no_toolkit__:%".to_string()),
_ => format!("mem_src:{}:%", source.id),
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::sources::types::SourceKind;

fn folder_source(id: &str) -> MemorySourceEntry {
MemorySourceEntry {
Expand Down Expand Up @@ -169,30 +162,21 @@ mod tests {
}
}

/// The prefix scheme itself is covered where it is defined
/// (`sources::status::source_id_prefix_dispatch`). What matters here is
/// that the adapter resolves through *that* definition, so a snapshot and
/// a status agree on which chunks belong to a source.
#[test]
fn source_id_prefix_folder() {
fn the_adapter_resolves_prefixes_through_the_shared_definition() {
let source = folder_source("src_abc");
let adapter =
ChunkStoreItemSource::single(std::sync::Arc::new(TestHostConfig::default()), &source);
assert_eq!(
source_id_prefix(&folder_source("src_abc")),
"mem_src:src_abc:%"
adapter.prefixes.get("src_abc").map(String::as_str),
Some(crate::sources::status::source_id_prefix(&source).as_str())
);
}

#[test]
fn source_id_prefix_composio() {
let mut entry = folder_source("src_cmp");
entry.kind = SourceKind::Composio;
entry.toolkit = Some("gmail".into());
assert_eq!(source_id_prefix(&entry), "gmail:%");
}

#[test]
fn source_id_prefix_composio_without_toolkit() {
let mut entry = folder_source("src_cmp");
entry.kind = SourceKind::Composio;
entry.toolkit = None;
assert_eq!(source_id_prefix(&entry), "__no_toolkit__:%");
}

#[test]
fn read_only_adapter_never_yields_items() {
let source = ChunkStoreItemSource::read_only(
Expand Down
Loading
Loading