Repository navigation
2026-10-18(Data-Loss Hardening + E2E Race Elimination)
Fixed
- Remote-deletion hardening (2026-07-20 data-loss incident): three layered defenses. (1) Per-pull deletion quota — refuses remote deletion batches > max(20, 25% of local files); the v3.0.4 50% full-index guard did not cover incremental IndexUpdate batches (114 files lost to a stale peer index). (2) Soft delete — puller quarantines remote-driven deletions to
<folder>/.sttrash/<date>/(7-day retention, cleaned per pull, scanner-ignored) instead of hard removal. (3) Deletion audit — every accepted remote deletion is logged at info level with source device and both version vectors. Also: stale-index health warning when a pull sees >10 NO_SUCH_FILE (error code 2) responses. - Version vector used hardcoded counter id 1: all local modifications bumped the vector under a constant device id, so concurrent offline edits on two nodes looked like linear history — a remote delete silently dominated and overwrote the peer's local changes (downstream Obsidian bridge report: deletions resurrecting / modifications lost). Local counter id is now derived from the real device ID (
DeviceId::counter_id(), first 8 bytes big-endian, Go ShortID semantics) and plumbed throughSyncService→FolderModel→Scanner. Concurrent delete-vs-modify is now detected and preserved as.sync-conflict-*.TestNodepopulatesconfig.local_device_id; a missing id falls back to 0 with a loud warning. - BEP Ping storm:
BepSessionno longer replies to received Ping messages. BEP Ping is unidirectional keepalive (no Pong exists); two syncthing-rust nodes previously ping-ponged at wire speed (thousands of Pings/sec, ~16MB debug logs in 16 minutes). Ping is now only sent by the 30s heartbeat timer. Reported via Obsidian bridge Android deployment (battery/log impact). - TUI Add Folder silently dropped:
submit_add_folderbuilt theFolderbut never pushed it into the config. Folder additions via TUI (a/Inson Folders tab) now persist and hot-reload into the running daemon. Stale share selections from a previous Edit Folder no longer leak into new folders. set_block_sourceafteradd_folderhad no effect:FolderModelcapturedblock_sourceonly at creation, so pulls failed with "No block source configured".Pullernow holds the source behind a lock andSyncService::set_block_sourcepropagates to all existing folder models.- Config changes now trigger session renegotiation:
update_config/add_device/remove_device/add_folder/remove_folderfire a renegotiation hook with the connected device list; the daemon reconnects those devices so ClusterConfig is re-exchanged (matching Go Syncthing behavior).syncthing-syncstays network-free via the injected hook. - Spurious remote deletion of in-flight files (found 2026-10-16, data-loss adjacent): a peer's watcher-triggered subtree scan could infer a deletion for a file whose blocks were still being pulled —
mark_deleted_subtree()saw a DB record (written by the remote index) with no file on disk, marked it deleted with a fresh version vector, and broadcast it. The peer holding the higher version then moved its only copy into.sttrash, and the file never synced.FolderModel::scan_incremental()now disables subtree delete inference while a pull is in flight (pending_pullsnon-empty or statusPulling); watcher-reported explicit delete events and idle-folder scans still infer deletions. Regression test:folder_model::tests::test_incremental_scan_skips_delete_inference_while_pulling(with an idle positive control). .stignoredirectory rules did not apply to watcher incremental paths:IgnoreMatcher::matchesonly honoureddir/rules whenis_dir=true, so edits inside ignored directories were still indexed and synced (a privacy leak). Addedis_ignored_with_ancestors()(walks ancestor directories) and used it forscan_changed_fileand sub-scan roots; full scans keep directory pruning.- Failed pulls were not retried until the next index event:
FolderModel::start_pull_loopnow retries early with a bounded exponential backoff (PULL_RETRY_BACKOFF_INIT_MS500 ms, doubling up toPULL_RETRY_BACKOFF_MAX_MS= the 10 s period, reset on success) when a pull round reportsfiles_failed > 0; correctness stays withcheck_needed_files()re-deriving the needed set from DB/FS. - E2E ClusterConfig race root-caused (test infrastructure): the
WARN Timeout waiting for ClusterConfigthat fired on every run came from both peers dialing each other — race resolution closes one of the two connections, and the BepSession on that connection then sat on a link nobody read until its 10 s timeout, pushing every case 10–30 s over its budget and into flakiness.registry.rsrace resolution was verified correct and left untouched. Fixes: (1) single-side dialing —e2e_crud::setup_two_nodeande2e_syncnow have Aconnect_to(B)while B onlyadd_peer_device(&A)(newTestNode::add_peer_device(), withconnect_to/reconnect_tosharingpeer_device()); (2)bep_bridgenow claims the session slot and the connection synchronously insideon_connected(so concurrent spawns can no longer overwrite each other or bind to a replaced connection) and disconnects only its ownconn_ideven when the session task is aborted (session slots hold anAbortHandle). Result: zero ClusterConfig-timeout warnings,e2e_crud6/6 green at ~2.5 s per run (was 26–85 s),e2e_sync3/3 at ~0.2 s. - Both
#[ignore]s removed:test_two_node_single_file_syncandtest_e2e_modify_fileare back to normal execution; the old 30 s disconnect/re-dial workaround ine2e_syncis gone.
Added
- MCP Bridge toolset 11 → 19:
get_health,get_metrics,get_db_status,browse_folder,get_file,pause_folder,resume_folder,poll_events. OptionalSYNCTHING_API_KEYis forwarded asX-API-Key; folder/file values are percent-encoded. The tool catalog moved tosrc/tools.rsto respect the 600-line soft cap, andtests/mcp_bridge.rsdrives the real binary against an in-process stub HTTP server (no HTTP framework dependency). TestNodereadiness primitives:wait_for_cluster_config()/wait_for_folder_ready(), backed by aSessionReadinessevent pump overBepSessionEvent::ClusterConfigComplete— replaces fixed sleeps in E2E setup (connection established ≠ BEP session ready).scripts/agent-facts.ps1: read-only export of drift-prone facts (version, ports, MCP tool count, CI job list, production files over 600 lines) straight from their single sources of truth.docs/design/AGENT_INTEGRATION.md: the Skill / MCP / DSH integration surfaces, including the DSH plugin feasibility conclusion.
Changed
- Top-level docs restructured for production use:
AGENTS.mdandCLAUDE.mdare now short entry pages holding only red lines and navigation; detailed rules live indocs/agent/. Baseline numbers (test counts, CI job counts) are no longer duplicated across files —docs/agent/testing.mdis the single source of truth and the hardcoded test-count README badge was dropped. - E2E block-request retry timeout shortened in the test harness (
TestBlockSource: 10 s → 5 s per attempt) so retries re-acquire a live connection faster under session churn. - Single source of truth for versions: root
Cargo.toml[workspace.package] version = "3.0.4"; all 14 member crates inherit viaversion.workspace = true. - Removed stale
acceptance-tests/package: referenced long-removed APIs (e.g.IrohDiscovery); coverage superseded bycmd/syncthing/tests/e2e_*andsyncthing-netdiscovery unit tests. - Removed stray git tags
mainandlatest(brokegit push origin mainrefspec resolution).
Docs
- Archived 7 completed/superseded files from
docs/plans/todocs/archive/plans/;docs/plans/now holds only 3 active plans + the audit report, with a rewrittenINDEX.md. - Registered
KNOWN_ISSUES.md §24(the CIe2e-testjob runs zero assertions because its only case is#[ignore]d) and refreshed §23 with the 2026-10-16 evidence and this round's mitigations. - Rewrote
.agents/skills/bep-protocol/SKILL.md(it still described v0.2.8 Alpha): it now points at the authoritative docs instead of restating drifting numbers. - Consolidated the documentation tree: the Obsidian
[[wikilink]]navigation layer (docs/_Home.md+ 9_MOC.md) moved todocs/archive/obsidian-nav/, leavingdocs/README.mdas the single OKF entry;docs/README.mdrewritten with a role-based entry table (developer / user / agent) and an accurate tree;ENGINEERING_DISCIPLINE.mdandGRAY_CLOUD_OPS_MANUAL.mdrelocated underdocs/operations/; 16 broken internal links repaired and the 4 orphan docs indexed.
Stats
- Tests: 454 passed / 5 ignored / 0 failed (+62 vs v3.0.4 baseline 392; 2026-10-16 实测,
cargo test --workspace) - Clippy: 0 warnings
- cargo deny / cargo audit: 通过(3 项上游 unmaintained 传递依赖按
.cargo/audit.toml显式接受) - E2E:
e2e_crud6/6 全绿 ~2.5s/次、e2e_sync3/3 ~0.2s/次、Timeout waiting for ClusterConfig告警 0 条 - Interop: syncthing-rust ↔ official Go Syncthing v2.1.1 verified on loopback (2026-07-21): handshake, bidirectional file sync, 1 MiB multi-block transfer with identical SHA-256