Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
467d525
refactor(runtime): remove the last sensd transport helpers
Sinity Oct 29, 2025
454a913
docs(target-state): rewrite JetStream-first doctrine and consolidate …
Sinity Oct 31, 2025
3dc9f45
feat(ingestd): align pipeline, assembler, and checkpoints with JetStream
Sinity Oct 31, 2025
a5f44e6
fix(ingestd,gateway): tighten JetStream validation and blob flow
Sinity Nov 2, 2025
a945c32
refactor(nixos): simplify default-module wiring and example configs
Sinity Nov 3, 2025
84ec839
refactor(nixos): flatten the module stack and inline service modules
Sinity Nov 3, 2025
efae9dc
fix(ingestd,gateway): harden the JetStream and blob integration paths
Sinity Nov 3, 2025
24ecd7d
refactor(config): collapse figment loaders into explicit config paths
Sinity Nov 3, 2025
25ef758
refactor(document-ingestor): route document retrieval through BlobMan…
Sinity Nov 3, 2025
4e63f7e
refactor(runtime): move stream runtime modules under `runtime/`
Sinity Nov 3, 2025
bb51dde
feat(runtime): add `ProcessorHandles` and split stream state helpers
Sinity Nov 3, 2025
179c1b4
refactor(terminal-satellite): initialize and operate via ProcessorHan…
Sinity Nov 3, 2025
4ddcf68
refactor(nodes): port remaining node crates to `ProcessorRuntimeState`
Sinity Nov 3, 2025
4e0a2ae
refactor(runtime): make `ProcessorInitContext` the only init API
Sinity Nov 3, 2025
e3331da
refactor(runtime): delete `StreamProcessorContext` after node ports land
Sinity Nov 4, 2025
4dde4dc
feat(runtime): add config, emission, and workdir convenience accessor…
Sinity Nov 5, 2025
65a16cf
feat(stage): bind Stage-As-You-Go context creation to processor runtime
Sinity Nov 5, 2025
8908e63
feat(acquisition): build AcquisitionManager directly from processor h…
Sinity Nov 5, 2025
f36e699
feat(runtime): add replay and stage-as-you-go builders on runtime sur…
Sinity Nov 5, 2025
3722dfd
feat(runtime): expose acquisition-manager construction on runtime state
Sinity Nov 5, 2025
0d11384
feat(runtime): derive heartbeat emitters from ProcessorRuntimeState a…
Sinity Nov 5, 2025
8fd9df3
refactor(runtime): extract `sinex-processor-runtime` and adopt it acr…
Sinity Nov 6, 2025
2c341fd
feat(core): restore system payload builders after runtime extraction
Sinity Nov 6, 2025
1a3a4aa
chore(ingestd): remove unused JetStreamConsumer environment field dur…
Sinity Nov 6, 2025
68a3509
refactor(canonicalizer): delete inline canonicalization and keep unif…
Sinity Nov 6, 2025
9242734
fix(ingestd): switch to `events.raw` subjects and harden tests
Sinity Nov 6, 2025
0ad460b
refactor(nixos): inject agenix and wire example secret paths
Sinity Nov 6, 2025
402824e
feat(nix): add agenix secrets module and encrypted secret scaffold
Sinity Nov 6, 2025
019a119
fix(core): harden provenance, cascade analysis, and repository tests
Sinity Nov 7, 2025
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
186 changes: 48 additions & 138 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 2 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"crate/lib/sinex-schema",
"crate/lib/sinex-macros",
"crate/lib/sinex-satellite-sdk",
"crate/lib/sinex-processor-runtime",
"crate/lib/sinex-services",
# Re-enabled to ensure tests compile
"crate/lib/sinex-test-utils",
Expand Down Expand Up @@ -134,11 +135,6 @@ indexmap = "2.0"
# Retry logic
tokio-retry = "0.3"

# gRPC and protocol buffers
tonic = "0.11"
prost = "0.12"
tonic-build = "0.11"

# Message bus
async-nats = "0.33"

Expand All @@ -150,6 +146,7 @@ tokio-stream = "0.1"
futures = "0.3"
dirs = "5.0"
lazy_static = "1.5"
base64 = "0.22"

