Forest Logo
search
package_2

anvil

By @kentiers

Roblox

Mirrored

Anvil

Verify Release License Wally

Server-authoritative Luau foundation for Roblox actions, runtime schemas, resource scopes, and safe remote boundaries.

Anvil gives server code one explicit path for untrusted input:

remote payload
  -> schema validation
  -> rate limit
  -> cooldown
  -> authorization
  -> domain execution
  -> output validation
  -> safe client response

Why Anvil

  • Trust boundaries are explicit. Raw client values do not reach an action executor before its input schema passes.
  • Failure is typed. Result and stable error codes model expected gameplay failures without Promise allocation.
  • Resource ownership is visible. Scope owns connections, Instances, callbacks, and cancellable work; destruction is idempotent.
  • Runtime cost is predictable. Core has no required third-party runtime dependency, polling loop, or hidden remote creation.

Non-goals

Anvil is not an anti-cheat system, DataStore wrapper, UI framework, networking replacement, or game-rule engine. Game code still owns prices, inventory, ownership, damage, permissions, and external-effect compensation.

Install

Anvil is a server-realm Wally package. Pin an exact version:

[server-dependencies]
Anvil = "kentiers/anvil@0.4.0"
wally install

Map Wally's server dependencies into ServerScriptService with Rojo:

{
  "name": "MyGame",
  "tree": {
    "$className": "DataModel",
    "ServerScriptService": {
      "ServerPackages": { "$path": "ServerPackages" }
    }
  }
}

Never map Anvil's Action or Transport modules into ReplicatedStorage.

Use case: server-authoritative purchase request

This example accepts only a bounded item identifier. Price, ownership, inventory mutation, and reward remain server decisions inside execute.

--!strict

local ServerScriptService = game:GetService("ServerScriptService")
local AnvilModule = ServerScriptService.ServerPackages.Anvil
local Anvil = require(AnvilModule)
local RobloxRemote = require(AnvilModule.Transport.RobloxRemote)

local purchase = Anvil.Action.new("Purchase", {
    input = Anvil.Schema.object({
        ItemId = Anvil.Schema.string():minLength(1):maxLength(64),
    }),
    output = Anvil.Schema.object({ Accepted = Anvil.Schema.boolean() }),
    cooldown = require(AnvilModule.Action.Cooldown).new(0.25, os.clock),
    rateLimit = require(AnvilModule.Action.RateLimit).new(10, 1, os.clock),
    authorize = function()
        return Anvil.Result.ok(nil)
    end,
    execute = function(context)
        local input = context.input :: { ItemId: string }

        -- Read catalog, price, balance, and ownership from server-owned state.
        if input.ItemId == "" then
            return Anvil.Result.err("PURCHASE_NOT_ALLOWED")
        end
        return Anvil.Result.ok({ Accepted = true })
    end,
})

local remotes = ServerScriptService:WaitForChild("Remotes")
local purchaseRemote = remotes:WaitForChild("Purchase") :: RemoteEvent
RobloxRemote.new():bindEvent(purchaseRemote, purchase, {})

The caller creates and owns purchaseRemote; Anvil does not create remotes implicitly.

Runtime schemas

Use schemas at every untrusted boundary. Roblox datatypes are explicit, and Instance references require both class and ancestry constraints:

local target = Anvil.Schema.instance({
    classNames = { "BasePart" },
    ancestor = workspace:WaitForChild("BuildArea"),
})

Available Roblox validators: Schema.vector3(), Schema.cframe(), Schema.color3(), and Schema.enumItem(expectedEnum?).

Passing Schema.instance only proves reference shape and location. It does not prove player ownership, entitlement, placement validity, or permission.

Security and lifecycle

  • Input order is validation, rate limit, cooldown, authorization, execution, then output validation.
  • Unknown object fields, non-finite numbers, unsupported datatypes, and unconfigured Instances are rejected.
  • Client failures expose stable codes, not stack traces or server state.
  • Each request transport dispatch owns a Scope and destroys it after completion.
  • Scope lifecycle audit is opt-in, server-only, and has no default telemetry or polling.

Read Security model before binding production remotes. Read Architecture for contracts, constraints, and cost model.

Roadmap

PhaseFocusStatus
0.1Core: Result, Schema, Scope, Action, transportReleased
0.2Reliability: lifecycle helpers, fakes, diagnosticsReleased (0.2.0)
0.3Optional integration adaptersReleased (0.3.0)
0.4Transactions and replayReleased (0.4.0)

Full scope and exit gates: ROADMAP.md.

Verification

wally install
powershell -ExecutionPolicy Bypass -File scripts/test.ps1
powershell -ExecutionPolicy Bypass -File scripts/test-consumer.ps1

The consumer smoke test downloads exact public Wally package version, maps it server-only through Rojo, and exercises valid and invalid Action requests. TestEZ runs through local Roblox Studio; GitHub CI runs format, lint, and strict analysis.

Documentation

  • Security model
  • Architecture and API contracts
  • Tooling and verification
  • Optional adapter contracts
  • Transactions and replay
  • Changelog
  • 0.2.0 migration notes
  • 0.3.0 migration notes
  • 0.4.0 migration notes
  • Release notes

Contributing and support

Open a focused issue for bugs, design proposals, or documentation gaps. Report vulnerabilities privately; see SECURITY.md.

Credits

Built for Roblox with Luau, Wally, Rojo, and TestEZ. Anvil is independent software; these projects are not bundled runtime dependencies.

License

MIT © 2026 kentiers.

Package Details

Install command (Click to copy)


Version

0.4.0

License

MIT

check_circle

Safe for commercial use

Automated license review — not legal advice.