mirror of
https://github.com/logos-messaging/nim-sds.git
synced 2026-07-02 13:59:41 +00:00
feat(persistence): add PersistenceV2 interface alongside legacy (phase 1)
Introduce the 5-proc snapshot-based Persistence interface that will replace the legacy 13-proc one. Both coexist on `ReliabilityManager` so phase 2 can migrate protocol ops one at a time without breaking existing callers. New file: - sds/types/persistence_v2.nim — `PersistenceV2` type with saveChannelMeta / updateHistory / loadChannel / dropChannel / setRetrievalHint. `noOpPersistenceV2()` default. Doc-comments capture the atomicity pairing (meta save + history update issued back-to-back under the channel lock) and the non-fatal failure policy from PLAN §8. Modified: - sds/types/reliability_manager.nim — adds `persistenceV2: PersistenceV2` field alongside `persistence`; constructor takes both, both default to no-op. - sds.nim — `newReliabilityManager` plumbs the new optional parameter. - AGENTS.md / CLAUDE.md — GitNexus index re-indexed after phase 0 + phase 1 additions; symbol counts updated by `npx gitnexus analyze`. No call site uses the new interface yet — that's phase 2. All existing tests still pass against the legacy interface. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
979a66360b
commit
f5946763c4
@ -1,7 +1,7 @@
|
||||
<!-- gitnexus:start -->
|
||||
# GitNexus — Code Intelligence
|
||||
|
||||
This project is indexed by GitNexus as **nim-sds** (889 symbols, 1437 relationships, 45 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
This project is indexed by GitNexus as **nim-sds** (1079 symbols, 1770 relationships, 61 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
|
||||
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
||||
|
||||
@ -40,4 +40,4 @@ This project is indexed by GitNexus as **nim-sds** (889 symbols, 1437 relationsh
|
||||
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
|
||||
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |
|
||||
|
||||
<!-- gitnexus:end -->
|
||||
<!-- gitnexus:end -->
|
||||
|
||||
@ -166,7 +166,7 @@ If using Nix, also recalculate the fixed-output hash in `nix/deps.nix` after upd
|
||||
<!-- gitnexus:start -->
|
||||
# GitNexus — Code Intelligence
|
||||
|
||||
This project is indexed by GitNexus as **nim-sds** (889 symbols, 1437 relationships, 45 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
This project is indexed by GitNexus as **nim-sds** (1079 symbols, 1770 relationships, 61 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
|
||||
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
||||
|
||||
|
||||
10
sds.nim
10
sds.nim
@ -8,13 +8,17 @@ proc newReliabilityManager*(
|
||||
participantId: SdsParticipantID,
|
||||
config: ReliabilityConfig = defaultConfig(),
|
||||
persistence: Persistence = noOpPersistence(),
|
||||
persistenceV2: PersistenceV2 = noOpPersistenceV2(),
|
||||
): Result[ReliabilityManager, ReliabilityError] =
|
||||
## Creates a new multi-channel ReliabilityManager.
|
||||
## `participantId` is REQUIRED (see `ReliabilityManager.new`).
|
||||
## `persistence` defaults to a no-op backend; supply a real one to durably
|
||||
## store SDS state across restarts.
|
||||
## `persistence` is the legacy fine-grained backend; defaults to a no-op.
|
||||
## `persistenceV2` is the snapshot-based backend (target interface; see
|
||||
## PLAN_SNAPSHOT_PERSISTENCE.md). Also defaults to a no-op. During the
|
||||
## phased refactor both backends coexist; phase 3 deletes the legacy
|
||||
## interface and renames `persistenceV2` to `persistence`.
|
||||
try:
|
||||
let rm = ReliabilityManager.new(participantId, config, persistence)
|
||||
let rm = ReliabilityManager.new(participantId, config, persistence, persistenceV2)
|
||||
return ok(rm)
|
||||
except Exception:
|
||||
error "Failed to create ReliabilityManager", msg = getCurrentExceptionMsg()
|
||||
|
||||
94
sds/types/persistence_v2.nim
Normal file
94
sds/types/persistence_v2.nim
Normal file
@ -0,0 +1,94 @@
|
||||
## Snapshot-based persistence interface (5 procs).
|
||||
##
|
||||
## This is the target interface for the refactor described in
|
||||
## PLAN_SNAPSHOT_PERSISTENCE.md. It coexists with the legacy 13-proc
|
||||
## `Persistence` (in `./persistence.nim`) during phase 2 of the refactor:
|
||||
## protocol ops are migrated one at a time. Phase 3 deletes the old
|
||||
## interface and renames `PersistenceV2` to `Persistence`.
|
||||
##
|
||||
## Why 5 procs instead of 13: every protocol op now issues at most ONE
|
||||
## meta save + ONE history update at the end of the op, eliminating
|
||||
## per-mutation persistence calls and the partial-write divergence they
|
||||
## made unavoidable. See PLAN_SNAPSHOT_PERSISTENCE.md §2 and §8.
|
||||
|
||||
import chronos, results
|
||||
import ./sds_message_id
|
||||
import ./channel_meta
|
||||
import ./history_update
|
||||
export results, sds_message_id, channel_meta, history_update
|
||||
|
||||
type PersistenceV2* = object
|
||||
## Snapshot-based persistence contract. Supplied at
|
||||
## `newReliabilityManager` construction time. Each proc field is invoked
|
||||
## by nim-sds AT MOST ONCE per protocol op, at the end of the op, under
|
||||
## the channel lock.
|
||||
##
|
||||
## Atomicity expectation: nim-sds issues `saveChannelMeta` and (when
|
||||
## non-empty) `updateHistory` back-to-back with NO intervening
|
||||
## `await`-of-other-work. The backend MAY treat the pair as one
|
||||
## transaction. The pair is keyed on the same `channelId`.
|
||||
##
|
||||
## Failure policy: a failed `saveChannelMeta` or `updateHistory` MUST NOT
|
||||
## abort the protocol op. The next op's save is fully self-contained and
|
||||
## will re-synchronise on-disk state. See PLAN §8.
|
||||
|
||||
saveChannelMeta*: proc(
|
||||
channelId: SdsChannelID, meta: ChannelMeta
|
||||
): Future[Result[void, string]] {.async: (raises: []), gcsafe.}
|
||||
## Persist the complete current per-channel snapshot. Idempotent: the
|
||||
## blob is the full state, so a missed write is recovered by any later
|
||||
## successful write.
|
||||
|
||||
updateHistory*: proc(
|
||||
channelId: SdsChannelID, update: HistoryUpdate
|
||||
): Future[Result[void, string]] {.async: (raises: []), gcsafe.}
|
||||
## Append newly-delivered messages and evict oldest ones past the
|
||||
## maxMessageHistory cap. Callers SHOULD skip this call entirely when
|
||||
## `update.isEmpty`.
|
||||
|
||||
loadChannel*: proc(
|
||||
channelId: SdsChannelID
|
||||
): Future[Result[ChannelData, string]] {.async: (raises: []), gcsafe.}
|
||||
## Bootstrap on `getOrCreateChannel`. Returns the full prior state, or
|
||||
## an empty `ChannelData` if the channel is new on disk.
|
||||
|
||||
dropChannel*: proc(
|
||||
channelId: SdsChannelID
|
||||
): Future[Result[void, string]] {.async: (raises: []), gcsafe.}
|
||||
## Wipe all persisted state for a channel. Called by `removeChannel` /
|
||||
## `resetReliabilityManager`. Backends SHOULD execute atomically.
|
||||
|
||||
setRetrievalHint*: proc(
|
||||
msgId: SdsMessageID, hint: seq[byte]
|
||||
): Future[Result[void, string]] {.async: (raises: []), gcsafe.}
|
||||
## Record a retrieval hint for a message id. Called from
|
||||
## `getRecentHistoryEntries` when an application-supplied hint provider
|
||||
## returns a non-empty hint. Out-of-band from the snapshot/history
|
||||
## write path because hints are populated lazily during read.
|
||||
|
||||
proc noOpPersistenceV2*(): PersistenceV2 =
|
||||
## Default backend: discards all writes, returns an empty snapshot on
|
||||
## load. Used when no real backend is supplied (existing tests and
|
||||
## non-durability-needing callers).
|
||||
PersistenceV2(
|
||||
saveChannelMeta: proc(
|
||||
channelId: SdsChannelID, meta: ChannelMeta
|
||||
): Future[Result[void, string]] {.async: (raises: []).} =
|
||||
ok(),
|
||||
updateHistory: proc(
|
||||
channelId: SdsChannelID, update: HistoryUpdate
|
||||
): Future[Result[void, string]] {.async: (raises: []).} =
|
||||
ok(),
|
||||
loadChannel: proc(
|
||||
channelId: SdsChannelID
|
||||
): Future[Result[ChannelData, string]] {.async: (raises: []).} =
|
||||
ok(ChannelData.init()),
|
||||
dropChannel: proc(
|
||||
channelId: SdsChannelID
|
||||
): Future[Result[void, string]] {.async: (raises: []).} =
|
||||
ok(),
|
||||
setRetrievalHint: proc(
|
||||
msgId: SdsMessageID, hint: seq[byte]
|
||||
): Future[Result[void, string]] {.async: (raises: []).} =
|
||||
ok(),
|
||||
)
|
||||
@ -6,16 +6,23 @@ import ./callbacks
|
||||
import ./reliability_config
|
||||
import ./channel_context
|
||||
import ./persistence
|
||||
import ./persistence_v2
|
||||
export
|
||||
sds_message_id, history_entry, callbacks, reliability_config, channel_context,
|
||||
persistence
|
||||
persistence, persistence_v2
|
||||
|
||||
type ReliabilityManager* = ref object
|
||||
channels*: Table[SdsChannelID, ChannelContext]
|
||||
config*: ReliabilityConfig
|
||||
participantId*: SdsParticipantID
|
||||
persistence*: Persistence
|
||||
## Pluggable durability backend; defaults to a no-op when not supplied.
|
||||
## Legacy fine-grained persistence interface. Phase 1 of the refactor
|
||||
## (see PLAN_SNAPSHOT_PERSISTENCE.md) keeps this alongside `persistenceV2`
|
||||
## so protocol ops can be migrated one at a time.
|
||||
persistenceV2*: PersistenceV2
|
||||
## Snapshot-based persistence interface. Defaults to a no-op when not
|
||||
## supplied. During phase 2 of the refactor, individual protocol ops
|
||||
## are migrated from `persistence.X` to `persistenceV2.X`.
|
||||
lock*: AsyncLock
|
||||
## Single-threaded Chronos cooperative lock. Serializes mutators against
|
||||
## one another at await points; the manager assumes all calls come from
|
||||
@ -38,6 +45,7 @@ proc new*(
|
||||
participantId: SdsParticipantID,
|
||||
config: ReliabilityConfig,
|
||||
persistence: Persistence = noOpPersistence(),
|
||||
persistenceV2: PersistenceV2 = noOpPersistenceV2(),
|
||||
): T =
|
||||
## `participantId` is REQUIRED — it is the per-manager identity SDS-R uses
|
||||
## to populate response groups and decide which incoming repair requests
|
||||
@ -50,6 +58,7 @@ proc new*(
|
||||
config: config,
|
||||
participantId: participantId,
|
||||
persistence: persistence,
|
||||
persistenceV2: persistenceV2,
|
||||
lock: newAsyncLock(),
|
||||
periodicTasks: @[],
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user