# File system watching
notify = "6.1"
Expand Down
43 changes: 40 additions & 3 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,30 @@ just pre-commit
```

**Prerequisites:** run `nix develop` (or source your local toolchain) so that
TimescaleDB, NATS, and the Cargo toolchain are available. Test helpers rely on
environment variables such as `DATABASE_URL` that the dev shell provides.
TimescaleDB/PostgreSQL, the `nats-server` binary, and the Cargo toolchain are
available. `TestContext` connects to the local Postgres socket at
`/run/postgresql` and spins up an ephemeral JetStream instance by shelling out
to `nats-server`, so both services must be reachable before running `just test`.

If you are inside the Nix dev shell (`nix develop`), `nats-server` is already
on `PATH` (see `flake.nix` exporting `NATS_SERVER_BIN`). For other toolchains,
install NATS manually:

```bash
# macOS (Homebrew)
brew install nats-server

# Linux (Debian/Ubuntu)
sudo apt-get install -y nats-server

# Or download a release binary
curl -L https://github.com/nats-io/nats-server/releases/latest/download/nats-server-amd64.zip -o /tmp/nats.zip
unzip /tmp/nats.zip -d /tmp/nats
sudo mv /tmp/nats/nats-server /usr/local/bin/
```

You can override the binary path with `NATS_SERVER_BIN=/custom/path/nats-server`
if you prefer to keep it outside `$PATH`.

## Test Layout at a Glance

Expand Down Expand Up @@ -51,7 +73,22 @@ tests are reserved for scenarios that truly span multiple crates or binaries.
- **Async hygiene:** use bounded concurrency (`buffer_unordered`, semaphores),
propagate errors rather than ignoring `JoinHandle`s, and avoid `std::thread::sleep`.
- **Fixtures:** prefer the fixture namespaces under `sinex_test_utils::fixtures`
rather than re-creating bespoke data builders.
rather than re-creating bespoke data builders. When you need a satellite
runtime for integration coverage, include the shared builder from
`sinex-satellite-sdk/tests/support/runtime.rs` instead of wiring pools and
emitters manually:

```rust
#[path = "support/runtime.rs"]
mod runtime;

use runtime::TestRuntimeBuilder;

