All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Repository maintenance updates committed from the current working tree.
- Version-bearing manifests synchronized to 0.3.4.
tests/test_live.sh: Addeduid=1000,gid=1000to the--tmpfs /datamount so thesyslogcontainer user (uid 1000) can write the SQLite database; previously the tmpfs was owned by root, causingunable to open database file: /data/syslog.dband CI health-check timeout.github/workflows/docker-publish.yml: Trivy scan now referencessteps.meta.outputs.version(e.g.main) instead of the baregithub.sha(full 40-char SHA); the image is pushed with the branch/tag name, not the full commit SHA, so the old ref causedMANIFEST_UNKNOWNscan failures
- Version sync: Aligned
.claude-plugin/plugin.json,.codex-plugin/plugin.json, andgemini-extension.jsonfrom 0.2.6 to 0.3.2 to matchCargo.toml
tests/TEST_COVERAGE.md: Test coverage documentationtests/mcporter/: MCPorter test infrastructure- Full documentation structure: Added plugin-lab template docs (README, CLAUDE.md, references, runbooks)
src/config.rs:cleanup_chunk_sizeupper bound replaced fromi64::MAXwith operational limit of1_000_000; values above this hold the SQLite write lock indefinitely. Error message now explains why the limit exists.
src/db.rs:DbStatsnow includesphantom_fts_rows— count of FTS5 index entries without a matching live log row (merge lag indicator, visible viaget_stats)src/db.rs:schema_migrationstable guards idempotent migrations; DROP TRIGGER migration now runs exactly once per database (version 1)src/db.rs: Composite indexidx_logs_hostname_received_at ON logs(hostname, received_at)— makesreconcile_hostsMIN/MAX queries O(1) instead of O(rows_per_host)CLAUDE.md: FTS5 phantom row gotcha with GDPR/HIPAA compliance guidance
src/db.rs: P1 —delete_oldest_logs_chunkrewritten with subquery DELETE; old dynamic IN-list exceeded SQLite's 1000-node expression depth limit at defaultcleanup_chunk_size=2000, silently failing every storage enforcement cyclesrc/db.rs: P1 —fts_incremental_mergenow runsceil(deleted_rows/5000)iterations capped at 20; escalates to forcedrebuildafter 3 consecutive failuressrc/db.rs:reconcile_hostsmoved outside the enforcement delete loop — batches all host updates to one call per enforcement cycle instead of one per chunksrc/syslog.rs: TCP rejectionwarn!is now rate-limited (once on first rejection, once per 10s thereafter withtotal_rejectedcount) to prevent log storms under connection floods
src/config.rs:StorageConfig::for_testdemoted topub(crate)src/mcp.rs:TestHarnessstruct wrapsAppState + TempDirin tests to prevent accidental early TempDir drop; all 10 test functions updatedsrc/mcp.rs:test_storage_configwrapper removed; callers useStorageConfig::for_testdirectly
src/db.rs: Extractedfts_incremental_merge()helper — eliminates duplicated FTS merge string acrosspurge_old_logsandenforce_storage_budgetsrc/mcp.rs:test_state()now delegates totest_state_with_token(None);mcp_post()gains optionalauthparam — auth integration tests no longer inline the request builder
src/config.rs: Addedaccepts_cleanup_chunk_size_at_i64_maxboundary test; tightened overflow test to assert error message; addedSYSLOG_MCP_CLEANUP_CHUNK_SIZEtodefaults_are_applied_without_env_varsclear list and assertionsrc/db.rs: Migratedtest_storage_config()toStorageConfig::for_test()src/syslog.rs:TryAcquireError::Closedbranch now logs aterror!before breakingCHANGELOG.md: Corrected v0.2.2 date (2026-04-04→2026-04-03)
src/mcp.rs: 9 HTTP-level integration tests for all 6 MCP tools and auth middleware usingtower::util::ServiceExt::oneshot— covers health endpoint, initialize, tools/list, get_stats, tail_logs, search_logs, unknown method error, auth rejection (missing token), and auth success (correct token)Cargo.toml:tower0.5 added to dev-dependencies for axum router integration testing
src/db.rs: FTS5 write-lock contention during retention purge and storage-budget bulk deletes — removedlogs_ad(AFTER DELETE) andlogs_au(AFTER UPDATE) triggers that fired per-row inside 10k-chunk transactions, starving the batch writer. Added migration to drop triggers from existing databases. FTS5 phantom rows are cleaned up by incremental merge (syslog-mcp-eg5)
src/db.rs: Incremental FTS merge (merge=500,250) after storage-budget enforcement bulk deletes, matching the existingpurge_old_logspattern
src/syslog.rs: TCP accept loop blocked when connection semaphore was at capacity — replaced blockingacquire_owned().awaitwith non-blockingtry_acquire_owned()so the accept loop rejects new connections immediately instead of stalling for up to 300s (idle timeout)
src/mcp.rs:summarize_json_valuepanicked on multi-byte UTF-8 input (non-ASCII syslog messages) — replaced&raw[..limit]with a char-boundary-aware walk-back loop; added test covering Greek/CJK inputsrc/db.rs: Storage enforcement deleted 1 row per cycle (extremely slow for large overages) — now configurable viaSYSLOG_MCP_CLEANUP_CHUNK_SIZE(default 2000); WAL checkpoint moved outside the recovery loopsrc/config.rs: Added validation rejectingcleanup_chunk_size == 0(would cause an infinite enforcement loop)- Clippy: Fixed 4
-D warningserrors blockingcargo test—derivable_implsonConfig::Default,match_like_matches_macroinis_transient_sqlite_lock,needless_late_initforclose_reason,len_zeroinbatch_writer
src/config.rs:cleanup_chunk_sizefield inStorageConfigwith env varSYSLOG_MCP_CLEANUP_CHUNK_SIZE(default 2000 rows per enforcement chunk)src/config.rs:#[cfg(test)] StorageConfig::for_test()constructor — centralizes test config;mcp.rsandsyslog.rstest helpers now delegate to itdocs/sessions/2026-04-03-mcp-test-code-review-simplify.md: Full session documentation
- OAuth discovery 401 cascade: BearerAuthMiddleware was blocking GET /.well-known/oauth-protected-resource, causing MCP clients to surface generic "unknown error". Added WellKnownMiddleware (RFC 9728) to return resource metadata.
- docs/AUTHENTICATION.md: New setup guide covering token generation and client config.
- README Authentication section: Added quick-start examples and link to full guide.
docker-compose.yml/.env.example/README.md:SYSLOG_UIDandSYSLOG_GIDenv vars — container now runs as a configurable user/group (default1000:1000) for bind-mounted data directoriessrc/db.rs:StorageBudgetStatestruct — write-blocked flag and storage metrics shared viaArc<Mutex<>>across syslog and MCP modulessrc/db.rs: Transient SQLite lock retry in batch insert — 3-attempt backoff (25/100/250ms) onSQLITE_BUSY/SQLITE_LOCKEDbefore failingsrc/db.rs:configure_connection_pragmas()helper — WAL mode and PRAGMA setup extracted frominit_poolso every pooled connection is configured consistentlysrc/main.rs: Initial storage budget enforcement check on startup before accepting syslog trafficsrc/main.rs:background_interval()helper — interval fires after the first period (not at t=0), preventing a burst on startupsrc/syslog.rs:start_with_storage_state()replacesstart()— storage state shared with batch writer for write-blocking under pressure
src/syslog.rs: TCP handler now enforcesmax_message_sizeper line instead of per connection — persistent forwarders (rsyslog, syslog-ng) that reuse a single TCP session no longer hit the connection-level byte limit and cause an OOM/disconnectsrc/mcp.rs: Auth rejection now logsmethod,path, andhas_auth_headerfor diagnostics
src/db.rs,src/main.rs,src/mcp.rs,src/syslog.rs: Structuredtracingfields added throughout — storage enforcement, batch insert, retention purge, TCP/UDP listeners, health check, and MCP request lifecycle all emit structured events.dockerignore: Reorganized with categorized sections; AI tooling dirs (.claude,.omc,.lavra,.beads) explicitly excluded.gitignore: Reorganized with categorized sections; editor dirs, cache, doc artifacts, and worktree dirs added
- Breaking: env var rename — dropped figment's nested
SYSLOG_MCP_SECTION__KEYformat for flatSYSLOG_*andSYSLOG_MCP_*prefixes. See.env.examplefor the new names. src/config.rs: Replacedfigmentwithtomlcrate + manual env var overlay — simpler, supports two prefixessrc/config.rs: Mergedudp_bind/tcp_bindintohost+port(UDP and TCP always share the same address)src/config.rs: Renamedflush_interval_mstoflush_intervaldocker-compose.yml: Host-side ports use${SYSLOG_PORT}and${SYSLOG_MCP_PORT}env varsdocker-compose.yml: Data volume uses${SYSLOG_MCP_DATA_VOLUME}(defaults to named volumesyslog-mcp-data)docker-compose.yml: Replacedenvironment:block withenv_file: .envdocker-compose.yml: Removed SWAG labels; network usesexternal: trueDockerfile:SYSLOG_MCP_STORAGE__DB_PATH→SYSLOG_MCP_DB_PATHCargo.toml:figmentdependency replaced withtoml
src/config.rs:SyslogConfig::bind_addr()andMcpConfig::bind_addr()helper methodssrc/config.rs:validate_host()rejects host strings containing portssrc/config.rs: 2 new tests —env_var_overrides_syslog_port,host_with_port_is_rejected
0.1.7 — 2026-03-30
src/db.rs: Retention purge now usesreceived_at(server clock) instead oftimestamp(device clock) — prevents misconfigured device clocks from causing immediate purge or infinite retention (syslog-mcp-x6l)src/db.rs: Added composite(severity, timestamp)index forget_error_summaryquery performance (syslog-mcp-ctj)src/db.rs:std::collections::HashMapimported at module level instead of inline paths (syslog-mcp-rva)src/mcp.rs:/healthendpoint now runsSELECT 1instead ofCOUNT(*)over entire logs table (syslog-mcp-068)src/mcp.rs:severity_to_nummoved todb.rsas single source of truth (syslog-mcp-nu6)src/mcp.rs: 401 response uses JSON-RPC 2.0 envelope; replacedfuturescrate withfutures-core(syslog-mcp-zr4)src/syslog.rs: TCP accept error now uses exponential backoff (100ms → 5s cap) instead of flat 100ms sleep (syslog-mcp-ve1)src/syslog.rs:looks_like_timestampnow validates digit positions, not just separator offsets (syslog-mcp-qus)src/syslog.rs: Removed false "octet-counting" claim from TCP listener doc comment (syslog-mcp-jsv)src/syslog.rs: Flush retry adds 250ms pause to avoid hammering a failing DB (syslog-mcp-rjt)src/config.rs: Renamedparse_addrtovalidate_addrfor clarity (syslog-mcp-e5m)bin/smoke-test.sh:assert_no_errornow fails on non-JSON output instead of silently passing (syslog-mcp-tef)Cargo.toml: Removed unusedwsfeature from axum; removed unusedjsonfeature from tracing-subscriber (syslog-mcp-3ou, syslog-mcp-avg)docker-compose.yml: SWAG labels updated toswag=enable+ url/port/proto format (syslog-mcp-j4m)
src/db.rs:PRAGMA wal_checkpoint(PASSIVE)after hourly purge to prevent unbounded WAL growth (syslog-mcp-dah)src/db.rs:pub fn severity_to_num()for reuse across modules (syslog-mcp-nu6)src/config.rs:batch_sizeandflush_interval_msfields inSyslogConfigwith serde defaults (syslog-mcp-7uv)src/db.rs: 4 new unit tests — timestamp range filtering, severity_to_num edge cases, error summary severity filter, severity_in filter (syslog-mcp-063, syslog-mcp-v9r, syslog-mcp-3su, syslog-mcp-94p)bin/backup.sh: WAL-safe SQLite backup script with cron scheduling and 30-day pruning (syslog-mcp-8zi)docs/runbooks/deploy.md: Rolling update, rollback, health check, and pre-deploy checklist (syslog-mcp-8np).env.example: Addedmax_message_size,batch_size,flush_interval_msdocumentation (syslog-mcp-vri)README.md: SSE endpoint stub behavior documented; Docker network prereq documented (syslog-mcp-3t7, syslog-mcp-7r4)CLAUDE.md: CEF hostname trust boundary, batch writer failure path, correlate_events 999 limit cap documented (syslog-mcp-dum, syslog-mcp-2oj, syslog-mcp-y1n)
0.1.6 — 2026-03-30
src/main.rs: Redactapi_tokenfrom startup log — log individual fields withauth_enabled=boolinstead of printing full config struct (syslog-mcp-4yw)src/mcp.rs: Add optional Bearer token auth middleware; restrict CORS to localhost origins only (syslog-mcp-gm3)
Dockerfile: FixENV SYSLOG_MCP__STORAGE__DB_PATH→SYSLOG_MCP_STORAGE__DB_PATH— double-underscore prefix was silently ignored by figment (syslog-mcp-s9b)src/syslog.rs: Drop TCP lines exceedingmax_message_sizeto prevent OOM from unbounded lines (syslog-mcp-zu9)src/syslog.rs: Warn when CEF heuristic fires but all fields extract as None — malformed CEF body now emits a log line instead of silently falling back (syslog-mcp-w5e)src/syslog.rs: Cap TCP connections at 512 with semaphore + 300s wall-clock timeout per connection (syslog-mcp-ct2)src/db.rs: Chunked DELETE + incremental FTS merge to release WAL write-lock during retention purge (syslog-mcp-75i)src/config.rs: Replace blockingto_socket_addrs()DNS call with non-blockingSocketAddr::parse()at config load timeDockerfile: Run container as non-root user uid/gid 10001 (syslog-mcp-ab8).lavra/memory/recall.sh: Remove straylocalkeyword outside function scope (syslog-mcp-1mg)
.github/workflows/ci.yml: GitHub Actions CI — fmt check, clippy-D warnings, test, cargo audit (syslog-mcp-7ee)src/db.rs: 7 unit tests covering insert, FTS search, severity filter, purge, stats, host aggregation (syslog-mcp-sd0).env.example: DocumentSYSLOG_MCP_MCP__API_TOKENbearer token option
0.1.5 — 2026-03-28
syslog.rs: Normalize stored timestamps to UTC (dt.with_timezone(&Utc)) — mixed-offset sources no longer misorder SQLite rows or break retention purgessmoke-test.sh:--urlflag now creates a temp mcporter config so health checks and tool calls always target the same server; guard$2dereference underset -u; fixlimit=0boundary test that was silently passinglimit=1recall.sh: Fix--all --recentordering (archive first → newest entries last intail); usegrep -Ffor literal bead matching; fix auto-build tosource + kb_sync(subprocess call was a no-op)knowledge.jsonl: Strip embedded shell command fragments fromcontentandbeadfields
knowledge-db.sh: Quoted temp file path insqlite3 .import; consolidated 7→1 jq invocations per JSONL line and 2→1 per beads-import row.gitignore: Narrow*.dbtodata/*.dbto avoid hiding fixture filesREADME.md/CLAUDE.md: Correct env var prefixSYSLOG_MCP__→SYSLOG_MCP_docker-compose.yml: Switch network from internalsyslog-mcpto externaljakenet- Session docs: blank lines after subsection headings; complete rollback command
0.1.4 — 2026-03-28
- Session docs for syslog host onboarding (tootie, dookie, squirts, steamy-wsl, vivobook-wsl) and systemd service cleanup
0.1.3 — 2026-03-28
- Clippy
type_complexityerrors: introducedLogBatchEntrytype alias for the 8-field batch tuple (src/db.rs,src/syslog.rs) ORDER BY timestamp→ORDER BY l.timestampfor consistency with table alias in non-FTS search path#[allow(dead_code)]→#[expect(dead_code, reason = "...")]onjsonrpcfield for self-cleaning lint suppression
- Removed single-insert
insert_login favour of batch-only path viainsert_logs_batch search_logsnon-FTS path now usesFROM logs lalias, consistent with the FTS join pathsyslog_loose::parse_messageupdated to explicitVariant::EitherAPI; timestamp handling simplified from 5-armIncompleteDatematch to directdt.to_rfc3339()- Removed unused imports (
NaiveDateTime,StreamExt,error/infofrom tracing,uuid,thiserror,axum-extra,tower) - Removed dead
idx += 1at end oftail_logs
0.1.2 — 2026-03-27
- Project documentation (
SETUP.md,docs/) - Lavra project config and codebase profile (
.lavra/) - Beads issue tracking init (
.beads/) - Session doc for 2026-03-27 repo init and restructure
- Updated Rust base image in
Dockerfile
- Removed root-level source files after
src/migration (duplicate artifact cleanup)
0.1.1 — 2026-03-27
- Restructured project to standard Rust layout (
src/modules) - Migrated flat source files into
src/config.rs,src/db.rs,src/mcp.rs,src/syslog.rs,src/main.rs
0.1.0 — 2026-03-27
- Initial release: syslog receiver + MCP server in Rust
- UDP + TCP syslog listeners on port 1514 (RFC 3164 / RFC 5424 / loose via
syslog_loose) - SQLite storage with FTS5 full-text index, WAL mode, and hourly retention purge
- Six MCP tools over JSON-RPC 2.0 (
POST /mcp):search_logs— FTS5 search with host/severity/app/time filterstail_logs— most recent N entriesget_errors— error/warning summary grouped by host and severitylist_hosts— all known hosts with first/last seen and log countscorrelate_events— cross-host event correlation in a time windowget_stats— DB stats (total logs, size, time range)
- SSE endpoint (
GET /sse) for legacy MCP transport - Health check endpoint (
GET /health) - figment-based config (
config.toml+SYSLOG_MCP_env vars) - Docker Compose deployment with bind-mounted
./data/volume - Batch writer with mpsc channel, 100-entry batches, 500ms flush interval