Skip to content

Intermediate Values

let declarations allow you to create intermediate values within a policy or rule block. They are useful for breaking down complex expressions into more readable and maintainable code.

let <name> = <expression>
-- With optional type annotation
let <name>: <type> = <expression>
-- Simple calculations
let isAdmin = user.role is "admin"
let totalPrice = item.price * quantity
-- With type annotations
let count: number = 10
let name: string = "example"
let isActive: bool = true

let declarations are scoped to their immediate block ({}). This means:

  • A let declaration inside a block is only accessible within that block
  • A let declaration at the policy level is accessible throughout the policy
  • A let declaration inside a rule’s block is only accessible within that rule
namespace com/example/auth
policy userAccess {
fact user: User as currentUser
-- Policy-level let declaration - accessible throughout the policy
let adminRoles = ["admin", "super_admin"]
rule canRead = default false {
-- Can access adminRoles here
yield user.role in adminRoles
}
rule canWrite = default false {
-- Can also access adminRoles here
yield user.role in adminRoles and user.verified
}
export decision of canRead
export decision of canWrite
}
namespace com/example/billing
policy pricing {
fact basePrice: number as price
fact quantity: number as qty
rule calculateTotal = default 0 {
-- Block-level let declarations - only accessible within this rule
let discount = 0.1
let taxRate = 0.08
let subtotal = price * qty
let discountAmount = subtotal * discount
let tax = (subtotal - discountAmount) * taxRate
let total = subtotal - discountAmount + tax
yield total
}
-- Cannot access discount, taxRate, subtotal, etc. here - they're scoped to the rule block
export decision of calculateTotal
}
namespace com/example/complex
policy example {
fact user: User as currentUser
-- Policy-level let
let globalValue = 100
rule complexRule = default false {
-- Rule-level let
let ruleValue = 50
-- Can access both globalValue and ruleValue here
let combined = globalValue + ruleValue
yield combined > 0
}
-- Can access globalValue here, but not ruleValue or combined
rule anotherRule = default false {
yield globalValue > 0
}
export decision of complexRule
export decision of anotherRule
}

let declarations cannot be exported. Only rules can be exported from a policy. If you need to expose a value, you must wrap it in a rule and export that rule.

namespace com/example/incorrect
policy example {
fact user: User as currentUser
let isAdmin = user.role == "admin"
-- Error: Cannot export let declarations
-- export decision of isAdmin -- This will cause an error
}
namespace com/example/correct
policy example {
fact user: User as currentUser
-- Use let for intermediate calculation
let isAdmin = user.role == "admin"
-- Wrap in a rule and export the rule
rule userIsAdmin = default false {
yield isAdmin
}
export decision of userIsAdmin
}

let declarations are immutable - once a value is assigned to a let declaration, it cannot be changed or reassigned within the same scope.

Each let declaration can only be assigned once. Attempting to reassign a let declaration will result in an error:

namespace com/example/immutability
policy example {
fact count: number as initialCount
-- First assignment is valid
let total = initialCount
-- Error: Cannot reassign let declaration
-- let total = total + 10 -- This will cause an error
rule calculateTotal = default 0 {
-- Create a new let declaration with a different name
let updatedTotal = total + 10
yield updatedTotal
}
export decision of calculateTotal
}

If you need to compute a new value based on an existing let declaration, create a new let declaration:

namespace com/example/calculations
policy pricing {
fact basePrice: number as price
-- Initial calculation
let subtotal = price * 1.0
rule calculateTotal = default 0 {
-- Create new let declarations for subsequent calculations
let withTax = subtotal * 1.08
let withDiscount = withTax * 0.9
let finalPrice = withDiscount
yield finalPrice
}
export decision of calculateTotal
}

Immutability provides several benefits:

  • Predictability: Once assigned, a let declaration’s value never changes, making code easier to reason about
  • Safety: Prevents accidental reassignment that could lead to bugs
  • Clarity: Makes it clear that intermediate values are computed once and used throughout the block
namespace com/example/benefits
policy example {
fact user: User as currentUser
-- Immutable value - guaranteed to be the same throughout the policy
let isAdmin = user.role == "admin"
rule canRead = default false {
-- isAdmin is guaranteed to be the same value here
yield isAdmin
}
rule canWrite = default false {
-- isAdmin is guaranteed to be the same value here too
yield isAdmin and user.verified
}
export decision of canRead
export decision of canWrite
}

Type annotations can be used with let declarations to help with type safety and readability.

