A column name containing ' was captured fine but ended the JSON-path SQL
literal early on the apply and snapshot paths, dead-lettering every row of the
table on the peer and faulting the snapshot page query for a quoted PK column.
Both paths now share JsonPathSql.For, which doubles ' after the JSON-path
escape. 2-node delta and wiped-peer snapshot tests fail pre-fix.
(scadaproj#46)
Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
The drift guard lived only in the delta path; SnapshotApplier applied every
row unchecked, so a future-stamped snapshot row was applied, observed by the
clock and persisted as last_seen_hlc - moving the receiver's HLC ahead across
restarts despite FailClosedOnDrift (LocalDb-004). SnapshotApplier now takes
ReplicationOptions and runs the same per-row check (warn once per snapshot;
dead-letter under FailClosedOnDrift via the shared HlcDriftGuard, with the
row's HLC kept out of last_seen_hlc).
Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
INSERT OR REPLACE resolved a secondary UNIQUE conflict by deleting a
different row (another PK) with capture suppressed and its ledger left live,
so a pair diverged silently (LocalDb-003). The applier now uses
INSERT ... ON CONFLICT(<pk>) DO UPDATE (DO NOTHING when every column is PK):
a clash on another unique key aborts the entry, which is dead-lettered, and
each node keeps its own row. Side effect: a remote update of a parent with
ON DELETE RESTRICT children now applies instead of being dead-lettered.
Also waits for the snapshot watermark in WipedPeer_SnapshotLiftsItsWatermark,
which raced SnapshotComplete under full-suite load.
Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
The snapshot streamed tables by name and rows by pk_json text, so a child
(or a self-reference whose parent's key sorts later) reached the receiver
first, failed its immediate FK check and was dead-lettered - permanently,
since the snapshot's watermark and ack covered it (LocalDb-002).
Sender: stream referenced tables first (Kahn order, name tie-break).
Receiver: SnapshotApplier defers rows that fail only
SQLITE_CONSTRAINT_FOREIGNKEY, retries them as rows land, and dead-letters
true orphans at SnapshotComplete. Delta path unchanged; wire unchanged.
Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
LocalDbSyncService never read request headers, so setting
LocalDb:Replication:ApiKey on both nodes left the passive endpoint open
(LocalDb-001). It now requires Authorization: Bearer <key> (constant-time)
before the handshake and before newest-wins takeover, refusing anything else
with Unauthenticated. A blank key keeps the old accept-all behaviour and
logs a startup warning. Wire-compatible: the initiator has always sent the
header when the key is set; ScadaBridge/OtOpcUa interceptors use the same key.
LocalDb 0.3.5 -> 0.3.6 (unpublished).
Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
First reviews of Secrets, LocalDb, GalaxyRepository and Overview, and
2026-11-09 re-reviews of Auth, Health, Theme, Telemetry, Audit and
Configuration, all at 91fb2b4. 49 new open findings, each with a P1/P2/P3
priority and a failure scenario: 1 P1, 13 P2, 35 P3. README regenerated;
REVIEW-PROCESS, template and regen script now cover all ten libraries and
the Priority field.
Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV