Repository navigation
docs(testing): document NATS-only ingest and JetStream e2e #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4a1680a
dc1ee1d
ce69a56
d7cb7b5
341e3be
b97bad8
2c8b0d7
03f8f1f
6da0b71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -31,14 +31,14 @@ The promise is threefold: to restore **agency** by placing you in control of you | |||||||||
|
|
||||||||||
| Sinex is a "sentient archive" implemented as a satellite constellation architecture – independent services orchestrated by NixOS/systemd that comprehensively capture, intelligently process, and powerfully query personal digital experiences. | ||||||||||
|
|
||||||||||
| **Current Core Flow (implemented in code)**: Satellites → ingestd (gRPC over Unix socket) → Postgres (`core.events`) → NATS JetStream fanout → Automata → Gateway (JSON‑RPC) → CLI | ||||||||||
| **Current Core Flow (implemented in code)**: Satellites → NATS JetStream → ingestd (consumer/archiver) → Postgres (`core.events`) + JetStream confirmations → Automata → Gateway (JSON‑RPC) → CLI | ||||||||||
|
|
||||||||||
| Note: A NATS‑native ingestion refactor (satellites publish directly to JetStream; ingestd acts as archiver/consumer) is planned. See `docs/plan_v3.txt`. Until that refactor lands, ingestion uses gRPC to ingestd as above. | ||||||||||
| Satellites publish events directly to NATS JetStream (`events.raw.*` subjects); ingestd consumes from JetStream, validates, persists to Postgres, and publishes confirmations back to JetStream for consumption by automata and other processors. | ||||||||||
|
|
||||||||||
| - **Satellites**: Independent services that capture events (filesystem, terminals, window managers, system events) | ||||||||||
| - **ingestd**: Central ingestion daemon that receives events via gRPC and stores data atomically; also fans out persisted events via NATS JetStream | ||||||||||
| - **NATS JetStream**: Durable message bus for event ingestion, distribution, and confirmation delivery | ||||||||||
| - **ingestd**: Consumes events from JetStream, validates, archives to Postgres, and publishes confirmations | ||||||||||
| - **Event Substrate**: PostgreSQL + TimescaleDB with ULID keys, stores immutable events | ||||||||||
| - **NATS JetStream**: Real-time event distribution and durable buffering | ||||||||||
| - **Automata**: Stream processors that transform raw events into canonical representations | ||||||||||
| - **Query Interface**: Python CLI (`exo.py`) for exploring captured events | ||||||||||
|
|
||||||||||
|
|
@@ -59,10 +59,10 @@ Note: A NATS‑native ingestion refactor (satellites publish directly to JetStre | |||||||||
| - **Dual-Mode Operation**: Satellites support both sensor (real-time) and scanner (batch) modes | ||||||||||
| - **Immutable Event Storage**: All events preserved with full fidelity | ||||||||||
| - **ULID Primary Keys**: Time-ordered, globally unique identifiers | ||||||||||
| - **Ingestion via gRPC (current)**: Satellites submit to ingestd over gRPC; ingestd validates/persists and fans out via NATS JetStream | ||||||||||
| - **NATS‑native ingest (planned)**: Per `docs/plan_v3.txt`, satellites will publish directly to JetStream; ingestd becomes an archiver/consumer | ||||||||||
| - **NATS JetStream Ingestion**: Satellites publish directly to JetStream; ingestd acts as consumer/archiver | ||||||||||
| - **Idempotency & Confirmation**: Message deduplication via NATS-Msg-Id headers; confirmation delivery for provisionals | ||||||||||
| - **Schema Validation**: JSON Schema validation for event payloads | ||||||||||
| - **Git-Annex Integration**: Large file storage for blobs | ||||||||||
| - **Git-Annex Integration**: Large file storage for blobs (source material slices) | ||||||||||
| - **NixOS Module**: First-class NixOS deployment support | ||||||||||
|
|
||||||||||
| ### Satellite Architecture Details | ||||||||||
|
|
@@ -92,21 +92,22 @@ All satellites support two operational modes: | |||||||||
|
|
||||||||||
| ### System Components Progress | ||||||||||
|
|
||||||||||
| - ✅ **Satellite Architecture**: Independent satellite services operational, unified SDK in progress | ||||||||||
| - ✅ **Satellite Architecture**: Independent satellite services operational, unified SDK complete | ||||||||||
| - ✅ **Data Substrate**: PostgreSQL + TimescaleDB with ULID keys; `core.events` operational | ||||||||||
| - 🚧 **Message Bus**: Converging on NATS JetStream for ingestion and distribution (see docs/plan_v3.txt) | ||||||||||
| - ✅ **Message Bus**: NATS JetStream ingestion and distribution fully implemented | ||||||||||
| - 🚧 **Event Sources**: Filesystem, Terminal, Desktop, System — expanding coverage | ||||||||||
| - 🚧 **Automaton Ecosystem**: Deterministic processors; more in progress | ||||||||||
| - 🚧 **Gateway & CLI**: Operational; iterative improvements | ||||||||||
| - 🔨 **AI/LLM Integration**: Early framework and schema; integration ongoing | ||||||||||
|
|
||||||||||
| ### ✅ Implemented (Working Code) | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix markdown heading spacing to satisfy lint. There should be a blank line before this heading to comply with MD022. 📝 Suggested markdown fix - 🔨 **AI/LLM Integration**: Early framework and schema; integration ongoing
+
### ✅ Implemented (Working Code)📝 Committable suggestion
Suggested change
🧰 Tools🪛 markdownlint-cli2 (0.22.0)[warning] 103-103: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 🤖 Prompt for AI Agents |
||||||||||
| - **Core Infrastructure**: Event storage, ULID keys, TimescaleDB integration | ||||||||||
| - **Satellite Architecture**: Independent satellites publish to NATS JetStream | ||||||||||
| - **Satellite Architecture**: Independent satellites publish to NATS JetStream with idempotency headers | ||||||||||
| - **Event Source Satellites**: Filesystem, Terminal (Kitty/Atuin/recordings), Desktop (clipboard/Hyprland), System (D-Bus/systemd/udev) | ||||||||||
| - **Dual-Mode Support**: All satellites support sensor (real-time) and scanner (batch) modes | ||||||||||
| - **Processing Pipeline**: Satellites → NATS JetStream → ingestd (archiver) → Automata | ||||||||||
| - **Storage**: Git-annex blob storage, JSON schema validation | ||||||||||
| - **Processing Pipeline**: Satellites → JetStream (`events.raw.*`) → ingestd (consumer/archiver) → Postgres + JetStream confirmations → Automata | ||||||||||
| - **Confirmation Flow**: Provisional events receive confirmations with canonical IDs after persistence | ||||||||||
| - **Storage**: Git-annex blob storage (source material slices), JSON schema validation | ||||||||||
| - **Deployment**: NixOS module with systemd satellite services | ||||||||||
| - **Testing**: Comprehensive test suite including satellite integration tests | ||||||||||
|
|
||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,275 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! End-to-end JetStream integration test | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! This test demonstrates the complete JetStream event flow as specified in docs/way.md: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! 1. Satellite publishes provisional event to JetStream (events.raw) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! 2. ingestd consumes event, persists to database | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! 3. ingestd publishes confirmation (events.confirmations) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! 4. Automaton consumes confirmed event | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| //! This validates Phase 1 (Event Backbone) and Phase 2 (Confirmation-Aware Consumption). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use async_nats::jetstream; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use serde_json::json; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use sinex_core::types::Ulid; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use sinex_core::DbPoolExt; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use sinex_ingestd::validator::EventValidator; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use sinex_ingestd::JetStreamConsumer; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use sinex_satellite_sdk::{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AutomatonEventHandler, JetStreamEventConsumer, JetStreamEventConsumerConfig, NatsPublisher, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ProcessingModel, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use sinex_test_utils::{sinex_test, TestContext}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::sync::Arc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::time::Duration; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use tracing::info; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[sinex_test] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async fn test_jetstream_e2e_event_flow() -> color_eyre::Result<()> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| info!("🚀 Starting E2E JetStream test"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Setup test infrastructure | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let ctx = TestContext::new().await?.with_nats().await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let nats_client = ctx.nats_client(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let pool = ctx.pool.clone(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let env = ctx.env(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create JetStream context | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let js = jetstream::new(nats_client.clone()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Bootstrap events_raw stream | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let events_raw_stream = env.nats_subject("events_raw"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| js.get_or_create_stream(jetstream::stream::Config { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: events_raw_stream.clone(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subjects: vec![env.nats_subject("events.raw.>")], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| retention: jetstream::stream::RetentionPolicy::Limits, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| max_messages: 10_000, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| storage: jetstream::stream::StorageType::File, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ..Default::default() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Bootstrap events_confirmations stream | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let confirmations_stream = env.nats_subject("events_confirmations"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| js.get_or_create_stream(jetstream::stream::Config { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: confirmations_stream.clone(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subjects: vec![env.nats_subject("events.confirmations.>")], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| retention: jetstream::stream::RetentionPolicy::Limits, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| max_messages: 10_000, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| storage: jetstream::stream::StorageType::File, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ..Default::default() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .await?; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+39
to
+62
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Bootstrap events_raw stream | |
| let events_raw_stream = env.nats_subject("events_raw"); | |
| js.get_or_create_stream(jetstream::stream::Config { | |
| name: events_raw_stream.clone(), | |
| subjects: vec![env.nats_subject("events.raw.>")], | |
| retention: jetstream::stream::RetentionPolicy::Limits, | |
| max_messages: 10_000, | |
| storage: jetstream::stream::StorageType::File, | |
| ..Default::default() | |
| }) | |
| .await?; | |
| // Bootstrap events_confirmations stream | |
| let confirmations_stream = env.nats_subject("events_confirmations"); | |
| js.get_or_create_stream(jetstream::stream::Config { | |
| name: confirmations_stream.clone(), | |
| subjects: vec![env.nats_subject("events.confirmations.>")], | |
| retention: jetstream::stream::RetentionPolicy::Limits, | |
| max_messages: 10_000, | |
| storage: jetstream::stream::StorageType::File, | |
| ..Default::default() | |
| }) | |
| .await?; | |
| // Do not pre-create the ingestd-owned streams here. | |
| // Let ingestd bootstrap them so this e2e test exercises the same | |
| // stream configuration that production uses. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dropping task handles does not cancel spawned tasks.
drop(ingestd_handle) and drop(automaton_handle) simply release the JoinHandle without aborting the underlying tasks. The tasks will continue running until the test process exits.
🧹 Proposed cleanup
// Cleanup
- drop(ingestd_handle);
- drop(automaton_handle);
+ ingestd_handle.abort();
+ automaton_handle.abort();
+ // Optionally wait for cleanup
+ let _ = ingestd_handle.await;
+ let _ = automaton_handle.await;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Cleanup | |
| drop(ingestd_handle); | |
| drop(automaton_handle); | |
| // Cleanup | |
| ingestd_handle.abort(); | |
| automaton_handle.abort(); | |
| // Optionally wait for cleanup | |
| let _ = ingestd_handle.await; | |
| let _ = automaton_handle.await; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@crate/core/sinex-ingestd/tests/jetstream_e2e_integration_test.rs` around
lines 188 - 191, The current cleanup just drops the JoinHandles (ingestd_handle
and automaton_handle) which doesn't stop the spawned tasks; call abort() on both
JoinHandle values (ingestd_handle.abort() and automaton_handle.abort()) and then
await their completions (await the handles) to ensure the tasks are cancelled
and any panics are observed before the test exits; update the cleanup section
that currently drops ingestd_handle and automaton_handle to explicitly abort and
await them instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same task cleanup issue in idempotency test.
The _ingestd_handle is not aborted on completion.
🧹 Add cleanup at end of test
info!("🎉 Idempotency test PASSED - 3 publishes → 1 database row");
+ _ingestd_handle.abort();
+
Ok(())
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@crate/core/sinex-ingestd/tests/jetstream_e2e_integration_test.rs` around
lines 221 - 222, The spawned task handle _ingestd_handle created from
tokio::spawn(async move { ingestd_consumer.run().await }) is never cleaned up at
the end of the test; ensure the background consumer is stopped by either
awaiting the handle to finish or aborting it when the test completes (e.g., call
_ingestd_handle.abort() or await _ingestd_handle with a timeout and then abort
if still running) so ingestd_consumer.run() is not left running after the test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider linking this TODO comment to a concrete tracking issue.
The “compilation errors remain” note is useful, but adding an issue/ADR reference here would prevent this from becoming stale and make re-enablement criteria explicit.
🤖 Prompt for AI Agents