use mem_chunk::{RecordSource, chunks, ChunkPolicy}; use mem_ingest::{PiSessionSource, ClaudeTranscriptSource}; use mem_core::Role; use futures::stream::StreamExt; use std::path::PathBuf; /// M0 composition gate — verify all M0 components work together /// This gate proves: /// 1. Both RecordSource implementations work /// 2. Records stream correctly into chunks /// 3. All domain types compose without errors #[tokio::test] async fn m0_gate_pi_session_composes() { // Pi session source creates records let fixture = PathBuf::from("fixtures/pi-session-small.jsonl"); let source = PiSessionSource::new(fixture.clone()); // Records stream through the chunker let policy = ChunkPolicy::default(); let mut chunk_stream = chunks(source, policy); let mut total_records = 0; let mut total_chunks = 0; while let Some(result) = chunk_stream.next().await { if let Ok(chunk) = result { total_chunks += 1; total_records += chunk.records.len(); } } assert!(total_records > 0, "Should have records from pi session"); assert!(total_chunks > 0, "Should have chunks from pi session"); } #[tokio::test] async fn m0_gate_claude_transcript_composes() { // Claude transcript source creates records let fixture = PathBuf::from("fixtures/claude-transcript-small.jsonl"); let source = ClaudeTranscriptSource::new(fixture); // Records stream through the chunker let policy = ChunkPolicy::default(); let mut chunk_stream = chunks(source, policy); let mut total_records = 0; let mut total_chunks = 0; while let Some(result) = chunk_stream.next().await { if let Ok(chunk) = result { total_chunks += 1; total_records += chunk.records.len(); } } assert!(total_records > 0, "Should have records from claude transcript"); assert!(total_chunks > 0, "Should have chunks from claude transcript"); } #[tokio::test] async fn m0_gate_sources_are_swappable() { // Key property: both sources implement RecordSource uniformly // The same chunking logic works for both let pi_source = PiSessionSource::new(PathBuf::from("fixtures/pi-session-small.jsonl")); let claude_source = ClaudeTranscriptSource::new(PathBuf::from("fixtures/claude-transcript-small.jsonl")); let policy = ChunkPolicy::default(); // Process both sources with identical code let mut pi_chunks = 0; let mut pi_records = 0; let mut pi_stream = chunks(pi_source, policy.clone()); while let Some(Ok(chunk)) = pi_stream.next().await { pi_chunks += 1; pi_records += chunk.records.len(); } let mut claude_chunks = 0; let mut claude_records = 0; let mut claude_stream = chunks(claude_source, policy.clone()); while let Some(Ok(chunk)) = claude_stream.next().await { claude_chunks += 1; claude_records += chunk.records.len(); } // Both sources work through the same interface assert!(pi_records > 0, "Pi source should produce records"); assert!(claude_records > 0, "Claude source should produce records"); assert!(pi_chunks > 0, "Pi source should produce chunks"); assert!(claude_chunks > 0, "Claude source should produce chunks"); } #[tokio::test] async fn m0_gate_all_role_types_present() { // Verify that M0 is comprehensive enough to handle all roles let pi_source = PiSessionSource::new(PathBuf::from("fixtures/pi-session-small.jsonl")); let mut stream = pi_source.records(); let mut has_user = false; let mut has_assistant = false; let mut has_tool_result = false; let mut has_system = false; while let Some(Ok(record)) = stream.next().await { match record.role { Role::User => has_user = true, Role::Assistant => has_assistant = true, Role::ToolResult => has_tool_result = true, Role::System => has_system = true, } } assert!(has_user, "Should have user records"); assert!(has_assistant, "Should have assistant records"); assert!(has_tool_result, "Should have tool result records"); assert!(has_system, "Should have system records (compaction)"); } #[tokio::test] async fn m0_gate_chunk_boundaries_respected() { // Verify that chunker respects boundaries and produces valid chunks let source = PiSessionSource::new(PathBuf::from("fixtures/pi-session-small.jsonl")); let policy = ChunkPolicy::default(); let mut stream = chunks(source, policy); let mut prev_t = 0u32; while let Some(Ok(chunk)) = stream.next().await { // T values must be contiguous and increasing assert!(chunk.t > prev_t, "Chunk turn numbers must increase"); // Each chunk must have records assert!(!chunk.records.is_empty(), "Chunk must not be empty"); // Records must not be split assert!(chunk.records.iter().all(|r| !r.text.is_empty()), "Records must have content"); prev_t = chunk.t; } assert!(prev_t > 0, "Should have produced at least one chunk"); }