> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# RULE Syntax

> Complete reference for LoT rule syntax, conditions, operation scopes, and access control patterns

## Write Your Broker's Access Policy

Every broker operation—creating users, deploying Actions, publishing to topics—passes through Rules before it executes. This page is the complete syntax reference for writing those rules: the conditions you can check, the scopes you can target, and the patterns that tie them together.

> [!TIP]
>
> **Like writing house rules on a whiteboard.** Each rule says "IF you are on the approved list, THEN you may enter the room—ELSE the door stays locked." LoT rule syntax works the same way: you name the rule, pick the door (scope), and write the condition.

### When You Need This Reference

| Scenario | You'll use |
| - | - |
| Restricting who can create or delete users | User Management scopes |
| Controlling who deploys Actions, Models, or Routes | Entity Management scopes |
| Locking down MQTT topic access per user or group | Publish / Subscribe scopes with `TO TOPIC` |
| Protecting `$SYS` command topics | System topic scopes |
| Building role-based access across teams | Groups with `USER IN GROUP` — see [RBAC](./rbac) |

> [!NOTE]
>
> **Deploying rules:** In a [LoT Notebook](/v2.14/quick-start/vscode), write the rule and run the cell. For MQTT clients, use `-addRule` ([broker commands](/v2.14/mqtt-broker/commands#rules)). Custom ACL rules require a Growth or Enterprise license.

***

## Rule Structure

Every rule follows this structure:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE RULE <RuleName> WITH PRIORITY <number> FOR <Scope> [TO TOPIC "<pattern>"]
    IF <condition> THEN
        ALLOW
    ELSE
        DENY
```

| Component | Description |
| - | - |
| `RuleName` | Bare identifier (letters, digits, underscores) — **do not quote** the name |
| `PRIORITY` | Integer `100`–`1 000 000`; **lower number wins** among matching rules (see [Priority and Evaluation](#priority-and-evaluation)) |
| `Scope` | The operation type this rule controls |
| `TO TOPIC` | Optional topic pattern for Publish/Subscribe (and `$SYS` variants) |
| `condition` | Logic that determines access (user, group, permission flags) |

> [!WARNING]
>
> Keep the entire header on **one line**. Indent the body one level (4 spaces); nest `ALLOW`/`DENY` under `IF`/`ELSE` another level. A single-line `THEN ALLOW ELSE DENY` body is rejected.

Unconditional forms are valid too:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE RULE OpenPublic WITH PRIORITY 100 FOR Subscribe TO TOPIC "public/#"
    ALLOW
```

***

## Conditions

Rules evaluate conditions to decide whether to ALLOW or DENY an operation.

### User Identity Checks

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
USER IS "root"
USER EQUALS "alice"
```

### Group Membership

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
USER IN GROUP "operators"
USER IN GROUP "zone1-staff"
```

Group lookup is case-insensitive. See [RBAC with LoT Rules](./rbac) for group management commands and patterns.

### Permission Flags

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
USER HAS AllowedUserManagement
USER HAS AllowedSystemConfiguration
USER HAS AllowedLogManagement
```

These are the three broker permission flags. Prefer groups for fine-grained topic access rather than inventing custom flag names.

### Combining Conditions

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
IF USER HAS AllowedUserManagement OR USER IS "root" THEN
    ALLOW

IF USER IN GROUP "supervisors" AND USER EQUALS "alice" THEN
    ALLOW
```

Omitting `ELSE` is valid. `THEN DENY` allows everyone else; `THEN ALLOW` denies everyone else — the same as writing the opposite decision in an `ELSE` branch. An explicit `ELSE` is still the clearer form.

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE RULE DenyDashboardPublish WITH PRIORITY 120 FOR Publish TO TOPIC "factory/#"
    IF USER IS "dashboard" THEN
        DENY
```

***

## Operation Scopes

Rules control different categories of operations. They split into two groups that behave very differently in practice.

**Topic scopes** — `Publish`, `Subscribe`, `PublishSys`, `SubscribeSys` — are the scopes you write rules for. The only built-ins that compete with you are the permissive catch-alls in the weakest band, so your rules decide.

**Connect** — `FOR Connect` decides whether the broker accepts an MQTT CONNECT after authentication. It takes no `TO TOPIC` clause. See [Connection admission](./rbac#connection-admission).

**Management scopes** — everything below — are already covered by locked built-in rules in the reserved band (`0–99`) that key off [permission flags](#permission-flags). Because your rules are floored at `100` and only the lowest-numbered matching band decides, a rule you write on a management scope is **never** the deciding band.

> [!WARNING]
>
> Do not try to tighten or loosen a management gate with a custom rule. The locked built-in always wins, and `-addRule` rejects both a priority below `100` and any attempt to reuse a built-in rule name. Change who can perform these operations by setting **permission flags** with `-changeUserSettings` instead.

The tables below name each scope and the flags its locked built-in accepts. In every case `root` is also accepted.

### User Management

| Scope | Controls | Built-in gate accepts |
| - | - | - |
| `UserManagementCreation` | Creating new users | `AllowedUserManagement`, `AllowedSystemConfiguration` |
| `UserManagementRemove` | Deleting users | `AllowedUserManagement`, `AllowedSystemConfiguration` |
| `UserManagementUpdate` | Modifying user settings and group membership | `AllowedUserManagement`, `AllowedSystemConfiguration` |
| `UserManagementPasswordChange` | Changing user passwords | `AllowedUserManagement`, `AllowedSystemConfiguration` |

### Entity Management

| Scope | Controls | Built-in gate accepts |
| - | - | - |
| `ActionManagementCreation` | Creating Actions and panels | `AllowedSystemConfiguration`, `admin` |
| `ActionManagementRemove` | Deleting Actions and panels | `AllowedSystemConfiguration`, `admin` |
| `ActionManagementRun` | Manually running Actions | `AllowedSystemConfiguration`, `admin` |
| `ModelManagementCreation` | Creating Models | `AllowedSystemConfiguration`, `admin` |
| `ModelManagementRemove` | Deleting Models | `AllowedSystemConfiguration`, `admin` |
| `RouteManagementCreation` | Creating Routes | `AllowedSystemConfiguration`, `admin` |
| `RouteManagementRemove` | Deleting Routes | `AllowedSystemConfiguration`, `admin` |
| `RuleManagementCreation` | Creating Rules (`-addRule`) | `AllowedSystemConfiguration` |
| `RuleManagementRemove` | Deleting Rules (`-removeRule`) | `AllowedSystemConfiguration` |

### System Operations

| Scope | Controls | Built-in gate accepts |
| - | - | - |
| `SystemConfiguration` | Changing broker configuration | `AllowedSystemConfiguration` |
| `ShellCommand` | Executing shell commands | `AllowedSystemConfiguration` |
| `CommandCall` | Calling broker commands via `$SYS/Coreflux/Command` | `AllowedUserManagement`, `AllowedSystemConfiguration`, `admin` |

### Log Management

| Scope | Controls | Built-in gate accepts |
| - | - | - |
| `LogManagementCreation` | Creating log traces | `AllowedLogManagement`, `AllowedSystemConfiguration` |
| `LogManagementRemove` | Deleting log traces | `AllowedLogManagement`, `AllowedSystemConfiguration` |
| `LogManagementUpdate` | Modifying log traces | `AllowedLogManagement`, `AllowedSystemConfiguration` |

***

## Topic Access Rules

### Standard Topics

Use `Publish` and `Subscribe` with `TO TOPIC`:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE RULE OperatorPublish WITH PRIORITY 100 FOR Publish TO TOPIC "machines/+/cmd"
    IF USER IN GROUP "operators" THEN
        ALLOW
    ELSE
        DENY
```

The broker ships catch-alls at the weakest priority (`1 000 000`) that allow all connect, publish, and subscribe. Any rule you add in the user band already outranks them for its topic — see [RBAC](./rbac).

### System Topics (`$SYS`)

`$SYS` topics use the `PublishSys` and `SubscribeSys` scopes. Most of this namespace is already locked down for you:

| Path | Coverage |
| - | - |
| `SubscribeSys` on `$SYS/#` | Locked built-in — admins only. Cannot be tightened or loosened by a custom rule |
| `PublishSys` on the broker's own command and management topics | Locked built-ins — flag-gated |
| `PublishSys` on any other `$SYS` subtree | No built-in, so **default-deny**. A rule you write is the only candidate and decides |

Because the locked `$SYS` rules sit in the reserved band, a custom rule for `$SYS/#` subscribe never decides. Write `PublishSys` rules only for subtrees the built-ins do not already cover:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE RULE AppStatusWriters WITH PRIORITY 100 FOR PublishSys TO TOPIC "$SYS/MyApp/#"
    IF USER IN GROUP "app-services" THEN
        ALLOW
    ELSE
        DENY
```

> [!WARNING]
>
> System topics (`$SYS/#`) contain sensitive broker information and command interfaces. Rely on the locked built-ins rather than writing your own `$SYS` subscribe rules, and never grant `$SYS` access to untrusted clients.

***

## Permission Flags

| Permission Flag | Grants Access To |
| - | - |
| `AllowedUserManagement` | User creation, removal, updates, group membership |
| `AllowedSystemConfiguration` | System config, many entity management paths, shell/admin |
| `AllowedLogManagement` | Log trace creation, removal, updates, read |

Assign flags with `-changeUserSettings` or in [Coreflux HUB User Management](/hub/system/user-management) (Full Access → `AllowedSystemConfiguration`).

***

## Priority and Evaluation

**Lower priority number wins** — the same convention as BACnet write priority and ISA-18.2 alarm priority. Among rules that match the operation (and topic), only the lowest-numbered band decides:

1. If any rule in that band evaluates to **DENY** → deny (**deny-wins**)
2. Else if any allows → allow
3. If **no** rule matches → **deny** (default-deny)

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
-- Strongest band for this topic (100) decides first
DEFINE RULE AllowSensors WITH PRIORITY 100 FOR Publish TO TOPIC "plant/sensors/#"
    ALLOW

DEFINE RULE DenyPlant WITH PRIORITY 1000 FOR Publish TO TOPIC "plant/#"
    DENY
```

| Priority band | Guideline |
| - | - |
| `0–99` | Reserved for locked system rules — rejected for your rules |
| `100` | The strongest rule you can write — keep it for the most specific case |
| `100–999 999` | The user band; step *down* toward `100` as rules get more specific |
| `1 000 000` | Shipped connect/publish/subscribe catch-alls — the weakest band |
| `> 1 000 000` | Rejected — weaker than the catch-alls, so the rule could never apply |

> [!TIP]
>
> Use a **lower** priority number than the rule you intend to override. Because the seed catch-alls sit at `1 000 000`, any rule in the user band is already enough to restrict a subtree — start broad rules at `1000` or higher so you have room to carve out exceptions later.

For cookbook patterns, Visu RBAC, and default-rule tiers, see [RBAC with LoT Rules](./rbac).

***

## Complete Examples

**Group-based topic access**

Operators and supervisors share a plant namespace; only supervisors may emergency-stop:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE RULE Zone1Cmd WITH PRIORITY 1000 FOR Publish TO TOPIC "zone1/+/cmd"
    IF USER IN GROUP "operators" OR USER IN GROUP "supervisors" THEN
        ALLOW
    ELSE
        DENY

DEFINE RULE EmergencyStop WITH PRIORITY 100 FOR Publish TO TOPIC "zone1/emergency/stop"
    IF USER IN GROUP "supervisors" THEN
        ALLOW
    ELSE
        DENY
```

**Device topic namespace**

Confine device accounts to their own tree, and deny them everything else with a coarse fallback:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE RULE DenyDeviceElsewhere WITH PRIORITY 1000 FOR Publish TO TOPIC "#"
    IF USER IN GROUP "devices" THEN
        DENY
    ELSE
        ALLOW

DEFINE RULE DevicePublish WITH PRIORITY 100 FOR Publish TO TOPIC "devices/#"
    IF USER IN GROUP "devices" THEN
        ALLOW
    ELSE
        DENY
```

Device accounts need no rule to keep them off `$SYS/Coreflux/Command` — the locked built-in already requires a permission flag, so simply leave all three flags unset on those users.

**Department isolation**

Separate publish access by group:

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE RULE EngineeringPublish WITH PRIORITY 100 FOR Publish TO TOPIC "engineering/#"
    IF USER IN GROUP "engineering" THEN
        ALLOW
    ELSE
        DENY

DEFINE RULE OperationsPublish WITH PRIORITY 100 FOR Publish TO TOPIC "operations/#"
    IF USER IN GROUP "operations" THEN
        ALLOW
    ELSE
        DENY

DEFINE RULE SharedTopicAccess WITH PRIORITY 100 FOR Publish TO TOPIC "shared/#"
    IF USER IN GROUP "engineering" OR USER IN GROUP "operations" THEN
        ALLOW
    ELSE
        DENY
```

***

## Best Practices

> **Prefer groups for topic access**
>
> Use `USER IN GROUP` for data and panel access. Reserve permission flags for system-level capabilities.

> **Any user-band rule overrides the catch-alls**
>
> You do not need to remove `AllowPublishTopic` / `AllowSubscribeTopic` / `AllowConnect` to deny a subtree (or a connection). They sit in the weakest band (`1 000 000`), so any rule you write for that pattern already wins.

> **Leave room to carve out exceptions**
>
> Exceptions need a *lower* number than the rule they override, and `100` is the floor. Place namespace-wide rules at `1000` or higher and keep `100` for the most specific case, so you never have to renumber deployed rules.

> **Protect \$SYS topics**
>
> Rely on the locked built-ins — they already gate `$SYS` on permission flags and cannot be overridden. Keep those flags off device and operator accounts rather than writing your own `$SYS` rules.

> **Name rules clearly**
>
> Prefer `DenyRawArea` or `OperatorControl` over `Rule1`.

***

## Next Steps

> **[RBAC with LoT Rules](/v2.14/lot-language/rules/rbac)**
>
> Groups, precedence deep-dive, cookbook, and Visu access.

> **[Broker Commands](/v2.14/mqtt-broker/commands)**
>
> Deploy rules and manage groups via MQTT commands.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.