refactor(permission)!: resolve permissions through an optional di module - #313
Merged
Merged
Conversation
Titan needs a permission source that is replaceable by DI instead of hard-wired to LuckPerms; this JDK-only contract lets any platform module answer a player permission check without leaking its own types across the classloader boundary. Claude-Session: https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH
…rovides one Without a permission platform in the DI scope, Titan must still start and behave safely; this @secondary fallback answers every check as not set so players get no permissions until a platform module overrides it. Claude-Session: https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH
Adds platform/luckperms (java-library, Avaje inject) and a platform/* directory scan in settings.gradle.kts, mirroring the features/* scan. The module declares @InjectModule(name = "luckpermsPlatform", provides = PermissionService.class) so a variant can pick it up. Claude-Session: https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH
LuckPermsResults translates a LuckPerms Tristate into the platform-neutral PermissionResult, tested for all three values. Runtime's CompatibilityUtil stays where it is for now; the runtime agent removes it once the platform module is wired in. Claude-Session: https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH
LuckPermsExtensionCheck rejects a duplicate extensions/luckperms.jar by name, case-insensitively, tested standalone as a pure function. LuckPermsPermissionService wires it into @PostConstruct before starting LuckPerms through the loader, then answers a permission check from the player's live or static query options, keyed by UUID so the CloudNet bridge can use it too. Claude-Session: https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH
…on service TitanPlayer now checks the injected PermissionService instead of calling LuckPerms directly, and Titan sets the Minestom player provider only after the BeanScope is built, once that service is available. The old LuckPerms mapping in runtime's own CompatibilityUtil moves to platform/luckperms. Claude-Session: https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH
…rvice PermissionBridgeResolver turns a PermissionService into the BiPredicate TitanPermissionBridge needs, and a new @singleton wires/unwires it around the BeanScope's lifecycle. TitanApplication.main no longer starts LuckPerms or builds a resolver by hand. Claude-Session: https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH
Titan logs "Permissions resolved by {}" with the resolved service's name
once the BeanScope is built, so the start log always names deny-all or a
platform's own service.
Claude-Session: https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH
Butterfly is unused beyond Titan.initialize() and its shutdown task; drop the dependency, the butterfly/butterfly-minestom catalog entries, and both call sites. runtime no longer bundles LuckPerms or Guava, and apps/cloudnet and apps/local drop the minestom-loader Gson exclude that existed only because runtime shipped that loader. BREAKING CHANGE: runtime no longer starts LuckPerms or loads Butterfly; a platform module now owns the permission platform's lifecycle. Claude-Session: https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH
Add titanVariant { platform(...) } so an app variant can depend on a
permission platform module and expect it at startup, the same way it
already does for feature columns.
Claude-Session: https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH
apps/cloudnet always bundles platform/luckperms; apps/local only with -Ptitan.luckperms, keeping the deny-all fallback as its default. Every scope-building test in apps/cloudnet now mocks PermissionService under Avaje's auto-derived "LuckPerms" qualifier so it never starts real LuckPerms, and apps/local gets a start test asserting the deny-all fallback is active without the switch. Claude-Session: https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH
Document titanVariant { platform(...) }, -Ptitan.luckperms for local
builds, the deny-all fallback, and the deploy-time removal of
extensions/luckperms.jar and extensions/butterfly.jar now that the
lobby starts LuckPerms itself and Butterfly is gone.
Claude-Session: https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH
Seven test call sites mocked PermissionService under Avaje's class-name-derived qualifier "LuckPerms", each re-explaining in a multi-line comment why the unnamed mock(Type) overload isn't enough. Giving LuckPermsPermissionService an explicit @nAmed(QUALIFIER) makes the name intentional and lets every test reference the constant instead of a magic string, with the comment shrunk to a doc pointer. Claude-Session: https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH
The generateAotCache training run boots the full BeanScope, including LuckPermsPermissionService's @PostConstruct, so it starts real LuckPerms against a data/ directory of its own rather than the deployment's. Claude-Session: https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH
Contributor
Test results210 files 210 suites 13m 49s ⏱️ Results for commit 80322a9. |
This was referenced Sep 29, 2026
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
New
PermissionServicecontract incore(JDK-only,ALLOWED/DENIED/NOT_SET);DenyAllPermissionServiceas@Secondaryfallback inruntime; new moduleplatform/luckperms(LuckPermsPermissionService,@Named("luckperms"), starts the LuckPerms minestom-loader in@PostConstruct, stops via the loader's JVM shutdown hook, aborts if a LuckPerms extension is loaded too);TitanPlayerand the CloudNet bridge resolver (TitanPermissionBridge) both resolve through the same service; startup logsPermissions resolved by <name>;titanVariant { platform("…") }in the app-variant convention adds platform modules and makes them expected modules (luckpermsPlatform);apps/cloudnetalways includes LuckPerms,apps/localonly with-Ptitan.luckperms; Butterfly removed.Behaviour changes
CloudNet permission queries now honour LuckPerms contexts (online player → the player's query options, otherwise static query options; previously no options); without a permission platform players have no permissions, console unaffected.
Deploy
Remove
extensions/luckperms.jarandextensions/butterfly.jarfrom the CloudNet template; keepdata/(LuckPerms data location unchanged); retrain the AOT cache (training now boots LuckPerms against a disposable data dir).Acceptance (local, titan-cloudnet.jar with existing data/)
luckperms, moduleluckpermsPlatformloadeddata/picked up (groupdefaultpresent)/stopwith permission not set → refused/stopwith permissionfalse→ refused/stopwith permission only inserver=survival→ refused/stopwith permission inserver=lobby(lobby's own context) → server stopsapps/localwithout the switch →Permissions resolved by deny-all, nodata/createdNoSuchMethodError); check covered by unit testDepends on
#312 (merged).
BREAKING CHANGE: Butterfly is removed and LuckPerms is no longer loaded from main() or the extensions folder but only through platform/luckperms; without it players have no permissions.
https://claude.ai/code/session_01A7aAe2E6wAEbi6jjnyPTWH