Skip to main content

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

type AccessPolicyApply = (AccessPolicyContext) → MaidTask?

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

AccessPolicy.new(
serviceBagServiceBag,
optionsAccessPolicyOptions
) → AccessPolicy

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

AccessPolicy.IsEnabledByDefault(selfAccessPolicy) → boolean

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(valueany) → boolean

GetPolicyName

AccessPolicy.GetPolicyName(selfAccessPolicy) → string

GetFactNames

AccessPolicy.GetFactNames(selfAccessPolicy) → {string}

GetFeatures

AccessPolicy.GetFeatures(selfAccessPolicy) → {AccessFeature}

GetRealm

AccessPolicy.GetRealm(selfAccessPolicy) → string

Where this policy's consequence runs. It is registered in both realms either way, so a console on either side can name it.

RunsInRealm

AccessPolicy.RunsInRealm(
selfAccessPolicy,
isServerboolean
) → boolean

DeclaresFact

AccessPolicy.DeclaresFact(
selfAccessPolicy,
factNamestring
) → boolean

DeclaresFeature

AccessPolicy.DeclaresFeature(
selfAccessPolicy,
featureAccessFeature
) → boolean

GetDebugState

AccessPolicy.GetDebugState(selfAccessPolicy) → {
policyNamestring,
realmstring,
facts{string},
features{string}
}

A plain snapshot of this policy: what it is, where it runs, and what it reads.

IsPolicyActiveForPlayer

AccessPolicy.IsPolicyActiveForPlayer(
selfAccessPolicy,
playerPlayer
) → boolean

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

AccessPolicy.ObserveIsPolicyActiveForPlayer(
selfAccessPolicy,
playerPlayer
) → Observable<boolean>

The same question, live.

PromiseIsPolicyActiveForPlayer

AccessPolicy.PromiseIsPolicyActiveForPlayer(
selfAccessPolicy,
playerPlayer
) → Promise<boolean>

Settles once this policy is running for the player.

Apply

AccessPolicy.Apply() → MaidTask?

Types

type AccessPolicyContext = AccessPolicyContextUtils.AccessPolicyContext

Runs 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.

