Harden challenge-progress SQLite storage and legacy migration for 3.5.0#146
Merged
Conversation
sqlite-jdbc's getObject(column, Long.class) throws "Bad value for type Long" for SQL NULL instead of returning null, so any island with a stored completion without an active cooldown became unreadable, breaking all challenge access for that island. Read the column via getLong + wasNull instead. Also raise repository error logging from FINE to WARNING so store and pragma failures are visible in production logs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The migration now reproduces exactly what the previous version read, per the configured challengeSharing mode: - challengeSharing=player: per-player completion files were live data; resolve them through island membership (party.members, not just party.leader-uuid) and merge them into the island, so non-leader members no longer lose their progress. - challengeSharing=island: only island-named files and the leader's file (which the old loader promoted on first load) are imported; other per-player files are stale residue from a former player-sharing setup and are left in place instead of inflating island progress. - players/*.yml challenge sections only merge when the island has no completion file, matching the old fallback-only behavior, and are skipped for players without an island. Scan failures now abort the migration (and plugin enable) instead of marking the one-shot import complete with data unread, and the migration logs summary counts for unmapped and stale sources. Also catch Guava's UncheckedExecutionException in the completion cache fallback (the previous catch of ExecutionException could never fire for runtime failures) and drop the dead re-creation of the legacy completion/ directory on startup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Skip stored progress for challenges no longer present in challenges.yml when re-granting permission rewards on member join (previously threw NoSuchElementException and skipped all grants). Inject the plugin logger into the SQLite repository via @PluginLog instead of Guice's JIT JUL logger. Document the new per-island SQLite storage and the challengeSharing removal in the admin docs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Player sharing: gate the players.yml fallback per player (matching the old per-player completion-file check) instead of per island, so an inactive member's progress is not skipped just because another member had a completion file; resolve per-player completion files through the player's own recorded island coordinates first (deterministic, the source the old runtime used) with island membership as fallback; leave island-named files in place (the player-sharing runtime never read them). - Island sharing: only promote the leader's completion file when no island-named file exists, matching the old rename-on-absence behavior instead of max-merging stale leader data. - Count superseded player-yml sources in the residue summary, and document that challengeSharing must stay in challenges.yml for the first start after upgrading so the importer can interpret legacy data. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Pre-release hardening of #137, fixing the confirmed findings from a deep adversarial review of the SQLite challenge-progress feature.
Release blocker fixed
getObject(column, Long.class)throwsBad value for type Longon SQL NULL instead of returning null, so any island with a stored completion without an active cooldown (e.g. any completed one-time challenge) madeload()throw on every challenge command, GUI open, and member join. Now read viagetLong+wasNull, with a regression test that fails on master.Legacy migration made faithful to the old sharing semantics
The one-shot YAML→SQLite import now reproduces exactly what the pre-SQLite runtime read, per the server's configured
challengeSharing(per-island is the only model going forward):challengeSharing: playerservers no longer lose non-leader progress. Per-player completion files were live data; they're resolved through the player's own recorded island coordinates (deterministic, the source the old runtime keyed by), with island membership (party.members) as fallback, and max-merged into the island.challengeSharing: island(default) servers no longer resurrect stale data. Only island-named files and the leader's file (the old loader promoted it when no island file existed — and only then) are imported; other per-player files are residue from a former player-sharing era and stay in place.players/*.ymlfallback gating matches the old loader: per player under player sharing, per island under island sharing, never when superseded by a completion file.Robustness fixes
UncheckedExecutionException, notExecutionException. DB read failures now degrade to an empty map with a WARNING instead of uncaught player-facing errors.NoSuchElementException(aborting all grants) when stored progress references a challenge removed from challenges.yml.@PluginLog(was Guice's JIT JUL logger) and logs store/pragma failures at WARNING instead of FINE.completion/directory that undid the migration's cleanup every boot.challengeSharingdocumented as ignored — with the explicit warning to leave the key in place for the first start after upgrading, since the importer reads it to interpret legacy data.Verification
ChallengeLogicin a test is impractical); the island-sharing residue test pins the no-import invariant.3599595e); all confirmed findings from that second pass are fixed in the last commit.Consciously deferred to 4.0 (where the catalog rework replaces the completion runtime): write-behind cache crash window,
PRAGMA synchronousconnection scope, sqlite-jdbc version shadowing by Paper's bundled driver,IslandKeystrictness. Tracking issue to follow.🤖 Generated with Claude Code