let test_runtime = TestRuntimeBuilder::new(&ctx, "my-service")
.with_dry_run(true)
.build()
.await?;
```
- **Property tests:** place proptest suites alongside the crate they fuzz (see
below). Capture any new failing seeds in that crate’s
`tests/property/*.proptest-regressions` files.
Expand Down
4 changes: 2 additions & 2 deletions cli/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def get_automata() -> List[str]:
try:
with get_db_connection() as conn:
with conn.cursor() as cur:
cur.execute("SELECT DISTINCT processor_name FROM sinex_schemas.processor_manifests WHERE processor_type = 'automaton' ORDER BY processor_name")
cur.execute("SELECT DISTINCT processor_name FROM core.processor_manifests WHERE processor_type = 'automaton' ORDER BY processor_name")
return [row['processor_name'] for row in cur.fetchall()]
except Exception:
return []
Expand Down Expand Up @@ -1157,4 +1157,4 @@ def install_completion(shell: str, completion_dir: Optional[str] = None) -> bool
print(' '.join(get_satellites()))
else:
print(f"Unknown command: {shell}")
sys.exit(1)
sys.exit(1)
182 changes: 97 additions & 85 deletions cli/exo.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import click
import psycopg2
from psycopg2.extras import RealDictCursor
from psycopg2.extras import RealDictCursor, Json
from rich.console import Console
from rich.table import Table
from rich.json import JSON
Expand Down Expand Up @@ -581,7 +581,7 @@ def processor_list(type: Optional[str], status: Optional[str]):
query_parts = [
"SELECT processor_name, processor_type, description, version, status, "
"produces_event_types, last_heartbeat_ts, registered_at "
"FROM sinex_schemas.processor_manifests"
"FROM core.processor_manifests"
]
params = []
where_conditions = []
Expand Down Expand Up @@ -654,7 +654,7 @@ def processor_status(processor_name: str):
with conn.cursor() as cur:
# Get processor manifest
cur.execute("""
SELECT * FROM sinex_schemas.processor_manifests
SELECT * FROM core.processor_manifests
WHERE processor_name = %s
""", (processor_name,))
processor = cur.fetchone()
Expand All @@ -667,10 +667,10 @@ def processor_status(processor_name: str):
checkpoints = []
if processor['processor_type'] == 'automaton':
cur.execute("""
SELECT automaton_name, last_processed_id, processed_count,
last_activity, state_data
FROM core.automaton_checkpoints
WHERE automaton_name = %s
SELECT processor_name, consumer_group, consumer_name,
last_processed_id, processed_count, last_activity, checkpoint_data
FROM core.processor_checkpoints
WHERE processor_name = %s
ORDER BY last_activity DESC
""", (processor_name,))
checkpoints = cur.fetchall()
Expand All @@ -692,11 +692,14 @@ def processor_status(processor_name: str):
if checkpoints:
console.print("\n[bold]Checkpoints:[/bold]")
for checkpoint in checkpoints:
console.print(f" Last processed: {checkpoint['last_processed_id'] or 'None'}")
console.print(f" Processed count: {checkpoint['processed_count']}")
console.print(f" Last activity: {checkpoint['last_activity']}")
if checkpoint['state_data']:
console.print(f" State: {checkpoint['state_data']}")
console.print(
f" Consumer: {checkpoint['consumer_group']}/{checkpoint['consumer_name']}"
)
console.print(f" Last processed: {checkpoint['last_processed_id'] or 'None'}")
console.print(f" Processed count: {checkpoint['processed_count']}")
console.print(f" Last activity: {checkpoint['last_activity']}")
if checkpoint['checkpoint_data']:
console.print(f" State: {checkpoint['checkpoint_data']}")

console.print(f"\nRegistered: {processor['registered_at']}")
console.print(f"Last seen: {processor['last_seen']}")
Expand All @@ -713,7 +716,7 @@ def automaton_list(status: Optional[str]):
query_parts = [
"SELECT processor_name, description, version, status, "
"produces_event_types, last_heartbeat_ts, registered_at "
"FROM sinex_schemas.processor_manifests "
"FROM core.processor_manifests "
"WHERE processor_type = 'automaton'"
]
params = []
Expand Down Expand Up @@ -774,7 +777,7 @@ def automaton_status(automaton_name: str):
with conn.cursor() as cur:
# Get automaton manifest
cur.execute("""
SELECT * FROM sinex_schemas.processor_manifests
SELECT * FROM core.processor_manifests
WHERE processor_name = %s AND processor_type = 'automaton'
""", (automaton_name,))
automaton = cur.fetchone()
Expand Down Expand Up @@ -3141,16 +3144,18 @@ def explore_source_state(ctx, satellite: str, verbose: bool):

with get_db_connection() as conn:
with conn.cursor() as cur:
# Get automaton checkpoint for this satellite
# Get checkpoints for this satellite's processor(s)
cur.execute("""
SELECT
automaton_name,
processor_name,
consumer_group,
consumer_name,
last_processed_id,
processed_count,
last_activity,
state_data
FROM core.automaton_checkpoints
WHERE automaton_name LIKE %s
checkpoint_data
FROM core.processor_checkpoints
WHERE processor_name LIKE %s
ORDER BY last_activity DESC
""", [f"%{satellite}%"])

Expand All @@ -3163,13 +3168,16 @@ def explore_source_state(ctx, satellite: str, verbose: bool):
console.print(f"\n[bold]Checkpoint Status:[/bold]")

for checkpoint in checkpoints:
console.print(f" [cyan]{checkpoint['automaton_name']}[/cyan]")
console.print(
f" [cyan]{checkpoint['processor_name']}[/cyan] :: "
f"{checkpoint['consumer_group']}/{checkpoint['consumer_name']}"
)
console.print(f" Last processed: {checkpoint['last_processed_id'] or 'None'}")
console.print(f" Processed count: {checkpoint['processed_count']}")
console.print(f" Last activity: {checkpoint['last_activity']}")
if verbose and checkpoint['state_data']:
console.print(f" State data: {JSON(checkpoint['state_data'])}")

if verbose and checkpoint['checkpoint_data']:
console.print(f" State: {JSON(checkpoint['checkpoint_data'])}")

console.print()

Expand Down Expand Up @@ -3606,44 +3614,44 @@ def find_satellite_binary(name):
sys.exit(1)
satellites_to_scan = [satellite]

# Log the operation (disabled for now - satellites don't have unified CLI yet)
operation_id = None
console.print(f"[blue]Note: Operations logging temporarily disabled pending satellite CLI unification[/blue]")

# TODO: Re-enable when satellites implement StatefulStreamProcessor with scan subcommand
# try:
# with get_db_connection() as conn:
# with conn.cursor() as cur:
# # Use ULID() PostgreSQL function to generate a proper ULID
# cur.execute("""
# INSERT INTO core.operations_log (
# operation_id, operation_type, status, invoked_by_user, parameters
# ) VALUES (ulid(), %s, %s, %s, %s)
# RETURNING operation_id
# """, (
# 'scan',
# 'started',
# os.getenv('USER', 'unknown'),
# json.dumps({
# 'satellites': satellites_to_scan,
# 'since': since,
# 'until': until,
# 'targets': list(targets),
# 'dry_run': dry_run,
# 'estimate': estimate,
# 'interactive': interactive,
# 'max_events': max_events,
# 'timeout': timeout,
# 'parallel': parallel
# })
# ))
#
# # Get the generated operation_id
# row = cur.fetchone()
# operation_id = str(row[0]) if row else None
# conn.commit()
# except Exception as e:
# console.print(f"[yellow]Warning: Could not log operation: {e}[/yellow]")
try:
with get_db_connection() as conn:
with conn.cursor() as cur:
scope = {
'satellites': satellites_to_scan,
'since': since,
'until': until,
'targets': list(targets),
'dry_run': dry_run,
'estimate': estimate,
'interactive': interactive,
'max_events': max_events,
'timeout': timeout,
'parallel': parallel,
}
cur.execute(
"""
INSERT INTO core.operations_log (
operation_type,
operator,
scope,
result_status
) VALUES (%s, %s, %s::jsonb, %s)
RETURNING id
""",
(
'scan',
os.getenv('USER', 'unknown'),
Json(scope),
'running',
),
)
row = cur.fetchone()
operation_id = str(row['id']) if row and 'id' in row else None
conn.commit()
except Exception as e:
console.print(f"[yellow]Warning: Could not log scan operation: {e}[/yellow]")

start_time = time.time()

Expand Down Expand Up @@ -3783,31 +3791,35 @@ def run_satellite_scan(satellite_name: str) -> Dict[str, Any]:
console.print("[dim]Output:[/dim]")
console.print(result['stdout'])

# Update operation log (disabled for now)
# TODO: Re-enable when satellites implement unified CLI
# if operation_id:
# try:
# with get_db_connection() as conn:
# with conn.cursor() as cur:
# cur.execute("""
# UPDATE core.operations_log
# SET status = %s, completed_at = NOW(),
# duration_ms = %s, summary = %s
# WHERE operation_id = %s
# """, (
# 'completed' if failed_scans == 0 else 'failed',
# int(total_duration * 1000),
# json.dumps({
# 'satellites_scanned': len(satellites_to_scan),
# 'successful_scans': successful_scans,
# 'failed_scans': failed_scans,
# 'results': results
# }),
# operation_id
# ))
# conn.commit()
# except Exception as e:
# console.print(f"[yellow]Warning: Could not update operation log: {e}[/yellow]")
if operation_id:
try:
with get_db_connection() as conn:
with conn.cursor() as cur:
cur.execute(
"""
UPDATE core.operations_log
SET result_status = %s,
result_message = %s,
preview_summary = %s::jsonb,
duration_ms = %s
WHERE id = %s::uuid::ulid
""",
(
'success' if failed_scans == 0 else 'failure',
None,
Json({
'satellites_scanned': len(satellites_to_scan),
'successful_scans': successful_scans,
'failed_scans': failed_scans,
'results': results,
}),
int(total_duration * 1000),
operation_id,
),
)
conn.commit()
except Exception as e:
console.print(f"[yellow]Warning: Could not update operation log: {e}[/yellow]")

# Exit with error if any scans failed
if failed_scans > 0:
Expand Down
2 changes: 2 additions & 0 deletions crate/core/sinex-gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ tokio-stream = { workspace = true, features = ["net"] }
# JSON-RPC
serde = { workspace = true }
serde_json = { workspace = true }
base64 = { workspace = true }

# Error handling
color-eyre = { workspace = true }
Expand All @@ -43,6 +44,7 @@ camino = { workspace = true }
# Logging
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
thiserror = { workspace = true }

# CLI
clap = { workspace = true, features = ["derive"] }
Expand Down
Loading
Loading