Review finding (Fix round 1, Task 1): the property getters read their
backing fields without synchronization while RecordSuccess/RecordFailure
write them under _gate, so a concurrent read could observe a torn
DateTimeOffset or a mix of fields from two different writes. Converted the
four auto-properties to explicit properties over private fields, each
getter taking the same lock the writers use. Public interface unchanged.
GrpcSecretSyncService's background sweep now records outcomes on a new
SecretsHubSyncStatus (registered singleton, exposed as public
ISecretsHubSyncStatus) and logs three new events on the outcome-recording
path: EventId 4 (Debug, every in-sync sweep), EventId 5 (Information, the
same outcome throttled to at most once per the new
SecretsGrpcHubClientOptions.InSyncLogInterval, default 1h), and EventId 6
(Information, the first sweep to succeed after one or more failures).
EventIds 1-3 are unchanged; the raw internal SweepAsync still throws on
failure exactly as before. Not yet published to the Gitea feed and
ScadaBridge is not re-pinned — that is a separate, later step.
Replaces the 2026-09-11 remote-exec hold: WinRM exec is allowed only for the on-box steps of a
user-requested deploy; ad-hoc diagnostics/version checks use SMB, HTTP or SQL. SMB file copies and
direct SQL logins remain always allowed. Scope rules live in the winrm skill's Scope section.
CLAUDE.md: ScadaBridge row records main @ 17864d1a (records only, code tip
2b9a7c3c) deployed on vd03 and the vq03/vq04 pair; Theme/Auth/LocalDb rows
lose their "not yet deployed" caveats (QA pair now runs LocalDb replication,
connected); tier note gains the deployed-state line and the winrm access path.
deployment.md: §2.1 access rows replaced by the winrm skill (WinRM 5985 +
SMB 445; servecli/psbridge retired), §7.1 port map gains 5985/445 and marks
6150 retired, live-state table updated (OtOpcUa active flipped to node-a),
new "Deployed builds on QA" paragraph, §9 deployed-state summary.
Claude-Session: https://claude.ai/code/session_01RgthVLVU5CJ8SUuEwruTkt
Row in the sister-project table, a BTDB-edges subsection (Galaxy write contract, ScadaBridge inbound-API SAPID lookup on db-connection BTDB, Historian CLR/INSQL co-location), break-surface note, Windows-only build commands, and the DARS bullet now points at the unversioned WW_ARCH/BatchTracker_DB fork. Full schema/contract notes and the 2026-09-03 gap analysis live in .claude/skills/sister-project-history/btdb.md.
Claude-Session: https://claude.ai/code/session_01Wzg6NUCWHipSexLs2cgRFv
.gitignore ignored all of .claude/, so the per-project history files the root index points at were never committed; the pushed index had lost that history. Un-ignore .claude/skills/ (settings.local.json and worktrees stay ignored) and add the three files.
Claude-Session: https://claude.ai/code/session_01Wzg6NUCWHipSexLs2cgRFv
SnapshotStreamer.SendAsync stamped as_of_seq = MAX(seq), which reads 0 once ack-pruning has
emptied a converged sender's oplog. A receiver whose watermark genuinely is 0 (a rebuilt node —
ScadaBridge's site-a-a since its 2026-08-15 rebuild) was healed by the snapshot but never lifted
by it (the receiver's watermark update is correctly MAX-guarded), so the 0.1.2 empty-oplog
horizon rule saw 0 < last_acked_seq as a gap on every subsequent handshake and re-streamed the
whole database on every reconnect until the sender wrote again. Observed live: the same 127k-row
full snapshot on two consecutive restarts of the pair.
Fix: as_of_seq = max(MAX(seq), last_acked_seq). Every delta pruned below last_acked_seq is
already folded into the rows the snapshot carries, so that is the floor it may claim; the
receiver's MAX guard then lifts the watermark and the next handshake finds no gap. Sender-side
only; wire-compatible; no schema change.
Test WipedPeer_SnapshotLiftsItsWatermark_SoTheNextReconnectSendsNoSecondSnapshot fails on the
unfixed code exactly as the rig does (watermark 0, expected 3) and passes with the fix: the
second episode exchanges only a delta, and a capturing logger on the snapshot streamer proves
no snapshot was sent. Suite 216/216 on 8 of 9 runs; one unnamed single-test failure on the first
run did not recur in eight further runs and could not be attributed.