AccessPolicy
Something that happens because of a verdict. Facts say what is true, features say what that means, and a policy is the consequence -- kicking, teleporting, closing a door.
maid:Add(AccessPolicy.new(serviceBag, {
policyName = "kickOnNonAdmin",
facts = { AccessFactNames.PLAYER_IS_ADMIN },
apply = function(context)
return context.observeFact(AccessFactNames.PLAYER_IS_ADMIN):Subscribe(function(isAdmin)
if isAdmin == false then
kick(context.player)
end
end)
end,
}))
A policy has a lifetime, so give it to a maid where you make it. Registering it does not take ownership.
A policy declares what it reads, the same way a feature does, and its context only hands back what it
declared. That is why policies may read facts directly when ordinary call sites may not: a policy is a
registered, named, declaring consumer whose inputs show up in a readout, not an anonymous if
somewhere in a UI file.
Policies are registered disabled unless built with isEnabledByDefault = true. Turning one on is a deliberate
act -- from a console command or a
test -- which is what makes a consequence as blunt as kicking safe to ship in the box.
Registration happens in both realms, so a console on either side knows every policy name. An
AccessPolicyRealm decides where apply actually runs: kicking is server-side, showing a locked
banner is client-side.
Types
AccessPolicyApply
Runs the policy for one player. Return a MaidTaskUtils.MaidTask -- a subscription, a connection, a function -- and it is cleaned up when the policy is disabled, the player leaves, or the policy itself is destroyed.
Functions
new
A ServiceBag rather than none, because a policy has to look itself up through
AccessPolicyServiceInterface and that lookup needs a tie realm. Taking the bag means the realm comes
from TieRealmService -- the same source production uses -- rather than being guessed from
RunService, which is what a test running both realms in one DataModel would get wrong.
Policies are built by application code, which already has a bag, so this costs the caller nothing.
IsEnabledByDefault
Whether registering this policy also switches it on.
False unless the policy asked for it. A policy is a consequence -- a kick, a teleport, a door -- and one
that ran the moment a package was added would be a side effect nobody chose. isEnabledByDefault = true is for
the case where the consequence is the reason the policy exists, and it reads as a deliberate line at
the point somebody writes it.
This is the starting state, not the current one. AccessPolicyService.SetPolicyEnabled and the console both override it, and AccessPolicyService.IsPolicyEnabled is what says whether it is on now.
isAccessPolicy
AccessPolicy.isAccessPolicy(value: any) → booleanGetPolicyName
GetFactNames
GetFeatures
GetRealm
Where this policy's consequence runs. It is registered in both realms either way, so a console on either side can name it.
RunsInRealm
DeclaresFact
DeclaresFeature
GetDebugState
AccessPolicy.GetDebugState(self: AccessPolicy) → {policyName: string,realm: string,facts: {string},features: {string}}A plain snapshot of this policy: what it is, where it runs, and what it reads.
IsPolicyActiveForPlayer
Whether this policy is active for the player right now: enabled, in this realm, and this player being tracked. See AccessPolicyService for the enabled-versus-active distinction.
Shallow: it asks the service through AccessPolicyServiceInterface rather than holding one, so a policy stays a description that happens to be able to look itself up, and answers false in a game with no access service rather than erroring.
ObserveIsPolicyActiveForPlayer
The same question, live.
PromiseIsPolicyActiveForPlayer
Settles once this policy is running for the player.
Apply
Types
type AccessPolicyContext = AccessPolicyContextUtils.AccessPolicyContextRuns the policy. Called by AccessPolicyService when the policy is enabled for a player.
The returned task is owned twice over: by the caller, which drops it when the policy is disabled or the player leaves, and by this policy, which drops it when the policy itself is destroyed. Unlike AccessFact and AccessFeature -- which are inert descriptions -- a policy is running, so it needs a lifetime of its own and everything it started has to end with it.