You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Phase 3: /id-sync/1 is used by both range and LogSync handshakes #179
The range session starts after a Request/Answer on /id-sync/1, and LogSync sends Request first. Confirm one handshake covers both and that a responder answering a method it was not asked for is refused.
id train triage (2026-10-24): needs-decision — project owner: single /id-sync/1 handshake for range + LogSync (confirm one covers both, and unanswered methods are refused).
One ALPN: log_sync.rsSYNC_ALPN and event_store/sync.rsALPN are both b"/id-sync/1", and log_sync's module doc states the design intent — range sync joins as a method of the same session, first frame is the Request.
One handshake shape covers both methods in sync_method.rs / log_sync.rs: initiator sends Request { topic, method } (SyncMethod::{Log, Range}, chosen by choose()), responder answers Answer::Agree(method) or Answer::Refuse.
The refusal the issue asks about is implemented and tested: the initiator errors when an Answer names a different method than requested (dedicated SyncError variant), the responder sends Refuse for a method it does not support, and a test in log_sync.rs covers "peer answers Range when Log was asked — the initiator must refuse it."
What is not yet true (the confirmed remainder):
run_agreed in log_sync.rs dispatches SyncMethod::Range to SyncError::NotWired, and LogSyncOnly refuses Range — so under the shared handshake, Range is a dead end today.
Route: scoped small_fix follow-up enqueued as queue task 2970: wire Range dispatch under the handshake, and bring serve's range acceptor under the same first frame (or split it to a distinct ALPN, documented). Refusal semantics stay as-is.
Agent: queue worker (repo-worker-code), lane A shift
Session / ID: queue task 2970
Found via: queue claim
Task: Fix Phase 3: /id-sync/1 is used by both range and LogSync handshakes #179 — wire SyncMethod::Range dispatch in log_sync run_agreed under the Request/Answer handshake and end the second session shape on /id-sync/1 (event_sync acceptor speaks the same first frame, or moves to a distinct ALPN with the split documented).
Expectation: branch + PR this shift (CI-first verification); refusal semantics unchanged and tested.
Progress — the two session shapes no longer share an ALPN in serve
Done (PR #2214, commit a472fec): the serve-registered range acceptor moved to a distinct ALPN, /id-range-sync/1 — the option this issue's text explicitly allows ("or split it onto a distinct ALPN with the split documented in code"). The split is documented in event_store/sync.rs (module + ALPN docs), event_sync.rs, and log_sync.rs (SYNC_ALPN docs); dialer and acceptor both read event_store::sync::ALPN, so they move together; a peer on an older build fails the dial and serve retries next interval. A new event_sync test pins both ALPN values and asserts they differ. CI on the PR is the verification (fleet CI-first rule); the two-node event_sync tests exercise the new ALPN end to end.
Remainder (why #179 stays open): wiring SyncMethod::Range in log_sync::run_agreed. Verified against current code, this is a design step, not a dispatch arm:
run_agreed(store: &SharedEventStore, topic, method, frames: &mut Frames) holds ONE already-open stream pair; range sync (event_store::sync::run) opens its own stream pair from a Connection and also uses the connection for the pull-gate peer check and cut/close handling. Range sync first needs a stream-level entry point (send/recv + peer id) that both call sites can use.
Range sync needs &mut EventStore for the whole session; SharedEventStore is Arc<RwLock<EventStore>> (std lock, short holds by design — its ingest path deliberately avoids session-length holds, which is why log_sync has an ingest worker). A session locking design is needed (e.g. a tokio-mutex session handle like LocalEvents uses, or a dedicated range_session method on SharedEventStore with documented starvation semantics).
Range sync's set is the whole store; the handshake's Request.topic has no range equivalent yet — the resumed work must say what topic means for an agreed Range session (store-wide, documented, is the honest current semantic).
SyncSupport: whoever registers SyncProtocol in production must then advertise Range; today LogSyncOnly refuses it, which stays correct until the dispatch exists.
Refusal semantics are untouched by PR #2214 and remain tested (log_sync mismatch/refusal tests).
Lane D (queue task 2970) resumed from checkpoint merge_ready. Fresh revalidation 2026-10-29T23:25Z: PR #2214 OPEN/MERGEABLE at 7154a76; all checks SUCCESS except clippy-lint (the failure prior shifts attributed to the #2401 infra pattern; tests incl. test-unit and all shards pass); both Codex inline findings have disposition replies on their threads. Blocker #2403 still open, main still 5a34fa7. Attempting one fresh merge-queue enqueue this shift (another PR is currently queued, so queue health can be observed); if ejected per #2403, the task re-parks.
Lane D (queue task 2970): merge-queue enqueue attempted — PR #2214 entered 23:26:29Z at position 2 behind #2447; by ~23:30Z the queue was empty, PR still OPEN, main still 5a34fa7. Fresh confirmation of blocker #2403 (evidence posted there). Task re-parked. Resume: MQ healthy / main advances → enqueue #2214 → merge → complete fixed_merged + close this issue.
Lane A (queue task 2970): verified terminal state fresh — PR #2214 MERGED 2026-10-30T01:00:10Z (merge commit 88636ed), blocker #2403 CLOSED, main advanced to 7d1b081. The parked resume condition was met and the merge landed under a prior shift; this issue closed 2026-10-30T01:16:04Z. Task 2970 completing as already_done; stale queue/status/work labels removed.
🤖 Agent note · picked up → done (already_done)
Agent: queue worker (repo-worker-code), Lane A shift
Activity
dezren39 commentedon Oct 24, 2026
id train triage (2026-10-24): needs-decision — project owner: single /id-sync/1 handshake for range + LogSync (confirm one covers both, and unanswered methods are refused).
dezren39 commentedon Oct 29, 2026
🤖 Agent note · done
Verdict (fresh HEAD reads, this shift)
What is already true:
log_sync.rsSYNC_ALPNandevent_store/sync.rsALPNare bothb"/id-sync/1", and log_sync's module doc states the design intent — range sync joins as a method of the same session, first frame is theRequest.sync_method.rs/log_sync.rs: initiator sendsRequest { topic, method }(SyncMethod::{Log, Range}, chosen bychoose()), responder answersAnswer::Agree(method)orAnswer::Refuse.Answernames a different method than requested (dedicatedSyncErrorvariant), the responder sendsRefusefor a method it does not support, and a test in log_sync.rs covers "peer answers Range when Log was asked — the initiator must refuse it."What is not yet true (the confirmed remainder):
run_agreedin log_sync.rs dispatchesSyncMethod::RangetoSyncError::NotWired, andLogSyncOnlyrefuses Range — so under the shared handshake, Range is a dead end today./id-sync/1acceptor serve actually registers (event_sync::EventSyncProtocolfor the LocalEvents store, added for id phase-8/platform-completion: ProseMirror save revisions are signed and stored locally but never replicated to peers #792) runs range sessions with no Request/Answer first frame — a second, different session shape on the same ALPN. A peer opening with one shape can be misread by an acceptor expecting the other, instead of refused.Route: scoped
small_fixfollow-up enqueued as queue task 2970: wire Range dispatch under the handshake, and bring serve's range acceptor under the same first frame (or split it to a distinct ALPN, documented). Refusal semantics stay as-is.dezren39 commentedon Oct 29, 2026
🤖 Agent note · picked up
dezren39 commentedon Oct 29, 2026
🤖 Agent note · parked
/id-range-sync/1); task parked on that PR's CI. Therun_agreedRange dispatch remains — precise remainder below.queue/2970-179-sync-handshake· laptop worktree~/git/fleet/2970-179-sync-handshakeProgress — the two session shapes no longer share an ALPN in
serveDone (PR #2214, commit a472fec): the serve-registered range acceptor moved to a distinct ALPN,
/id-range-sync/1— the option this issue's text explicitly allows ("or split it onto a distinct ALPN with the split documented in code"). The split is documented inevent_store/sync.rs(module +ALPNdocs),event_sync.rs, andlog_sync.rs(SYNC_ALPNdocs); dialer and acceptor both readevent_store::sync::ALPN, so they move together; a peer on an older build fails the dial andserveretries next interval. A newevent_synctest pins both ALPN values and asserts they differ. CI on the PR is the verification (fleet CI-first rule); the two-nodeevent_synctests exercise the new ALPN end to end.Remainder (why #179 stays open): wiring
SyncMethod::Rangeinlog_sync::run_agreed. Verified against current code, this is a design step, not a dispatch arm:run_agreed(store: &SharedEventStore, topic, method, frames: &mut Frames)holds ONE already-open stream pair; range sync (event_store::sync::run) opens its own stream pair from aConnectionand also uses the connection for the pull-gate peer check and cut/close handling. Range sync first needs a stream-level entry point (send/recv + peer id) that both call sites can use.&mut EventStorefor the whole session;SharedEventStoreisArc<RwLock<EventStore>>(std lock, short holds by design — its ingest path deliberately avoids session-length holds, which is whylog_synchas an ingest worker). A session locking design is needed (e.g. a tokio-mutex session handle likeLocalEventsuses, or a dedicatedrange_sessionmethod onSharedEventStorewith documented starvation semantics).Request.topichas no range equivalent yet — the resumed work must say what topic means for an agreed Range session (store-wide, documented, is the honest current semantic).SyncSupport: whoever registersSyncProtocolin production must then advertise Range; todayLogSyncOnlyrefuses it, which stays correct until the dispatch exists.Refusal semantics are untouched by PR #2214 and remain tested (
log_syncmismatch/refusal tests).86 remaining items
dezren39 commentedon Oct 29, 2026
Lane D (queue task 2970) resumed from checkpoint merge_ready. Fresh revalidation 2026-10-29T23:25Z: PR #2214 OPEN/MERGEABLE at 7154a76; all checks SUCCESS except clippy-lint (the failure prior shifts attributed to the #2401 infra pattern; tests incl. test-unit and all shards pass); both Codex inline findings have disposition replies on their threads. Blocker #2403 still open, main still 5a34fa7. Attempting one fresh merge-queue enqueue this shift (another PR is currently queued, so queue health can be observed); if ejected per #2403, the task re-parks.
dezren39 commentedon Oct 29, 2026
Lane D (queue task 2970): merge-queue enqueue attempted — PR #2214 entered 23:26:29Z at position 2 behind #2447; by ~23:30Z the queue was empty, PR still OPEN, main still 5a34fa7. Fresh confirmation of blocker #2403 (evidence posted there). Task re-parked. Resume: MQ healthy / main advances → enqueue #2214 → merge → complete fixed_merged + close this issue.
dezren39 commentedon Oct 30, 2026
🤖 Agent note · picked up → parked
Ingest::Voideddisposition through a range session #2414). Two checks were red —clippy-lintand the aggregatenix flake checkon run 40944390237 — and the failure log is pure infra: Magic Nix Cache throttling (HTTP 418 narinfo downloads, GitHub API rate-limit), not code. Re-ran the failed jobs once (gh run rerun --failed).dezren39 commentedon Oct 30, 2026
Lane A (queue task 2970): verified terminal state fresh — PR #2214 MERGED 2026-10-30T01:00:10Z (merge commit 88636ed), blocker #2403 CLOSED, main advanced to 7d1b081. The parked resume condition was met and the merge landed under a prior shift; this issue closed 2026-10-30T01:16:04Z. Task 2970 completing as already_done; stale queue/status/work labels removed.
🤖 Agent note · picked up → done (already_done)