Policy Language Reference
This is the complete reference for the Sentrie policy language. It covers all language features, syntax, and semantics.
Table of Contents
Section titled “Table of Contents”- Program Structure
- Namespaces
- Policies
- Rules
- Expressions
- Primitives, Collections, Shapes, and Aliases
- Literals
- Operators
- Control Flow
- TypeScript Modules
- Facts and Variables
- Exports and Imports
Program Structure
Section titled “Program Structure”A Sentrie program consists of:
- Namespace declaration (required)
- Top-level declarations (policies, shapes)
- Comments (anywhere)
namespace com/example/myapp
-- This is a commentpolicy auth { -- policy content}
shape User { -- shape definition}
export shape User -- export shapes to allow visibility to other namespacesNamespaces
Section titled “Namespaces”Namespaces organize your policies and shapes hierarchically and prevent naming conflicts.
Syntax
Section titled “Syntax”namespace FQNWhere FQN (Fully Qualified Name) is a slash-separated identifier:
namespace com/example/authnamespace com/example/billing/v2namespace mycompany/policies/securityNamespace statements
Section titled “Namespace statements”A namespace can contain:
- policies:
policy IDENT { ... } - shapes:
shape IDENT { ... } - shape exports:
export shape IDENT
- Namespaces must be declared at the top of the file (only comments can be placed before the namespace declaration)
- Only one namespace per file
- Namespace names must be valid identifiers
- Use slash-separated (
/) hierarchical names for organization - Multiple root namespaces are allowed in a policy pack
- Namespace forms the visibility boundary for unexported shapes
Policies
Section titled “Policies”Policies are containers for rules, facts, and other declarations.
Syntax
Section titled “Syntax”policy IDENT { policyStatement*}Policy Statements
Section titled “Policy Statements”A policy can contain:
- Rules:
rule IDENT = ... - Facts:
fact IDENT ('?'?) : primitive/shape ('as' IDENT)? ('default' expr)? - Shapes:
shape IDENT { ... } - Variables:
let IDENT : primitive/shape = expr - Use statements:
use { function1, function2 } from source as alias - Exports:
export decision of IDENT - Comments:
-- comment
Example
Section titled “Example”namespace com/example/auth
policy user { fact user: User as currentUser fact context?: Context as ctx default {"environment": "production"}
let adminRoles = ["admin", "super_admin"]
rule canLogin = default false when user.role is defined { yield user.role in adminRoles }
export decision of canLogin}Rules are the core of Sentrie policies. They define what decisions to make based on input data.
Syntax
Section titled “Syntax”rule IDENT = (default expr)? (when expr)? blockExprComponents
Section titled “Components”- Name:
rule IDENT - Default (optional):
default expr- value whenwhenis false or rule body doesn’t yield - When (optional):
when expr- condition that must be true - Body:
blockExpr- block expression that must contain ayieldstatement
Examples
Section titled “Examples”-- Simple rulerule allow = default false { yield true}
-- Rule with conditionrule canEdit = default false when user.role == "admin" { yield true}
-- Rule with default valuerule getPrice = default 0 when product.price is defined { yield product.price}
-- Rule with complex bodyrule calculateDiscount = default 0 { let basePrice = product.price let discount = user.isPremium ? 0.1 : 0.05 let finalPrice = basePrice * (1 - discount) yield finalPrice}Expressions
Section titled “Expressions”Sentrie has a rich expression language with multiple operator types and precedence levels.
Precedence (highest to lowest)
Section titled “Precedence (highest to lowest)”- Primary expressions: literals, identifiers, function calls
- Unary operators:
not,! - Arithmetic:
*,/,% - Arithmetic:
+,- - Comparison:
<,<=,>,>= - Equality:
==,!= - Logical AND:
and - Logical XOR:
xor - Logical OR:
or - Ternary:
? :
Primary Expressions
Section titled “Primary Expressions”-- Literals423.14"hello"truefalseunknownnull[1, 2, 3]{"key": "value"}
-- Identifiersuserproduct.nameconfig.maxRetries
-- Function callstime.now()hash.sha256("data")json.parse("{}")
-- Index accessusers[0]config["maxRetries"]
-- Field accessuser.nameproduct.price
-- Parentheses(1 + 2) * 3Ternary Expressions
Section titled “Ternary Expressions”condition ? trueValue : falseValue
-- Examplesuser.role == "admin" ? "full_access" : "limited_access"age >= 18 ? "adult" : "minor"Block Expressions
Section titled “Block Expressions”{ let variable = expression -- other statements yield result}Primitives, Collections, Shapes, and Aliases
Section titled “Primitives, Collections, Shapes, and Aliases”Sentrie provides primitives, collections, shapes, and aliases for defining data structures.
Primitives
Section titled “Primitives”number- Numeric values (backed by float64)string- Text stringstrinary- Trinary values (true/false/unknown)bool- Boolean values (true/false) - a special case oftrinary
document- JSON-like objects
Collections
Section titled “Collections”list[T]- Lists of primitive Tmap[T]- Maps with string keys and primitive T valuesrecord[T1, T2, ...]- Tuples with specific primitives
Shape Definitions
Section titled “Shape Definitions”Shapes define structured data with fields and constraints.
Field Modifiers:
!- Non-nullable (required field)?- Optional field- No modifier - Default field (required by default)
shape User { id!: string -- Required field (non-nullable) name!: string -- Required field (non-nullable) email?: string -- Optional field age?: number -- Optional field roles: list[string] -- Required field (default) metadata: document -- Required field (default)}
shape Product { id!: string name!: string price!: number tags?: list[string] dimensions: record[number, number, number] -- width, height, depth}Shape Composition
Section titled “Shape Composition”Shapes can be composed from other shapes using the with keyword:
shape BaseUser { id!: string name!: string}
shape AdminUser with BaseUser { permissions: list[string] lastLogin?: string}The composed shape includes all fields from the base shape plus any additional fields defined in the composed shape.
Constraints
Section titled “Constraints”Constraints can be applied to primitives, collections, and shape fields:
shape User { name: string @length(1, 100) age: number @min(0) @max(150) email: string @email tags: list[string] @maxlength(10)}
let numbers: list[number] = [1, 2, 3]let scores: map[number @min(0) @max(100)] = {"alice": 95, "bob": 87}Aliases
Section titled “Aliases”You can create aliases using shapes:
shape Positive100 number @min(0) @max(100)
let score: Positive100 = 50Literals
Section titled “Literals”String Literals
Section titled “String Literals”"hello world""escaped \"quotes\"""line 1\nline 2"Numeric Literals
Section titled “Numeric Literals”42 -- Number-42 -- Negative number3.14 -- Float-3.14 -- Negative float1e5 -- Scientific notation1.5e-3 -- Scientific notation with negative exponentBoolean and Trinary Literals
Section titled “Boolean and Trinary Literals”true -- Boolean truefalse -- Boolean falseunknown -- Trinary unknown (neither true nor false)Collection Literals
Section titled “Collection Literals”-- Lists[1, 2, 3]["hello", "world"][true, false, unknown]
-- Maps{"name": "Alice", "age": 30}{"key1": "value1", "key2": 42}
-- Records["one", 1, true] -- record[string, number, boolean]
-- Empty collections[]{}Null Literal
Section titled “Null Literal”null -- Null valueOperators
Section titled “Operators”Arithmetic Operators
Section titled “Arithmetic Operators”+ -- Addition- -- Subtraction* -- Multiplication/ -- Division% -- ModuloComparison Operators
Section titled “Comparison Operators”== -- Equality!= -- Inequality< -- Less than<= -- Less than or equal> -- Greater than>= -- Greater than or equalLogical Operators
Section titled “Logical Operators”and -- Logical ANDor -- Logical ORxor -- Logical XORnot -- Logical NOT! -- Logical NOT (alternative)Collection Operators
Section titled “Collection Operators”in -- Membershipnot in -- Non-membershipcontains -- Containsnot contains -- Does not containmatches -- Pattern matchingnot matches -- Pattern non-matchingShape Checking Operators
Section titled “Shape Checking Operators”is defined -- Check if definedis not defined -- Check if not definedis empty -- Check if emptyis not empty -- Check if not emptyis -- Shape checkingQuantifier Operators
Section titled “Quantifier Operators”any -- Any element satisfies conditionall -- All elements satisfy conditionfilter -- Filter elementsmap -- Transform elementsdistinct -- Remove duplicatesreduce -- Reduce collection to single valuecount -- Count elementsCasting
Section titled “Casting”cast -- Casting between primitivesExample:
let y = "99"let x: number = cast y as numberTypeScript Modules
Section titled “TypeScript Modules”Sentrie supports importing functions from TypeScript modules, including built-in @sentrie/* modules and local TypeScript files.
Use Statement
Section titled “Use Statement”The use statement allows you to import functions from TypeScript modules:
use { function1, function2 } from @sentrie/module as aliasNote: Built-in @sentrie/* modules do not use quotes. Local TypeScript files use quotes for relative paths.
The as clause is optional. If omitted, the default alias is the last part of the module path (e.g., time for @sentrie/time).
Built-in Modules
Section titled “Built-in Modules”Built-in modules are prefixed with @sentrie/:
namespace com/example/auth
policy mypolicy { use { now } from @sentrie/time as time use { sha256 } from @sentrie/hash use { parse, format } from @sentrie/json as json
fact data!: string
rule processData = default false { let timestamp = time.now() let hash = sha256(data) let parsed = json.parse(data) yield hash != "" and timestamp > 0 }
export decision of processData}Local TypeScript Files
Section titled “Local TypeScript Files”You can import TypeScript files from your policy pack using relative paths:
namespace com/example/auth
policy mypolicy { use { calculateAge, validateEmail } from "./utils.ts" as utils
fact user!: User
rule validateUser = default false { yield utils.calculateAge(user.birthDate) >= 18 and utils.validateEmail(user.email) }
export decision of validateUser}Note: All relative paths are normalized to @local paths internally. The @local prefix indicates paths relative to the pack root. For example, @local/user/id evaluates to $PACKROOT/user/id.ts.
Available Built-in Modules
Section titled “Available Built-in Modules”@sentrie/collection- List and map manipulation utilities@sentrie/crypto- Cryptographic functions (SHA-256)@sentrie/encoding- Base64, Hex, and URL encoding/decoding@sentrie/hash- Hash functions (MD5, SHA-1, SHA-256, SHA-512, HMAC)@sentrie/json- JSON marshaling and unmarshaling@sentrie/jwt- JSON Web Token decoding and verification@sentrie/math- Mathematical constants and functions@sentrie/net- Network and IP address utilities@sentrie/regex- Regular expression pattern matching@sentrie/semver- Semantic version comparison and validation@sentrie/string- String manipulation utilities@sentrie/time- Date and time manipulation@sentrie/url- URL parsing and manipulation@sentrie/uuid- UUID generation (v4, v6, v7)
See the Built-in TypeScript Modules documentation for detailed information on each module.
Facts and Variables
Section titled “Facts and Variables”Facts are named values that can be injected into policy evaluation:
-- Required facts (must be provided)fact userId: string as idfact user: User as currentUser
-- Optional facts (can be omitted, marked with ?)fact maxRetries?: number as limit default 3fact apiKey?: string as key default ""fact config?: document as settings default {}fact context?: Context as ctx default {"role": "guest"}Facts can have:
- Annotation:
: primitive/shape- primitive or shape annotation - Optional modifier:
?- marks fact as optional (defaults are only allowed for optional facts) - Alias:
as alias- name used in the policy - Default value:
default expr- value if not provided (only for optional facts)
Variables
Section titled “Variables”Variables are local to a policy or rule:
let maxRetries = 3let adminRoles = ["admin", "super_admin"]let userAge = user.birthDate ? calculateAge(user.birthDate) : 0let numbers: list[number] = [1, 2, 3]let scores: map[number @min(0) @max(100)] = {"alice": 95}Variables can have:
- Annotation:
: primitive/shape(optional) - primitive or shape annotation - Initial value:
= expr(required)
Reduce Expressions
Section titled “Reduce Expressions”Variables can be computed using reduce expressions:
let numbers: list[number] = [1, 2, 3, 4, 5]
let sum: number = reduce numbers from 0 as acc, num, idx { yield acc + num}
let max: number = reduce numbers from numbers[0] as acc, num, idx { yield num > acc ? num : acc}Exports and Imports
Section titled “Exports and Imports”Exports
Section titled “Exports”Export rules to make them available for external evaluation:
export decision of ruleNameexport decision of ruleName attach attachmentName as expression attach anotherAttachment as anotherExpressionExports can include attachments that provide additional data:
export decision of allow_admin attach the_float as (10 + 5) * (5 - 2) / 2 attach the_number as 8 / 6 attach the_list as [1, 2, 3] attach the_map as {"key": "value"} attach the_string as "hello" attach the_bool as true attach the_null as nullImports
Section titled “Imports”Import rules from other policies:
rule importedRule = import decision ruleName from com/example/other/policyrule importedRule = import decision ruleName from com/example/other/policy with param as valueComments
Section titled “Comments”Comments start with -- and continue to the end of the line:
-- This is a commentrule allow = default false { -- Inline comment yield true}Error Handling
Section titled “Error Handling”Sentrie provides comprehensive error handling and validation:
Validation Errors
Section titled “Validation Errors”-- This will cause a validation errorrule invalid = default false { yield "string" + 42 -- Cannot add string and number}Constraint Violations
Section titled “Constraint Violations”-- This will cause a constraint violationrule invalid = default false { let age: number @min(0) @max(150) = -5 -- Age constraint violation yield age > 0}Undefined Values
Section titled “Undefined Values”Accessing non-existent fields returns undefined rather than causing an error:
rule example = default false { let value = user.nonexistent.field -- Returns undefined yield value -- undefined}Any operation on undefined will also yield undefined:
rule example = default false { let value = user.nonexistent.field -- undefined let result = value + 1 -- undefined (operation on undefined) let comparison = value == "test" -- undefined (operation on undefined) yield result -- undefined}Use the is defined operator to check if a value is defined before using it:
rule example = default false when user.nonexistent.field is defined { yield user.nonexistent.field}Best Practices
Section titled “Best Practices”1. Use Clear Names
Section titled “1. Use Clear Names”-- Goodrule canUserEditPost = default false when user.role == "admin" { yield true}
-- Badrule x = default false when a == "b" { yield true}2. Organize by Namespace
Section titled “2. Organize by Namespace”namespace com/example/authnamespace com/example/billingnamespace com/example/analytics3. Use Facts for Configuration
Section titled “3. Use Facts for Configuration”-- Required facts (must be provided)fact maxLoginAttempts: number as limit
-- Optional facts with defaultsfact sessionTimeout?: number as timeout default 3600fact retryCount?: number as retries default 34. Validate Inputs
Section titled “4. Validate Inputs”rule validateUser = default false when user is defined { yield user.id is defined and user.id != ""}5. Use Shapes for Validation
Section titled “5. Use Shapes for Validation”shape User { id!: string name!: string role!: string}
rule processUser = default false when user is User { yield user.role in ["admin", "user"]}6. Leverage TypeScript Modules
Section titled “6. Leverage TypeScript Modules”-- Use built-in modules for common operationsuse { sha256 } from @sentrie/hashuse { now } from @sentrie/time as timeuse { parse } from @sentrie/json as jsonExamples
Section titled “Examples”Simple Authorization
Section titled “Simple Authorization”namespace com/example/auth
policy user { fact user: User as currentUser
rule isAdmin = default false when user.role == "admin" { yield true }
rule canAccess = default false when user.role in ["admin", "user"] { yield true }
export decision of isAdmin export decision of canAccess}Resource-Based Access Control
Section titled “Resource-Based Access Control”namespace com/example/resources
policy document { fact user: User as user fact document: Document as document
rule canRead = default false when user.role == "admin" or document.owner == user.id { yield true }
rule canWrite = default false when user.role == "admin" or document.owner == user.id { yield true }
rule canDelete = default false when user.role == "admin" { yield true }
export decision of canRead export decision of canWrite export decision of canDelete}Complex Business Logic with TypeScript
Section titled “Complex Business Logic with TypeScript”namespace com/example/billing
policy pricing { fact basePrice: number as price fact discountRate?: number as rate default 0.1 fact user: User as currentUser
use { max, min } from @sentrie/math as math
rule calculatePrice = default 0 { let base = price let discount = user.isPremium ? rate : rate * 0.5 let tax = base * 0.08 let total = base * (1 - discount) + tax let finalPrice = math.max(0, math.min(total, 10000))
yield finalPrice }
export decision of calculatePrice}See Also
Section titled “See Also”- Using TypeScript - Complete guide to using TypeScript in Sentrie
- Built-in TypeScript Modules - Reference for all built-in modules
- Policies - Detailed information about policies
- Rules - Detailed information about rules
- Facts - Detailed information about facts
- Let Declarations - Detailed information about let declarations
- Shapes - Detailed information about shapes