Show raw api
{
    "functions": [
        {
            "name": "new",
            "desc": "A [ServiceBag] rather than none, because a policy has to look itself up through\n[AccessPolicyServiceInterface] and that lookup needs a tie realm. Taking the bag means the realm comes\nfrom [TieRealmService] -- the same source production uses -- rather than being guessed from\n`RunService`, which is what a test running both realms in one DataModel would get wrong.\n\nPolicies are built by application code, which already has a bag, so this costs the caller nothing.",
            "params": [
                {
                    "name": "serviceBag",
                    "desc": "",
                    "lua_type": "ServiceBag"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "AccessPolicyOptions"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "AccessPolicy"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 111,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "IsEnabledByDefault",
            "desc": "Whether registering this policy also switches it on.\n\nFalse unless the policy asked for it. A policy is a consequence -- a kick, a teleport, a door -- and one\nthat ran the moment a package was added would be a side effect nobody chose. `isEnabledByDefault = true` is for\nthe case where the consequence *is* the reason the policy exists, and it reads as a deliberate line at\nthe point somebody writes it.\n\nThis is the starting state, not the current one. [AccessPolicyService.SetPolicyEnabled] and the console\nboth override it, and [AccessPolicyService.IsPolicyEnabled] is what says whether it is on now.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "AccessPolicy"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 151,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "isAccessPolicy",
            "desc": "",
            "params": [
                {
                    "name": "value",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 159,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "GetPolicyName",
            "desc": "",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "AccessPolicy"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 166,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "GetFactNames",
            "desc": "",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "AccessPolicy"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "{ string }"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 173,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "GetFeatures",
            "desc": "",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "AccessPolicy"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "{ AccessFeature }"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 180,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "GetRealm",
            "desc": "Where this policy's consequence runs. It is registered in both realms either way, so a console on\neither side can name it.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "AccessPolicy"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 190,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "RunsInRealm",
            "desc": "",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "AccessPolicy"
                },
                {
                    "name": "isServer",
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 198,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "DeclaresFact",
            "desc": "",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "AccessPolicy"
                },
                {
                    "name": "factName",
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 206,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "DeclaresFeature",
            "desc": "",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "AccessPolicy"
                },
                {
                    "name": "feature",
                    "desc": "",
                    "lua_type": "AccessFeature"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 214,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "GetDebugState",
            "desc": "A plain snapshot of this policy: what it is, where it runs, and what it reads.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "AccessPolicy"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "{ policyName: string, realm: string, facts: { string }, features: { string } }"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 223,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "IsPolicyActiveForPlayer",
            "desc": "Whether this policy is *active* for the player right now: enabled, in this realm, and this player\nbeing tracked. See [AccessPolicyService] for the enabled-versus-active distinction.\n\nShallow: it asks the service through [AccessPolicyServiceInterface] rather than holding one, so a\npolicy stays a description that happens to be able to look itself up, and answers false in a game\nwith no access service rather than erroring.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "AccessPolicy"
                },
                {
                    "name": "player",
                    "desc": "",
                    "lua_type": "Player"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 261,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "ObserveIsPolicyActiveForPlayer",
            "desc": "The same question, live.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "AccessPolicy"
                },
                {
                    "name": "player",
                    "desc": "",
                    "lua_type": "Player"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Observable<boolean>"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 276,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "PromiseIsPolicyActiveForPlayer",
            "desc": "Settles once this policy is running for the player.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "AccessPolicy"
                },
                {
                    "name": "player",
                    "desc": "",
                    "lua_type": "Player"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Promise<boolean>"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 288,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "Apply",
            "desc": "Runs the policy. Called by [AccessPolicyService] when the policy is enabled for a player.\n\nThe returned task is owned twice over: by the caller, which drops it when the policy is disabled or\nthe player leaves, and by this policy, which drops it when the policy itself is destroyed. Unlike\n[AccessFact] and [AccessFeature] -- which are inert descriptions -- a policy is *running*, so it needs\na lifetime of its own and everything it started has to end with it.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "AccessPolicy"
                },
                {
                    "name": "context",
                    "desc": "",
                    "lua_type": "AccessPolicyContext"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "MaidTask?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 305,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        }
    ],
    "properties": [],
    "types": [
        {
            "name": "AccessPolicyContext",
            "desc": "",
            "lua_type": "AccessPolicyContextUtils.AccessPolicyContext",
            "source": {
                "line": 60,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        },
        {
            "name": "AccessPolicyApply",
            "desc": "Runs the policy for one player. Return a [MaidTaskUtils.MaidTask] -- a subscription, a connection, a\nfunction -- and it is cleaned up when the policy is disabled, the player leaves, or the policy itself\nis destroyed.",
            "lua_type": "(AccessPolicyContext) -> MaidTask?",
            "source": {
                "line": 70,
                "path": "src/access/src/Shared/AccessPolicy.lua"
            }
        }
    ],
    "name": "AccessPolicy",
    "desc": "Something that *happens* because of a verdict. Facts say what is true, features say what that means,\nand a policy is the consequence -- kicking, teleporting, closing a door.\n\n```lua\nmaid:Add(AccessPolicy.new(serviceBag, {\n\tpolicyName = \"kickOnNonAdmin\",\n\tfacts = { AccessFactNames.PLAYER_IS_ADMIN },\n\tapply = function(context)\n\t\treturn context.observeFact(AccessFactNames.PLAYER_IS_ADMIN):Subscribe(function(isAdmin)\n\t\t\tif isAdmin == false then\n\t\t\t\tkick(context.player)\n\t\t\tend\n\t\tend)\n\tend,\n}))\n```\n\nA policy has a lifetime, so give it to a maid where you make it. Registering it does not take ownership.\n\nA policy declares what it reads, the same way a feature does, and its context only hands back what it\ndeclared. That is why policies may read facts directly when ordinary call sites may not: a policy is a\nregistered, named, declaring consumer whose inputs show up in a readout, not an anonymous `if`\nsomewhere in a UI file.\n\nPolicies are registered disabled unless built with `isEnabledByDefault = true`. Turning one on is a deliberate\nact -- from a console command or a\ntest -- which is what makes a consequence as blunt as kicking safe to ship in the box.\n\nRegistration happens in **both realms**, so a console on either side knows every policy name. An\n[AccessPolicyRealm] decides where `apply` actually runs: kicking is server-side, showing a locked\nbanner is client-side.",
    "source": {
        "line": 37,
        "path": "src/access/src/Shared/AccessPolicy.lua"
    }
}