-- Type is inferred from the expression
let count = 10 -- inferred as number
let name = "John Doe" -- inferred as string
let isActive = true -- inferred as bool
let items = [1, 2, 3] -- inferred as list[number]
-- Explicit type annotations
let count: number = 10
let name: string = "example"
let isActive: bool = true
let items: list[number] = [1, 2, 3]
let scores: map[number] = {"alice": 95, "bob": 87}
let invalid: number = "10" -- This will cause a type error
-- Type annotations with constraints
let age: number @min(0) @max(150) = 25
let score: number @min(0) @max(100) = 85
let tags: list[string] @maxlength(10) = ["tag1", "tag2"]

let declarations are particularly useful for breaking down complex expressions:

rule complexCalculation = default 0 {
yield (user.age * 0.5 + user.experience * 0.3 + user.education * 0.2) *
(user.isPremium ? 1.2 : 1.0) *
(user.location == "US" ? 1.1 : 1.0)
}
rule complexCalculation = default 0 {
let baseScore = user.age * 0.5 + user.experience * 0.3 + user.education * 0.2
let premiumMultiplier = user.isPremium ? 1.2 : 1.0
let locationMultiplier = user.location == "US" ? 1.1 : 1.0
let finalScore = baseScore * premiumMultiplier * locationMultiplier
yield finalScore
}

let declarations work well with TypeScript functions imported via use statements:

namespace com/example/utils
policy processing {
use { sha256, now } from @sentrie/hash as hash
use { parse } from @sentrie/json as json
fact data: string as inputData
rule processData = default false {
let timestamp = hash.now()
let hashValue = hash.sha256(inputData)
let parsedData = json.parse(inputData)
let isValid = hashValue != "" and parsedData.aField is defined
yield isValid
}
export decision of processData
}

let declarations can be used with reduce expressions for aggregations:

namespace com/example/aggregation
policy calculations {
fact numbers: list[number] as values
rule calculateSum = default 0 {
let sum: number = reduce values from 0 as acc, num, idx {
yield acc + num
}
yield sum
}
rule calculateMax = default 0 {
let max: number = reduce values from values[0] as acc, num, idx {
yield num > acc ? num : acc
}
yield max
}
export decision of calculateSum
export decision of calculateMax
}
-- Good: Clear, descriptive names
let isResourceOwner = user.id == resource.owner
let hasValidSignature = auth.verifySignature(user.id, resource.id)
let isWithinBusinessHours = auth.isBusinessHours()
-- Avoid: Generic or unclear names
let x = user.id == resource.owner
let y = auth.verifySignature(user.id, resource.id)
-- Good: Break down complex logic
rule calculatePrice = default 0 {
let basePrice = product.price
let discount = user.isPremium ? 0.1 : 0.05
let tax = basePrice * 0.08
let finalPrice = basePrice * (1 - discount) + tax
yield finalPrice
}
-- Avoid: One-line complex expression
rule calculatePrice = default 0 {
yield product.price * (1 - (user.isPremium ? 0.1 : 0.05)) + product.price * 0.08
}
-- Good: Explicit types make code clearer
let count: number = 10
let items: list[string] = ["item1", "item2"]
let config: map[string] = {"key": "value"}
-- Acceptable: Type inference works, but explicit types are clearer
let count = 10
let items = ["item1", "item2"]
-- Good: Let declarations close to where they're used
rule calculateTotal = default 0 {
let subtotal = price * quantity
let tax = subtotal * 0.08
let total = subtotal + tax
yield total
}
-- Avoid: Policy-level lets that are only used in one rule
policy example {
let subtotal = price * quantity -- Only used in one rule below
rule calculateTotal = default 0 {
let tax = subtotal * 0.08
let total = subtotal + tax
yield total
}
}
rule processUser = default false {
let userRole = user.role is defined ? user.role : "guest"
let accessLevel = userRole == "admin" ? "full" : "limited"
yield accessLevel == "full"
}
rule calculateDiscount = default 0 {
let basePrice = product.price
let quantityDiscount = quantity > 10 ? 0.1 : 0.0
let memberDiscount = user.isMember ? 0.15 : 0.0
let totalDiscount = quantityDiscount + memberDiscount
let finalPrice = basePrice * (1 - totalDiscount)
yield finalPrice
}
rule transformData = default false {
let normalizedName = user.name.lower()
let sanitizedEmail = user.email.trim()
let formattedRole = user.role.upper()
yield normalizedName != "" and sanitizedEmail != ""
}
  • Policies - Learn about policies and their structure
  • Rules - Learn about rules and how they work
  • Facts - Learn about facts and input data declarations
  • Expressions - Learn about expressions and operators