Fix player fact loss on server switch, add MongoDB auth, make reload safe - #1
Open
Chikage04 wants to merge 12 commits into
Open
Fix player fact loss on server switch, add MongoDB auth, make reload safe#1Chikage04 wants to merge 12 commits into
Chikage04 wants to merge 12 commits into
Conversation
Two defects in the per-player fact sync could delete facts from the shared MongoDB on a server switch: 1. reconcileGroup() keyed its delete-missing on the *persistable* subset of the cache. Any fact whose entry isn't defined/loaded on the current server failed isPersistable() and was wiped from the shared store (symptom: "Loaded 10 -> Synced 2 -> lost 8"). Deletion is now keyed on cache *presence*: reconcileGroup(groupId, upserts, present) only removes facts the player genuinely no longer has, never ones this server merely can't persist. 2. The periodic async flush could run after a player was evicted on quit, observing an empty cache and issuing deleteMany() that wiped every fact. FactSessionSync now gates flushes on per-player ownership and serialises load/flush/evict with a striped lock, so an evicted or never-loaded player is never reconciled. Adds an injectable cache provider to FactSessionSync for testing, a MockK unit test for the eviction race, and Testcontainers tests covering presence-based reconciliation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DatabaseConfig only read `uri` and `name`, so the `username`, `password` and `auth_source` config keys were silently ignored — a secured MongoDB rejected every operation with "Command find requires authentication" (error 13). The Mongo client is now built from MongoClientSettings: - optional username/password/auth_source are applied as a MongoCredential (auth_source defaults to the database name), so the password no longer has to be URL-encoded into the URI; an explicit credential overrides one embedded in the URI; - `timeout_ms` (previously parsed but unused) is applied as the server selection timeout. The default config.yml documents the new auth keys. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Typewriter resolves Inkwell's FactStorage once via `by lazy` at its own startup and caches it (FactDatabase.storage). Closing the MongoClient on a runtime disable (PlugMan / /reload) therefore left that cached binding pointing at a dead connection, so Typewriter's periodic storeFacts failed with "state should be: open". onDisable no longer closes the client or unloads the Koin module on a reload — it still flushes online players' facts first, so nothing is lost. Because the binding is cached, a full server restart is needed to re-point Typewriter's hook; the README's reload note documents this. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Specs the fact_catalog (name↔id, upsert-only) + PlaceholderAPI %inkwell_fact_<name>% reading the value from Typewriter's already-loaded fact cache, so facts display on servers without the defining page — no page merging. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSbnaoxxpzqfrNexWsaVj5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSbnaoxxpzqfrNexWsaVj5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSbnaoxxpzqfrNexWsaVj5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSbnaoxxpzqfrNexWsaVj5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSbnaoxxpzqfrNexWsaVj5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSbnaoxxpzqfrNexWsaVj5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSbnaoxxpzqfrNexWsaVj5
…he plugin Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSbnaoxxpzqfrNexWsaVj5
The placeholder now takes the entry id directly and reads the value from the already-loaded fact cache, so the name->id catalog is unnecessary. Removes MongoFactCatalog/FactCatalogPublisher and their tests/config; the fact_catalog toggle becomes fact_placeholder. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DSbnaoxxpzqfrNexWsaVj5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three fixes around the MongoDB-backed fact persistence layer on a network.
1. Stop player fact loss when switching servers (
5857a00)reconcileGroupkeyed its delete-missing on the persistable subset of the cache, so any fact whose entry isn't defined/loaded on the current server was wiped from the shared DB ("Loaded 10 → Synced 2 → lost 8"). Deletion is now keyed on cache presence — only facts the player genuinely no longer has are removed, never ones this server merely can't persist.deleteMany()every fact. Flushes are now gated on per-player ownership and serialised with a striped lock, so an evicted/never-loaded player is never reconciled.2. Support separate MongoDB credentials (
0a608af)username/password/auth_sourceconfig keys were silently ignored (onlyuri/namewere read) → a secured MongoDB rejected everything with "Command find requires authentication" (error 13). The client is now built fromMongoClientSettingswith an optionalMongoCredential(auth_source defaults to the database name, so the password no longer has to be URL-encoded into the URI; an explicit credential overrides one embedded in the URI). The previously-unusedtimeout_msis applied as the server-selection timeout, and the defaultconfig.ymldocuments the new keys.3. Keep the Mongo connection open across runtime reloads (
08dadad)Typewriter resolves Inkwell's
FactStorageonce viaby lazyat its own startup and caches it (FactDatabase.storage), so closing theMongoClienton a PlugMan//reloadleft that binding on a dead connection → "Mongo storeFacts failed: state should be: open".onDisableno longer closes the client or unloads the Koin module on a runtime disable — it still flushes online players' facts first, so nothing is lost. Because the binding is cached, a full server restart is needed to re-point Typewriter's hook; the README's reload note documents this.Testing
./gradlew shadowJarbuilds cleanly.🤖 Generated with Claude Code